Skip to content

Commit 5567bc3

Browse files
committed
HHH-19297 Register SingleStore json functions to SingleStoreDialect
1 parent 7cf008b commit 5567bc3

14 files changed

+1041
-68
lines changed

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

Lines changed: 52 additions & 67 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;
@@ -504,31 +514,35 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
504514
castType( BINARY ),
505515
this
506516
)
507-
.withTypeCapacity( maxTinyLobLen, "tinyblob" )
508-
.withTypeCapacity( maxMediumLobLen, "mediumblob" )
509-
.withTypeCapacity( maxLobLen, "blob" )
510-
.build() );
517+
.withTypeCapacity( maxTinyLobLen, "tinyblob" )
518+
.withTypeCapacity( maxMediumLobLen, "mediumblob" )
519+
.withTypeCapacity( maxLobLen, "blob" )
520+
.build() );
511521

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

523533
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() );
534+
NCLOB,
535+
columnType( NCLOB ),
536+
castType( NCHAR ),
537+
this
538+
)
539+
.withTypeCapacity(
540+
maxTinyLobLen,
541+
"tinytext character set utf8"
542+
)
543+
.withTypeCapacity( maxMediumLobLen, "mediumtext character set utf8" )
544+
.withTypeCapacity( maxLobLen, "text character set utf8" )
545+
.build() );
532546

533547
ddlTypeRegistry.addDescriptor( new NativeEnumDdlTypeImpl( this ) );
534548
ddlTypeRegistry.addDescriptor( new NativeOrdinalEnumDdlTypeImpl( this ) );
@@ -586,14 +600,13 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
586600
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
587601
commonFunctionFactory.listagg_groupConcat();
588602
SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();
603+
final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
589604
BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();
590-
functionRegistry
591-
.namedDescriptorBuilder( "time" )
605+
functionRegistry.namedDescriptorBuilder( "time" )
592606
.setExactArgumentCount( 1 )
593607
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.STRING ) )
594608
.register();
595-
functionRegistry
596-
.patternDescriptorBuilder( "median", "median(?1) over ()" )
609+
functionRegistry.patternDescriptorBuilder( "median", "median(?1) over ()" )
597610
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.DOUBLE ) )
598611
.setExactArgumentCount( 1 )
599612
.setParameterTypes( NUMERIC )
@@ -610,45 +623,18 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
610623
.setParameterTypes( FunctionParameterType.INTEGER )
611624
.register();
612625
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" );
626+
functionRegistry.register( "json_object", new SingleStoreJsonObjectFunction( typeConfiguration ) );
627+
functionRegistry.register( "json_array", new SingleStoreJsonArrayFunction( typeConfiguration ) );
628+
functionRegistry.register( "json_value", new SingleStoreJsonValueFunction( typeConfiguration ) );
629+
functionRegistry.register( "json_exists", new SingleStoreJsonExistsFunction( typeConfiguration ) );
630+
functionRegistry.register( "json_query", new SingleStoreJsonQueryFunction( typeConfiguration ) );
631+
functionRegistry.register( "json_arrayagg", new SingleStoreJsonArrayAggFunction( typeConfiguration ) );
632+
functionRegistry.register( "json_objectagg", new SingleStoreJsonObjectAggFunction( typeConfiguration ) );
633+
functionRegistry.register( "json_set", new SingleStoreJsonSetFunction( typeConfiguration ) );
634+
functionRegistry.register( "json_remove", new SingleStoreJsonRemoveFunction( typeConfiguration ) );
635+
functionRegistry.register( "json_mergepatch", new SingleStoreJsonMergepatchFunction( typeConfiguration ) );
636+
functionRegistry.register( "json_array_append", new SingleStoreJsonArrayAppendFunction( typeConfiguration ) );
637+
functionRegistry.register( "json_array_insert", new SingleStoreJsonArrayInsertFunction( typeConfiguration ) );
652638
}
653639

654640

@@ -1253,8 +1239,7 @@ public String getAddForeignKeyConstraintString(
12531239
}
12541240

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

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +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;
12+
import org.hibernate.dialect.DialectDelegateWrapper;
1013
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
14+
import org.hibernate.engine.jdbc.Size;
1115
import org.hibernate.dialect.sql.ast.MySQLSqlAstTranslator;
1216
import org.hibernate.engine.spi.SessionFactoryImplementor;
1317
import org.hibernate.internal.util.collections.Stack;
@@ -49,6 +53,7 @@
4953
*/
5054
public class SingleStoreSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
5155

56+
private static final int MAX_CHAR_SIZE = 8192;
5257
private final SingleStoreDialect dialect;
5358

5459
public SingleStoreSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement, SingleStoreDialect dialect) {
@@ -403,9 +408,60 @@ private boolean supportsWindowFunctions() {
403408
return true;
404409
}
405410

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