Skip to content

Commit 34fc204

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

14 files changed

+1040
-69
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

+56-2
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) {
@@ -404,9 +407,60 @@ private boolean supportsWindowFunctions() {
404407
return true;
405408
}
406409

410+
public static String getSqlType(CastTarget castTarget, SessionFactoryImplementor factory) {
411+
final String sqlType = getCastTypeName( castTarget, factory.getTypeConfiguration() );
412+
return getSqlType( castTarget, sqlType, factory.getJdbcServices().getDialect() );
413+
}
414+
415+
private static String getSqlType(CastTarget castTarget, String sqlType, Dialect dialect) {
416+
if ( sqlType != null ) {
417+
int parenthesesIndex = sqlType.indexOf( '(' );
418+
final String baseName = parenthesesIndex == -1 ? sqlType : sqlType.substring( 0, parenthesesIndex ).trim();
419+
switch ( baseName.toLowerCase( Locale.ROOT ) ) {
420+
case "bit":
421+
return "unsigned";
422+
case "tinyint":
423+
case "smallint":
424+
case "integer":
425+
case "bigint":
426+
return "signed";
427+
case "float":
428+
case "real":
429+
case "double precision":
430+
final int precision = castTarget.getPrecision() == null ?
431+
dialect.getDefaultDecimalPrecision() :
432+
castTarget.getPrecision();
433+
final int scale = castTarget.getScale() == null ? Size.DEFAULT_SCALE : castTarget.getScale();
434+
return "decimal(" + precision + "," + scale + ")";
435+
case "char":
436+
case "varchar":
437+
case "text":
438+
case "mediumtext":
439+
case "longtext":
440+
case "set":
441+
case "enum":
442+
if ( castTarget.getLength() == null ) {
443+
if ( castTarget.getJdbcMapping().getJdbcJavaType().getJavaType() == Character.class ) {
444+
return "char(1)";
445+
}
446+
else {
447+
return "char";
448+
}
449+
}
450+
return castTarget.getLength() > MAX_CHAR_SIZE ? "char" : "char(" + castTarget.getLength() + ")";
451+
case "binary":
452+
case "varbinary":
453+
case "mediumblob":
454+
case "longblob":
455+
return castTarget.getLength() == null ? "binary" : "binary(" + castTarget.getLength() + ")";
456+
}
457+
}
458+
return sqlType;
459+
}
460+
407461
@Override
408462
public void visitCastTarget(CastTarget castTarget) {
409-
String sqlType = MySQLSqlAstTranslator.getSqlType( castTarget, getSessionFactory() );
463+
String sqlType = getSqlType( castTarget, getSessionFactory() );
410464
if ( sqlType != null ) {
411465
appendSql( sqlType );
412466
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.community.dialect.function.json;
6+
7+
import java.util.List;
8+
9+
import org.hibernate.QueryException;
10+
import org.hibernate.dialect.function.json.JsonArrayAggFunction;
11+
import org.hibernate.metamodel.model.domain.ReturnableType;
12+
import org.hibernate.sql.ast.Clause;
13+
import org.hibernate.sql.ast.SqlAstTranslator;
14+
import org.hibernate.sql.ast.spi.SqlAppender;
15+
import org.hibernate.sql.ast.tree.SqlAstNode;
16+
import org.hibernate.sql.ast.tree.expression.Distinct;
17+
import org.hibernate.sql.ast.tree.expression.Expression;
18+
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
19+
import org.hibernate.sql.ast.tree.predicate.Predicate;
20+
import org.hibernate.sql.ast.tree.select.SortSpecification;
21+
import org.hibernate.type.spi.TypeConfiguration;
22+
23+
/**
24+
* SingleStore json_arrayagg function.
25+
*/
26+
public class SingleStoreJsonArrayAggFunction extends JsonArrayAggFunction {
27+
28+
public SingleStoreJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
29+
super( false, typeConfiguration );
30+
}
31+
32+
@Override
33+
public void render(
34+
SqlAppender sqlAppender,
35+
List<? extends SqlAstNode> sqlAstArguments,
36+
Predicate filter,
37+
List<SortSpecification> withinGroup,
38+
ReturnableType<?> returnType,
39+
SqlAstTranslator<?> translator) {
40+
final boolean caseWrapper = filter != null;
41+
sqlAppender.appendSql( "concat('[',group_concat(" );
42+
final JsonNullBehavior nullBehavior;
43+
if ( sqlAstArguments.size() > 1 ) {
44+
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( 1 );
45+
}
46+
else {
47+
nullBehavior = JsonNullBehavior.ABSENT;
48+
}
49+
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
50+
final Expression arg;
51+
if ( firstArg instanceof Distinct ) {
52+
sqlAppender.appendSql( "distinct " );
53+
arg = ( (Distinct) firstArg ).getExpression();
54+
}
55+
else {
56+
arg = (Expression) firstArg;
57+
}
58+
if ( caseWrapper ) {
59+
if ( nullBehavior != JsonNullBehavior.ABSENT ) {
60+
throw new QueryException( "Can't emulate json_arrayagg filter clause when using 'null on null' clause." );
61+
}
62+
translator.getCurrentClauseStack().push( Clause.WHERE );
63+
sqlAppender.appendSql( "case when " );
64+
filter.accept( translator );
65+
translator.getCurrentClauseStack().pop();
66+
sqlAppender.appendSql( " then " );
67+
renderArgument( sqlAppender, arg, nullBehavior, translator );
68+
sqlAppender.appendSql( " else null end)" );
69+
}
70+
else {
71+
renderArgument( sqlAppender, arg, nullBehavior, translator );
72+
}
73+
if ( withinGroup != null && !withinGroup.isEmpty() ) {
74+
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
75+
sqlAppender.appendSql( " order by " );
76+
withinGroup.get( 0 ).accept( translator );
77+
for ( int i = 1; i < withinGroup.size(); i++ ) {
78+
sqlAppender.appendSql( ',' );
79+
withinGroup.get( i ).accept( translator );
80+
}
81+
translator.getCurrentClauseStack().pop();
82+
}
83+
sqlAppender.appendSql( " separator ','),']')" );
84+
}
85+
86+
@Override
87+
protected void renderArgument(
88+
SqlAppender sqlAppender, Expression arg, JsonNullBehavior nullBehavior, SqlAstTranslator<?> translator) {
89+
sqlAppender.appendSql( "to_json(" );
90+
if ( nullBehavior != JsonNullBehavior.NULL ) {
91+
sqlAppender.appendSql( "nullif(" );
92+
}
93+
arg.accept( translator );
94+
if ( nullBehavior != JsonNullBehavior.NULL ) {
95+
sqlAppender.appendSql( ",'null')" );
96+
}
97+
sqlAppender.appendSql( ')' );
98+
}
99+
}

0 commit comments

Comments
 (0)