Skip to content

Commit

Permalink
[Blazebit#841] Add json functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Mobe91 committed Jul 20, 2020
1 parent acf93b6 commit 1d3f9eb
Show file tree
Hide file tree
Showing 33 changed files with 1,291 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ before_script:
source setup-graalvm.sh
fi
- bash -c "if [ '$RDBMS' = 'mysql' ]; then MYSQL_VERSION=5.7 bash travis/before_script_mysql.sh; fi"
- bash -c "if [ '$RDBMS' = 'mysql8' ]; then MYSQL_VERSION=8.0 bash travis/before_script_mysql.sh; fi"
- bash -c "if [ '$RDBMS' = 'mysql8' ]; then MYSQL_VERSION=8.0.21 bash travis/before_script_mysql.sh; fi"
- bash -c "if [ '$RDBMS' = 'postgresql' ]; then psql -c 'create database test;' -U postgres; fi"
- bash -c "if [ '$RDBMS' = 'db2' ]; then bash travis/before_script_db2.sh; fi"
- bash -c "if [ '$RDBMS' = 'firebird' ]; then bash travis/before_script_firebird.sh; fi"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.blazebit.persistence.impl.function.base64.Base64Function;
import com.blazebit.persistence.impl.function.base64.PostgreSQLBase64Function;
import com.blazebit.persistence.impl.function.cast.CastFunction;
import com.blazebit.persistence.impl.function.cast.DB2CastFunction;
import com.blazebit.persistence.impl.function.chr.CharChrFunction;
import com.blazebit.persistence.impl.function.chr.ChrFunction;
import com.blazebit.persistence.impl.function.colldml.CollectionDmlSupportFunction;
Expand Down Expand Up @@ -344,6 +345,18 @@
import com.blazebit.persistence.impl.function.groupconcat.MySQLGroupConcatFunction;
import com.blazebit.persistence.impl.function.groupconcat.OracleListaggGroupConcatFunction;
import com.blazebit.persistence.impl.function.groupconcat.PostgreSQLGroupConcatFunction;
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.OracleJsonGetFunction;
import com.blazebit.persistence.impl.function.jsonget.PostgreSQLJsonGetFunction;
import com.blazebit.persistence.impl.function.jsonset.AbstractJsonSetFunction;
import com.blazebit.persistence.impl.function.jsonset.DB2JsonSetFunction;
import com.blazebit.persistence.impl.function.jsonset.MSSQLJsonSetFunction;
import com.blazebit.persistence.impl.function.jsonset.MySQL8JsonSetFunction;
import com.blazebit.persistence.impl.function.jsonset.OracleJsonSetFunction;
import com.blazebit.persistence.impl.function.jsonset.PostgreSQLJsonSetFunction;
import com.blazebit.persistence.impl.function.least.AbstractLeastFunction;
import com.blazebit.persistence.impl.function.least.DefaultLeastFunction;
import com.blazebit.persistence.impl.function.least.MinLeastFunction;
Expand All @@ -369,9 +382,9 @@
import com.blazebit.persistence.impl.function.set.SetFunction;
import com.blazebit.persistence.impl.function.stringjsonagg.AbstractStringJsonAggFunction;
import com.blazebit.persistence.impl.function.stringjsonagg.GroupConcatBasedStringJsonAggFunction;
import com.blazebit.persistence.impl.function.stringjsonagg.MySQLStringJsonAggFunction;
import com.blazebit.persistence.impl.function.stringjsonagg.OracleStringJsonAggFunction;
import com.blazebit.persistence.impl.function.stringjsonagg.PostgreSQLStringJsonAggFunction;
import com.blazebit.persistence.impl.function.stringjsonagg.MySQLStringJsonAggFunction;
import com.blazebit.persistence.impl.function.stringxmlagg.AbstractStringXmlAggFunction;
import com.blazebit.persistence.impl.function.stringxmlagg.GroupConcatBasedStringXmlAggFunction;
import com.blazebit.persistence.impl.function.stringxmlagg.OracleGroupConcatBasedStringXmlAggFunction;
Expand All @@ -381,9 +394,9 @@
import com.blazebit.persistence.impl.function.tostringjson.AbstractToStringJsonFunction;
import com.blazebit.persistence.impl.function.tostringjson.ForJsonPathToStringJsonFunction;
import com.blazebit.persistence.impl.function.tostringjson.GroupConcatBasedToStringJsonFunction;
import com.blazebit.persistence.impl.function.tostringjson.MySQLToStringJsonFunction;
import com.blazebit.persistence.impl.function.tostringjson.OracleToStringJsonFunction;
import com.blazebit.persistence.impl.function.tostringjson.PostgreSQLToStringJsonFunction;
import com.blazebit.persistence.impl.function.tostringjson.MySQLToStringJsonFunction;
import com.blazebit.persistence.impl.function.tostringxml.AbstractToStringXmlFunction;
import com.blazebit.persistence.impl.function.tostringxml.ForXmlPathToStringXmlFunction;
import com.blazebit.persistence.impl.function.tostringxml.GroupConcatBasedToStringXmlFunction;
Expand Down Expand Up @@ -703,7 +716,13 @@ private void loadFunctions() {

for (Map.Entry<String, DbmsDialect> dbmsDialectEntry : dbmsDialects.entrySet()) {
for (Class<?> type : BasicCastTypes.TYPES) {
functions.get("cast_" + type.getSimpleName().toLowerCase()).add(dbmsDialectEntry.getKey(), new CastFunction(type, dbmsDialectEntry.getValue()));
CastFunction castFunction;
if ("db2".equals(dbmsDialectEntry.getKey())) {
castFunction = new DB2CastFunction(type, dbmsDialectEntry.getValue());
} else {
castFunction = new CastFunction(type, dbmsDialectEntry.getValue());
}
functions.get("cast_" + type.getSimpleName().toLowerCase()).add(dbmsDialectEntry.getKey(), castFunction);
}
}

Expand Down Expand Up @@ -1758,6 +1777,24 @@ private void loadFunctions() {
jpqlFunctionGroup.add(dialectEntry.getKey(), new NthValueFunction(dialectEntry.getValue()));
}
registerFunction(jpqlFunctionGroup);

// json_get
jpqlFunctionGroup = new JpqlFunctionGroup(AbstractJsonGetFunction.FUNCTION_NAME, false);
jpqlFunctionGroup.add("postgresql", new PostgreSQLJsonGetFunction());
jpqlFunctionGroup.add("mysql8", new MySQL8JsonGetFunction());
jpqlFunctionGroup.add("oracle", new OracleJsonGetFunction());
jpqlFunctionGroup.add("db2", new DB2JsonGetFunction());
jpqlFunctionGroup.add("microsoft", new MSSQLJsonGetFunction());
registerFunction(jpqlFunctionGroup);

// json_set
jpqlFunctionGroup = new JpqlFunctionGroup(AbstractJsonSetFunction.FUNCTION_NAME, false);
jpqlFunctionGroup.add("postgresql", new PostgreSQLJsonSetFunction());
jpqlFunctionGroup.add("mysql8", new MySQL8JsonSetFunction());
jpqlFunctionGroup.add("oracle", new OracleJsonSetFunction());
jpqlFunctionGroup.add("db2", new DB2JsonSetFunction());
jpqlFunctionGroup.add("microsoft", new MSSQLJsonSetFunction());
registerFunction(jpqlFunctionGroup);
}

private <T extends JpqlFunction> T findFunction(String name, String dbms) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ protected static Map<Class<?>, String> getSqlTypes() {
Map<Class<?>, String> types = new HashMap<Class<?>, String>();

types.put(String.class, "longtext");
types.put(Integer.class, "signed");
types.put(Boolean.class, "unsigned");

return types;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
*/
public class CastFunction implements JpqlFunction {

private final String functionName;
private final Class<?> castType;
private final String defaultSqlCastType;
protected final String functionName;
protected final Class<?> castType;
protected final String defaultSqlCastType;

public CastFunction(Class<?> castType, DbmsDialect dbmsDialect) {
this.functionName = "CAST_" + castType.getSimpleName().toUpperCase();
Expand Down Expand Up @@ -64,7 +64,7 @@ public void render(FunctionRenderContext context) {
if (context.getArgumentsSize() == 1) {
context.addChunk(defaultSqlCastType);
} else {
context.addChunk(JpqlFunctionUtil.unquote(context.getArgument(1)));
context.addChunk(JpqlFunctionUtil.unquoteSingleQuotes(context.getArgument(1)));
}
context.addChunk(")");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2014 - 2020 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.blazebit.persistence.impl.function.cast;

import com.blazebit.persistence.impl.util.JpqlFunctionUtil;
import com.blazebit.persistence.spi.DbmsDialect;
import com.blazebit.persistence.spi.FunctionRenderContext;

/**
* @author Christian Beikov
* @since 1.2.0
*/
public class DB2CastFunction extends CastFunction {

private final String[] clobReturningFunctions = new String[] {
"json_value",
"json_query"
};
private final String[] clobCompatibleCastTargetTypes = new String[] {
"char",
"varchar",
"graphic",
"vargraphic",
"dbclob",
"blob",
"xml"
};

public DB2CastFunction(Class<?> castType, DbmsDialect dbmsDialect) {
super(castType, dbmsDialect);
}

@Override
public void render(FunctionRenderContext context) {
if (context.getArgumentsSize() != 1 && context.getArgumentsSize() != 2) {
throw new RuntimeException("The " + functionName + " function needs one argument <expression> with an optional second argument <sql-type-name>! args=" + context);
}
String effectiveCastTargetType;
if (context.getArgumentsSize() == 1) {
effectiveCastTargetType = defaultSqlCastType;
} else {
effectiveCastTargetType = JpqlFunctionUtil.unquoteSingleQuotes(context.getArgument(1));
}
boolean insertVarcharCast = isClobReturningFunction(context.getArgument(0)) && !isClobCompatibleCastTarget(effectiveCastTargetType);
context.addChunk("cast(");
if (insertVarcharCast) {
context.addChunk("cast(");
}
context.addArgument(0);
if (insertVarcharCast) {
context.addChunk(" as varchar(32000))");
}
context.addChunk(" as ");
context.addChunk(effectiveCastTargetType);
context.addChunk(")");
}

@Override
public String getCastExpression(String argument) {
boolean insertVarcharCast = isClobReturningFunction(argument) && !isClobCompatibleCastTarget(defaultSqlCastType);
if (insertVarcharCast) {
return "cast(cast(" + argument + " as varchar(32000)) as " + defaultSqlCastType + ")";
} else {
return "cast(" + argument + " as " + defaultSqlCastType + ")";
}
}

private boolean isClobReturningFunction(String castSource) {
for (int i = 0; i < clobReturningFunctions.length; i++) {
if (castSource.toLowerCase().startsWith(clobReturningFunctions[i] + "(")) {
return true;
}
}
return false;
}

private boolean isClobCompatibleCastTarget(String castTargetType) {
for (int i = 0; i < clobCompatibleCastTargetTypes.length; i++) {
if (castTargetType.equalsIgnoreCase(clobCompatibleCastTargetTypes[i]) ||
castTargetType.toLowerCase().startsWith(clobCompatibleCastTargetTypes[i] + "(")) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ public void render(FunctionRenderContext functionRenderContext) {
aliasStartIndex--;
}

String entityName = JpqlFunctionUtil.unquote(functionRenderContext.getArgument(1));
String valuesClause = JpqlFunctionUtil.unquote(functionRenderContext.getArgument(2));
String valuesAliases = JpqlFunctionUtil.unquote(functionRenderContext.getArgument(3));
String syntheticPredicate = JpqlFunctionUtil.unquote(functionRenderContext.getArgument(4));
String entityName = JpqlFunctionUtil.unquoteSingleQuotes(functionRenderContext.getArgument(1));
String valuesClause = JpqlFunctionUtil.unquoteSingleQuotes(functionRenderContext.getArgument(2));
String valuesAliases = JpqlFunctionUtil.unquoteSingleQuotes(functionRenderContext.getArgument(3));
String syntheticPredicate = JpqlFunctionUtil.unquoteSingleQuotes(functionRenderContext.getArgument(4));
String valuesTableSqlAlias = subquery.substring(aliasStartIndex, aliasEndIndex);
appendSubqueryPart(sb, subquery, 1, subqueryEndIndex, subquery.length() - 1);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2014 - 2020 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.spi.FunctionRenderContext;
import com.blazebit.persistence.spi.JpqlFunction;

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

public static final String FUNCTION_NAME = "json_get";

@Override
public boolean hasArguments() {
return true;
}

@Override
public boolean hasParenthesesIfNoArguments() {
return true;
}

@Override
public Class<?> getReturnType(Class<?> firstArgumentType) {
return String.class;
}

@Override
public void render(FunctionRenderContext context) {
if (context.getArgumentsSize() < 2) {
throw new RuntimeException("The " + FUNCTION_NAME + " function requires at least two arguments <jsonField>, <key1|arrayIndex1>, ..., <keyN|arrayIndexN>! args=" + context);
}
render0(context);
}

protected abstract void render0(FunctionRenderContext context);

public static String toJsonPath(Object[] pathElements, int to, boolean quotePathElements) {
StringBuilder jsonPathBuilder = new StringBuilder("$");
for (int i = 0; i < to; i++) {
if (pathElements[i] instanceof Integer) {
jsonPathBuilder.append('[');
jsonPathBuilder.append((int) pathElements[i]);
jsonPathBuilder.append(']');
} else {
jsonPathBuilder.append('.');
if (quotePathElements) {
jsonPathBuilder.append("\"");
}
jsonPathBuilder.append((String) pathElements[i]);
if (quotePathElements) {
jsonPathBuilder.append("\"");
}
}
}
return jsonPathBuilder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2014 - 2020 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.blazebit.persistence.impl.function.jsonget;

import com.blazebit.persistence.impl.util.JpqlFunctionUtil;
import com.blazebit.persistence.spi.FunctionRenderContext;

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

@Override
protected void render0(FunctionRenderContext context) {
Object[] jsonPathElements = new Object[context.getArgumentsSize()];
jsonPathElements[0] = "val";
for (int i = 1; i < context.getArgumentsSize(); i++) {
try {
jsonPathElements[i] = Integer.parseInt(JpqlFunctionUtil.unquoteSingleQuotes(context.getArgument(i)));
} catch (NumberFormatException e) {
jsonPathElements[i] = JpqlFunctionUtil.unquoteDoubleQuotes(JpqlFunctionUtil.unquoteSingleQuotes(context.getArgument(i)));
}
}
String jsonPath = AbstractJsonGetFunction.toJsonPath(jsonPathElements, jsonPathElements.length, false);

context.addChunk("json_query(concat('{\"val\":', concat(");
context.addArgument(0);
context.addChunk(", '}'))");
context.addChunk(",'");
context.addChunk(jsonPath);
context.addChunk("' OMIT QUOTES)");
}
}
Loading

0 comments on commit 1d3f9eb

Please sign in to comment.