Skip to content

Commit

Permalink
[Blazebit#1928] Add parameter support for JSON_GET
Browse files Browse the repository at this point in the history
  • Loading branch information
Mobe91 committed Aug 19, 2024
1 parent da0f358 commit c035738
Show file tree
Hide file tree
Showing 16 changed files with 374 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import com.blazebit.persistence.impl.dialect.DB2DbmsDialect;
import com.blazebit.persistence.impl.dialect.DefaultDbmsDialect;
import com.blazebit.persistence.impl.dialect.H2DbmsDialect;
import com.blazebit.persistence.impl.dialect.MariaDBDbmsDialect;
import com.blazebit.persistence.impl.dialect.MSSQLDbmsDialect;
import com.blazebit.persistence.impl.dialect.MariaDBDbmsDialect;
import com.blazebit.persistence.impl.dialect.MySQL8DbmsDialect;
import com.blazebit.persistence.impl.dialect.MySQLDbmsDialect;
import com.blazebit.persistence.impl.dialect.OracleDbmsDialect;
Expand Down Expand Up @@ -359,7 +359,7 @@
import com.blazebit.persistence.impl.function.jsonget.AbstractJsonGetFunction;
import com.blazebit.persistence.impl.function.jsonget.DB2JsonGetFunction;
import com.blazebit.persistence.impl.function.jsonget.MSSQLJsonGetFunction;
import com.blazebit.persistence.impl.function.jsonget.MySQL8JsonGetFunction;
import com.blazebit.persistence.impl.function.jsonget.MySQLJsonGetFunction;
import com.blazebit.persistence.impl.function.jsonget.OracleJsonGetFunction;
import com.blazebit.persistence.impl.function.jsonget.PostgreSQLJsonGetFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonSetFunction;
Expand Down Expand Up @@ -543,8 +543,6 @@
import com.blazebit.persistence.spi.LateralStyle;
import com.blazebit.persistence.spi.PackageOpener;
import com.blazebit.persistence.spi.SetOperationType;

import javax.persistence.EntityManagerFactory;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
Expand All @@ -561,6 +559,7 @@
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TimeZone;
import javax.persistence.EntityManagerFactory;

/**
*
Expand Down Expand Up @@ -2093,13 +2092,23 @@ private void loadJsonFunctions() {
JpqlFunctionGroup jpqlFunctionGroup;
// JSON_GET
jpqlFunctionGroup = new JpqlFunctionGroup(AbstractJsonGetFunction.FUNCTION_NAME, false);
jpqlFunctionGroup.add("postgresql", new PostgreSQLJsonGetFunction());
jpqlFunctionGroup.add("cockroach", new PostgreSQLJsonGetFunction());
jpqlFunctionGroup.add("mariadb", new MySQL8JsonGetFunction());
jpqlFunctionGroup.add("mysql8", new MySQL8JsonGetFunction());
jpqlFunctionGroup.add("oracle", new OracleJsonGetFunction());
jpqlFunctionGroup.add("db2", new DB2JsonGetFunction());
jpqlFunctionGroup.add("microsoft", new MSSQLJsonGetFunction());
jpqlFunctionGroup.add("postgresql", new PostgreSQLJsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"postgresql")));
jpqlFunctionGroup.add("cockroach", new PostgreSQLJsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"cockroach")));
jpqlFunctionGroup.add("mariadb", new MySQLJsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"mariadb")));
jpqlFunctionGroup.add("mysql", new MySQLJsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"mysql")));
jpqlFunctionGroup.add("mysql8", new MySQLJsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"mysql8")));
jpqlFunctionGroup.add("oracle", new OracleJsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"oracle")));
jpqlFunctionGroup.add("db2", new DB2JsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"db2")));
jpqlFunctionGroup.add("microsoft", new MSSQLJsonGetFunction(
(ConcatFunction) findFunction(ConcatFunction.FUNCTION_NAME,"microsoft"),
(CastFunction) findFunction("cast_string","microsoft")));
registerFunction(jpqlFunctionGroup);

// JSON_SET
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,12 @@ public String getCastExpression(String argument) {
return "cast(" + argument + " as " + defaultSqlCastType + ")";
}

public String startCastExpression() {
return "cast(";
}

public String endCastExpression(String castType) {
return " as " + (castType == null ? defaultSqlCastType : castType) + ")";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.impl.util.JpqlFunctionUtil;
import com.blazebit.persistence.impl.function.concat.ConcatFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonFunction;
import com.blazebit.persistence.spi.FunctionRenderContext;
import com.blazebit.persistence.spi.JpqlFunction;

import java.util.ArrayList;
import java.util.List;

/**
* @author Moritz Becker
* @since 1.5.0
*/
public abstract class AbstractJsonGetFunction implements JpqlFunction {
public abstract class AbstractJsonGetFunction extends AbstractJsonFunction {

public static final String FUNCTION_NAME = "JSON_GET";

protected AbstractJsonGetFunction(ConcatFunction concatFunction) {
super(concatFunction);
}

@Override
public boolean hasArguments() {
return true;
Expand All @@ -54,38 +55,4 @@ public void render(FunctionRenderContext context) {
}

protected abstract void render0(FunctionRenderContext context);

public static String toJsonPath(List<Object> pathElements, int to, boolean quotePathElements) {
StringBuilder jsonPathBuilder = new StringBuilder("$");
for (int i = 0; i < to; i++) {
Object currentPathElement = pathElements.get(i);
if (currentPathElement instanceof Integer) {
jsonPathBuilder.append('[');
jsonPathBuilder.append((int) currentPathElement);
jsonPathBuilder.append(']');
} else {
jsonPathBuilder.append('.');
if (quotePathElements) {
jsonPathBuilder.append("\"");
}
jsonPathBuilder.append((String) currentPathElement);
if (quotePathElements) {
jsonPathBuilder.append("\"");
}
}
}
return jsonPathBuilder.toString();
}

public static List<Object> retrieveJsonPathElements(FunctionRenderContext context, int pathStartOffset) {
List<Object> jsonPathElements = new ArrayList<>(context.getArgumentsSize() - pathStartOffset);
for (int i = pathStartOffset; i < context.getArgumentsSize(); i++) {
try {
jsonPathElements.add(Integer.parseInt(JpqlFunctionUtil.unquoteSingleQuotes(context.getArgument(i))));
} catch (NumberFormatException e) {
jsonPathElements.add(JpqlFunctionUtil.unquoteDoubleQuotes(JpqlFunctionUtil.unquoteSingleQuotes(context.getArgument(i))));
}
}
return jsonPathElements;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.impl.function.concat.ConcatFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonFunction;
import com.blazebit.persistence.spi.FunctionRenderContext;

import java.util.List;

/**
Expand All @@ -25,17 +26,20 @@
*/
public class DB2JsonGetFunction extends AbstractJsonGetFunction {

public DB2JsonGetFunction(ConcatFunction concatFunction) {
super(concatFunction);
}

@Override
protected void render0(FunctionRenderContext context) {
List<Object> jsonPathElements = AbstractJsonGetFunction.retrieveJsonPathElements(context, 1);
jsonPathElements.add(0, "val");
String jsonPath = AbstractJsonGetFunction.toJsonPath(jsonPathElements, jsonPathElements.size(), false);

List<Object> jsonPathElements = AbstractJsonFunction.retrieveJsonPathElements(context, 1);
String jsonPathTemplate = AbstractJsonFunction.toJsonPathTemplate(jsonPathElements, jsonPathElements.size(), false);
jsonPathTemplate = "$.val" + jsonPathTemplate.substring(1);
context.addChunk("json_query(concat('{\"val\":', concat(");
context.addArgument(0);
context.addChunk(", '}'))");
context.addChunk(",'");
context.addChunk(jsonPath);
context.addChunk("' OMIT QUOTES)");
context.addChunk(",");
renderJsonPathTemplate(context, jsonPathTemplate, jsonPathElements.size() + 1);
context.addChunk(" OMIT QUOTES)");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.impl.function.cast.CastFunction;
import com.blazebit.persistence.impl.function.concat.ConcatFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonFunction;
import com.blazebit.persistence.spi.FunctionRenderContext;

import java.util.List;

/**
Expand All @@ -25,19 +27,33 @@
*/
public class MSSQLJsonGetFunction extends AbstractJsonGetFunction {

private final CastFunction castToStringFunction;

public MSSQLJsonGetFunction(ConcatFunction concatFunction, CastFunction castToStringFunction) {
super(concatFunction);
this.castToStringFunction = castToStringFunction;
}

@Override
protected void render0(FunctionRenderContext context) {
List<Object> jsonPathElements = AbstractJsonGetFunction.retrieveJsonPathElements(context, 1);
String jsonPath = AbstractJsonGetFunction.toJsonPath(jsonPathElements, jsonPathElements.size(), true);
List<Object> jsonPathElements = AbstractJsonFunction.retrieveJsonPathElements(context, 1);
String jsonPathTemplate = AbstractJsonFunction.toJsonPathTemplate(jsonPathElements, jsonPathElements.size(), true);

context.addChunk("coalesce(json_value(");
// We need to combine json_value and json_query here because json_value is for querying scalar values while
// json_query is for querying JSON objects and arrays.
context.addChunk("(select coalesce(json_value(");
context.addArgument(0);
context.addChunk(",'");
context.addChunk(jsonPath);
context.addChunk("'),json_query(");
context.addChunk(",temp.val),json_query(");
context.addArgument(0);
context.addChunk(",'");
context.addChunk(jsonPath);
context.addChunk("'))");
context.addChunk(",temp.val)) from (values(");
renderJsonPathTemplate(context, jsonPathTemplate, jsonPathElements.size() + 1);
context.addChunk(")) temp(val))");
}

@Override
protected void renderJsonPathTemplateParameter(FunctionRenderContext context, int parameterIdx) {
context.addChunk(castToStringFunction.startCastExpression());
context.addArgument(parameterIdx);
context.addChunk(castToStringFunction.endCastExpression(null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,30 @@
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.impl.function.concat.ConcatFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonFunction;
import com.blazebit.persistence.spi.FunctionRenderContext;

import java.util.List;

/**
* @author Moritz Becker
* @since 1.5.0
*/
public class MySQL8JsonGetFunction extends AbstractJsonGetFunction {
public class MySQLJsonGetFunction extends AbstractJsonGetFunction {

public MySQLJsonGetFunction(ConcatFunction concatFunction) {
super(concatFunction);
}

@Override
protected void render0(FunctionRenderContext context) {
List<Object> jsonPathElements = AbstractJsonGetFunction.retrieveJsonPathElements(context, 1);
String jsonPath = AbstractJsonGetFunction.toJsonPath(jsonPathElements, jsonPathElements.size(), true);
List<Object> jsonPathElements = AbstractJsonFunction.retrieveJsonPathElements(context, 1);
String jsonPathTemplate = AbstractJsonFunction.toJsonPathTemplate(jsonPathElements, jsonPathElements.size(), true);

context.addChunk("json_unquote(");
context.addChunk("nullif(json_extract(");
context.addChunk("json_unquote(nullif(json_extract(");
context.addArgument(0);
context.addChunk(",'");
context.addChunk(jsonPath);
context.addChunk("')");
context.addChunk(",cast('null' as json)))");
context.addChunk(",");
renderJsonPathTemplate(context, jsonPathTemplate, jsonPathElements.size() + 1);
context.addChunk("),cast('null' as json)))");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,37 @@
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.impl.function.concat.ConcatFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonFunction;
import com.blazebit.persistence.spi.FunctionRenderContext;

import java.util.List;

/**
* This function does not support parameterized JSON path templates because the json_value and json_query
* functions in Oracle only work with literals.
*
* @author Moritz Becker
* @since 1.5.0
*/
public class OracleJsonGetFunction extends AbstractJsonGetFunction {

public OracleJsonGetFunction(ConcatFunction concatFunction) {
super(concatFunction);
}

@Override
protected void render0(FunctionRenderContext context) {
List<Object> jsonPathElements = AbstractJsonGetFunction.retrieveJsonPathElements(context, 1);
String jsonPath = toJsonPath(jsonPathElements, jsonPathElements.size(), true);
List<Object> jsonPathElements = AbstractJsonFunction.retrieveJsonPathElements(context, 1);
String jsonPathTemplate = AbstractJsonFunction.toJsonPathTemplate(jsonPathElements, jsonPathElements.size(), true);

context.addChunk("coalesce(json_value(");
context.addArgument(0);
context.addChunk(" format json,'");
context.addChunk(jsonPath);
context.addChunk("'),nullif(json_query(");
context.addChunk(" format json,");
renderJsonPathTemplate(context, jsonPathTemplate, jsonPathElements.size() + 1);
context.addChunk("),nullif(json_query(");
context.addArgument(0);
context.addChunk(" format json,'");
context.addChunk(jsonPath);
context.addChunk("'),'null'))");
context.addChunk(" format json,");
renderJsonPathTemplate(context, jsonPathTemplate, jsonPathElements.size() + 1);
context.addChunk("),'null'))");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,45 @@
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.impl.function.concat.ConcatFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonFunction;
import com.blazebit.persistence.impl.util.JpqlFunctionUtil;
import com.blazebit.persistence.spi.FunctionRenderContext;
import java.util.List;

/**
* @author Moritz Becker
* @since 1.5.0
*/
public class PostgreSQLJsonGetFunction extends AbstractJsonGetFunction {

public PostgreSQLJsonGetFunction(ConcatFunction concatFunction) {
super(concatFunction);
}

@Override
protected void render0(FunctionRenderContext context) {
context.addChunk("cast(");
context.addArgument(0);
context.addChunk(" as json)");
context.addChunk("#>>'{");
addUnquotedArgument(context, 1);
for (int i = 2; i < context.getArgumentsSize(); i++) {
context.addChunk(",");
addUnquotedArgument(context, i);
List<Object> jsonPathElements = AbstractJsonFunction.retrieveJsonPathElements(context, 1);
Object firstArgument = jsonPathElements.get(0);
if (firstArgument instanceof String && isJsonPathTemplate((String) firstArgument)) {
String jsonPathTemplate = AbstractJsonFunction.toJsonPathTemplate(jsonPathElements, jsonPathElements.size(), false);
context.addChunk("jsonb_path_query(cast(");
context.addArgument(0);
context.addChunk(" as jsonb),cast(");
renderJsonPathTemplate(context, jsonPathTemplate, jsonPathElements.size() + 1);
context.addChunk(" as jsonpath))");
} else {
context.addChunk("cast(");
context.addArgument(0);
context.addChunk(" as json)");
context.addChunk("#>>'{");
addUnquotedArgument(context, 1);
for (int i = 2; i < context.getArgumentsSize(); i++) {
context.addChunk(",");
addUnquotedArgument(context, i);
}
context.addChunk("}'");
}
context.addChunk("}'");
}

private void addUnquotedArgument(FunctionRenderContext context, int argIndex) {
Expand Down
Loading

0 comments on commit c035738

Please sign in to comment.