HHH-18604 Fix some issues with old SQL Server versions

This commit is contained in:
Christian Beikov 2024-09-19 19:30:27 +02:00
parent 791152d858
commit ec502138b1
14 changed files with 457 additions and 91 deletions

View File

@ -403,20 +403,20 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
if ( getVersion().isSameOrAfter( 13 ) ) {
functionFactory.jsonValue_sqlserver();
functionFactory.jsonQuery_sqlserver();
functionFactory.jsonExists_sqlserver();
functionFactory.jsonObject_sqlserver();
functionFactory.jsonArray_sqlserver();
functionFactory.jsonExists_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonObject_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonArray_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonSet_sqlserver();
functionFactory.jsonRemove_sqlserver();
functionFactory.jsonReplace_sqlserver();
functionFactory.jsonInsert_sqlserver();
functionFactory.jsonArrayAppend_sqlserver();
functionFactory.jsonReplace_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonInsert_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonArrayInsert_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
functionFactory.jsonArrayAgg_sqlserver();
functionFactory.jsonObjectAgg_sqlserver();
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonObjectAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
}
if ( getVersion().isSameOrAfter( 16 ) ) {
functionFactory.leastGreatest();

View File

@ -421,20 +421,20 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
if ( getVersion().isSameOrAfter( 13 ) ) {
functionFactory.jsonValue_sqlserver();
functionFactory.jsonQuery_sqlserver();
functionFactory.jsonExists_sqlserver();
functionFactory.jsonObject_sqlserver();
functionFactory.jsonArray_sqlserver();
functionFactory.jsonExists_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonObject_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonArray_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonSet_sqlserver();
functionFactory.jsonRemove_sqlserver();
functionFactory.jsonReplace_sqlserver();
functionFactory.jsonInsert_sqlserver();
functionFactory.jsonArrayAppend_sqlserver();
functionFactory.jsonReplace_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonInsert_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonArrayInsert_sqlserver();
}
if ( getVersion().isSameOrAfter( 14 ) ) {
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
functionFactory.jsonArrayAgg_sqlserver();
functionFactory.jsonObjectAgg_sqlserver();
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
functionFactory.jsonObjectAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
}
if ( getVersion().isSameOrAfter( 16 ) ) {
functionFactory.leastGreatest();

View File

@ -3557,8 +3557,8 @@ public class CommonFunctionFactory {
/**
* SQL Server json_exists() function
*/
public void jsonExists_sqlserver() {
functionRegistry.register( "json_exists", new SQLServerJsonExistsFunction( typeConfiguration ) );
public void jsonExists_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_exists", new SQLServerJsonExistsFunction( supportsExtendedJson, typeConfiguration ) );
}
/**
@ -3613,8 +3613,8 @@ public class CommonFunctionFactory {
/**
* SQL Server json_object() function
*/
public void jsonObject_sqlserver() {
functionRegistry.register( "json_object", new SQLServerJsonObjectFunction( typeConfiguration ) );
public void jsonObject_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_object", new SQLServerJsonObjectFunction( supportsExtendedJson, typeConfiguration ) );
}
/**
@ -3669,8 +3669,8 @@ public class CommonFunctionFactory {
/**
* SQL Server json_array() function
*/
public void jsonArray_sqlserver() {
functionRegistry.register( "json_array", new SQLServerJsonArrayFunction( typeConfiguration ) );
public void jsonArray_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_array", new SQLServerJsonArrayFunction( supportsExtendedJson, typeConfiguration ) );
}
/**
@ -3739,8 +3739,8 @@ public class CommonFunctionFactory {
/**
* SQL Server json_arrayagg() function
*/
public void jsonArrayAgg_sqlserver() {
functionRegistry.register( "json_arrayagg", new SQLServerJsonArrayAggFunction( typeConfiguration ) );
public void jsonArrayAgg_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_arrayagg", new SQLServerJsonArrayAggFunction( supportsExtendedJson, typeConfiguration ) );
}
/**
@ -3809,8 +3809,8 @@ public class CommonFunctionFactory {
/**
* SQL Server json_objectagg() function
*/
public void jsonObjectAgg_sqlserver() {
functionRegistry.register( "json_objectagg", new SQLServerJsonObjectAggFunction( typeConfiguration ) );
public void jsonObjectAgg_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_objectagg", new SQLServerJsonObjectAggFunction( supportsExtendedJson, typeConfiguration ) );
}
/**
@ -3943,8 +3943,8 @@ public class CommonFunctionFactory {
/**
* SQL server json_replace() function
*/
public void jsonReplace_sqlserver() {
functionRegistry.register( "json_replace", new SQLServerJsonReplaceFunction( typeConfiguration ) );
public void jsonReplace_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_replace", new SQLServerJsonReplaceFunction( supportsExtendedJson, typeConfiguration ) );
}
/**
@ -3981,8 +3981,8 @@ public class CommonFunctionFactory {
/**
* SQL server json_insert() function
*/
public void jsonInsert_sqlserver() {
functionRegistry.register( "json_insert", new SQLServerJsonInsertFunction( typeConfiguration ) );
public void jsonInsert_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_insert", new SQLServerJsonInsertFunction( supportsExtendedJson, typeConfiguration ) );
}
/**
@ -4056,8 +4056,8 @@ public class CommonFunctionFactory {
/**
* SQL server json_array_append() function
*/
public void jsonArrayAppend_sqlserver() {
functionRegistry.register( "json_array_append", new SQLServerJsonArrayAppendFunction( typeConfiguration ) );
public void jsonArrayAppend_sqlserver(boolean supportsExtendedJson) {
functionRegistry.register( "json_array_append", new SQLServerJsonArrayAppendFunction( supportsExtendedJson, typeConfiguration ) );
}
/**

View File

@ -9,6 +9,7 @@ import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.CastType;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@Internal
public class ExpressionTypeHelper {
@ -34,6 +35,12 @@ public class ExpressionTypeHelper {
&& expressionType.getSingleJdbcMapping().getJdbcType().isJson();
}
public static JdbcType getSingleJdbcType(SqlAstNode node) {
final Expression expression = (Expression) node;
final JdbcMappingContainer expressionType = expression.getExpressionType();
return expressionType.getSingleJdbcMapping().getJdbcType();
}
public static boolean isBoolean(CastType castType) {
switch ( castType ) {
case BOOLEAN:

View File

@ -204,6 +204,51 @@ public class JsonPathHelper {
return i + 1;
}
public static void inlinePassingClause(
List<JsonPathElement> jsonPathElements,
JsonPathPassingClause passingClause,
SqlAstTranslator<?> walker) {
for ( int i = 0; i < jsonPathElements.size(); i++ ) {
final JsonPathElement jsonPathElement = jsonPathElements.get( i );
if ( jsonPathElement instanceof JsonParameterIndexAccess parameterIndexAccess ) {
final Expression expression = passingClause.getPassingExpressions()
.get( parameterIndexAccess.parameterName() );
if ( expression == null ) {
throw new QueryException( "JSON path [" + toJsonPath( jsonPathElements ) + "] uses parameter [" + parameterIndexAccess.parameterName() + "] that is not passed" );
}
jsonPathElements.set( i, new JsonIndexAccess( walker.getLiteralValue( expression ) ) );
}
}
}
public static String toJsonPath(List<JsonPathElement> pathElements) {
return toJsonPath( pathElements, 0, pathElements.size() );
}
public static String toJsonPath(List<JsonPathElement> pathElements, int start, int end) {
final StringBuilder jsonPath = new StringBuilder();
jsonPath.append( "$" );
for ( int i = start; i < end; i++ ) {
final JsonPathElement jsonPathElement = pathElements.get( i );
if ( jsonPathElement instanceof JsonAttribute pathAttribute ) {
jsonPath.append( '.' );
jsonPath.append( pathAttribute.attribute() );
}
else if ( jsonPathElement instanceof JsonParameterIndexAccess parameterIndexAccess ) {
jsonPath.append( "[$" );
jsonPath.append( parameterIndexAccess.parameterName() );
jsonPath.append( "]" );
}
else {
assert jsonPathElement instanceof JsonIndexAccess;
jsonPath.append( "[" );
jsonPath.append( ( (JsonIndexAccess) jsonPathElement ).index() );
jsonPath.append( "]" );
}
}
return jsonPath.toString();
}
public sealed interface JsonPathElement {}
public record JsonAttribute(String attribute) implements JsonPathElement {}
public record JsonIndexAccess(int index) implements JsonPathElement {}

View File

@ -17,6 +17,8 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -24,8 +26,11 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class SQLServerJsonArrayAggFunction extends JsonArrayAggFunction {
public SQLServerJsonArrayAggFunction(TypeConfiguration typeConfiguration) {
private final boolean supportsExtendedJson;
public SQLServerJsonArrayAggFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( false, typeConfiguration );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
@ -90,10 +95,38 @@ public class SQLServerJsonArrayAggFunction extends JsonArrayAggFunction {
Expression arg,
JsonNullBehavior nullBehavior,
SqlAstTranslator<?> translator) {
sqlAppender.appendSql( "substring(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( " null on null),2,len(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( " null on null))-2)" );
if ( supportsExtendedJson ) {
sqlAppender.appendSql( "substring(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( " null on null),2,len(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( "))-2)" );
}
else {
sqlAppender.appendSql( "substring(json_modify('[]','append $'," );
final boolean needsConversion = needsConversion( arg );
if ( needsConversion ) {
sqlAppender.appendSql( "convert(nvarchar(max)," );
}
arg.accept( translator );
if ( needsConversion ) {
sqlAppender.appendSql( ')' );
}
sqlAppender.appendSql( "),2,len(json_modify('[]','append $'," );
if ( needsConversion ) {
sqlAppender.appendSql( "convert(nvarchar(max)," );
}
arg.accept( translator );
if ( needsConversion ) {
sqlAppender.appendSql( ')' );
}
sqlAppender.appendSql( "))-2)" );
}
}
static boolean needsConversion(Expression arg) {
final JdbcType jdbcType = ExpressionTypeHelper.getSingleJdbcType( arg );
// json_modify() doesn't seem to like UUID values
return jdbcType.getDdlTypeCode() == SqlTypes.UUID;
}
}

View File

@ -18,8 +18,11 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class SQLServerJsonArrayAppendFunction extends AbstractJsonArrayAppendFunction {
public SQLServerJsonArrayAppendFunction(TypeConfiguration typeConfiguration) {
private final boolean supportsExtendedJson;
public SQLServerJsonArrayAppendFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( typeConfiguration );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
@ -33,7 +36,31 @@ public class SQLServerJsonArrayAppendFunction extends AbstractJsonArrayAppendFun
final SqlAstNode value = arguments.get( 2 );
sqlAppender.appendSql( "(select coalesce(" );
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,");
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,','))),");
sqlAppender.appendSql("json_modify(t.d,t.p,json_query('['+coalesce(json_value(t.d,t.p),case when ");
if ( supportsExtendedJson ) {
sqlAppender.appendSql( "json_path_exists(t.d,t.p)=1" );
}
else {
final List<JsonPathHelper.JsonPathElement> pathElements =
JsonPathHelper.parseJsonPathElements( translator.getLiteralValue( jsonPath ) );
final JsonPathHelper.JsonPathElement lastPathElement = pathElements.get( pathElements.size() - 1 );
final String prefix = JsonPathHelper.toJsonPath( pathElements, 0, pathElements.size() - 1 );
final String terminalKey;
if ( lastPathElement instanceof JsonPathHelper.JsonIndexAccess indexAccess ) {
terminalKey = String.valueOf( indexAccess.index() );
}
else {
assert lastPathElement instanceof JsonPathHelper.JsonAttribute;
terminalKey = ( (JsonPathHelper.JsonAttribute) lastPathElement ).attribute();
}
sqlAppender.appendSql( "(select 1 from openjson(t.d," );
sqlAppender.appendSingleQuoteEscapedString( prefix );
sqlAppender.appendSql( ") t where t.[key]=" );
sqlAppender.appendSingleQuoteEscapedString( terminalKey );
sqlAppender.appendSql( ")=1" );
}
sqlAppender.appendSql( " then 'null' end)+stuff(json_modify('[]','append $',t.v),1,1,','))),");
sqlAppender.appendSql( "t.d) from (values (" );
json.accept( translator );
sqlAppender.appendSql( ',' );
@ -49,6 +76,11 @@ public class SQLServerJsonArrayAppendFunction extends AbstractJsonArrayAppendFun
value.accept( translator );
sqlAppender.appendSql( " as bit)" );
}
else if ( ExpressionTypeHelper.isJson( value ) ) {
sqlAppender.appendSql( "json_query(" );
value.accept( translator );
sqlAppender.appendSql( ')' );
}
else {
value.accept( translator );
}

View File

@ -4,9 +4,13 @@
*/
package org.hibernate.dialect.function.json;
import java.util.List;
import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -14,8 +18,66 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class SQLServerJsonArrayFunction extends JsonArrayFunction {
public SQLServerJsonArrayFunction(TypeConfiguration typeConfiguration) {
private final boolean supportsExtendedJson;
public SQLServerJsonArrayFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( typeConfiguration );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
if ( supportsExtendedJson ) {
super.render( sqlAppender, sqlAstArguments, returnType, walker );
}
else {
if ( sqlAstArguments.isEmpty() ) {
sqlAppender.appendSql( "'[]'" );
}
else {
final SqlAstNode lastArgument = sqlAstArguments.get( sqlAstArguments.size() - 1 );
final JsonNullBehavior nullBehavior;
final int argumentsCount;
if ( lastArgument instanceof JsonNullBehavior ) {
nullBehavior = (JsonNullBehavior) lastArgument;
argumentsCount = sqlAstArguments.size() - 1;
}
else {
nullBehavior = JsonNullBehavior.ABSENT;
argumentsCount = sqlAstArguments.size();
}
if ( nullBehavior == JsonNullBehavior.ABSENT ) {
sqlAppender.appendSql( "(select '['+string_agg(substring(t.d,2,len(t.d)-2),',')" );
sqlAppender.appendSql( "within group (order by t.k)+']' from (values" );
char separator = ' ';
for ( int i = 0; i < argumentsCount; i++ ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( '(' );
sqlAppender.appendSql( i );
sqlAppender.appendSql( ",json_modify('[]','append $'," );
renderValue( sqlAppender, sqlAstArguments.get( i ), walker );
sqlAppender.appendSql( "))" );
separator = ',';
}
sqlAppender.appendSql( ") t(k,d))" );
}
else {
for ( int i = 0; i < argumentsCount; i++ ) {
sqlAppender.appendSql( "json_modify(" );
}
sqlAppender.appendSql( "'[]'" );
for ( int i = 0; i < argumentsCount; i++ ) {
sqlAppender.appendSql( ",'append $'," );
renderValue( sqlAppender, sqlAstArguments.get( i ), walker );
sqlAppender.appendSql( ')' );
}
}
}
}
}
@Override
@ -25,6 +87,11 @@ public class SQLServerJsonArrayFunction extends JsonArrayFunction {
value.accept( walker );
sqlAppender.appendSql( " as bit)" );
}
else if ( !supportsExtendedJson && ExpressionTypeHelper.isJson( value ) ) {
sqlAppender.appendSql( "json_query(" );
value.accept( walker );
sqlAppender.appendSql( ')' );
}
else {
value.accept( walker );
}

View File

@ -79,10 +79,10 @@ public class SQLServerJsonArrayInsertFunction extends AbstractJsonArrayInsertFun
SqlAppender sqlAppender,
SqlAstNode arg,
SqlAstTranslator<?> translator) {
sqlAppender.appendSql( "substring(json_array(" );
sqlAppender.appendSql( "substring(json_modify('[]','append $'," );
arg.accept( translator );
sqlAppender.appendSql( " null on null),2,len(json_array(" );
sqlAppender.appendSql( "),2,len(json_modify('[]','append $'," );
arg.accept( translator );
sqlAppender.appendSql( " null on null))-2)" );
sqlAppender.appendSql( "))-2)" );
}
}

View File

@ -4,6 +4,8 @@
*/
package org.hibernate.dialect.function.json;
import java.util.List;
import org.hibernate.QueryException;
import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
@ -17,13 +19,11 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class SQLServerJsonExistsFunction extends JsonExistsFunction {
public SQLServerJsonExistsFunction(TypeConfiguration typeConfiguration) {
super( typeConfiguration, true, false );
}
private final boolean supportsExtendedJson;
@Override
public boolean isPredicate() {
return false;
public SQLServerJsonExistsFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( typeConfiguration, true, false );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
@ -35,35 +35,14 @@ public class SQLServerJsonExistsFunction extends JsonExistsFunction {
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.TRUE ) {
throw new QueryException( "Can't emulate json_exists(... true on error) on SQL Server" );
}
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.ERROR ) {
sqlAppender.append( '(' );
}
sqlAppender.appendSql( "json_path_exists(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( ',' );
final JsonPathPassingClause passingClause = arguments.passingClause();
if ( passingClause != null ) {
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
sqlAppender,
"",
arguments.jsonPath(),
passingClause,
walker
);
}
else {
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
sqlAppender,
walker.getLiteralValue( arguments.jsonPath() )
);
}
sqlAppender.appendSql( ')' );
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.ERROR ) {
// json_path_exists returns 0 if an invalid JSON is given,
// so we have to run openjson to be sure the json is valid and potentially throw an error
sqlAppender.appendSql( "=1 or (select v from openjson(" );
if ( supportsExtendedJson ) {
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.ERROR ) {
sqlAppender.append( '(' );
}
sqlAppender.appendSql( "json_path_exists(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( ") with (v varchar(max) " );
sqlAppender.appendSql( ',' );
final JsonPathPassingClause passingClause = arguments.passingClause();
if ( passingClause != null ) {
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
sqlAppender,
@ -79,7 +58,59 @@ public class SQLServerJsonExistsFunction extends JsonExistsFunction {
walker.getLiteralValue( arguments.jsonPath() )
);
}
sqlAppender.appendSql( ")) is null)" );
sqlAppender.appendSql( ")=1" );
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.ERROR ) {
// json_path_exists returns 0 if an invalid JSON is given,
// so we have to run openjson to be sure the json is valid and potentially throw an error
sqlAppender.appendSql( " or (select v from openjson(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( ") with (v varchar(max) " );
if ( passingClause != null ) {
JsonPathHelper.appendInlinedJsonPathIncludingPassingClause(
sqlAppender,
"",
arguments.jsonPath(),
passingClause,
walker
);
}
else {
walker.getSessionFactory().getJdbcServices().getDialect().appendLiteral(
sqlAppender,
walker.getLiteralValue( arguments.jsonPath() )
);
}
sqlAppender.appendSql( ")) is null)" );
}
}
else {
if ( arguments.errorBehavior() == JsonExistsErrorBehavior.FALSE ) {
throw new QueryException( "Can't emulate json_exists(... false on error) on SQL Server" );
}
final String jsonPath = walker.getLiteralValue( arguments.jsonPath() );
final JsonPathPassingClause passingClause = arguments.passingClause();
final List<JsonPathHelper.JsonPathElement> pathElements = JsonPathHelper.parseJsonPathElements( jsonPath );
if ( passingClause != null ) {
JsonPathHelper.inlinePassingClause( pathElements, passingClause, walker );
}
final JsonPathHelper.JsonPathElement lastPathElement = pathElements.get( pathElements.size() - 1 );
final String prefix = JsonPathHelper.toJsonPath( pathElements, 0, pathElements.size() - 1 );
final String terminalKey;
if ( lastPathElement instanceof JsonPathHelper.JsonIndexAccess indexAccess ) {
terminalKey = String.valueOf( indexAccess.index() );
}
else {
assert lastPathElement instanceof JsonPathHelper.JsonAttribute;
terminalKey = ( (JsonPathHelper.JsonAttribute) lastPathElement ).attribute();
}
sqlAppender.appendSql( "(select 1 from openjson(" );
arguments.jsonDocument().accept( walker );
sqlAppender.appendSql( ',' );
sqlAppender.appendSingleQuoteEscapedString( prefix );
sqlAppender.appendSql( ") t where t.[key]=" );
sqlAppender.appendSingleQuoteEscapedString( terminalKey );
sqlAppender.appendSql( ")=1" );
}
}
}

View File

@ -18,8 +18,11 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class SQLServerJsonInsertFunction extends AbstractJsonInsertFunction {
public SQLServerJsonInsertFunction(TypeConfiguration typeConfiguration) {
private final boolean supportsExtendedJson;
public SQLServerJsonInsertFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( typeConfiguration );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
@ -28,10 +31,35 @@ public class SQLServerJsonInsertFunction extends AbstractJsonInsertFunction {
List<? extends SqlAstNode> arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> translator) {
sqlAppender.appendSql( "(select case when coalesce(json_query(t.d,t.p),json_value(t.d,t.p)) is not null then t.d else json_modify(t.d,t.p," );
final Expression json = (Expression) arguments.get( 0 );
final Expression jsonPath = (Expression) arguments.get( 1 );
final SqlAstNode value = arguments.get( 2 );
sqlAppender.appendSql( "(select case when " );
if ( supportsExtendedJson ) {
sqlAppender.appendSql( "json_path_exists(t.d,t.p)=1" );
}
else {
final List<JsonPathHelper.JsonPathElement> pathElements =
JsonPathHelper.parseJsonPathElements( translator.getLiteralValue( jsonPath ) );
final JsonPathHelper.JsonPathElement lastPathElement = pathElements.get( pathElements.size() - 1 );
final String prefix = JsonPathHelper.toJsonPath( pathElements, 0, pathElements.size() - 1 );
final String terminalKey;
if ( lastPathElement instanceof JsonPathHelper.JsonIndexAccess indexAccess ) {
terminalKey = String.valueOf( indexAccess.index() );
}
else {
assert lastPathElement instanceof JsonPathHelper.JsonAttribute;
terminalKey = ( (JsonPathHelper.JsonAttribute) lastPathElement ).attribute();
}
sqlAppender.appendSql( "(select 1 from openjson(t.d," );
sqlAppender.appendSingleQuoteEscapedString( prefix );
sqlAppender.appendSql( ") t where t.[key]=" );
sqlAppender.appendSingleQuoteEscapedString( terminalKey );
sqlAppender.appendSql( ")=1" );
}
sqlAppender.appendSql( " then t.d else json_modify(t.d,t.p," );
renderValue( sqlAppender, value, translator );
sqlAppender.appendSql( ") end from (values(");
json.accept( translator );

View File

@ -15,13 +15,18 @@ import org.hibernate.sql.ast.tree.expression.JsonObjectAggUniqueKeysBehavior;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.dialect.function.json.SQLServerJsonArrayAggFunction.needsConversion;
/**
* SQL Server json_objectagg function.
*/
public class SQLServerJsonObjectAggFunction extends JsonObjectAggFunction {
public SQLServerJsonObjectAggFunction(TypeConfiguration typeConfiguration) {
private final boolean supportsExtendedJson;
public SQLServerJsonObjectAggFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( ",", false, typeConfiguration );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
@ -66,11 +71,33 @@ public class SQLServerJsonObjectAggFunction extends JsonObjectAggFunction {
if ( nullBehavior != JsonNullBehavior.NULL ) {
sqlAppender.appendSql( "nullif(" );
}
sqlAppender.appendSql( "substring(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( " null on null),2,len(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( " null on null))-2)" );
if ( supportsExtendedJson ) {
sqlAppender.appendSql( "substring(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( " null on null),2,len(json_array(" );
arg.accept( translator );
sqlAppender.appendSql( " null on null))-2)" );
}
else {
sqlAppender.appendSql( "substring(json_modify('[]','append $'," );
final boolean needsConversion = needsConversion( arg );
if ( needsConversion ) {
sqlAppender.appendSql( "convert(nvarchar(max)," );
}
arg.accept( translator );
if ( needsConversion ) {
sqlAppender.appendSql( ')' );
}
sqlAppender.appendSql( "),2,len(json_modify('[]','append $'," );
if ( needsConversion ) {
sqlAppender.appendSql( "convert(nvarchar(max)," );
}
arg.accept( translator );
if ( needsConversion ) {
sqlAppender.appendSql( ')' );
}
sqlAppender.appendSql( "))-2)" );
}
if ( nullBehavior != JsonNullBehavior.NULL ) {
sqlAppender.appendSql( ",'null')" );
}

View File

@ -4,9 +4,13 @@
*/
package org.hibernate.dialect.function.json;
import java.util.List;
import org.hibernate.query.ReturnableType;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.JsonNullBehavior;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -14,8 +18,67 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class SQLServerJsonObjectFunction extends JsonObjectFunction {
public SQLServerJsonObjectFunction(TypeConfiguration typeConfiguration) {
private final boolean supportsExtendedJson;
public SQLServerJsonObjectFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( typeConfiguration, true );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> sqlAstArguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> walker) {
if ( supportsExtendedJson ) {
super.render( sqlAppender, sqlAstArguments, returnType, walker );
}
else {
if ( sqlAstArguments.isEmpty() ) {
sqlAppender.appendSql( "'{}'" );
}
else {
final JsonNullBehavior nullBehavior;
final int argumentsCount;
if ( ( sqlAstArguments.size() & 1 ) == 1 ) {
nullBehavior = (JsonNullBehavior) sqlAstArguments.get( sqlAstArguments.size() - 1 );
argumentsCount = sqlAstArguments.size() - 1;
}
else {
nullBehavior = JsonNullBehavior.NULL;
argumentsCount = sqlAstArguments.size();
}
if ( nullBehavior == JsonNullBehavior.ABSENT ) {
for ( int i = 0; i < argumentsCount; i += 2 ) {
sqlAppender.appendSql( "json_modify(" );
}
sqlAppender.appendSql( "'{}'" );
for ( int i = 0; i < argumentsCount; i += 2 ) {
sqlAppender.appendSql( ",'$.'+" );
sqlAstArguments.get( i ).accept( walker );
sqlAppender.appendSql( ',' );
renderValue( sqlAppender, sqlAstArguments.get( i + 1 ), walker );
sqlAppender.appendSql( ')' );
}
}
else {
sqlAppender.appendSql( "(select '{'+string_agg(substring(t.k,2,len(t.k)-2)" );
sqlAppender.appendSql( "+':'+substring(t.d,2,len(t.d)-2),',')+'}' from (values" );
char separator = ' ';
for ( int i = 0; i < argumentsCount; i += 2 ) {
sqlAppender.appendSql( separator );
sqlAppender.appendSql( "(json_modify('[]','append $'," );
sqlAstArguments.get( i ).accept( walker );
sqlAppender.appendSql( "),json_modify('[]','append $'," );
renderValue( sqlAppender, sqlAstArguments.get( i + 1 ), walker );
sqlAppender.appendSql( "))" );
separator = ',';
}
sqlAppender.appendSql( ") t(k,d))" );
}
}
}
}
@Override
@ -25,6 +88,11 @@ public class SQLServerJsonObjectFunction extends JsonObjectFunction {
value.accept( walker );
sqlAppender.appendSql( " as bit)" );
}
else if ( !supportsExtendedJson && ExpressionTypeHelper.isJson( value ) ) {
sqlAppender.appendSql( "json_query(" );
value.accept( walker );
sqlAppender.appendSql( ')' );
}
else {
value.accept( walker );
}

View File

@ -18,8 +18,11 @@ import org.hibernate.type.spi.TypeConfiguration;
*/
public class SQLServerJsonReplaceFunction extends AbstractJsonReplaceFunction {
public SQLServerJsonReplaceFunction(TypeConfiguration typeConfiguration) {
private final boolean supportsExtendedJson;
public SQLServerJsonReplaceFunction(boolean supportsExtendedJson, TypeConfiguration typeConfiguration) {
super( typeConfiguration );
this.supportsExtendedJson = supportsExtendedJson;
}
@Override
@ -28,12 +31,37 @@ public class SQLServerJsonReplaceFunction extends AbstractJsonReplaceFunction {
List<? extends SqlAstNode> arguments,
ReturnableType<?> returnType,
SqlAstTranslator<?> translator) {
sqlAppender.appendSql( "(select case when coalesce(json_query(t.d,t.p),json_value(t.d,t.p)) is null then t.d else json_modify(t.d,t.p," );
final Expression json = (Expression) arguments.get( 0 );
final Expression jsonPath = (Expression) arguments.get( 1 );
final SqlAstNode value = arguments.get( 2 );
sqlAppender.appendSql( "(select case when " );
if ( supportsExtendedJson ) {
sqlAppender.appendSql( "json_path_exists(t.d,t.p)=1" );
}
else {
final List<JsonPathHelper.JsonPathElement> pathElements =
JsonPathHelper.parseJsonPathElements( translator.getLiteralValue( jsonPath ) );
final JsonPathHelper.JsonPathElement lastPathElement = pathElements.get( pathElements.size() - 1 );
final String prefix = JsonPathHelper.toJsonPath( pathElements, 0, pathElements.size() - 1 );
final String terminalKey;
if ( lastPathElement instanceof JsonPathHelper.JsonIndexAccess indexAccess ) {
terminalKey = String.valueOf( indexAccess.index() );
}
else {
assert lastPathElement instanceof JsonPathHelper.JsonAttribute;
terminalKey = ( (JsonPathHelper.JsonAttribute) lastPathElement ).attribute();
}
sqlAppender.appendSql( "(select 1 from openjson(t.d," );
sqlAppender.appendSingleQuoteEscapedString( prefix );
sqlAppender.appendSql( ") t where t.[key]=" );
sqlAppender.appendSingleQuoteEscapedString( terminalKey );
sqlAppender.appendSql( ")=1" );
}
sqlAppender.appendSql( " then json_modify(t.d,t.p," );
renderValue( sqlAppender, value, translator );
sqlAppender.appendSql( ") end from (values(");
sqlAppender.appendSql( ") else t.d end from (values(");
json.accept( translator );
sqlAppender.appendSql( ',' );
jsonPath.accept( translator );