Skip to content

Commit 9c7c200

Browse files
committed
HHH-19297 Register SingleStore json functions to SingleStoreDialect
1 parent c73ded8 commit 9c7c200

14 files changed

+1055
-92
lines changed

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

+52-67
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@
2424
import org.hibernate.boot.model.relational.Exportable;
2525
import org.hibernate.boot.model.relational.Sequence;
2626
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
27+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayAggFunction;
28+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayAppendFunction;
29+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayFunction;
30+
import org.hibernate.community.dialect.function.json.SingleStoreJsonArrayInsertFunction;
31+
import org.hibernate.community.dialect.function.json.SingleStoreJsonExistsFunction;
32+
import org.hibernate.community.dialect.function.json.SingleStoreJsonMergepatchFunction;
33+
import org.hibernate.community.dialect.function.json.SingleStoreJsonObjectAggFunction;
34+
import org.hibernate.community.dialect.function.json.SingleStoreJsonObjectFunction;
35+
import org.hibernate.community.dialect.function.json.SingleStoreJsonQueryFunction;
36+
import org.hibernate.community.dialect.function.json.SingleStoreJsonRemoveFunction;
37+
import org.hibernate.community.dialect.function.json.SingleStoreJsonSetFunction;
38+
import org.hibernate.community.dialect.function.json.SingleStoreJsonValueFunction;
2739
import org.hibernate.dialect.DatabaseVersion;
2840
import org.hibernate.dialect.Dialect;
2941
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
@@ -64,14 +76,14 @@
6476
import org.hibernate.mapping.UniqueKey;
6577
import org.hibernate.metamodel.mapping.EntityMappingType;
6678
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
79+
import org.hibernate.query.common.TemporalUnit;
6780
import org.hibernate.query.sqm.CastType;
6881
import org.hibernate.query.sqm.IntervalType;
69-
import org.hibernate.query.common.TemporalUnit;
7082
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
71-
import org.hibernate.query.sqm.mutation.spi.AfterUseAction;
72-
import org.hibernate.query.sqm.mutation.spi.BeforeUseAction;
7383
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy;
7484
import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy;
85+
import org.hibernate.query.sqm.mutation.spi.AfterUseAction;
86+
import org.hibernate.query.sqm.mutation.spi.BeforeUseAction;
7587
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
7688
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
7789
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
@@ -83,7 +95,6 @@
8395
import org.hibernate.sql.ast.tree.Statement;
8496
import org.hibernate.sql.exec.spi.JdbcOperation;
8597
import org.hibernate.tool.schema.spi.Exporter;
86-
import org.hibernate.type.BasicType;
8798
import org.hibernate.type.BasicTypeRegistry;
8899
import org.hibernate.type.NullType;
89100
import org.hibernate.type.SqlTypes;
@@ -99,13 +110,12 @@
99110
import org.hibernate.type.descriptor.sql.internal.NativeEnumDdlTypeImpl;
100111
import org.hibernate.type.descriptor.sql.internal.NativeOrdinalEnumDdlTypeImpl;
101112
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
113+
import org.hibernate.type.spi.TypeConfiguration;
102114

103115
import jakarta.persistence.TemporalType;
104116

105117
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
106-
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY;
107118
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
108-
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
109119
import static org.hibernate.type.SqlTypes.BIGINT;
110120
import static org.hibernate.type.SqlTypes.BINARY;
111121
import static org.hibernate.type.SqlTypes.BIT;
@@ -503,31 +513,35 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
503513
castType( BINARY ),
504514
this
505515
)
506-
.withTypeCapacity( maxTinyLobLen, "tinyblob" )
507-
.withTypeCapacity( maxMediumLobLen, "mediumblob" )
508-
.withTypeCapacity( maxLobLen, "blob" )
509-
.build() );
516+
.withTypeCapacity( maxTinyLobLen, "tinyblob" )
517+
.withTypeCapacity( maxMediumLobLen, "mediumblob" )
518+
.withTypeCapacity( maxLobLen, "blob" )
519+
.build() );
510520

511521
ddlTypeRegistry.addDescriptor( CapacityDependentDdlType.builder(
512522
CLOB,
513523
columnType( CLOB ),
514524
castType( CHAR ),
515525
this
516526
)
517-
.withTypeCapacity( maxTinyLobLen, "tinytext" )
518-
.withTypeCapacity( maxMediumLobLen, "mediumtext" )
519-
.withTypeCapacity( maxLobLen, "text" )
520-
.build() );
527+
.withTypeCapacity( maxTinyLobLen, "tinytext" )
528+
.withTypeCapacity( maxMediumLobLen, "mediumtext" )
529+
.withTypeCapacity( maxLobLen, "text" )
530+
.build() );
521531

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

532546
ddlTypeRegistry.addDescriptor( new NativeEnumDdlTypeImpl( this ) );
533547
ddlTypeRegistry.addDescriptor( new NativeOrdinalEnumDdlTypeImpl( this ) );
@@ -585,14 +599,13 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
585599
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
586600
commonFunctionFactory.listagg_groupConcat();
587601
SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();
602+
final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
588603
BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();
589-
functionRegistry
590-
.namedDescriptorBuilder( "time" )
604+
functionRegistry.namedDescriptorBuilder( "time" )
591605
.setExactArgumentCount( 1 )
592606
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.STRING ) )
593607
.register();
594-
functionRegistry
595-
.patternDescriptorBuilder( "median", "median(?1) over ()" )
608+
functionRegistry.patternDescriptorBuilder( "median", "median(?1) over ()" )
596609
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.DOUBLE ) )
597610
.setExactArgumentCount( 1 )
598611
.setParameterTypes( NUMERIC )
@@ -609,45 +622,18 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
609622
.setParameterTypes( FunctionParameterType.INTEGER )
610623
.register();
611624
functionRegistry.registerAlternateKey( "char", "chr" );
612-
BasicType<Boolean> booleanType = basicTypeRegistry.resolve( StandardBasicTypes.BOOLEAN );
613-
functionRegistry.namedDescriptorBuilder( "json_array_contains_string" )
614-
.setInvariantType( booleanType )
615-
.setExactArgumentCount( 2 )
616-
.setParameterTypes( ANY, STRING )
617-
.register();
618-
functionRegistry.registerAlternateKey( "json_array_contains", "json_array_contains_string" );
619-
functionRegistry.namedDescriptorBuilder( "json_array_contains_json" )
620-
.setInvariantType( booleanType )
621-
.setExactArgumentCount( 2 )
622-
.setParameterTypes( ANY, ANY )
623-
.register();
624-
functionRegistry.namedDescriptorBuilder( "json_array_contains_double" )
625-
.setInvariantType( booleanType )
626-
.setExactArgumentCount( 2 )
627-
.setParameterTypes( ANY, NUMERIC )
628-
.register();
629-
functionRegistry.namedDescriptorBuilder( "json_match_any_exists" )
630-
.setInvariantType( booleanType )
631-
.setMinArgumentCount( 1 )
632-
.register();
633-
functionRegistry.namedDescriptorBuilder( "json_match_any" )
634-
.setInvariantType( booleanType )
635-
.setMinArgumentCount( 1 )
636-
.register();
637-
functionRegistry.namedDescriptorBuilder( "json_extract_string" )
638-
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.STRING ) )
639-
.setMinArgumentCount( 1 )
640-
.register();
641-
functionRegistry.namedDescriptorBuilder( "json_extract_double" )
642-
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.DOUBLE ) )
643-
.setMinArgumentCount( 1 )
644-
.register();
645-
functionRegistry.namedDescriptorBuilder( "json_extract_bigint" )
646-
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.BIG_INTEGER ) )
647-
.setMinArgumentCount( 1 )
648-
.register();
649-
functionRegistry.registerAlternateKey( "json_extract", "json_extract_string" );
650-
functionRegistry.registerAlternateKey( "json_extract_json", "json_extract_string" );
625+
functionRegistry.register( "json_object", new SingleStoreJsonObjectFunction( typeConfiguration ) );
626+
functionRegistry.register( "json_array", new SingleStoreJsonArrayFunction( typeConfiguration ) );
627+
functionRegistry.register( "json_value", new SingleStoreJsonValueFunction( typeConfiguration ) );
628+
functionRegistry.register( "json_exists", new SingleStoreJsonExistsFunction( typeConfiguration ) );
629+
functionRegistry.register( "json_query", new SingleStoreJsonQueryFunction( typeConfiguration ) );
630+
functionRegistry.register( "json_arrayagg", new SingleStoreJsonArrayAggFunction( typeConfiguration ) );
631+
functionRegistry.register( "json_objectagg", new SingleStoreJsonObjectAggFunction( typeConfiguration ) );
632+
functionRegistry.register( "json_set", new SingleStoreJsonSetFunction( typeConfiguration ) );
633+
functionRegistry.register( "json_remove", new SingleStoreJsonRemoveFunction( typeConfiguration ) );
634+
functionRegistry.register( "json_mergepatch", new SingleStoreJsonMergepatchFunction( typeConfiguration ) );
635+
functionRegistry.register( "json_array_append", new SingleStoreJsonArrayAppendFunction( typeConfiguration ) );
636+
functionRegistry.register( "json_array_insert", new SingleStoreJsonArrayInsertFunction( typeConfiguration ) );
651637
}
652638

653639

@@ -1252,8 +1238,7 @@ public String getAddForeignKeyConstraintString(
12521238
}
12531239

12541240
@Override
1255-
public String getAddForeignKeyConstraintString(
1256-
String constraintName, String foreignKeyDefinition) {
1241+
public String getAddForeignKeyConstraintString(String constraintName, String foreignKeyDefinition) {
12571242
throw new UnsupportedOperationException( "SingleStore does not support foreign keys and referential integrity." );
12581243
}
12591244

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

+71-25
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
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.DialectDelegateWrapper;
1113
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
12-
import org.hibernate.dialect.MySQLSqlAstTranslator;
14+
import org.hibernate.engine.jdbc.Size;
1315
import org.hibernate.engine.spi.SessionFactoryImplementor;
1416
import org.hibernate.internal.util.collections.Stack;
1517
import org.hibernate.query.sqm.ComparisonOperator;
@@ -50,6 +52,7 @@
5052
*/
5153
public class SingleStoreSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
5254

55+
private static final int MAX_CHAR_SIZE = 8192;
5356
private final SingleStoreDialect dialect;
5457

5558
public SingleStoreSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
@@ -72,8 +75,7 @@ public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeti
7275
}
7376

7477
@Override
75-
protected void renderSelectTupleComparison(
76-
List<SqlSelection> lhsExpressions, SqlTuple tuple, ComparisonOperator operator) {
78+
protected void renderSelectTupleComparison(List<SqlSelection> lhsExpressions, SqlTuple tuple, ComparisonOperator operator) {
7779
emulateSelectTupleComparison( lhsExpressions, tuple.getExpressions(), operator, true );
7880
}
7981

@@ -87,13 +89,9 @@ protected void visitInsertSource(InsertSelectStatement statement) {
8789
columnNames.add( targetColumnReference.getColumnExpression() );
8890
}
8991
appendSql( "select * from " );
90-
emulateQueryPartTableReferenceColumnAliasing( new QueryPartTableReference(
91-
new SelectStatement( statement.getSourceSelectStatement() ),
92-
"excluded",
93-
columnNames,
94-
false,
95-
getSessionFactory()
96-
) );
92+
emulateQueryPartTableReferenceColumnAliasing(
93+
new QueryPartTableReference( new SelectStatement( statement.getSourceSelectStatement() ),
94+
"excluded", columnNames, false, getSessionFactory() ) );
9795
}
9896
else {
9997
statement.getSourceSelectStatement().accept( this );
@@ -107,7 +105,8 @@ protected void visitInsertSource(InsertSelectStatement statement) {
107105
@Override
108106
public void visitColumnReference(ColumnReference columnReference) {
109107
final Statement currentStatement;
110-
if ( "excluded".equals( columnReference.getQualifier() ) && ( currentStatement = getStatementStack().getCurrent() ) instanceof InsertSelectStatement && ( (InsertSelectStatement) currentStatement ).getSourceSelectStatement() == null ) {
108+
if ( "excluded".equals(
109+
columnReference.getQualifier() ) && (currentStatement = getStatementStack().getCurrent()) instanceof InsertSelectStatement && ((InsertSelectStatement) currentStatement).getSourceSelectStatement() == null ) {
111110
// Accessing the excluded row for an insert-values statement in the conflict clause requires the values qualifier
112111
appendSql( "values(" );
113112
columnReference.appendReadExpression( this, null );
@@ -170,8 +169,8 @@ protected String determineColumnReferenceQualifier(ColumnReference columnReferen
170169
// Since SingleStore does not support aliasing the insert target table,
171170
// we must detect column reference that are used in the conflict clause
172171
// and use the table expression as qualifier instead
173-
if ( getClauseStack().getCurrent() != Clause.SET || !( ( currentDmlStatement = getCurrentDmlStatement() ) instanceof InsertSelectStatement ) || ( dmlAlias = currentDmlStatement.getTargetTable()
174-
.getIdentificationVariable() ) == null || !dmlAlias.equals( columnReference.getQualifier() ) ) {
172+
if ( getClauseStack().getCurrent() != Clause.SET || !((currentDmlStatement = getCurrentDmlStatement()) instanceof InsertSelectStatement) || (dmlAlias = currentDmlStatement.getTargetTable()
173+
.getIdentificationVariable()) == null || !dmlAlias.equals( columnReference.getQualifier() ) ) {
175174
return columnReference.getQualifier();
176175
}
177176
// Qualify the column reference with the table expression also when in subqueries
@@ -203,7 +202,8 @@ public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanEx
203202

204203
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
205204
// Check if current query part is already row numbering to avoid infinite recursion
206-
return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart && supportsWindowFunctions() && !isRowsOnlyFetchClauseType(
205+
return useOffsetFetchClause(
206+
queryPart ) && getQueryPartForRowNumbering() != queryPart && supportsWindowFunctions() && !isRowsOnlyFetchClauseType(
207207
queryPart );
208208
}
209209

@@ -300,11 +300,7 @@ private void renderDistinct(Expression lhs, ComparisonOperator operator, Express
300300
}
301301

302302
@Override
303-
protected void emulateTupleComparison(
304-
final List<? extends SqlAstNode> lhsExpressions,
305-
final List<? extends SqlAstNode> rhsExpressions,
306-
ComparisonOperator operator,
307-
boolean indexOptimized) {
303+
protected void emulateTupleComparison(final List<? extends SqlAstNode> lhsExpressions, final List<? extends SqlAstNode> rhsExpressions, ComparisonOperator operator, boolean indexOptimized) {
308304
if ( operator == ComparisonOperator.DISTINCT_FROM || operator == ComparisonOperator.NOT_DISTINCT_FROM ) {
309305
final int size = lhsExpressions.size();
310306
assert size == rhsExpressions.size();
@@ -324,8 +320,9 @@ protected void emulateTupleComparison(
324320
@Override
325321
protected void renderCombinedLimitClause(Expression offsetExpression, Expression fetchExpression) {
326322
if ( offsetExpression != null || fetchExpression != null ) {
327-
if ( getCurrentQueryPart() instanceof QueryGroup && ( ( (QueryGroup) getCurrentQueryPart() ).getSetOperator() == SetOperator.UNION || ( (QueryGroup) getCurrentQueryPart() ).getSetOperator() == SetOperator.UNION_ALL ) ) {
328-
throw new UnsupportedOperationException( "SingleStore doesn't support UNION/UNION ALL with limit clause" );
323+
if ( getCurrentQueryPart() instanceof QueryGroup && (((QueryGroup) getCurrentQueryPart()).getSetOperator() == SetOperator.UNION || ((QueryGroup) getCurrentQueryPart()).getSetOperator() == SetOperator.UNION_ALL) ) {
324+
throw new UnsupportedOperationException(
325+
"SingleStore doesn't support UNION/UNION ALL with limit clause" );
329326
}
330327
}
331328
super.renderCombinedLimitClause( offsetExpression, fetchExpression );
@@ -362,8 +359,7 @@ public void visitLikePredicate(LikePredicate likePredicate) {
362359
}
363360

364361
@Override
365-
protected void renderBackslashEscapedLikePattern(
366-
Expression pattern, Expression escapeCharacter, boolean noBackslashEscapes) {
362+
protected void renderBackslashEscapedLikePattern(Expression pattern, Expression escapeCharacter, boolean noBackslashEscapes) {
367363
if ( escapeCharacter != null ) {
368364
appendSql( "replace" );
369365
appendSql( OPEN_PARENTHESIS );
@@ -377,7 +373,7 @@ protected void renderBackslashEscapedLikePattern(
377373
// Since escape with empty or null character is ignored we need
378374
// four backslashes to render a single one in a like pattern
379375
if ( pattern instanceof Literal ) {
380-
Object literalValue = ( (Literal) pattern ).getLiteralValue();
376+
Object literalValue = ((Literal) pattern).getLiteralValue();
381377
if ( literalValue == null ) {
382378
pattern.accept( this );
383379
}
@@ -404,9 +400,59 @@ private boolean supportsWindowFunctions() {
404400
return true;
405401
}
406402

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

0 commit comments

Comments
 (0)