Skip to content

HHH-19297 Register SingleStore json functions to community SingleStoreDialect #9927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@
import org.hibernate.boot.model.relational.Exportable;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayAggFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayAppendFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayInsertFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonExistsFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonMergepatchFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonObjectAggFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonObjectFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonQueryFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonRemoveFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonSetFunction;
import org.hibernate.community.dialect.function.json.SingleStoreJsonValueFunction;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
Expand Down Expand Up @@ -64,14 +76,14 @@
import org.hibernate.mapping.UniqueKey;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.query.common.TemporalUnit;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.common.TemporalUnit;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.query.sqm.mutation.spi.AfterUseAction;
import org.hibernate.query.sqm.mutation.spi.BeforeUseAction;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy;
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
import org.hibernate.query.sqm.mutation.spi.AfterUseAction;
import org.hibernate.query.sqm.mutation.spi.BeforeUseAction;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
Expand All @@ -98,6 +110,7 @@
import org.hibernate.type.descriptor.sql.internal.NativeEnumDdlTypeImpl;
import org.hibernate.type.descriptor.sql.internal.NativeOrdinalEnumDdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;

import jakarta.persistence.TemporalType;

Expand Down Expand Up @@ -500,31 +513,35 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
castType( BINARY ),
this
)
.withTypeCapacity( maxTinyLobLen, "tinyblob" )
.withTypeCapacity( maxMediumLobLen, "mediumblob" )
.withTypeCapacity( maxLobLen, "blob" )
.build() );
.withTypeCapacity( maxTinyLobLen, "tinyblob" )
.withTypeCapacity( maxMediumLobLen, "mediumblob" )
.withTypeCapacity( maxLobLen, "blob" )
.build() );

ddlTypeRegistry.addDescriptor( CapacityDependentDdlType.builder(
CLOB,
columnType( CLOB ),
castType( CHAR ),
this
)
.withTypeCapacity( maxTinyLobLen, "tinytext" )
.withTypeCapacity( maxMediumLobLen, "mediumtext" )
.withTypeCapacity( maxLobLen, "text" )
.build() );
.withTypeCapacity( maxTinyLobLen, "tinytext" )
.withTypeCapacity( maxMediumLobLen, "mediumtext" )
.withTypeCapacity( maxLobLen, "text" )
.build() );

ddlTypeRegistry.addDescriptor( CapacityDependentDdlType.builder(
NCLOB,
columnType( NCLOB ),
castType( NCHAR ),
this
).withTypeCapacity( maxTinyLobLen, "tinytext character set utf8" ).withTypeCapacity(
maxMediumLobLen,
"mediumtext character set utf8"
).withTypeCapacity( maxLobLen, "text character set utf8" ).build() );
NCLOB,
columnType( NCLOB ),
castType( NCHAR ),
this
)
.withTypeCapacity(
maxTinyLobLen,
"tinytext character set utf8"
)
.withTypeCapacity( maxMediumLobLen, "mediumtext character set utf8" )
.withTypeCapacity( maxLobLen, "text character set utf8" )
.build() );

ddlTypeRegistry.addDescriptor( new NativeEnumDdlTypeImpl( this ) );
ddlTypeRegistry.addDescriptor( new NativeOrdinalEnumDdlTypeImpl( this ) );
Expand Down Expand Up @@ -581,23 +598,18 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
commonFunctionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
commonFunctionFactory.listagg_groupConcat();
functionContributions.getFunctionRegistry()
.namedDescriptorBuilder( "time" )
SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();
final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();
functionRegistry.namedDescriptorBuilder( "time" )
.setExactArgumentCount( 1 )
.setInvariantType( functionContributions.getTypeConfiguration()
.getBasicTypeRegistry()
.resolve( StandardBasicTypes.STRING ) )
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.STRING ) )
.register();
functionContributions.getFunctionRegistry()
.patternDescriptorBuilder( "median", "median(?1) over ()" )
.setInvariantType( functionContributions.getTypeConfiguration()
.getBasicTypeRegistry()
.resolve( StandardBasicTypes.DOUBLE ) )
functionRegistry.patternDescriptorBuilder( "median", "median(?1) over ()" )
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.DOUBLE ) )
.setExactArgumentCount( 1 )
.setParameterTypes( NUMERIC )
.register();
BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();
SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();
functionRegistry.noArgsBuilder( "localtime" )
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.TIMESTAMP ) )
.setUseParenthesesWhenNoArgs( false )
Expand All @@ -610,6 +622,18 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
.setParameterTypes( FunctionParameterType.INTEGER )
.register();
functionRegistry.registerAlternateKey( "char", "chr" );
functionRegistry.register( "json_object", new SingleStoreJsonObjectFunction( typeConfiguration ) );
functionRegistry.register( "json_array", new SingleStoreJsonArrayFunction( typeConfiguration ) );
functionRegistry.register( "json_value", new SingleStoreJsonValueFunction( typeConfiguration ) );
functionRegistry.register( "json_exists", new SingleStoreJsonExistsFunction( typeConfiguration ) );
functionRegistry.register( "json_query", new SingleStoreJsonQueryFunction( typeConfiguration ) );
functionRegistry.register( "json_arrayagg", new SingleStoreJsonArrayAggFunction( typeConfiguration ) );
functionRegistry.register( "json_objectagg", new SingleStoreJsonObjectAggFunction( typeConfiguration ) );
functionRegistry.register( "json_set", new SingleStoreJsonSetFunction( typeConfiguration ) );
functionRegistry.register( "json_remove", new SingleStoreJsonRemoveFunction( typeConfiguration ) );
functionRegistry.register( "json_mergepatch", new SingleStoreJsonMergepatchFunction( typeConfiguration ) );
functionRegistry.register( "json_array_append", new SingleStoreJsonArrayAppendFunction( typeConfiguration ) );
functionRegistry.register( "json_array_insert", new SingleStoreJsonArrayInsertFunction( typeConfiguration ) );
}


Expand Down Expand Up @@ -1214,8 +1238,7 @@ public String getAddForeignKeyConstraintString(
}

@Override
public String getAddForeignKeyConstraintString(
String constraintName, String foreignKeyDefinition) {
public String getAddForeignKeyConstraintString(String constraintName, String foreignKeyDefinition) {
throw new UnsupportedOperationException( "SingleStore does not support foreign keys and referential integrity." );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
*/
package org.hibernate.community.dialect;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.sql.ast.MySQLSqlAstTranslator;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.ComparisonOperator;
Expand Down Expand Up @@ -42,13 +40,18 @@
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
* A SQL AST translator for SingleStore.
*
* @author Oleksandr Yeliseiev
*/
public class SingleStoreSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {

private static final int MAX_CHAR_SIZE = 8192;
private final SingleStoreDialect dialect;

public SingleStoreSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement, SingleStoreDialect dialect) {
Expand Down Expand Up @@ -403,9 +406,60 @@ private boolean supportsWindowFunctions() {
return true;
}

public static String getSqlType(CastTarget castTarget, SessionFactoryImplementor factory) {
final String sqlType = getCastTypeName( castTarget, factory.getTypeConfiguration() );
return getSqlType( castTarget, sqlType, factory.getJdbcServices().getDialect() );
}

private static String getSqlType(CastTarget castTarget, String sqlType, Dialect dialect) {
if ( sqlType != null ) {
int parenthesesIndex = sqlType.indexOf( '(' );
final String baseName = parenthesesIndex == -1 ? sqlType : sqlType.substring( 0, parenthesesIndex ).trim();
switch ( baseName.toLowerCase( Locale.ROOT ) ) {
case "bit":
return "unsigned";
case "tinyint":
case "smallint":
case "integer":
case "bigint":
return "signed";
case "float":
case "real":
case "double precision":
final int precision = castTarget.getPrecision() == null ?
dialect.getDefaultDecimalPrecision() :
castTarget.getPrecision();
final int scale = castTarget.getScale() == null ? Size.DEFAULT_SCALE : castTarget.getScale();
return "decimal(" + precision + "," + scale + ")";
case "char":
case "varchar":
case "text":
case "mediumtext":
case "longtext":
case "set":
case "enum":
if ( castTarget.getLength() == null ) {
if ( castTarget.getJdbcMapping().getJdbcJavaType().getJavaType() == Character.class ) {
return "char(1)";
}
else {
return "char";
}
}
return castTarget.getLength() > MAX_CHAR_SIZE ? "char" : "char(" + castTarget.getLength() + ")";
case "binary":
case "varbinary":
case "mediumblob":
case "longblob":
return castTarget.getLength() == null ? "binary" : "binary(" + castTarget.getLength() + ")";
}
}
return sqlType;
}

@Override
public void visitCastTarget(CastTarget castTarget) {
String sqlType = MySQLSqlAstTranslator.getSqlType( castTarget, getSessionFactory() );
String sqlType = getSqlType( castTarget, getSessionFactory() );
if ( sqlType != null ) {
appendSql( sqlType );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.community.dialect.function.json;

import java.util.List;

import org.hibernate.QueryException;
import org.hibernate.dialect.function.json.JsonArrayAggFunction;
import org.hibernate.metamodel.model.domain.ReturnableType;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.spi.TypeConfiguration;

/**
* SingleStore json_arrayagg function.
*/
public class SingleStoreJsonArrayAggFunction extends JsonArrayAggFunction {

public SingleStoreJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
super( false, typeConfiguration );
}

@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
Predicate filter,
List<SortSpecification> withinGroup,
ReturnableType<?> returnType,
SqlAstTranslator<?> translator) {
final boolean caseWrapper = filter != null;
sqlAppender.appendSql( "concat('[',group_concat(" );
final JsonNullBehavior nullBehavior;
if ( sqlAstArguments.size() > 1 ) {
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
}
else {
nullBehavior = JsonNullBehavior.ABSENT;
}
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
final Expression arg;
if ( firstArg instanceof Distinct ) {
sqlAppender.appendSql( "distinct " );
arg = ( (Distinct) firstArg ).getExpression();
}
else {
arg = (Expression) firstArg;
}
if ( caseWrapper ) {
if ( nullBehavior != JsonNullBehavior.ABSENT ) {
throw new QueryException( "Can't emulate json_arrayagg filter clause when using 'null on null' clause." );
}
translator.getCurrentClauseStack().push( Clause.WHERE );
sqlAppender.appendSql( "case when " );
filter.accept( translator );
translator.getCurrentClauseStack().pop();
sqlAppender.appendSql( " then " );
renderArgument( sqlAppender, arg, nullBehavior, translator );
sqlAppender.appendSql( " else null end)" );
}
else {
renderArgument( sqlAppender, arg, nullBehavior, translator );
}
if ( withinGroup != null && !withinGroup.isEmpty() ) {
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
sqlAppender.appendSql( " order by " );
withinGroup.get( 0 ).accept( translator );
for ( int i = 1; i < withinGroup.size(); i++ ) {
sqlAppender.appendSql( ',' );
withinGroup.get( i ).accept( translator );
}
translator.getCurrentClauseStack().pop();
}
sqlAppender.appendSql( " separator ','),']')" );
}

@Override
protected void renderArgument(
SqlAppender sqlAppender, Expression arg, JsonNullBehavior nullBehavior, SqlAstTranslator<?> translator) {
if ( nullBehavior != JsonNullBehavior.NULL ) {
sqlAppender.appendSql( "nullif(" );
}
sqlAppender.appendSql( "to_json(" );
arg.accept( translator );
sqlAppender.appendSql( ')' );
if ( nullBehavior != JsonNullBehavior.NULL ) {
sqlAppender.appendSql( ",to_json('null'))" );
}
}
}
Loading