Skip to content

Commit a4b0729

Browse files
#131: adding new Json function (#132)
* #131: adding new Json function * #131: added Javadocs and tests Co-authored-by: Muhammet Orazov <m.orazow@gmail.com>
1 parent e28d1fd commit a4b0729

28 files changed

+699
-144
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>com.exasol</groupId>
66
<artifactId>virtual-schema-common-java</artifactId>
7-
<version>8.0.2</version>
7+
<version>9.0.0</version>
88
<name>Common module of Exasol Virtual Schemas Adapters</name>
99
<description>This is one of the modules of Virtual Schemas Adapters. The libraries provided by this project are the foundation of the adapter development, i.e. adapters must be implemented on top of them.</description>
1010
<url>https://github.com/exasol/virtual-schema-common-java</url>

src/main/java/com/exasol/adapter/capabilities/PredicateCapability.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ public enum PredicateCapability {
1010
/**
1111
* The LIKE predicate with the optional escape character defined
1212
*/
13-
LIKE_ESCAPE(Predicate.LIKE), REGEXP_LIKE, BETWEEN, IN_CONSTLIST, IS_NULL, IS_NOT_NULL;
13+
LIKE_ESCAPE(Predicate.LIKE), REGEXP_LIKE, BETWEEN, IN_CONSTLIST, IS_NULL, IS_NOT_NULL,
14+
IS_JSON, IS_NOT_JSON;
1415

1516
private final Predicate predicate;
1617

src/main/java/com/exasol/adapter/capabilities/ScalarFunctionCapability.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public enum ScalarFunctionCapability {
3333
BIT_AND, BIT_CHECK, BIT_NOT, BIT_OR, BIT_SET, BIT_TO_NUM, BIT_XOR,
3434

3535
CASE, CURRENT_SCHEMA, CURRENT_SESSION, CURRENT_STATEMENT, CURRENT_USER, HASH_MD5, HASH_SHA, HASH_SHA1, HASH_SHA256,
36-
HASH_SHA512, HASH_TIGER, NULLIFZERO, SYS_GUID, ZEROIFNULL;
36+
HASH_SHA512, HASH_TIGER, NULLIFZERO, SYS_GUID, ZEROIFNULL,
37+
JSON_VALUE;
3738

3839
public ScalarFunction getFunction() {
3940
return ScalarFunction.valueOf(name());

src/main/java/com/exasol/adapter/request/parser/PushdownSqlParser.java

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package com.exasol.adapter.request.parser;
22

3+
import java.math.BigDecimal;
4+
import java.util.*;
5+
6+
import javax.json.JsonArray;
7+
import javax.json.JsonObject;
8+
39
import com.exasol.adapter.metadata.*;
410
import com.exasol.adapter.metadata.DataType.ExaCharset;
511
import com.exasol.adapter.metadata.DataType.IntervalType;
612
import com.exasol.adapter.sql.*;
713

8-
import javax.json.JsonArray;
9-
import javax.json.JsonObject;
10-
import java.math.BigDecimal;
11-
import java.util.ArrayList;
12-
import java.util.List;
13-
1414
public final class PushdownSqlParser extends AbstractRequestParser {
1515
private static final String ORDER_BY = "orderBy";
1616
private static final String EXPRESSION = "expression";
@@ -78,6 +78,10 @@ public SqlNode parseExpression(final JsonObject expression) {
7878
return parsePredicateBetween(expression);
7979
case PREDICATE_IN_CONSTLIST:
8080
return parsePredicateInConstlist(expression);
81+
case PREDICATE_IS_JSON:
82+
return parsePredicateIsJson(expression);
83+
case PREDICATE_IS_NOT_JSON:
84+
return parsePredicateIsNotJson(expression);
8185
case PREDICATE_IS_NULL:
8286
return parsePredicateIsNull(expression);
8387
case PREDICATE_IS_NOT_NULL:
@@ -90,6 +94,8 @@ public SqlNode parseExpression(final JsonObject expression) {
9094
return parseFunctionScalarCase(expression);
9195
case FUNCTION_SCALAR_CAST:
9296
return parseFunctionScalarCast(expression);
97+
case FUNCTION_SCALAR_JSON_VALUE:
98+
return parseFunctionScalarJsonValue(expression);
9399
case FUNCTION_AGGREGATE:
94100
return parseFunctionAggregate(expression);
95101
case FUNCTION_AGGREGATE_GROUP_CONCAT:
@@ -277,18 +283,22 @@ private SqlNode parsePredicateNot(final JsonObject exp) {
277283
}
278284

279285
private SqlNode parsePredicateOr(final JsonObject exp) {
280-
final List<SqlNode> orPredicates = new ArrayList<>();
281-
for (final JsonObject pred : exp.getJsonArray("expressions").getValuesAs(JsonObject.class)) {
282-
orPredicates.add(parseExpression(pred));
283-
}
286+
final List<SqlNode> orPredicates = getListOfSqlNodes(exp, "expressions");
284287
return new SqlPredicateOr(orPredicates);
285288
}
286289

287-
private SqlNode parsePredicateAnd(final JsonObject exp) {
288-
final List<SqlNode> andedPredicates = new ArrayList<>();
289-
for (final JsonObject pred : exp.getJsonArray("expressions").getValuesAs(JsonObject.class)) {
290-
andedPredicates.add(parseExpression(pred));
290+
private List<SqlNode> getListOfSqlNodes(final JsonObject jsonExpression, final String key) {
291+
final List<SqlNode> arguments = new ArrayList<>();
292+
if (jsonExpression.containsKey(key)) {
293+
for (final JsonObject jsonObject : jsonExpression.getJsonArray(key).getValuesAs(JsonObject.class)) {
294+
arguments.add(parseExpression(jsonObject));
295+
}
291296
}
297+
return arguments;
298+
}
299+
300+
private SqlNode parsePredicateAnd(final JsonObject exp) {
301+
final List<SqlNode> andedPredicates = getListOfSqlNodes(exp, "expressions");
292302
return new SqlPredicateAnd(andedPredicates);
293303
}
294304

@@ -420,12 +430,27 @@ private SqlNode parsePredicateBetween(final JsonObject exp) {
420430
return new SqlPredicateBetween(betweenExp, betweenLeft, betweenRight);
421431
}
422432

433+
private SqlNode parsePredicateIsJson(final JsonObject jsonExpression) {
434+
final SqlNode expression = parseExpression(jsonExpression.getJsonObject(EXPRESSION));
435+
final SqlPredicateIsJson.TypeConstraints typeConstraint = SqlPredicateIsJson.TypeConstraints
436+
.valueOf(jsonExpression.getString("typeConstraint").toUpperCase());
437+
final SqlPredicateIsJson.KeyUniquenessConstraint keyUniquenessConstraint = SqlPredicateIsJson.KeyUniquenessConstraint
438+
.of(jsonExpression.getString("keyUniquenessConstraint"));
439+
return new SqlPredicateIsJson(expression, typeConstraint, keyUniquenessConstraint);
440+
}
441+
442+
private SqlNode parsePredicateIsNotJson(final JsonObject jsonExpression) {
443+
final SqlNode expression = parseExpression(jsonExpression.getJsonObject(EXPRESSION));
444+
final SqlPredicateIsJson.TypeConstraints typeConstraint = SqlPredicateIsJson.TypeConstraints
445+
.valueOf(jsonExpression.getString("typeConstraint").toUpperCase());
446+
final SqlPredicateIsJson.KeyUniquenessConstraint keyUniquenessConstraint = SqlPredicateIsJson.KeyUniquenessConstraint
447+
.of(jsonExpression.getString("keyUniquenessConstraint"));
448+
return new SqlPredicateIsNotJson(expression, typeConstraint, keyUniquenessConstraint);
449+
}
450+
423451
private SqlNode parsePredicateInConstlist(final JsonObject exp) {
424452
final SqlNode inExp = parseExpression(exp.getJsonObject(EXPRESSION));
425-
final List<SqlNode> inArguments = new ArrayList<>();
426-
for (final JsonObject pred : exp.getJsonArray(ARGUMENTS).getValuesAs(JsonObject.class)) {
427-
inArguments.add(parseExpression(pred));
428-
}
453+
final List<SqlNode> inArguments = getListOfSqlNodes(exp, ARGUMENTS);
429454
return new SqlPredicateInConstList(inExp, inArguments);
430455
}
431456

@@ -436,10 +461,7 @@ private SqlNode parseFunctionScalar(final JsonObject exp) {
436461
if (exp.containsKey("variableInputArgs")) {
437462
hasVariableInputArgs = exp.getBoolean("variableInputArgs");
438463
}
439-
final List<SqlNode> arguments = new ArrayList<>();
440-
for (final JsonObject argument : exp.getJsonArray(ARGUMENTS).getValuesAs(JsonObject.class)) {
441-
arguments.add(parseExpression(argument));
442-
}
464+
final List<SqlNode> arguments = getListOfSqlNodes(exp, ARGUMENTS);
443465
if (!hasVariableInputArgs) {
444466
numArgs = exp.getInt("numArgs");
445467
assert numArgs == arguments.size();
@@ -457,58 +479,64 @@ private SqlNode parseFunctionScalar(final JsonObject exp) {
457479

458480
private SqlNode parseFunctionScalarExtract(final JsonObject exp) {
459481
final String toExtract = exp.getString("toExtract");
460-
final List<SqlNode> extractArguments = new ArrayList<>();
461-
if (exp.containsKey(ARGUMENTS)) {
462-
for (final JsonObject argument : exp.getJsonArray(ARGUMENTS).getValuesAs(JsonObject.class)) {
463-
extractArguments.add(parseExpression(argument));
464-
}
465-
}
482+
final List<SqlNode> extractArguments = getListOfSqlNodes(exp, ARGUMENTS);
466483
return new SqlFunctionScalarExtract(toExtract, extractArguments);
467484
}
468485

469486
private SqlNode parseFunctionScalarCase(final JsonObject exp) {
470-
final List<SqlNode> caseArguments = new ArrayList<>();
471-
final List<SqlNode> caseResults = new ArrayList<>();
487+
final List<SqlNode> caseArguments = getListOfSqlNodes(exp, ARGUMENTS);
488+
final List<SqlNode> caseResults = getListOfSqlNodes(exp, "results");
472489
SqlNode caseBasis = null;
473-
if (exp.containsKey(ARGUMENTS)) {
474-
for (final JsonObject argument : exp.getJsonArray(ARGUMENTS).getValuesAs(JsonObject.class)) {
475-
caseArguments.add(parseExpression(argument));
476-
}
477-
}
478-
if (exp.containsKey("results")) {
479-
for (final JsonObject argument : exp.getJsonArray("results").getValuesAs(JsonObject.class)) {
480-
caseResults.add(parseExpression(argument));
481-
}
482-
}
483490
if (exp.containsKey("basis")) {
484491
caseBasis = parseExpression(exp.getJsonObject("basis"));
485492
}
486493
return new SqlFunctionScalarCase(caseArguments, caseResults, caseBasis);
487494
}
488495

489-
private SqlNode parseFunctionScalarCast(final JsonObject exp) {
490-
final DataType castDataType = getDataType(exp.getJsonObject(DATA_TYPE));
491-
final List<SqlNode> castArguments = new ArrayList<>();
492-
if (exp.containsKey(ARGUMENTS)) {
493-
for (final JsonObject argument : exp.getJsonArray(ARGUMENTS).getValuesAs(JsonObject.class)) {
494-
castArguments.add(parseExpression(argument));
495-
}
496-
}
496+
private SqlNode parseFunctionScalarCast(final JsonObject jsonObject) {
497+
final DataType castDataType = getDataType(jsonObject.getJsonObject(DATA_TYPE));
498+
final List<SqlNode> castArguments = getListOfSqlNodes(jsonObject, ARGUMENTS);
497499
return new SqlFunctionScalarCast(castDataType, castArguments);
498500
}
499501

502+
private SqlNode parseFunctionScalarJsonValue(final JsonObject jsonObject) {
503+
final String functionName = jsonObject.getString("name");
504+
final List<SqlNode> arguments = getListOfSqlNodes(jsonObject, ARGUMENTS);
505+
final DataType returningDataType = getDataType(jsonObject.getJsonObject("returningDataType"));
506+
final SqlFunctionScalarJsonValue.Behavior emptyBehavior = getScalarJsonValueBehavior(jsonObject,
507+
"emptyBehavior");
508+
final SqlFunctionScalarJsonValue.Behavior errorBehavior = getScalarJsonValueBehavior(jsonObject,
509+
"errorBehavior");
510+
return new SqlFunctionScalarJsonValue(fromScalarFunctionName(functionName), arguments, returningDataType,
511+
emptyBehavior, errorBehavior);
512+
}
513+
514+
private SqlFunctionScalarJsonValue.Behavior getScalarJsonValueBehavior(final JsonObject jsonObject,
515+
final String key) {
516+
final JsonObject behaviorJson = jsonObject.getJsonObject(key);
517+
final String behaviorTypeString = behaviorJson.getString("type");
518+
final SqlFunctionScalarJsonValue.BehaviorType behaviorType = SqlFunctionScalarJsonValue.BehaviorType
519+
.valueOf(behaviorTypeString);
520+
final Optional<SqlNode> expression = getScalarJsonValueExpression(behaviorJson, behaviorType);
521+
return new SqlFunctionScalarJsonValue.Behavior(behaviorType, expression);
522+
}
523+
524+
private Optional<SqlNode> getScalarJsonValueExpression(final JsonObject jsonObject,
525+
final SqlFunctionScalarJsonValue.BehaviorType behaviorType) {
526+
if (behaviorType == SqlFunctionScalarJsonValue.BehaviorType.DEFAULT) {
527+
return Optional.of(parseExpression(jsonObject.getJsonObject(EXPRESSION)));
528+
} else {
529+
return Optional.empty();
530+
}
531+
}
532+
500533
private SqlNode parseFunctionAggregate(final JsonObject exp) {
501534
final String setFunctionName = exp.getString("name");
502-
final List<SqlNode> setArguments = new ArrayList<>();
535+
final List<SqlNode> setArguments = getListOfSqlNodes(exp, ARGUMENTS);
503536
boolean distinct = false;
504537
if (exp.containsKey(DISTINCT)) {
505538
distinct = exp.getBoolean(DISTINCT);
506539
}
507-
if (exp.containsKey(ARGUMENTS)) {
508-
for (final JsonObject argument : exp.getJsonArray(ARGUMENTS).getValuesAs(JsonObject.class)) {
509-
setArguments.add(parseExpression(argument));
510-
}
511-
}
512540
return new SqlFunctionAggregate(fromAggregationFunctionName(setFunctionName), setArguments, distinct);
513541
}
514542

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.exasol.adapter.sql;
2+
3+
/**
4+
* This class contains a common behavior for the {@link SqlNodeType#PREDICATE_IS_JSON} and
5+
* {@link SqlNodeType#PREDICATE_IS_NOT_JSON} predicates.
6+
*/
7+
public abstract class AbstractSqlPredicateJson extends SqlPredicate {
8+
protected final SqlNode expression;
9+
protected final SqlPredicateIsJson.TypeConstraints typeConstraint;
10+
protected final SqlPredicateIsJson.KeyUniquenessConstraint keyUniquenessConstraint;
11+
12+
public AbstractSqlPredicateJson(final Predicate predicate, final SqlNode expression,
13+
final SqlPredicateIsJson.TypeConstraints typeConstraint,
14+
final SqlPredicateIsJson.KeyUniquenessConstraint keyUniquenessConstraint) {
15+
super(predicate);
16+
this.expression = expression;
17+
this.typeConstraint = typeConstraint;
18+
this.keyUniquenessConstraint = keyUniquenessConstraint;
19+
}
20+
21+
public SqlNode getExpression() {
22+
return this.expression;
23+
}
24+
25+
public String getTypeConstraint() {
26+
return this.typeConstraint.toString();
27+
}
28+
29+
public String getKeyUniquenessConstraint() {
30+
return this.keyUniquenessConstraint.toString();
31+
}
32+
33+
/**
34+
* A list of expected type constraints.
35+
*/
36+
public enum TypeConstraints {
37+
VALUE, ARRAY, OBJECT, SCALAR
38+
}
39+
40+
/**
41+
* A list of expected key uniqueness constraints.
42+
*/
43+
public enum KeyUniquenessConstraint {
44+
WITH_UNIQUE_KEYS("WITH UNIQUE KEYS"), WITHOUT_UNIQUE_KEYS("WITHOUT UNIQUE KEYS");
45+
46+
private final String value;
47+
48+
KeyUniquenessConstraint(final String value) {
49+
this.value = value;
50+
}
51+
52+
@Override
53+
public String toString() {
54+
return this.value;
55+
}
56+
57+
public static SqlPredicateIsJson.KeyUniquenessConstraint of(final String value) {
58+
final String formattedValue = String.join("_", value.split(" ")).toUpperCase();
59+
return SqlPredicateIsJson.KeyUniquenessConstraint.valueOf(formattedValue);
60+
}
61+
}
62+
}

src/main/java/com/exasol/adapter/sql/Predicate.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
* List of all predicates (scalar functions returning bool) supported by EXASOL.
55
*/
66
public enum Predicate {
7-
AND, OR, NOT, EQUAL, NOTEQUAL, LESS, LESSEQUAL, LIKE, REGEXP_LIKE, BETWEEN, IN_CONSTLIST, IS_NULL, IS_NOT_NULL
7+
AND, OR, NOT, EQUAL, NOTEQUAL, LESS, LESSEQUAL, LIKE, REGEXP_LIKE, BETWEEN, IN_CONSTLIST, IS_NULL, IS_NOT_NULL,
8+
IS_JSON, IS_NOT_JSON
89
}

src/main/java/com/exasol/adapter/sql/ScalarFunction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ CURRENT_TIMESTAMP, DATE_TRUNC, DAY, DAYS_BETWEEN, DBTIMEZONE, EXTRACT(false), HO
3030
BIT_AND, BIT_CHECK, BIT_NOT, BIT_OR, BIT_SET, BIT_TO_NUM, BIT_XOR, //
3131

3232
CASE(false), CURRENT_SCHEMA, CURRENT_SESSION, CURRENT_STATEMENT, CURRENT_USER, HASH_MD5, HASH_SHA, HASH_SHA1,
33-
HASH_SHA256, HASH_SHA512, HASH_TIGER, NULLIFZERO, SYS_GUID, ZEROIFNULL;
33+
HASH_SHA256, HASH_SHA512, HASH_TIGER, NULLIFZERO, SYS_GUID, ZEROIFNULL,
34+
JSON_VALUE;
3435

3536
private final boolean isSimple;
3637

src/main/java/com/exasol/adapter/sql/SqlArgumentValidator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ private SqlArgumentValidator() {
77
// Intentionally left blank
88
}
99

10-
public static void validateSqlFunctionArguments(final List<SqlNode> arguments, final Class<?> usedClass) {
10+
public static void validateSingleAgrumentFunctionParameter(final List<SqlNode> arguments,
11+
final Class<?> usedClass) {
1112
if (arguments == null) {
1213
throw new IllegalArgumentException(
13-
usedClass.getName() + " constructor expects an argument." + "But the argument is NULL.");
14+
usedClass.getName() + " constructor expects an argument. But the argument is NULL.");
1415
}
1516
if (arguments.size() != 1) {
1617
throw new IllegalArgumentException(usedClass.getName() + " constructor expects exactly one argument."

src/main/java/com/exasol/adapter/sql/SqlFunctionAggregateGroupConcat.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class SqlFunctionAggregateGroupConcat extends SqlNode {
1414

1515
public SqlFunctionAggregateGroupConcat(final AggregateFunction function, final List<SqlNode> arguments,
1616
final SqlOrderBy orderBy, final boolean distinct, final String separator) {
17-
SqlArgumentValidator.validateSqlFunctionArguments(arguments, SqlFunctionAggregateGroupConcat.class);
17+
SqlArgumentValidator.validateSingleAgrumentFunctionParameter(arguments, SqlFunctionAggregateGroupConcat.class);
1818
this.function = function;
1919
this.distinct = distinct;
2020
this.arguments = arguments;
@@ -98,4 +98,4 @@ public SqlNodeType getType() {
9898
public <R> R accept(final SqlNodeVisitor<R> visitor) throws AdapterException {
9999
return visitor.visit(this);
100100
}
101-
}
101+
}

src/main/java/com/exasol/adapter/sql/SqlFunctionScalarCase.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,4 @@ public SqlNodeType getType() {
6363
public <R> R accept(final SqlNodeVisitor<R> visitor) throws AdapterException {
6464
return visitor.visit(this);
6565
}
66-
67-
public ScalarFunction getFunction() {
68-
return ScalarFunction.CASE;
69-
}
7066
}

0 commit comments

Comments
 (0)