Skip to content

Commit ac3e9ca

Browse files
oyeliseiev-uabeikov
authored andcommitted
HHH-19297 Register SingleStore json functions to SingleStoreDialect
1 parent 3a1a862 commit ac3e9ca

14 files changed

+1061
-83
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreDialect.java

Lines changed: 63 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@
2525
import org.hibernate.boot.model.relational.Exportable;
2626
import org.hibernate.boot.model.relational.Sequence;
2727
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
28+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayAggFunction;
29+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayAppendFunction;
30+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayFunction;
31+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayInsertFunction;
32+
import org.hibernate.community.dialect.function.json.SingleStoreJsonExistsFunction;
33+
import org.hibernate.community.dialect.function.json.SingleStoreJsonMergepatchFunction;
34+
import org.hibernate.community.dialect.function.json.SingleStoreJsonObjectAggFunction;
35+
import org.hibernate.community.dialect.function.json.SingleStoreJsonObjectFunction;
36+
import org.hibernate.community.dialect.function.json.SingleStoreJsonQueryFunction;
37+
import org.hibernate.community.dialect.function.json.SingleStoreJsonRemoveFunction;
38+
import org.hibernate.community.dialect.function.json.SingleStoreJsonSetFunction;
39+
import org.hibernate.community.dialect.function.json.SingleStoreJsonValueFunction;
2840
import org.hibernate.dialect.DatabaseVersion;
2941
import org.hibernate.dialect.Dialect;
3042
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
@@ -65,14 +77,14 @@
6577
import org.hibernate.mapping.UniqueKey;
6678
import org.hibernate.metamodel.mapping.EntityMappingType;
6779
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
80+
import org.hibernate.query.common.TemporalUnit;
6881
import org.hibernate.query.sqm.CastType;
6982
import org.hibernate.query.sqm.IntervalType;
70-
import org.hibernate.query.common.TemporalUnit;
7183
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
72-
import org.hibernate.query.sqm.mutation.spi.AfterUseAction;
73-
import org.hibernate.query.sqm.mutation.spi.BeforeUseAction;
7484
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy;
7585
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
86+
import org.hibernate.query.sqm.mutation.spi.AfterUseAction;
87+
import org.hibernate.query.sqm.mutation.spi.BeforeUseAction;
7688
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
7789
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
7890
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
@@ -84,7 +96,6 @@
8496
import org.hibernate.sql.ast.tree.Statement;
8597
import org.hibernate.sql.exec.spi.JdbcOperation;
8698
import org.hibernate.tool.schema.spi.Exporter;
87-
import org.hibernate.type.BasicType;
8899
import org.hibernate.type.BasicTypeRegistry;
89100
import org.hibernate.type.NullType;
90101
import org.hibernate.type.SqlTypes;
@@ -100,13 +111,12 @@
100111
import org.hibernate.type.descriptor.sql.internal.NativeEnumDdlTypeImpl;
101112
import org.hibernate.type.descriptor.sql.internal.NativeOrdinalEnumDdlTypeImpl;
102113
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
114+
import org.hibernate.type.spi.TypeConfiguration;
103115

104116
import jakarta.persistence.TemporalType;
105117

106118
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
107-
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
108119
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
109-
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
110120
import static org.hibernate.type.SqlTypes.BIGINT;
111121
import static org.hibernate.type.SqlTypes.BINARY;
112122
import static org.hibernate.type.SqlTypes.BIT;
@@ -326,7 +336,7 @@ public void appendDateTimeLiteral(
326336
break;
327337
case TIMESTAMP:
328338
if ( temporalAccessor instanceof ZonedDateTime ) {
329-
temporalAccessor = ( (ZonedDateTime) temporalAccessor ).toOffsetDateTime();
339+
temporalAccessor = ((ZonedDateTime) temporalAccessor).toOffsetDateTime();
330340
}
331341
appender.appendSql( "timestamp('" );
332342
appendAsTimestampWithMicros(
@@ -418,15 +428,16 @@ public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
418428
return EXTRACTOR;
419429
}
420430

421-
private static final ViolatedConstraintNameExtractor EXTRACTOR = new TemplatedViolatedConstraintNameExtractor( sqle -> {
422-
final String sqlState = JdbcExceptionHelper.extractSqlState( sqle );
423-
if ( sqlState != null ) {
424-
if ( Integer.parseInt( sqlState ) == 23000 ) {
425-
return extractUsingTemplate( " for key '", "'", sqle.getMessage() );
426-
}
427-
}
428-
return null;
429-
} );
431+
private static final ViolatedConstraintNameExtractor EXTRACTOR = new TemplatedViolatedConstraintNameExtractor(
432+
sqle -> {
433+
final String sqlState = JdbcExceptionHelper.extractSqlState( sqle );
434+
if ( sqlState != null ) {
435+
if ( Integer.parseInt( sqlState ) == 23000 ) {
436+
return extractUsingTemplate( " for key '", "'", sqle.getMessage() );
437+
}
438+
}
439+
return null;
440+
} );
430441

431442
@Override
432443
public boolean qualifyIndexName() {
@@ -521,14 +532,18 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
521532
.build() );
522533

523534
ddlTypeRegistry.addDescriptor( CapacityDependentDdlType.builder(
524-
NCLOB,
525-
columnType( NCLOB ),
526-
castType( NCHAR ),
527-
this
528-
).withTypeCapacity( maxTinyLobLen, "tinytext character set utf8" ).withTypeCapacity(
529-
maxMediumLobLen,
530-
"mediumtext character set utf8"
531-
).withTypeCapacity( maxLobLen, "text character set utf8" ).build() );
535+
NCLOB,
536+
columnType( NCLOB ),
537+
castType( NCHAR ),
538+
this
539+
)
540+
.withTypeCapacity(
541+
maxTinyLobLen,
542+
"tinytext character set utf8"
543+
)
544+
.withTypeCapacity( maxMediumLobLen, "mediumtext character set utf8" )
545+
.withTypeCapacity( maxLobLen, "text character set utf8" )
546+
.build() );
532547

533548
ddlTypeRegistry.addDescriptor( new NativeEnumDdlTypeImpl( this ) );
534549
ddlTypeRegistry.addDescriptor( new NativeOrdinalEnumDdlTypeImpl( this ) );
@@ -586,14 +601,13 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
586601
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
587602
commonFunctionFactory.listagg_groupConcat();
588603
SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();
604+
final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
589605
BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();
590-
functionRegistry
591-
.namedDescriptorBuilder( "time" )
606+
functionRegistry.namedDescriptorBuilder( "time" )
592607
.setExactArgumentCount( 1 )
593608
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.STRING ) )
594609
.register();
595-
functionRegistry
596-
.patternDescriptorBuilder( "median", "median(?1) over ()" )
610+
functionRegistry.patternDescriptorBuilder( "median", "median(?1) over ()" )
597611
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.DOUBLE ) )
598612
.setExactArgumentCount( 1 )
599613
.setParameterTypes( NUMERIC )
@@ -610,45 +624,18 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
610624
.setParameterTypes( FunctionParameterType.INTEGER )
611625
.register();
612626
functionRegistry.registerAlternateKey( "char", "chr" );
613-
BasicType<Boolean> booleanType = basicTypeRegistry.resolve( StandardBasicTypes.BOOLEAN );
614-
functionRegistry.namedDescriptorBuilder( "json_array_contains_string" )
615-
.setInvariantType( booleanType )
616-
.setExactArgumentCount( 2 )
617-
.setParameterTypes( ANY, STRING )
618-
.register();
619-
functionRegistry.registerAlternateKey( "json_array_contains", "json_array_contains_string" );
620-
functionRegistry.namedDescriptorBuilder( "json_array_contains_json" )
621-
.setInvariantType( booleanType )
622-
.setExactArgumentCount( 2 )
623-
.setParameterTypes( ANY, ANY )
624-
.register();
625-
functionRegistry.namedDescriptorBuilder( "json_array_contains_double" )
626-
.setInvariantType( booleanType )
627-
.setExactArgumentCount( 2 )
628-
.setParameterTypes( ANY, NUMERIC )
629-
.register();
630-
functionRegistry.namedDescriptorBuilder( "json_match_any_exists" )
631-
.setInvariantType( booleanType )
632-
.setMinArgumentCount( 1 )
633-
.register();
634-
functionRegistry.namedDescriptorBuilder( "json_match_any" )
635-
.setInvariantType( booleanType )
636-
.setMinArgumentCount( 1 )
637-
.register();
638-
functionRegistry.namedDescriptorBuilder( "json_extract_string" )
639-
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.STRING ) )
640-
.setMinArgumentCount( 1 )
641-
.register();
642-
functionRegistry.namedDescriptorBuilder( "json_extract_double" )
643-
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.DOUBLE ) )
644-
.setMinArgumentCount( 1 )
645-
.register();
646-
functionRegistry.namedDescriptorBuilder( "json_extract_bigint" )
647-
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.BIG_INTEGER ) )
648-
.setMinArgumentCount( 1 )
649-
.register();
650-
functionRegistry.registerAlternateKey( "json_extract", "json_extract_string" );
651-
functionRegistry.registerAlternateKey( "json_extract_json", "json_extract_string" );
627+
functionRegistry.register( "json_object", new SingleStoreJsonObjectFunction( typeConfiguration ) );
628+
functionRegistry.register( "json_array", new SingleStoreJsonArrayFunction( typeConfiguration ) );
629+
functionRegistry.register( "json_value", new SingleStoreJsonValueFunction( typeConfiguration ) );
630+
functionRegistry.register( "json_exists", new SingleStoreJsonExistsFunction( typeConfiguration ) );
631+
functionRegistry.register( "json_query", new SingleStoreJsonQueryFunction( typeConfiguration ) );
632+
functionRegistry.register( "json_arrayagg", new SingleStoreJsonArrayAggFunction( typeConfiguration ) );
633+
functionRegistry.register( "json_objectagg", new SingleStoreJsonObjectAggFunction( typeConfiguration ) );
634+
functionRegistry.register( "json_set", new SingleStoreJsonSetFunction( typeConfiguration ) );
635+
functionRegistry.register( "json_remove", new SingleStoreJsonRemoveFunction( typeConfiguration ) );
636+
functionRegistry.register( "json_mergepatch", new SingleStoreJsonMergepatchFunction( typeConfiguration ) );
637+
functionRegistry.register( "json_array_append", new SingleStoreJsonArrayAppendFunction( typeConfiguration ) );
638+
functionRegistry.register( "json_array_insert", new SingleStoreJsonArrayInsertFunction( typeConfiguration ) );
652639
}
653640

654641

@@ -978,7 +965,8 @@ public static Replacer datetimeFormat(String format) {
978965

979966
@Override
980967
public String getDropForeignKeyString() {
981-
throw new UnsupportedOperationException( "SingleStore does not support foreign keys and referential integrity" );
968+
throw new UnsupportedOperationException(
969+
"SingleStore does not support foreign keys and referential integrity" );
982970
}
983971

984972
@Override
@@ -1024,12 +1012,12 @@ public boolean canCreateCatalog() {
10241012

10251013
@Override
10261014
public String[] getCreateCatalogCommand(String catalogName) {
1027-
return new String[] { "create database " + catalogName };
1015+
return new String[] {"create database " + catalogName};
10281016
}
10291017

10301018
@Override
10311019
public String[] getDropCatalogCommand(String catalogName) {
1032-
return new String[] { "drop database " + catalogName };
1020+
return new String[] {"drop database " + catalogName};
10331021
}
10341022

10351023
@Override
@@ -1249,13 +1237,14 @@ public String getAddForeignKeyConstraintString(
12491237
String referencedTable,
12501238
String[] primaryKey,
12511239
boolean referencesPrimaryKey) {
1252-
throw new UnsupportedOperationException( "SingleStore does not support foreign keys and referential integrity." );
1240+
throw new UnsupportedOperationException(
1241+
"SingleStore does not support foreign keys and referential integrity." );
12531242
}
12541243

12551244
@Override
1256-
public String getAddForeignKeyConstraintString(
1257-
String constraintName, String foreignKeyDefinition) {
1258-
throw new UnsupportedOperationException( "SingleStore does not support foreign keys and referential integrity." );
1245+
public String getAddForeignKeyConstraintString(String constraintName, String foreignKeyDefinition) {
1246+
throw new UnsupportedOperationException(
1247+
"SingleStore does not support foreign keys and referential integrity." );
12591248
}
12601249

12611250
@Override

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SingleStoreSqlAstTranslator.java

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
import java.util.ArrayList;
88
import java.util.List;
9+
import java.util.Locale;
910

11+
import org.hibernate.dialect.Dialect;
1012
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
11-
import org.hibernate.dialect.sql.ast.MySQLSqlAstTranslator;
13+
import org.hibernate.engine.jdbc.Size;
1214
import org.hibernate.engine.spi.SessionFactoryImplementor;
1315
import org.hibernate.internal.util.collections.Stack;
1416
import org.hibernate.query.sqm.ComparisonOperator;
@@ -49,6 +51,7 @@
4951
*/
5052
public class SingleStoreSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
5153

54+
private static final int MAX_CHAR_SIZE = 8192;
5255
private final SingleStoreDialect dialect;
5356

5457
public SingleStoreSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement, SingleStoreDialect dialect) {
@@ -106,7 +109,8 @@ protected void visitInsertSource(InsertSelectStatement statement) {
106109
@Override
107110
public void visitColumnReference(ColumnReference columnReference) {
108111
final Statement currentStatement;
109-
if ( "excluded".equals( columnReference.getQualifier() ) && ( currentStatement = getStatementStack().getCurrent() ) instanceof InsertSelectStatement && ( (InsertSelectStatement) currentStatement ).getSourceSelectStatement() == null ) {
112+
if ( "excluded".equals(
113+
columnReference.getQualifier() ) && (currentStatement = getStatementStack().getCurrent()) instanceof InsertSelectStatement && ((InsertSelectStatement) currentStatement).getSourceSelectStatement() == null ) {
110114
// Accessing the excluded row for an insert-values statement in the conflict clause requires the values qualifier
111115
appendSql( "values(" );
112116
columnReference.appendReadExpression( this, null );
@@ -169,8 +173,8 @@ protected String determineColumnReferenceQualifier(ColumnReference columnReferen
169173
// Since SingleStore does not support aliasing the insert target table,
170174
// we must detect column reference that are used in the conflict clause
171175
// and use the table expression as qualifier instead
172-
if ( getClauseStack().getCurrent() != Clause.SET || !( ( currentDmlStatement = getCurrentDmlStatement() ) instanceof InsertSelectStatement ) || ( dmlAlias = currentDmlStatement.getTargetTable()
173-
.getIdentificationVariable() ) == null || !dmlAlias.equals( columnReference.getQualifier() ) ) {
176+
if ( getClauseStack().getCurrent() != Clause.SET || !((currentDmlStatement = getCurrentDmlStatement()) instanceof InsertSelectStatement) || (dmlAlias = currentDmlStatement.getTargetTable()
177+
.getIdentificationVariable()) == null || !dmlAlias.equals( columnReference.getQualifier() ) ) {
174178
return columnReference.getQualifier();
175179
}
176180
// Qualify the column reference with the table expression also when in subqueries
@@ -202,7 +206,8 @@ public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanEx
202206

203207
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
204208
// Check if current query part is already row numbering to avoid infinite recursion
205-
return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart && supportsWindowFunctions() && !isRowsOnlyFetchClauseType(
209+
return useOffsetFetchClause(
210+
queryPart ) && getQueryPartForRowNumbering() != queryPart && supportsWindowFunctions() && !isRowsOnlyFetchClauseType(
206211
queryPart );
207212
}
208213

@@ -323,8 +328,9 @@ protected void emulateTupleComparison(
323328
@Override
324329
protected void renderCombinedLimitClause(Expression offsetExpression, Expression fetchExpression) {
325330
if ( offsetExpression != null || fetchExpression != null ) {
326-
if ( getCurrentQueryPart() instanceof QueryGroup && ( ( (QueryGroup) getCurrentQueryPart() ).getSetOperator() == SetOperator.UNION || ( (QueryGroup) getCurrentQueryPart() ).getSetOperator() == SetOperator.UNION_ALL ) ) {
327-
throw new UnsupportedOperationException( "SingleStore doesn't support UNION/UNION ALL with limit clause" );
331+
if ( getCurrentQueryPart() instanceof QueryGroup && (((QueryGroup) getCurrentQueryPart()).getSetOperator() == SetOperator.UNION || ((QueryGroup) getCurrentQueryPart()).getSetOperator() == SetOperator.UNION_ALL) ) {
332+
throw new UnsupportedOperationException(
333+
"SingleStore doesn't support UNION/UNION ALL with limit clause" );
328334
}
329335
}
330336
super.renderCombinedLimitClause( offsetExpression, fetchExpression );
@@ -376,7 +382,7 @@ protected void renderBackslashEscapedLikePattern(
376382
// Since escape with empty or null character is ignored we need
377383
// four backslashes to render a single one in a like pattern
378384
if ( pattern instanceof Literal ) {
379-
Object literalValue = ( (Literal) pattern ).getLiteralValue();
385+
Object literalValue = ((Literal) pattern).getLiteralValue();
380386
if ( literalValue == null ) {
381387
pattern.accept( this );
382388
}
@@ -403,9 +409,60 @@ private boolean supportsWindowFunctions() {
403409
return true;
404410
}
405411

412+
public static String getSqlType(CastTarget castTarget, SessionFactoryImplementor factory) {
413+
final String sqlType = getCastTypeName( castTarget, factory.getTypeConfiguration() );
414+
return getSqlType( castTarget, sqlType, factory.getJdbcServices().getDialect() );
415+
}
416+
417+
private static String getSqlType(CastTarget castTarget, String sqlType, Dialect dialect) {
418+
if ( sqlType != null ) {
419+
int parenthesesIndex = sqlType.indexOf( '(' );
420+
final String baseName = parenthesesIndex == -1 ? sqlType : sqlType.substring( 0, parenthesesIndex ).trim();
421+
switch ( baseName.toLowerCase( Locale.ROOT ) ) {
422+
case "bit":
423+
return "unsigned";
424+
case "tinyint":
425+
case "smallint":
426+
case "integer":
427+
case "bigint":
428+
return "signed";
429+
case "float":
430+
case "real":
431+
case "double precision":
432+
final int precision = castTarget.getPrecision() == null ?
433+
dialect.getDefaultDecimalPrecision() :
434+
castTarget.getPrecision();
435+
final int scale = castTarget.getScale() == null ? Size.DEFAULT_SCALE : castTarget.getScale();
436+
return "decimal(" + precision + "," + scale + ")";
437+
case "char":
438+
case "varchar":
439+
case "text":
440+
case "mediumtext":
441+
case "longtext":
442+
case "set":
443+
case "enum":
444+
if ( castTarget.getLength() == null ) {
445+
if ( castTarget.getJdbcMapping().getJdbcJavaType().getJavaType() == Character.class ) {
446+
return "char(1)";
447+
}
448+
else {
449+
return "char";
450+
}
451+
}
452+
return castTarget.getLength() > MAX_CHAR_SIZE ? "char" : "char(" + castTarget.getLength() + ")";
453+
case "binary":
454+
case "varbinary":
455+
case "mediumblob":
456+
case "longblob":
457+
return castTarget.getLength() == null ? "binary" : "binary(" + castTarget.getLength() + ")";
458+
}
459+
}
460+
return sqlType;
461+
}
462+
406463
@Override
407464
public void visitCastTarget(CastTarget castTarget) {
408-
String sqlType = MySQLSqlAstTranslator.getSqlType( castTarget, getSessionFactory() );
465+
String sqlType = getSqlType( castTarget, getSessionFactory() );
409466
if ( sqlType != null ) {
410467
appendSql( sqlType );
411468
}

0 commit comments

Comments
 (0)