Skip to content

Commit ec50213

Browse files
committed
HHH-18604 Fix some issues with old SQL Server versions
1 parent 791152d commit ec50213

14 files changed

+457
-91
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -403,20 +403,20 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
403403
if ( getVersion().isSameOrAfter( 13 ) ) {
404404
functionFactory.jsonValue_sqlserver();
405405
functionFactory.jsonQuery_sqlserver();
406-
functionFactory.jsonExists_sqlserver();
407-
functionFactory.jsonObject_sqlserver();
408-
functionFactory.jsonArray_sqlserver();
406+
functionFactory.jsonExists_sqlserver( getVersion().isSameOrAfter( 16 ) );
407+
functionFactory.jsonObject_sqlserver( getVersion().isSameOrAfter( 16 ) );
408+
functionFactory.jsonArray_sqlserver( getVersion().isSameOrAfter( 16 ) );
409409
functionFactory.jsonSet_sqlserver();
410410
functionFactory.jsonRemove_sqlserver();
411-
functionFactory.jsonReplace_sqlserver();
412-
functionFactory.jsonInsert_sqlserver();
413-
functionFactory.jsonArrayAppend_sqlserver();
411+
functionFactory.jsonReplace_sqlserver( getVersion().isSameOrAfter( 16 ) );
412+
functionFactory.jsonInsert_sqlserver( getVersion().isSameOrAfter( 16 ) );
413+
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
414414
functionFactory.jsonArrayInsert_sqlserver();
415415
}
416416
if ( getVersion().isSameOrAfter( 14 ) ) {
417417
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
418-
functionFactory.jsonArrayAgg_sqlserver();
419-
functionFactory.jsonObjectAgg_sqlserver();
418+
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
419+
functionFactory.jsonObjectAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
420420
}
421421
if ( getVersion().isSameOrAfter( 16 ) ) {
422422
functionFactory.leastGreatest();

hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -421,20 +421,20 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
421421
if ( getVersion().isSameOrAfter( 13 ) ) {
422422
functionFactory.jsonValue_sqlserver();
423423
functionFactory.jsonQuery_sqlserver();
424-
functionFactory.jsonExists_sqlserver();
425-
functionFactory.jsonObject_sqlserver();
426-
functionFactory.jsonArray_sqlserver();
424+
functionFactory.jsonExists_sqlserver( getVersion().isSameOrAfter( 16 ) );
425+
functionFactory.jsonObject_sqlserver( getVersion().isSameOrAfter( 16 ) );
426+
functionFactory.jsonArray_sqlserver( getVersion().isSameOrAfter( 16 ) );
427427
functionFactory.jsonSet_sqlserver();
428428
functionFactory.jsonRemove_sqlserver();
429-
functionFactory.jsonReplace_sqlserver();
430-
functionFactory.jsonInsert_sqlserver();
431-
functionFactory.jsonArrayAppend_sqlserver();
429+
functionFactory.jsonReplace_sqlserver( getVersion().isSameOrAfter( 16 ) );
430+
functionFactory.jsonInsert_sqlserver( getVersion().isSameOrAfter( 16 ) );
431+
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
432432
functionFactory.jsonArrayInsert_sqlserver();
433433
}
434434
if ( getVersion().isSameOrAfter( 14 ) ) {
435435
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
436-
functionFactory.jsonArrayAgg_sqlserver();
437-
functionFactory.jsonObjectAgg_sqlserver();
436+
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
437+
functionFactory.jsonObjectAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
438438
}
439439
if ( getVersion().isSameOrAfter( 16 ) ) {
440440
functionFactory.leastGreatest();

hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3557,8 +3557,8 @@ public void jsonExists_h2() {
35573557
/**
35583558
* SQL Server json_exists() function
35593559
*/
3560-
public void jsonExists_sqlserver() {
3561-
functionRegistry.register( "json_exists", new SQLServerJsonExistsFunction( typeConfiguration ) );
3560+
public void jsonExists_sqlserver(boolean supportsExtendedJson) {
3561+
functionRegistry.register( "json_exists", new SQLServerJsonExistsFunction( supportsExtendedJson, typeConfiguration ) );
35623562
}
35633563

35643564
/**
@@ -3613,8 +3613,8 @@ public void jsonObject_oracle() {
36133613
/**
36143614
* SQL Server json_object() function
36153615
*/
3616-
public void jsonObject_sqlserver() {
3617-
functionRegistry.register( "json_object", new SQLServerJsonObjectFunction( typeConfiguration ) );
3616+
public void jsonObject_sqlserver(boolean supportsExtendedJson) {
3617+
functionRegistry.register( "json_object", new SQLServerJsonObjectFunction( supportsExtendedJson, typeConfiguration ) );
36183618
}
36193619

36203620
/**
@@ -3669,8 +3669,8 @@ public void jsonArray_oracle() {
36693669
/**
36703670
* SQL Server json_array() function
36713671
*/
3672-
public void jsonArray_sqlserver() {
3673-
functionRegistry.register( "json_array", new SQLServerJsonArrayFunction( typeConfiguration ) );
3672+
public void jsonArray_sqlserver(boolean supportsExtendedJson) {
3673+
functionRegistry.register( "json_array", new SQLServerJsonArrayFunction( supportsExtendedJson, typeConfiguration ) );
36743674
}
36753675

36763676
/**
@@ -3739,8 +3739,8 @@ public void jsonArrayAgg_postgresql(boolean supportsStandard) {
37393739
/**
37403740
* SQL Server json_arrayagg() function
37413741
*/
3742-
public void jsonArrayAgg_sqlserver() {
3743-
functionRegistry.register( "json_arrayagg", new SQLServerJsonArrayAggFunction( typeConfiguration ) );
3742+
public void jsonArrayAgg_sqlserver(boolean supportsExtendedJson) {
3743+
functionRegistry.register( "json_arrayagg", new SQLServerJsonArrayAggFunction( supportsExtendedJson, typeConfiguration ) );
37443744
}
37453745

37463746
/**
@@ -3809,8 +3809,8 @@ public void jsonObjectAgg_mariadb() {
38093809
/**
38103810
* SQL Server json_objectagg() function
38113811
*/
3812-
public void jsonObjectAgg_sqlserver() {
3813-
functionRegistry.register( "json_objectagg", new SQLServerJsonObjectAggFunction( typeConfiguration ) );
3812+
public void jsonObjectAgg_sqlserver(boolean supportsExtendedJson) {
3813+
functionRegistry.register( "json_objectagg", new SQLServerJsonObjectAggFunction( supportsExtendedJson, typeConfiguration ) );
38143814
}
38153815

38163816
/**
@@ -3943,8 +3943,8 @@ public void jsonReplace_oracle() {
39433943
/**
39443944
* SQL server json_replace() function
39453945
*/
3946-
public void jsonReplace_sqlserver() {
3947-
functionRegistry.register( "json_replace", new SQLServerJsonReplaceFunction( typeConfiguration ) );
3946+
public void jsonReplace_sqlserver(boolean supportsExtendedJson) {
3947+
functionRegistry.register( "json_replace", new SQLServerJsonReplaceFunction( supportsExtendedJson, typeConfiguration ) );
39483948
}
39493949

39503950
/**
@@ -3981,8 +3981,8 @@ public void jsonInsert_oracle() {
39813981
/**
39823982
* SQL server json_insert() function
39833983
*/
3984-
public void jsonInsert_sqlserver() {
3985-
functionRegistry.register( "json_insert", new SQLServerJsonInsertFunction( typeConfiguration ) );
3984+
public void jsonInsert_sqlserver(boolean supportsExtendedJson) {
3985+
functionRegistry.register( "json_insert", new SQLServerJsonInsertFunction( supportsExtendedJson, typeConfiguration ) );
39863986
}
39873987

39883988
/**
@@ -4056,8 +4056,8 @@ public void jsonArrayAppend_oracle() {
40564056
/**
40574057
* SQL server json_array_append() function
40584058
*/
4059-
public void jsonArrayAppend_sqlserver() {
4060-
functionRegistry.register( "json_array_append", new SQLServerJsonArrayAppendFunction( typeConfiguration ) );
4059+
public void jsonArrayAppend_sqlserver(boolean supportsExtendedJson) {
4060+
functionRegistry.register( "json_array_append", new SQLServerJsonArrayAppendFunction( supportsExtendedJson, typeConfiguration ) );
40614061
}
40624062

40634063
/**

hibernate-core/src/main/java/org/hibernate/dialect/function/json/ExpressionTypeHelper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hibernate.query.sqm.CastType;
1010
import org.hibernate.sql.ast.tree.SqlAstNode;
1111
import org.hibernate.sql.ast.tree.expression.Expression;
12+
import org.hibernate.type.descriptor.jdbc.JdbcType;
1213

1314
@Internal
1415
public class ExpressionTypeHelper {
@@ -34,6 +35,12 @@ public static boolean isJson(SqlAstNode node) {
3435
&& expressionType.getSingleJdbcMapping().getJdbcType().isJson();
3536
}
3637

38+
public static JdbcType getSingleJdbcType(SqlAstNode node) {
39+
final Expression expression = (Expression) node;
40+
final JdbcMappingContainer expressionType = expression.getExpressionType();
41+
return expressionType.getSingleJdbcMapping().getJdbcType();
42+
}
43+
3744
public static boolean isBoolean(CastType castType) {
3845
switch ( castType ) {
3946
case BOOLEAN:

hibernate-core/src/main/java/org/hibernate/dialect/function/json/JsonPathHelper.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,51 @@ private static int lastIndexOfWhitespace(String jsonPath, int i) {
204204
return i + 1;
205205
}
206206

207+
public static void inlinePassingClause(
208+
List<JsonPathElement> jsonPathElements,
209+
JsonPathPassingClause passingClause,
210+
SqlAstTranslator<?> walker) {
211+
for ( int i = 0; i < jsonPathElements.size(); i++ ) {
212+
final JsonPathElement jsonPathElement = jsonPathElements.get( i );
213+
if ( jsonPathElement instanceof JsonParameterIndexAccess parameterIndexAccess ) {
214+
final Expression expression = passingClause.getPassingExpressions()
215+
.get( parameterIndexAccess.parameterName() );
216+
if ( expression == null ) {
217+
throw new QueryException( "JSON path [" + toJsonPath( jsonPathElements ) + "] uses parameter [" + parameterIndexAccess.parameterName() + "] that is not passed" );
218+
}
219+
jsonPathElements.set( i, new JsonIndexAccess( walker.getLiteralValue( expression ) ) );
220+
}
221+
}
222+
}
223+
224+
public static String toJsonPath(List<JsonPathElement> pathElements) {
225+
return toJsonPath( pathElements, 0, pathElements.size() );
226+
}
227+
228+
public static String toJsonPath(List<JsonPathElement> pathElements, int start, int end) {
229+
final StringBuilder jsonPath = new StringBuilder();
230+
jsonPath.append( "$" );
231+
for ( int i = start; i < end; i++ ) {
232+
final JsonPathElement jsonPathElement = pathElements.get( i );
233+
if ( jsonPathElement instanceof JsonAttribute pathAttribute ) {
234+
jsonPath.append( '.' );
235+
jsonPath.append( pathAttribute.attribute() );
236+
}
237+
else if ( jsonPathElement instanceof JsonParameterIndexAccess parameterIndexAccess ) {
238+
jsonPath.append( "[$" );
239+
jsonPath.append( parameterIndexAccess.parameterName() );
240+
jsonPath.append( "]" );
241+
}
242+
else {
243+
assert jsonPathElement instanceof JsonIndexAccess;
244+
jsonPath.append( "[" );
245+
jsonPath.append( ( (JsonIndexAccess) jsonPathElement ).index() );
246+
jsonPath.append( "]" );
247+
}
248+
}
249+
return jsonPath.toString();
250+
}
251+
207252
public sealed interface JsonPathElement {}
208253
public record JsonAttribute(String attribute) implements JsonPathElement {}
209254
public record JsonIndexAccess(int index) implements JsonPathElement {}

hibernate-core/src/main/java/org/hibernate/dialect/function/json/SQLServerJsonArrayAggFunction.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@
1717
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
1818
import org.hibernate.sql.ast.tree.predicate.Predicate;
1919
import org.hibernate.sql.ast.tree.select.SortSpecification;
20+
import org.hibernate.type.SqlTypes;
21+
import org.hibernate.type.descriptor.jdbc.JdbcType;
2022
import org.hibernate.type.spi.TypeConfiguration;
2123

2224
/**
2325
* SQL Server json_arrayagg function.
2426
*/
2527
public class SQLServerJsonArrayAggFunction extends JsonArrayAggFunction {
2628

27-
public SQLServerJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
29+
private final boolean supportsExtendedJson;
30+
31+
public SQLServerJsonArrayAggFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
2832
super( false, typeConfiguration );
33+
this.supportsExtendedJson = supportsExtendedJson;
2934
}
3035

3136
@Override
@@ -90,10 +95,38 @@ protected void renderArgument(
9095
Expression arg,
9196
JsonNullBehavior nullBehavior,
9297
SqlAstTranslator<?> translator) {
93-
sqlAppender.appendSql( "substring(json_array(" );
94-
arg.accept( translator );
95-
sqlAppender.appendSql( " null on null),2,len(json_array(" );
96-
arg.accept( translator );
97-
sqlAppender.appendSql( " null on null))-2)" );
98+
if ( supportsExtendedJson ) {
99+
sqlAppender.appendSql( "substring(json_array(" );
100+
arg.accept( translator );
101+
sqlAppender.appendSql( " null on null),2,len(json_array(" );
102+
arg.accept( translator );
103+
sqlAppender.appendSql( "))-2)" );
104+
}
105+
else {
106+
sqlAppender.appendSql( "substring(json_modify('[]','append $'," );
107+
final boolean needsConversion = needsConversion( arg );
108+
if ( needsConversion ) {
109+
sqlAppender.appendSql( "convert(nvarchar(max)," );
110+
}
111+
arg.accept( translator );
112+
if ( needsConversion ) {
113+
sqlAppender.appendSql( ')' );
114+
}
115+
sqlAppender.appendSql( "),2,len(json_modify('[]','append $'," );
116+
if ( needsConversion ) {
117+
sqlAppender.appendSql( "convert(nvarchar(max)," );
118+
}
119+
arg.accept( translator );
120+
if ( needsConversion ) {
121+
sqlAppender.appendSql( ')' );
122+
}
123+
sqlAppender.appendSql( "))-2)" );
124+
}
125+
}
126+
127+
static boolean needsConversion(Expression arg) {
128+
final JdbcType jdbcType = ExpressionTypeHelper.getSingleJdbcType( arg );
129+
// json_modify() doesn't seem to like UUID values
130+
return jdbcType.getDdlTypeCode() == SqlTypes.UUID;
98131
}
99132
}

hibernate-core/src/main/java/org/hibernate/dialect/function/json/SQLServerJsonArrayAppendFunction.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
*/
1919
public class SQLServerJsonArrayAppendFunction extends AbstractJsonArrayAppendFunction {
2020

21-
public SQLServerJsonArrayAppendFunction(TypeConfiguration typeConfiguration) {
21+
private final boolean supportsExtendedJson;
22+
23+
public SQLServerJsonArrayAppendFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
2224
super( typeConfiguration );
25+
this.supportsExtendedJson = supportsExtendedJson;
2326
}
2427

2528
@Override
@@ -33,7 +36,31 @@ public void render(
3336
final SqlAstNode value = arguments.get( 2 );
3437
sqlAppender.appendSql( "(select coalesce(" );
3538
sqlAppender.appendSql("case when json_modify(json_query(t.d,t.p),'append $',t.v) is not null then json_modify(t.d,t.p,json_modify(json_query(t.d,t.p),'append $',t.v)) end,");
36-
sqlAppender.appendSql("json_modify(t.d,t.p,json_query('['+coalesce(json_value(t.d,t.p),case when json_path_exists(t.d,t.p)=1 then 'null' end)+stuff(json_array(t.v),1,1,','))),");
39+
sqlAppender.appendSql("json_modify(t.d,t.p,json_query('['+coalesce(json_value(t.d,t.p),case when ");
40+
if ( supportsExtendedJson ) {
41+
sqlAppender.appendSql( "json_path_exists(t.d,t.p)=1" );
42+
}
43+
else {
44+
final List<JsonPathHelper.JsonPathElement> pathElements =
45+
JsonPathHelper.parseJsonPathElements( translator.getLiteralValue( jsonPath ) );
46+
final JsonPathHelper.JsonPathElement lastPathElement = pathElements.get( pathElements.size() - 1 );
47+
final String prefix = JsonPathHelper.toJsonPath( pathElements, 0, pathElements.size() - 1 );
48+
final String terminalKey;
49+
if ( lastPathElement instanceof JsonPathHelper.JsonIndexAccess indexAccess ) {
50+
terminalKey = String.valueOf( indexAccess.index() );
51+
}
52+
else {
53+
assert lastPathElement instanceof JsonPathHelper.JsonAttribute;
54+
terminalKey = ( (JsonPathHelper.JsonAttribute) lastPathElement ).attribute();
55+
}
56+
57+
sqlAppender.appendSql( "(select 1 from openjson(t.d," );
58+
sqlAppender.appendSingleQuoteEscapedString( prefix );
59+
sqlAppender.appendSql( ") t where t.[key]=" );
60+
sqlAppender.appendSingleQuoteEscapedString( terminalKey );
61+
sqlAppender.appendSql( ")=1" );
62+
}
63+
sqlAppender.appendSql( " then 'null' end)+stuff(json_modify('[]','append $',t.v),1,1,','))),");
3764
sqlAppender.appendSql( "t.d) from (values (" );
3865
json.accept( translator );
3966
sqlAppender.appendSql( ',' );
@@ -49,6 +76,11 @@ protected void renderValue(SqlAppender sqlAppender, SqlAstNode value, SqlAstTran
4976
value.accept( translator );
5077
sqlAppender.appendSql( " as bit)" );
5178
}
79+
else if ( ExpressionTypeHelper.isJson( value ) ) {
80+
sqlAppender.appendSql( "json_query(" );
81+
value.accept( translator );
82+
sqlAppender.appendSql( ')' );
83+
}
5284
else {
5385
value.accept( translator );
5486
}

0 commit comments

Comments
 (0)