Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions babel/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ data: {
"JSON_KEYS"
"JSON_LENGTH"
"JSON_PRETTY"
"JSON_REMOVE"
"JSON_TYPE"
"K"
"KEY"
Expand Down
1 change: 1 addition & 0 deletions core/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ data: {
"JSON_KEYS"
"JSON_LENGTH"
"JSON_PRETTY"
"JSON_REMOVE"
"JSON_TYPE"
"K"
"KEY"
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -4873,6 +4873,8 @@ SqlNode BuiltinFunctionCall() :
node = JsonArrayFunctionCall() { return node; }
|
node = JsonArrayAggFunctionCall() { return node; }
|
node = JsonRemoveFunctionCall() { return node; }
)
}

Expand Down Expand Up @@ -5379,6 +5381,32 @@ SqlCall JsonLengthFunctionCall() :
}
}

SqlCall JsonRemoveFunctionCall() :
{
final List<SqlNode> pathExprs = new ArrayList<SqlNode>();
final SqlNode[] jsonDoc = new SqlNode[1];
SqlNode e;
final Span span;
}
{
<JSON_REMOVE> { span = span(); }
<LPAREN> e = JsonValueExpression(true) {
jsonDoc[0] = e;
}
(
<COMMA>
e = Expression(ExprContext.ACCEPT_NON_QUERY) {
pathExprs.add(e);
}
)*
<RPAREN> {
final List<SqlNode> args = new ArrayList();
args.addAll(Arrays.asList(jsonDoc));
args.addAll(pathExprs);
return SqlStdOperatorTable.JSON_REMOVE.createCall(span.end(this), args);
}
}

SqlCall JsonObjectAggFunctionCall() :
{
final SqlNode[] args = new SqlNode[2];
Expand Down Expand Up @@ -6480,6 +6508,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < JSON_OBJECTAGG: "JSON_OBJECTAGG">
| < JSON_PRETTY: "JSON_PRETTY" >
| < JSON_QUERY: "JSON_QUERY" >
| < JSON_REMOVE: "JSON_REMOVE" >
| < JSON_TYPE: "JSON_TYPE">
| < JSON_VALUE: "JSON_VALUE" >
| < K: "K" >
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_OBJECTAGG;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_PRETTY;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_QUERY;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_REMOVE;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_STRUCTURED_VALUE_EXPRESSION;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_TYPE;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_VALUE_ANY;
Expand Down Expand Up @@ -470,6 +471,7 @@ public Expression implement(RexToLixTranslator translator,
defineMethod(JSON_KEYS, BuiltInMethod.JSON_KEYS.method, NullPolicy.NONE);
defineMethod(JSON_PRETTY, BuiltInMethod.JSON_PRETTY.method, NullPolicy.NONE);
defineMethod(JSON_LENGTH, BuiltInMethod.JSON_LENGTH.method, NullPolicy.NONE);
defineMethod(JSON_REMOVE, BuiltInMethod.JSON_REMOVE.method, NullPolicy.NONE);
aggMap.put(JSON_OBJECTAGG.with(SqlJsonConstructorNullClause.ABSENT_ON_NULL),
JsonObjectAggImplementor
.supplierFor(BuiltInMethod.JSON_OBJECTAGG_ADD.method));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,9 @@ ExInst<CalciteException> invalidTypesForComparison(String clazzName0, String op,

@BaseMessage("Not a valid input for JSON_KEYS: ''{0}''")
ExInst<CalciteException> invalidInputForJsonKeys(String value);

@BaseMessage("Invalid input for JSON_REMOVE: jsonDoc: ''{0}'', jsonPaths: ''{1}''")
ExInst<CalciteException> invalidInputForJsonRemove(String jsonDoc, String jsonPaths);
}

// End CalciteResource.java
21 changes: 21 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2869,6 +2869,27 @@ public static String jsonKeys(Object input) {
return jsonize(list);
}

public static String jsonRemove(Object jsonDoc, Object... jsonPaths) {
try {
DocumentContext ctx = JsonPath.parse(jsonDoc,
Configuration
.builder()
.options(Option.SUPPRESS_EXCEPTIONS)
.jsonProvider(JSON_PATH_JSON_PROVIDER)
.mappingProvider(JSON_PATH_MAPPING_PROVIDER)
.build());
for (Object jsonPath : jsonPaths) {
if ((jsonPath != null) && (ctx.read(jsonPath.toString()) != null)) {
ctx.delete(jsonPath.toString());
}
}
return ctx.jsonString();
} catch (Exception ex) {
throw RESOURCE.invalidInputForJsonRemove(
jsonDoc.toString(), jsonize(jsonPaths)).ex();
}
}

public static boolean isJsonPathContext(Object input) {
try {
PathContext context = (PathContext) input;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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 org.apache.calcite.sql.fun;

import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperandCountRange;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.validate.SqlValidator;

import java.util.Locale;

/**
* The <code>JSON_REMOVE</code> function.
*/
public class SqlJsonRemoveFunction extends SqlFunction {
public SqlJsonRemoveFunction() {
super("JSON_REMOVE", SqlKind.OTHER_FUNCTION, ReturnTypes.VARCHAR_2000, null,
OperandTypes.VARIADIC, SqlFunctionCategory.SYSTEM);
}

@Override public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.from(2);
}

@Override protected void checkOperandCount(SqlValidator validator,
SqlOperandTypeChecker argType, SqlCall call) {
assert call.operandCount() >= 2;
}

@Override public String getSignatureTemplate(int operandsCount) {
assert operandsCount >= 2;
final StringBuilder sb = new StringBuilder();
sb.append("{0}(");
for (int i = 1; i < operandsCount; i++) {
sb.append(String.format(Locale.ROOT, "{%d} ", i + 1));
}
sb.append("{1})");
return sb.toString();
}

@Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec,
int rightPrec) {
assert call.operandCount() >= 2;
final SqlWriter.Frame frame = writer.startFunCall(getName());
call.operand(0).unparse(writer, leftPrec, rightPrec);
SqlWriter.Frame listFrame = writer.startList("", "");
writer.sep(",");
for (int i = 1; i < call.operandCount(); i++) {
writer.sep(",");
call.operand(i).unparse(writer, leftPrec, rightPrec);
}
writer.endList(listFrame);
writer.endFunCall(frame);
}
}

// End SqlJsonRemoveFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,8 @@ public boolean argumentMustBeScalar(int ordinal) {

public static final SqlFunction JSON_LENGTH = new SqlJsonLengthFunction();

public static final SqlFunction JSON_REMOVE = new SqlJsonRemoveFunction();

public static final SqlJsonObjectAggAggFunction JSON_OBJECTAGG =
new SqlJsonObjectAggAggFunction(SqlKind.JSON_OBJECTAGG,
SqlJsonConstructorNullClause.NULL_ON_NULL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ public enum BuiltInMethod {
JSON_KEYS(SqlFunctions.class, "jsonKeys", Object.class),
JSON_PRETTY(SqlFunctions.class, "jsonPretty", Object.class),
JSON_LENGTH(SqlFunctions.class, "jsonLength", Object.class),
JSON_REMOVE(SqlFunctions.class, "jsonRemove", Object.class, Object.class),
JSON_OBJECTAGG_ADD(SqlFunctions.class, "jsonObjectAggAdd", Map.class,
String.class, Object.class, SqlJsonConstructorNullClause.class),
JSON_ARRAY(SqlFunctions.class, "jsonArray",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,5 @@ InvalidInputForJsonDepth=Not a valid input for JSON_DEPTH: ''{0}''
ExceptionWhileSerializingToJson=Cannot serialize object to JSON, and the object is: ''{0}''
InvalidInputForJsonLength=Not a valid input for JSON_LENGTH: ''{0}''
InvalidInputForJsonKeys=Not a valid input for JSON_KEYS: ''{0}''
InvalidInputForJsonRemove=Invalid input for JSON_REMOVE: jsonDoc: ''{0}'', jsonPaths: ''{1}''
# End CalciteResource.properties
1 change: 1 addition & 0 deletions core/src/test/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ data: {
"JSON_KEYS"
"JSON_LENGTH"
"JSON_PRETTY"
"JSON_REMOVE"
"JSON_TYPE"
"K"
"KEY"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3246,6 +3246,13 @@ private void checkLiteral2(String expression, String expected) {
sql(query).ok(expected);
}

@Test public void testJsonRemove() {
String query = "select json_remove(\"product_name\", '$[0]') from \"product\"";
final String expected = "SELECT JSON_REMOVE(\"product_name\" FORMAT JSON, '$[0]')\n"
+ "FROM \"foodmart\".\"product\"";
sql(query).ok(expected);
}

/** Fluid interface to run tests. */
static class Sql {
private final SchemaPlus schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8481,6 +8481,13 @@ public void subTestIntervalSecondFailsValidation() {
"JSON_KEYS('{\"foo\": \"bar\"}' FORMAT JSON, 'invalid $')");
}

@Test public void testJsonRemove() {
checkExp("json_remove('[\"a\", [\"b\", \"c\"], \"d\"]', '$')",
"JSON_REMOVE('[\"a\", [\"b\", \"c\"], \"d\"]' FORMAT JSON, '$')");
checkExp("json_remove('[\"a\", [\"b\", \"c\"], \"d\"]', '$[1]', '$[0]')",
"JSON_REMOVE('[\"a\", [\"b\", \"c\"], \"d\"]' FORMAT JSON, '$[1]', '$[0]')");
}

@Test public void testJsonObjectAgg() {
checkExp("json_objectagg(k_column: v_column)",
"JSON_OBJECTAGG(KEY `K_COLUMN` VALUE `V_COLUMN` NULL ON NULL)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4643,6 +4643,21 @@ private void checkNullOperand(SqlTester tester, String op) {
"(?s).*No results for path.*", true);
}

@Test public void testJsonRemove() {
tester.checkString("json_remove('{\"foo\":100}', '$.foo')",
"{}", "VARCHAR(2000) NOT NULL");
tester.checkString("json_remove('{\"foo\":100, \"foo1\":100}', '$.foo')",
"{\"foo1\":100}", "VARCHAR(2000) NOT NULL");
tester.checkString("json_remove('[\"a\", [\"b\", \"c\"], \"d\"]', '$[1][0]')",
"[\"a\",[\"c\"],\"d\"]", "VARCHAR(2000) NOT NULL");
tester.checkString("json_remove('[\"a\", [\"b\", \"c\"], \"d\"]', '$[1]')",
"[\"a\",\"d\"]", "VARCHAR(2000) NOT NULL");
tester.checkString("json_remove('[\"a\", [\"b\", \"c\"], \"d\"]', '$[0]', '$[0]')",
"[\"d\"]", "VARCHAR(2000) NOT NULL");
tester.checkFails("json_remove('[\"a\", [\"b\", \"c\"], \"d\"]', '$')",
"(?s).*Invalid input for.*", true);
}

@Test public void testJsonObjectAgg() {
checkAggType(tester, "json_objectagg('foo': 'bar')", "VARCHAR(2000) NOT NULL");
tester.checkFails("^json_objectagg(100: 'bar')^",
Expand Down
8 changes: 8 additions & 0 deletions core/src/test/java/org/apache/calcite/test/JdbcTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6876,6 +6876,14 @@ public void testJsonPretty() {
.returns("C1=[\"a\",\"b\"]; C2=null; C3=[\"c\"]; C4=null; C5=null\n");
}

@Test public void testJsonRemove() {
CalciteAssert.that()
.query("SELECT JSON_REMOVE(v, '$[1]') AS c1\n"
+ "FROM (VALUES ('[\"a\", [\"b\", \"c\"], \"d\"]')) AS t(v)\n"
+ "limit 10")
.returns("C1=[\"a\",\"d\"]\n");
}

/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-2609">[CALCITE-2609]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,18 @@ public void testJsonKeys() {
is("null"));
}

@Test
public void testJsonRemove() {
assertJsonRemove(
SqlFunctions.jsonValueExpression("{\"a\": 1, \"b\": [2]}"),
new Object[]{"$.a"},
is("{\"b\":[2]}"));
assertJsonRemove(
SqlFunctions.jsonValueExpression("{\"a\": 1, \"b\": [2]}"),
new Object[]{"$.a", "$.b"},
is("{}"));
}

@Test
public void testJsonObjectAggAdd() {
Map<String, Object> map = new HashMap<>();
Expand Down Expand Up @@ -737,6 +749,13 @@ private void assertJsonKeysFailed(Object input,
matcher);
}

private void assertJsonRemove(Object jsonDoc, Object[] elems,
Matcher<? super String> matcher) {
assertThat(invocationDesc(BuiltInMethod.JSON_REMOVE.getMethodName(), jsonDoc, elems),
SqlFunctions.jsonRemove(jsonDoc, elems),
matcher);
}

private void assertDejsonize(String input,
Matcher<Object> matcher) {
assertThat(invocationDesc(BuiltInMethod.DEJSONIZE.getMethodName(), input),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10968,6 +10968,13 @@ private void checkCustomColumnResolving(String table) {
checkExpType("json_keys('{\"foo\":\"bar\"}', 'strict $')", "VARCHAR(2000) NOT NULL");
}

@Test public void testJsonRemove() {
checkExp("json_remove('{\"foo\":\"bar\"}', '$')");
checkExpType("json_remove('{\"foo\":\"bar\"}', '$')", "VARCHAR(2000) NOT NULL");
checkFails("select ^json_remove('{\"foo\":\"bar\"}')^",
"(?s).*Invalid number of arguments.*");
}

@Test public void testJsonObjectAgg() {
check("select json_objectagg(ename: empno) from emp");
checkFails("select ^json_objectagg(empno: ename)^ from emp",
Expand Down
1 change: 1 addition & 0 deletions server/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ data: {
"JSON_KEYS"
"JSON_LENGTH"
"JSON_PRETTY"
"JSON_REMOVE"
"JSON_TYPE"
"K"
"KEY"
Expand Down
19 changes: 18 additions & 1 deletion site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ JSON_LENGTH,
**JSON_OBJECTAGG**,
JSON_PRETTY,
**JSON_QUERY**,
JSON_REMOVE,
JSON_TYPE,
**JSON_VALUE**,
K,
Expand Down Expand Up @@ -2054,6 +2055,7 @@ Note:
| JSON_PRETTY(jsonValue) | Returns a pretty-printing of *jsonValue*
| JSON_LENGTH(jsonValue [, path ]) | Returns a integer indicating the length of *jsonValue*
| JSON_KEYS(jsonValue [, path ]) | Returns a string indicating the keys of a JSON *jsonValue*
| JSON_REMOVE(jsonValue, path[, path]) | Returns a JSON document by remove a data of *path*

Note:

Expand Down Expand Up @@ -2157,12 +2159,27 @@ LIMIT 10;
| ---------- | ---- | ----- | ---- | ---- |
| ["a", "b"] | NULL | ["c"] | NULL | NULL |

##### JSON_REMOVE example

SQL

```SQL
SELECT JSON_REMOVE(v, '$[1]') AS c1
FROM (VALUES ('["a", ["b", "c"], "d"]')) AS t(v)
LIMIT 10;
```

Result

| c1 |
| ---------- |
| ["a", "d"] |

Not implemented:

* JSON_INSERT
* JSON_SET
* JSON_REPLACE
* JSON_REMOVE

## User-defined functions

Expand Down