keywordArguments) {
+ this.keywordArguments = keywordArguments;
+ return this;
+ }
+
+ /**
+ * Convenience lookup: returns the expression for the first keyword argument matching the given
+ * keyword (case-insensitive), or {@code null}.
+ */
+ public Expression getKeywordArgumentValue(String keyword) {
+ if (keywordArguments == null) {
+ return null;
+ }
+ for (KeywordArgument ka : keywordArguments) {
+ if (ka.getKeyword().equalsIgnoreCase(keyword)) {
+ return ka.getExpression();
+ }
+ }
+ return null;
+ }
+
@Override
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
public String toString() {
@@ -324,6 +399,13 @@ public String toString() {
b.append(" ON OVERFLOW ").append(onOverflowTruncate);
}
+ // Generic keyword arguments (e.g. SEPARATOR ',')
+ if (keywordArguments != null) {
+ for (KeywordArgument ka : keywordArguments) {
+ ka.appendTo(b);
+ }
+ }
+
b.append(")");
params = b.toString();
} else {
@@ -335,6 +417,10 @@ public String toString() {
String ans = getName() + params;
+ if (chainedParameters != null) {
+ ans += "(" + chainedParameters + ")";
+ }
+
if (nullHandling != null && isIgnoreNullsOutside()) {
switch (nullHandling) {
case IGNORE_NULLS:
@@ -393,6 +479,11 @@ public Function withParameters(Expression... parameters) {
return withParameters(new ExpressionList<>(parameters));
}
+ public Function withChainedParameters(ExpressionList> chainedParameters) {
+ this.setChainedParameters(chainedParameters);
+ return this;
+ }
+
public Function withNamedParameters(NamedExpressionList> namedParameters) {
this.setNamedParameters(namedParameters);
return this;
@@ -438,6 +529,74 @@ public enum NullHandling {
IGNORE_NULLS, RESPECT_NULLS;
}
+ // ── KeywordArgument inner class ────────────────────────────────────
+
+ /**
+ * Represents a generic {@code KEYWORD expression} pair inside a function call.
+ *
+ * Examples:
+ *
+ * {@code GROUP_CONCAT(col SEPARATOR ',')} → keyword="SEPARATOR", expression=','
+ *
+ */
+ public static class KeywordArgument implements Serializable {
+ private String keyword;
+ private Expression expression;
+
+ public KeywordArgument() {}
+
+ public KeywordArgument(String keyword, Expression expression) {
+ this.keyword = keyword;
+ this.expression = expression;
+ }
+
+ public String getKeyword() {
+ return keyword;
+ }
+
+ public KeywordArgument setKeyword(String keyword) {
+ this.keyword = keyword;
+ return this;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public KeywordArgument setExpression(Expression expression) {
+ this.expression = expression;
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append(" ").append(keyword).append(" ").append(expression);
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return keyword + " " + expression;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof KeywordArgument)) {
+ return false;
+ }
+ KeywordArgument that = (KeywordArgument) o;
+ return Objects.equals(keyword, that.keyword)
+ && Objects.equals(expression, that.expression);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(keyword, expression);
+ }
+ }
+
public static class HavingClause extends ASTNodeAccessImpl implements Expression {
HavingType havingType;
Expression expression;
diff --git a/src/main/java/net/sf/jsqlparser/expression/HexValue.java b/src/main/java/net/sf/jsqlparser/expression/HexValue.java
index cba9a3ac1..55ee5d0e9 100644
--- a/src/main/java/net/sf/jsqlparser/expression/HexValue.java
+++ b/src/main/java/net/sf/jsqlparser/expression/HexValue.java
@@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.expression;
+import java.util.Locale;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import java.nio.charset.StandardCharsets;
@@ -60,7 +61,7 @@ public String toString() {
}
public String getDigits() {
- return value.toUpperCase().startsWith("0X")
+ return value.toUpperCase(Locale.ROOT).startsWith("0X")
? value.substring(2)
: value.substring(2, value.length() - 1);
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java b/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java
index 9c028c769..7e496b4ce 100644
--- a/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java
+++ b/src/main/java/net/sf/jsqlparser/expression/IntervalExpression.java
@@ -18,6 +18,7 @@ public class IntervalExpression extends ASTNodeAccessImpl implements Expression
private final boolean intervalKeyword;
private String parameter = null;
private String intervalType = null;
+ private IntervalQualifier intervalQualifier = null;
private Expression expression = null;
public IntervalExpression() {
@@ -47,12 +48,42 @@ public void setParameter(String parameter) {
this.parameter = parameter;
}
+ /**
+ * Returns the non-standard single-identifier interval type (e.g. MySQL {@code INTERVAL 1 foo}),
+ * or {@code null} when a structured {@link IntervalQualifier} is present.
+ *
+ * @deprecated the structured {@link #getIntervalQualifier()} is the canonical representation.
+ * This legacy field is kept only for the non-standard single-identifier form.
+ * @return the legacy interval type, or {@code null} if a qualifier is set
+ */
+ @Deprecated
public String getIntervalType() {
return intervalType;
}
+ /**
+ * Sets the non-standard single-identifier interval type (e.g. MySQL {@code INTERVAL 1 foo}) and
+ * clears any structured qualifier.
+ *
+ * @deprecated use {@link #setIntervalQualifier(IntervalQualifier)} for the standard form.
+ * @param intervalType the legacy interval type
+ */
+ @Deprecated
public void setIntervalType(String intervalType) {
this.intervalType = intervalType;
+ this.intervalQualifier = null;
+ }
+
+ public IntervalQualifier getIntervalQualifier() {
+ return intervalQualifier;
+ }
+
+ /**
+ * Sets the structured interval qualifier and clears the legacy interval type.
+ */
+ public void setIntervalQualifier(IntervalQualifier intervalQualifier) {
+ this.intervalQualifier = intervalQualifier;
+ this.intervalType = null;
}
public Expression getExpression() {
@@ -67,7 +98,9 @@ public void setExpression(Expression expression) {
public String toString() {
return (intervalKeyword ? "INTERVAL " : "")
+ Objects.toString(expression, parameter)
- + (intervalType != null ? " " + intervalType : "");
+ + (intervalQualifier != null
+ ? " " + intervalQualifier.toString()
+ : (intervalType != null ? " " + intervalType : ""));
}
@Override
@@ -80,11 +113,22 @@ public IntervalExpression withParameter(String parameter) {
return this;
}
+ /**
+ * @deprecated use {@link #withIntervalQualifier(IntervalQualifier)} for the standard form.
+ * @param intervalType the legacy interval type
+ * @return this instance
+ */
+ @Deprecated
public IntervalExpression withIntervalType(String intervalType) {
this.setIntervalType(intervalType);
return this;
}
+ public IntervalExpression withIntervalQualifier(IntervalQualifier intervalQualifier) {
+ this.setIntervalQualifier(intervalQualifier);
+ return this;
+ }
+
public IntervalExpression withExpression(Expression expression) {
this.setExpression(expression);
return this;
diff --git a/src/main/java/net/sf/jsqlparser/expression/IntervalQualifier.java b/src/main/java/net/sf/jsqlparser/expression/IntervalQualifier.java
new file mode 100644
index 000000000..a9676c69c
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/IntervalQualifier.java
@@ -0,0 +1,123 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * Models an SQL interval qualifier as defined by the SQL standard (see
+ * PostgreSQL: Interval
+ * Input ), e.g. {@code YEAR}, {@code DAY TO SECOND}, {@code DAY(9) TO SECOND} or
+ * {@code SECOND(2, 4)}.
+ *
+ * It is shared by {@link IntervalExpression} (the {@code INTERVAL ...} expression literal, used in
+ * DML) and by {@code net.sf.jsqlparser.statement.create.table.ColDataType} (the {@code INTERVAL}
+ * column / cast target type, used in DDL), so the qualifier is represented once and round-trips
+ * consistently across all contexts.
+ */
+public final class IntervalQualifier implements Serializable {
+
+ private final String leadingField;
+ private final Integer leadingFieldPrecision;
+ private final String trailingField;
+ private final Integer fractionalSecondsPrecision;
+
+ public IntervalQualifier(
+ String leadingField, Integer leadingFieldPrecision, String trailingField,
+ Integer fractionalSecondsPrecision) {
+ this.leadingField = leadingField;
+ this.leadingFieldPrecision = leadingFieldPrecision;
+ this.trailingField = trailingField;
+ this.fractionalSecondsPrecision = fractionalSecondsPrecision;
+ }
+
+ public String getLeadingField() {
+ return leadingField;
+ }
+
+ public Integer getLeadingFieldPrecision() {
+ return leadingFieldPrecision;
+ }
+
+ public String getTrailingField() {
+ return trailingField;
+ }
+
+ public Integer getFractionalSecondsPrecision() {
+ return fractionalSecondsPrecision;
+ }
+
+ public IntervalQualifier withLeadingField(String leadingField) {
+ return new IntervalQualifier(
+ leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
+ }
+
+ public IntervalQualifier withLeadingFieldPrecision(Integer leadingFieldPrecision) {
+ return new IntervalQualifier(
+ leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
+ }
+
+ public IntervalQualifier withTrailingField(String trailingField) {
+ return new IntervalQualifier(
+ leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
+ }
+
+ public IntervalQualifier withFractionalSecondsPrecision(Integer fractionalSecondsPrecision) {
+ return new IntervalQualifier(
+ leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof IntervalQualifier)) {
+ return false;
+ }
+ IntervalQualifier that = (IntervalQualifier) o;
+ return Objects.equals(leadingField, that.leadingField)
+ && Objects.equals(leadingFieldPrecision, that.leadingFieldPrecision)
+ && Objects.equals(trailingField, that.trailingField)
+ && Objects.equals(fractionalSecondsPrecision, that.fractionalSecondsPrecision);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ leadingField, leadingFieldPrecision, trailingField, fractionalSecondsPrecision);
+ }
+
+ /**
+ * Renders the qualifier back to SQL, e.g. {@code DAY}, {@code DAY TO SECOND},
+ * {@code DAY(9) TO SECOND} or {@code SECOND(2, 4)}.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(Objects.toString(leadingField, ""));
+ if (leadingFieldPrecision != null) {
+ sb.append("(").append(leadingFieldPrecision);
+ // For a single-field qualifier such as SECOND(2, 4), the fractional seconds
+ // precision is rendered together with the leading precision.
+ if (trailingField == null && fractionalSecondsPrecision != null) {
+ sb.append(", ").append(fractionalSecondsPrecision);
+ }
+ sb.append(")");
+ }
+ if (trailingField != null) {
+ sb.append(" TO ").append(trailingField);
+ if (fractionalSecondsPrecision != null) {
+ sb.append("(").append(fractionalSecondsPrecision).append(")");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java
index 20bfa272e..a60e52b7f 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java
@@ -10,6 +10,7 @@
package net.sf.jsqlparser.expression;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import net.sf.jsqlparser.statement.select.OrderByElement;
@@ -73,7 +74,7 @@ public void setType(String typeName) {
.valueOf(Objects
.requireNonNull(typeName,
"The Type of the JSON Aggregate Function must not be null")
- .toUpperCase());
+ .toUpperCase(Locale.ROOT));
}
public JsonAggregateFunction withType(JsonFunctionType type) {
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateOnNullType.java b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateOnNullType.java
index 898dad7c0..0cfcf4e02 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateOnNullType.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateOnNullType.java
@@ -25,6 +25,8 @@
package net.sf.jsqlparser.expression;
+import java.util.Locale;
+
/**
* @author Andreas Reichel
*/
@@ -32,6 +34,6 @@ public enum JsonAggregateOnNullType {
NULL, ABSENT;
public static JsonAggregateOnNullType from(String type) {
- return Enum.valueOf(JsonAggregateOnNullType.class, type.toUpperCase());
+ return Enum.valueOf(JsonAggregateOnNullType.class, type.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateUniqueKeysType.java b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateUniqueKeysType.java
index 097aad552..b1c1cd235 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateUniqueKeysType.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateUniqueKeysType.java
@@ -25,6 +25,8 @@
package net.sf.jsqlparser.expression;
+import java.util.Locale;
+
/**
* @author Andreas Reichel
*/
@@ -32,6 +34,6 @@ public enum JsonAggregateUniqueKeysType {
WITH, WITHOUT;
public static JsonAggregateUniqueKeysType from(String type) {
- return Enum.valueOf(JsonAggregateUniqueKeysType.class, type.toUpperCase());
+ return Enum.valueOf(JsonAggregateUniqueKeysType.class, type.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java b/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java
index f258e855c..7cd34b53d 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java
@@ -87,7 +87,10 @@ public String toString() {
StringBuilder b = new StringBuilder();
b.append(expr.toString());
for (Map.Entry ident : idents) {
- b.append(ident.getValue()).append(ident.getKey());
+ // Separate the operator with spaces: without them `#>`/`#>>` glue onto
+ // the operand (e.g. `a#>'{b}'`), and since `#` is a legal identifier
+ // character the result re-parses as `a#` `>` `...` -- a different tree.
+ b.append(' ').append(ident.getValue()).append(' ').append(ident.getKey());
}
return b.toString();
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
index 4422c1beb..dfad9451a 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
@@ -10,25 +10,160 @@
package net.sf.jsqlparser.expression;
import java.util.ArrayList;
+import java.util.Locale;
import java.util.Objects;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.create.table.ColDataType;
/**
+ * Represents a JSON-Function.
+ * Currently supported are the types in {@link JsonFunctionType}.
+ *
+ * For JSON_OBJECT the parameters are available from {@link #getKeyValuePairs()}
+ *
+ * For JSON_ARRAY the parameters are availble from {@link #getExpressions()}.
+ *
* @author Andreas Reichel
*/
-
public class JsonFunction extends ASTNodeAccessImpl implements Expression {
+ public enum JsonOnResponseBehaviorType {
+ ERROR, NULL, DEFAULT, EMPTY, EMPTY_ARRAY, EMPTY_OBJECT, TRUE, FALSE, UNKNOWN
+ }
+
+ public enum JsonWrapperType {
+ WITHOUT, WITH
+ }
+
+ public enum JsonWrapperMode {
+ CONDITIONAL, UNCONDITIONAL
+ }
+
+ public enum JsonQuotesType {
+ KEEP, OMIT
+ }
+
+ public enum ScalarsType {
+ ALLOW, DISALLOW
+ }
+
+ public static class JsonOnResponseBehavior {
+ private JsonOnResponseBehaviorType type;
+ private Expression expression;
+
+ public JsonOnResponseBehavior(JsonOnResponseBehaviorType type) {
+ this(type, null);
+ }
+
+ public JsonOnResponseBehavior(JsonOnResponseBehaviorType type, Expression expression) {
+ this.type = type;
+ this.expression = expression;
+ }
+
+ public JsonOnResponseBehaviorType getType() {
+ return type;
+ }
+
+ public void setType(JsonOnResponseBehaviorType type) {
+ this.type = type;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public void setExpression(Expression expression) {
+ this.expression = expression;
+ }
+
+ public StringBuilder append(StringBuilder builder) {
+ switch (type) {
+ case ERROR:
+ builder.append("ERROR");
+ break;
+ case NULL:
+ builder.append("NULL");
+ break;
+ case DEFAULT:
+ builder.append("DEFAULT ").append(expression);
+ break;
+ case EMPTY:
+ builder.append("EMPTY ");
+ break;
+ case EMPTY_ARRAY:
+ builder.append("EMPTY ARRAY");
+ break;
+ case EMPTY_OBJECT:
+ builder.append("EMPTY OBJECT");
+ break;
+ case TRUE:
+ builder.append("TRUE");
+ break;
+ case FALSE:
+ builder.append("FALSE");
+ break;
+ case UNKNOWN:
+ builder.append("UNKNOWN");
+ break;
+ default:
+ throw new IllegalStateException("Unhandled JsonOnResponseBehavior: " + type);
+ // this should never happen
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return append(new StringBuilder()).toString();
+ }
+ }
+
private final ArrayList keyValuePairs = new ArrayList<>();
private final ArrayList expressions = new ArrayList<>();
+ private final ArrayList passingExpressions = new ArrayList<>();
+ private final ArrayList additionalQueryPathArguments = new ArrayList<>();
private JsonFunctionType functionType;
private JsonAggregateOnNullType onNullType;
private JsonAggregateUniqueKeysType uniqueKeysType;
+ private boolean isStrict = false;
+ private JsonFunctionExpression inputExpression;
+ private Expression jsonPathExpression;
+ private ColDataType returningType;
+ private boolean returningFormatJson;
+ private String returningEncoding;
+ private JsonOnResponseBehavior onEmptyBehavior;
+ private JsonOnResponseBehavior onErrorBehavior;
+ private JsonWrapperType wrapperType;
+ private JsonWrapperMode wrapperMode;
+ private boolean wrapperArray;
+ private JsonQuotesType quotesType;
+ private boolean quotesOnScalarString;
+ private ScalarsType scalarsType;
+
+ public JsonFunction() {}
+
+ public JsonFunction(JsonFunctionType functionType) {
+ this.functionType = functionType;
+ }
+
+ /**
+ * Returns the Parameters of an JSON_OBJECT
+ * The KeyValuePairs may not have both key and value set, in some cases only the Key is set.
+ *
+ * @see net.sf.jsqlparser.parser.feature.Feature#allowCommaAsKeyValueSeparator
+ *
+ * @return A List of KeyValuePairs, never NULL
+ */
public ArrayList getKeyValuePairs() {
return keyValuePairs;
}
+ /**
+ * Returns the parameters of JSON_ARRAY
+ *
+ * @return A List of {@link JsonFunctionExpression}s, never NULL
+ */
public ArrayList getExpressions() {
return expressions;
}
@@ -57,6 +192,126 @@ public void add(int i, JsonFunctionExpression expression) {
expressions.add(i, expression);
}
+ public ArrayList getPassingExpressions() {
+ return passingExpressions;
+ }
+
+ public boolean addPassingExpression(Expression expression) {
+ return passingExpressions.add(expression);
+ }
+
+ public ArrayList getAdditionalQueryPathArguments() {
+ return additionalQueryPathArguments;
+ }
+
+ public boolean addAdditionalQueryPathArgument(String argument) {
+ return additionalQueryPathArguments.add(argument);
+ }
+
+ public JsonFunctionExpression getInputExpression() {
+ return inputExpression;
+ }
+
+ public void setInputExpression(JsonFunctionExpression inputExpression) {
+ this.inputExpression = inputExpression;
+ }
+
+ public Expression getJsonPathExpression() {
+ return jsonPathExpression;
+ }
+
+ public void setJsonPathExpression(Expression jsonPathExpression) {
+ this.jsonPathExpression = jsonPathExpression;
+ }
+
+ public ColDataType getReturningType() {
+ return returningType;
+ }
+
+ public void setReturningType(ColDataType returningType) {
+ this.returningType = returningType;
+ }
+
+ public boolean isReturningFormatJson() {
+ return returningFormatJson;
+ }
+
+ public void setReturningFormatJson(boolean returningFormatJson) {
+ this.returningFormatJson = returningFormatJson;
+ }
+
+ public String getReturningEncoding() {
+ return returningEncoding;
+ }
+
+ public void setReturningEncoding(String returningEncoding) {
+ this.returningEncoding = returningEncoding;
+ }
+
+ public JsonOnResponseBehavior getOnEmptyBehavior() {
+ return onEmptyBehavior;
+ }
+
+ public void setOnEmptyBehavior(JsonOnResponseBehavior onEmptyBehavior) {
+ this.onEmptyBehavior = onEmptyBehavior;
+ }
+
+ public JsonOnResponseBehavior getOnErrorBehavior() {
+ return onErrorBehavior;
+ }
+
+ public void setOnErrorBehavior(JsonOnResponseBehavior onErrorBehavior) {
+ this.onErrorBehavior = onErrorBehavior;
+ }
+
+ public JsonWrapperType getWrapperType() {
+ return wrapperType;
+ }
+
+ public void setWrapperType(JsonWrapperType wrapperType) {
+ this.wrapperType = wrapperType;
+ }
+
+ public JsonWrapperMode getWrapperMode() {
+ return wrapperMode;
+ }
+
+ public void setWrapperMode(JsonWrapperMode wrapperMode) {
+ this.wrapperMode = wrapperMode;
+ }
+
+ public boolean isWrapperArray() {
+ return wrapperArray;
+ }
+
+ public void setWrapperArray(boolean wrapperArray) {
+ this.wrapperArray = wrapperArray;
+ }
+
+ public JsonQuotesType getQuotesType() {
+ return quotesType;
+ }
+
+ public void setQuotesType(JsonQuotesType quotesType) {
+ this.quotesType = quotesType;
+ }
+
+ public boolean isQuotesOnScalarString() {
+ return quotesOnScalarString;
+ }
+
+ public void setQuotesOnScalarString(boolean quotesOnScalarString) {
+ this.quotesOnScalarString = quotesOnScalarString;
+ }
+
+ public ScalarsType getScalarsType() {
+ return scalarsType;
+ }
+
+ public void setScalarsType(ScalarsType type) {
+ this.scalarsType = type;
+ }
+
public boolean isEmpty() {
return keyValuePairs.isEmpty();
}
@@ -101,7 +356,7 @@ public void setType(String typeName) {
this.functionType = JsonFunctionType.valueOf(
Objects.requireNonNull(typeName,
"The Type of the JSON Aggregate Function must not be null")
- .toUpperCase());
+ .toUpperCase(Locale.ROOT));
}
public JsonFunction withType(JsonFunctionType type) {
@@ -114,6 +369,19 @@ public JsonFunction withType(String typeName) {
return this;
}
+ public boolean isStrict() {
+ return isStrict;
+ }
+
+ public void setStrict(boolean strict) {
+ isStrict = strict;
+ }
+
+ public JsonFunction withStrict(boolean strict) {
+ this.setStrict(strict);
+ return this;
+ }
+
@Override
public T accept(ExpressionVisitor expressionVisitor, S context) {
return expressionVisitor.visit(this, context);
@@ -123,17 +391,22 @@ public T accept(ExpressionVisitor expressionVisitor, S context) {
public StringBuilder append(StringBuilder builder) {
switch (functionType) {
case OBJECT:
- appendObject(builder);
- break;
case POSTGRES_OBJECT:
- appendPostgresObject(builder);
- break;
case MYSQL_OBJECT:
- appendMySqlObject(builder);
+ appendObject(builder);
break;
case ARRAY:
appendArray(builder);
break;
+ case VALUE:
+ appendValue(builder);
+ break;
+ case QUERY:
+ appendQuery(builder);
+ break;
+ case EXISTS:
+ appendExists(builder);
+ break;
default:
// this should never happen really
}
@@ -148,35 +421,38 @@ public StringBuilder appendObject(StringBuilder builder) {
if (i > 0) {
builder.append(", ");
}
- if (keyValuePair.isUsingValueKeyword()) {
- if (keyValuePair.isUsingKeyKeyword()) {
- builder.append("KEY ");
- }
- builder.append(keyValuePair.getKey()).append(" VALUE ")
- .append(keyValuePair.getValue());
- } else {
- builder.append(keyValuePair.getKey()).append(":").append(keyValuePair.getValue());
- }
-
- if (keyValuePair.isUsingFormatJson()) {
- builder.append(" FORMAT JSON");
- }
+ keyValuePair.append(builder);
i++;
}
+ appendOnNullType(builder);
+ if (isStrict) {
+ builder.append(" STRICT");
+ }
+ appendUniqueKeys(builder);
+ appendReturningClause(builder, true);
+
+ builder.append(" ) ");
+
+ return builder;
+ }
+
+ private void appendOnNullType(StringBuilder builder) {
if (onNullType != null) {
switch (onNullType) {
case NULL:
builder.append(" NULL ON NULL");
break;
case ABSENT:
- builder.append(" ABSENT On NULL");
+ builder.append(" ABSENT ON NULL");
break;
default:
// this should never happen
}
}
+ }
+ private void appendUniqueKeys(StringBuilder builder) {
if (uniqueKeysType != null) {
switch (uniqueKeysType) {
case WITH:
@@ -189,71 +465,140 @@ public StringBuilder appendObject(StringBuilder builder) {
// this should never happen
}
}
+ }
- builder.append(" ) ");
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ public StringBuilder appendArray(StringBuilder builder) {
+ builder.append("JSON_ARRAY( ");
+ int i = 0;
+
+ for (JsonFunctionExpression expr : expressions) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ expr.append(builder);
+ i++;
+ }
+
+ appendOnNullType(builder);
+ appendReturningClause(builder, true);
+ builder.append(") ");
return builder;
}
-
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
- public StringBuilder appendPostgresObject(StringBuilder builder) {
- builder.append("JSON_OBJECT( ");
- for (JsonKeyValuePair keyValuePair : keyValuePairs) {
- builder.append(keyValuePair.getKey());
- if (keyValuePair.getValue() != null) {
- builder.append(", ").append(keyValuePair.getValue());
- }
+ public StringBuilder appendValue(StringBuilder builder) {
+ builder.append("JSON_VALUE(");
+ appendValueOrQueryPrefix(builder);
+
+ if (returningType != null) {
+ builder.append(" RETURNING ").append(returningType);
}
- builder.append(" ) ");
+ appendOnResponseClause(builder, onEmptyBehavior, "EMPTY");
+ appendOnResponseClause(builder, onErrorBehavior, "ERROR");
+
+ builder.append(")");
return builder;
}
- public StringBuilder appendMySqlObject(StringBuilder builder) {
- builder.append("JSON_OBJECT( ");
- int i = 0;
- for (JsonKeyValuePair keyValuePair : keyValuePairs) {
- if (i > 0) {
- builder.append(", ");
- }
- builder.append(keyValuePair.getKey());
- builder.append(", ").append(keyValuePair.getValue());
- i++;
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ public StringBuilder appendQuery(StringBuilder builder) {
+ builder.append("JSON_QUERY(");
+ appendValueOrQueryPrefix(builder);
+
+ appendReturningClause(builder, true);
+
+ appendWrapperClause(builder);
+ appendQuotesClause(builder);
+ appendOnResponseClause(builder, onEmptyBehavior, "EMPTY");
+ appendOnResponseClause(builder, onErrorBehavior, "ERROR");
+
+ for (String additionalQueryPathArgument : additionalQueryPathArguments) {
+ builder.append(", ").append(additionalQueryPathArgument);
}
- builder.append(" ) ");
+ builder.append(")");
return builder;
}
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
- public StringBuilder appendArray(StringBuilder builder) {
- builder.append("JSON_ARRAY( ");
- int i = 0;
+ public StringBuilder appendExists(StringBuilder builder) {
+ builder.append("JSON_EXISTS(");
+ appendValueOrQueryPrefix(builder);
+ appendOnResponseClause(builder, onErrorBehavior, "ERROR");
+ builder.append(")");
+ return builder;
+ }
- for (JsonFunctionExpression expr : expressions) {
- if (i > 0) {
+ private void appendValueOrQueryPrefix(StringBuilder builder) {
+ if (inputExpression != null) {
+ inputExpression.append(builder);
+ }
+
+ if (jsonPathExpression != null) {
+ if (inputExpression != null) {
builder.append(", ");
}
- expr.append(builder);
- i++;
+ builder.append(jsonPathExpression);
}
- if (onNullType != null) {
- switch (onNullType) {
- case NULL:
- builder.append(" NULL ON NULL ");
- break;
- case ABSENT:
- builder.append(" ABSENT ON NULL ");
- break;
- default:
- // "ON NULL" was omitted
+ if (!passingExpressions.isEmpty()) {
+ builder.append(" PASSING ");
+ boolean comma = false;
+ for (Expression passingExpression : passingExpressions) {
+ if (comma) {
+ builder.append(", ");
+ } else {
+ comma = true;
+ }
+ builder.append(passingExpression);
}
}
- builder.append(") ");
+ }
- return builder;
+ private void appendOnResponseClause(StringBuilder builder, JsonOnResponseBehavior behavior,
+ String clause) {
+ if (behavior != null) {
+ builder.append(" ");
+ behavior.append(builder);
+ builder.append(" ON ").append(clause);
+ }
+ }
+
+ private void appendReturningClause(StringBuilder builder, boolean formatJsonAllowed) {
+ if (returningType != null) {
+ builder.append(" RETURNING ").append(returningType);
+ if (formatJsonAllowed && returningFormatJson) {
+ builder.append(" FORMAT JSON");
+ if (returningEncoding != null) {
+ builder.append(" ENCODING ").append(returningEncoding);
+ }
+ }
+ }
+ }
+
+ private void appendWrapperClause(StringBuilder builder) {
+ if (wrapperType != null) {
+ builder.append(" ").append(wrapperType);
+ if (wrapperMode != null) {
+ builder.append(" ").append(wrapperMode);
+ }
+ if (wrapperArray) {
+ builder.append(" ARRAY");
+ }
+ builder.append(" WRAPPER");
+ }
+ }
+
+ private void appendQuotesClause(StringBuilder builder) {
+ if (quotesType != null) {
+ builder.append(" ").append(quotesType).append(" QUOTES");
+ if (quotesOnScalarString) {
+ builder.append(" ON SCALAR STRING");
+ }
+ }
}
@Override
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionExpression.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionExpression.java
index 5df7ad310..738c09fc2 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionExpression.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionExpression.java
@@ -21,6 +21,7 @@ public class JsonFunctionExpression implements Serializable {
private final Expression expression;
private boolean usingFormatJson = false;
+ private String encoding;
public JsonFunctionExpression(Expression expression) {
this.expression = Objects.requireNonNull(expression, "The EXPRESSION must not be null");
@@ -43,8 +44,28 @@ public JsonFunctionExpression withUsingFormatJson(boolean usingFormatJson) {
return this;
}
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public JsonFunctionExpression withEncoding(String encoding) {
+ this.setEncoding(encoding);
+ return this;
+ }
+
public StringBuilder append(StringBuilder builder) {
- return builder.append(getExpression()).append(isUsingFormatJson() ? " FORMAT JSON" : "");
+ builder.append(getExpression());
+ if (isUsingFormatJson()) {
+ builder.append(" FORMAT JSON");
+ if (encoding != null) {
+ builder.append(" ENCODING ").append(encoding);
+ }
+ }
+ return builder;
}
@Override
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java
index 43a33aab6..b9889d1ea 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java
@@ -10,13 +10,27 @@
package net.sf.jsqlparser.expression;
+import java.util.Locale;
+
/**
* @author Andreas Reichel
*/
public enum JsonFunctionType {
- OBJECT, ARRAY, POSTGRES_OBJECT, MYSQL_OBJECT;
+ OBJECT, ARRAY, VALUE, QUERY, EXISTS,
+
+ /**
+ * Not used anymore
+ */
+ @Deprecated
+ POSTGRES_OBJECT,
+
+ /**
+ * Not used anymore
+ */
+ @Deprecated
+ MYSQL_OBJECT;
public static JsonFunctionType from(String type) {
- return Enum.valueOf(JsonFunctionType.class, type.toUpperCase());
+ return Enum.valueOf(JsonFunctionType.class, type.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java
index 82c8a355a..18fb4752d 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java
@@ -20,16 +20,28 @@
public class JsonKeyValuePair implements Serializable {
private final Object key;
private final Object value;
- private boolean usingKeyKeyword = false;
- private boolean usingValueKeyword = false;
+ private boolean usingKeyKeyword;
+ private JsonKeyValuePairSeparator separator;
private boolean usingFormatJson = false;
+ private String encoding;
+ /**
+ * Please use the Constructor with {@link JsonKeyValuePairSeparator} parameter.
+ */
+ @Deprecated
public JsonKeyValuePair(Object key, Object value, boolean usingKeyKeyword,
boolean usingValueKeyword) {
+ this(key, value, usingKeyKeyword, usingValueKeyword ? JsonKeyValuePairSeparator.VALUE
+ : JsonKeyValuePairSeparator.COLON);
+ }
+
+ public JsonKeyValuePair(Object key, Object value, boolean usingKeyKeyword,
+ JsonKeyValuePairSeparator separator) {
this.key = Objects.requireNonNull(key, "The KEY of the Pair must not be null");
this.value = value;
this.usingKeyKeyword = usingKeyKeyword;
- this.usingValueKeyword = usingValueKeyword;
+ this.separator =
+ Objects.requireNonNull(separator, "The KeyValuePairSeparator must not be NULL");
}
public boolean isUsingKeyKeyword() {
@@ -45,19 +57,45 @@ public JsonKeyValuePair withUsingKeyKeyword(boolean usingKeyKeyword) {
return this;
}
+ /**
+ * Use {@link #getSeparator()}
+ */
+ @Deprecated
public boolean isUsingValueKeyword() {
- return usingValueKeyword;
+ return separator == JsonKeyValuePairSeparator.VALUE;
}
+ /**
+ * Use {@link #setSeparator(JsonKeyValuePairSeparator)}
+ */
+ @Deprecated
public void setUsingValueKeyword(boolean usingValueKeyword) {
- this.usingValueKeyword = usingValueKeyword;
+ separator = usingValueKeyword ? JsonKeyValuePairSeparator.VALUE
+ : JsonKeyValuePairSeparator.COLON;
}
+ /**
+ * Use {@link #withSeparator(JsonKeyValuePairSeparator)}
+ */
+ @Deprecated
public JsonKeyValuePair withUsingValueKeyword(boolean usingValueKeyword) {
this.setUsingValueKeyword(usingValueKeyword);
return this;
}
+ public JsonKeyValuePairSeparator getSeparator() {
+ return separator;
+ }
+
+ public void setSeparator(JsonKeyValuePairSeparator separator) {
+ this.separator = separator;
+ }
+
+ public JsonKeyValuePair withSeparator(JsonKeyValuePairSeparator separator) {
+ this.setSeparator(separator);
+ return this;
+ }
+
public boolean isUsingFormatJson() {
return usingFormatJson;
}
@@ -71,6 +109,19 @@ public JsonKeyValuePair withUsingFormatJson(boolean usingFormatJson) {
return this;
}
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+ public JsonKeyValuePair withEncoding(String encoding) {
+ this.setEncoding(encoding);
+ return this;
+ }
+
@Override
public int hashCode() {
int hash = 7;
@@ -102,17 +153,21 @@ public Object getValue() {
}
public StringBuilder append(StringBuilder builder) {
- if (isUsingValueKeyword()) {
- if (isUsingKeyKeyword()) {
- builder.append("KEY ");
- }
- builder.append(getKey()).append(" VALUE ").append(getValue());
- } else {
- builder.append(getKey()).append(":").append(getValue());
+ if (isUsingKeyKeyword() && getSeparator() == JsonKeyValuePairSeparator.VALUE) {
+ builder.append("KEY ");
+ }
+ builder.append(getKey());
+
+ if (getValue() != null) {
+ builder.append(getSeparator().getSeparatorString());
+ builder.append(getValue());
}
if (isUsingFormatJson()) {
builder.append(" FORMAT JSON");
+ if (encoding != null) {
+ builder.append(" ENCODING ").append(encoding);
+ }
}
return builder;
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePairSeparator.java b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePairSeparator.java
new file mode 100644
index 000000000..e4e998aa5
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePairSeparator.java
@@ -0,0 +1,33 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2025 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+/**
+ * Describes the string used to separate the key from the value.
+ */
+public enum JsonKeyValuePairSeparator {
+ VALUE(" VALUE "), COLON(":"),
+
+ // Used in MySQL dialect
+ COMMA(","),
+
+ // Is used in case they KeyValuePair has only a key and no value
+ NOT_USED("");
+
+ private final String separator;
+
+ JsonKeyValuePairSeparator(String separator) {
+ this.separator = separator;
+ }
+
+ public String getSeparatorString() {
+ return separator;
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java
new file mode 100644
index 000000000..5ee166e6f
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java
@@ -0,0 +1,864 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.create.table.ColDataType;
+
+public class JsonTableFunction extends Function {
+
+ private Expression jsonInputExpression;
+ private Expression jsonPathExpression;
+ private String pathName;
+ private final List passingClauses = new ArrayList<>();
+ private JsonTableColumnsClause columnsClause;
+ private JsonTablePlanClause planClause;
+ private JsonTableOnErrorClause onErrorClause;
+ private JsonTableParsingTypeClause parsingTypeClause;
+ private JsonTableOnEmptyClause onEmptyClause;
+ private boolean formatJson;
+
+ public enum JsonTablePlanOperator {
+ COMMA(", "), INNER(" INNER "), OUTER(" OUTER "), CROSS(" CROSS "), UNION(" UNION ");
+
+ private final String display;
+
+ JsonTablePlanOperator(String display) {
+ this.display = display;
+ }
+
+ public String getDisplay() {
+ return display;
+ }
+ }
+
+ public enum JsonTableOnErrorType {
+ ERROR, NULL, EMPTY, TRUE, FALSE
+ }
+
+ public enum JsonTableOnEmptyType {
+ ERROR, NULL, EMPTY, TRUE, FALSE
+ }
+
+ public enum JsonTableParsingType {
+ STRICT, LAX
+ }
+
+ public static class JsonTablePassingClause extends ASTNodeAccessImpl implements Serializable {
+ private Expression valueExpression;
+ private String parameterName;
+
+ public JsonTablePassingClause() {}
+
+ public JsonTablePassingClause(Expression valueExpression, String parameterName) {
+ this.valueExpression = valueExpression;
+ this.parameterName = parameterName;
+ }
+
+ public Expression getValueExpression() {
+ return valueExpression;
+ }
+
+ public JsonTablePassingClause setValueExpression(Expression valueExpression) {
+ this.valueExpression = valueExpression;
+ return this;
+ }
+
+ public String getParameterName() {
+ return parameterName;
+ }
+
+ public JsonTablePassingClause setParameterName(String parameterName) {
+ this.parameterName = parameterName;
+ return this;
+ }
+
+ public void collectExpressions(List expressions) {
+ if (valueExpression != null) {
+ expressions.add(valueExpression);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return valueExpression + " AS " + parameterName;
+ }
+ }
+
+ public static class JsonTableWrapperClause extends ASTNodeAccessImpl implements Serializable {
+ private boolean beforePathExpression;
+ private JsonFunction.JsonWrapperType wrapperType;
+ private JsonFunction.JsonWrapperMode wrapperMode;
+ private boolean array;
+
+ /**
+ * Creates a wrapper clause. Depending on the dialect, this clause can come before or after
+ * the PATH expression.
+ *
+ * Trino: after PATH
+ * Oracle: before PATH
+ *
+ *
+ * @param beforePathExpression A flag to determine wether the clause is rendered before or
+ * after the PATH expression
+ */
+ public JsonTableWrapperClause(boolean beforePathExpression) {
+ this.beforePathExpression = beforePathExpression;
+ }
+
+ public boolean isBeforePathExpression() {
+ return beforePathExpression;
+ }
+
+ public JsonFunction.JsonWrapperType getWrapperType() {
+ return wrapperType;
+ }
+
+ public JsonTableWrapperClause setWrapperType(JsonFunction.JsonWrapperType wrapperType) {
+ this.wrapperType = wrapperType;
+ return this;
+ }
+
+ public JsonFunction.JsonWrapperMode getWrapperMode() {
+ return wrapperMode;
+ }
+
+ public JsonTableWrapperClause setWrapperMode(JsonFunction.JsonWrapperMode wrapperMode) {
+ this.wrapperMode = wrapperMode;
+ return this;
+ }
+
+ public boolean isArray() {
+ return array;
+ }
+
+ public JsonTableWrapperClause setArray(boolean array) {
+ this.array = array;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(wrapperType);
+ if (wrapperMode != null) {
+ builder.append(" ").append(wrapperMode);
+ }
+ if (array) {
+ builder.append(" ARRAY");
+ }
+ builder.append(" WRAPPER");
+ return builder.toString();
+ }
+ }
+
+ public static class JsonTableQuotesClause extends ASTNodeAccessImpl implements Serializable {
+ private JsonFunction.JsonQuotesType quotesType;
+ private boolean onScalarString;
+
+ public JsonFunction.JsonQuotesType getQuotesType() {
+ return quotesType;
+ }
+
+ public JsonTableQuotesClause setQuotesType(JsonFunction.JsonQuotesType quotesType) {
+ this.quotesType = quotesType;
+ return this;
+ }
+
+ public boolean isOnScalarString() {
+ return onScalarString;
+ }
+
+ public JsonTableQuotesClause setOnScalarString(boolean onScalarString) {
+ this.onScalarString = onScalarString;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(quotesType).append(" QUOTES");
+ if (onScalarString) {
+ builder.append(" ON SCALAR STRING");
+ }
+ return builder.toString();
+ }
+ }
+
+ public static class JsonTableOnErrorClause extends ASTNodeAccessImpl implements Serializable {
+ private JsonTableOnErrorType type;
+ private boolean beforeColumns = true;
+
+ public JsonTableOnErrorClause(boolean beforeColumns) {
+ this.beforeColumns = beforeColumns;
+ }
+
+ public boolean isBeforeColumns() {
+ return beforeColumns;
+ }
+
+ public JsonTableOnErrorType getType() {
+ return type;
+ }
+
+ public JsonTableOnErrorClause setType(JsonTableOnErrorType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return type + " ON ERROR";
+ }
+ }
+
+ public static class JsonTableOnEmptyClause extends ASTNodeAccessImpl implements Serializable {
+ private JsonTableOnEmptyType type;
+
+ public JsonTableOnEmptyType getType() {
+ return type;
+ }
+
+ public JsonTableOnEmptyClause setType(JsonTableOnEmptyType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return type + " ON EMPTY";
+ }
+ }
+
+ public static class JsonTableParsingTypeClause extends ASTNodeAccessImpl
+ implements Serializable {
+ private JsonTableParsingType type;
+
+ public JsonTableParsingType getType() {
+ return type;
+ }
+
+ public JsonTableParsingTypeClause setType(JsonTableParsingType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "TYPE(" + type + ")";
+ }
+ }
+
+ public static class JsonTablePlanTerm extends ASTNodeAccessImpl implements Serializable {
+ private JsonTablePlanExpression nestedPlanExpression;
+ private String name;
+ private Expression expression;
+
+ public JsonTablePlanExpression getNestedPlanExpression() {
+ return nestedPlanExpression;
+ }
+
+ public JsonTablePlanTerm setNestedPlanExpression(
+ JsonTablePlanExpression nestedPlanExpression) {
+ this.nestedPlanExpression = nestedPlanExpression;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public JsonTablePlanTerm setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public JsonTablePlanTerm setExpression(Expression expression) {
+ this.expression = expression;
+ return this;
+ }
+
+ public void collectExpressions(List expressions) {
+ if (expression != null) {
+ expressions.add(expression);
+ }
+ if (nestedPlanExpression != null) {
+ nestedPlanExpression.collectExpressions(expressions);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (nestedPlanExpression != null) {
+ return "(" + nestedPlanExpression + ")";
+ }
+ if (name != null) {
+ return name;
+ }
+ return expression != null ? expression.toString() : "";
+ }
+ }
+
+ public static class JsonTablePlanExpression extends ASTNodeAccessImpl implements Serializable {
+ private final List terms = new ArrayList<>();
+ private final List operators = new ArrayList<>();
+
+ public List getTerms() {
+ return terms;
+ }
+
+ public JsonTablePlanExpression addTerm(JsonTablePlanTerm term) {
+ terms.add(term);
+ return this;
+ }
+
+ public List getOperators() {
+ return operators;
+ }
+
+ public JsonTablePlanExpression addOperator(JsonTablePlanOperator operator) {
+ operators.add(operator);
+ return this;
+ }
+
+ public void collectExpressions(List expressions) {
+ for (JsonTablePlanTerm term : terms) {
+ if (term != null) {
+ term.collectExpressions(expressions);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (!terms.isEmpty()) {
+ builder.append(terms.get(0));
+ }
+ for (int i = 0; i < operators.size() && i + 1 < terms.size(); i++) {
+ builder.append(operators.get(i).getDisplay()).append(terms.get(i + 1));
+ }
+ return builder.toString();
+ }
+ }
+
+ public static class JsonTablePlanClause extends ASTNodeAccessImpl implements Serializable {
+ private boolean defaultPlan;
+ private JsonTablePlanExpression planExpression;
+
+ public boolean isDefaultPlan() {
+ return defaultPlan;
+ }
+
+ public JsonTablePlanClause setDefaultPlan(boolean defaultPlan) {
+ this.defaultPlan = defaultPlan;
+ return this;
+ }
+
+ public JsonTablePlanExpression getPlanExpression() {
+ return planExpression;
+ }
+
+ public JsonTablePlanClause setPlanExpression(JsonTablePlanExpression planExpression) {
+ this.planExpression = planExpression;
+ return this;
+ }
+
+ public void collectExpressions(List expressions) {
+ if (planExpression != null) {
+ planExpression.collectExpressions(expressions);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("PLAN");
+ if (defaultPlan) {
+ builder.append(" DEFAULT");
+ }
+ builder.append(" (").append(planExpression).append(")");
+ return builder.toString();
+ }
+ }
+
+ public abstract static class JsonTableColumnDefinition extends ASTNodeAccessImpl
+ implements Serializable {
+ public abstract void collectExpressions(List expressions);
+ }
+
+ public static class JsonTableNestedColumnDefinition extends JsonTableColumnDefinition {
+ private boolean pathKeyword;
+ private Expression pathExpression;
+ private String pathName;
+ private JsonTableColumnsClause columnsClause;
+
+ public boolean isPathKeyword() {
+ return pathKeyword;
+ }
+
+ public JsonTableNestedColumnDefinition setPathKeyword(boolean pathKeyword) {
+ this.pathKeyword = pathKeyword;
+ return this;
+ }
+
+ public Expression getPathExpression() {
+ return pathExpression;
+ }
+
+ public JsonTableNestedColumnDefinition setPathExpression(Expression pathExpression) {
+ this.pathExpression = pathExpression;
+ return this;
+ }
+
+ public String getPathName() {
+ return pathName;
+ }
+
+ public JsonTableNestedColumnDefinition setPathName(String pathName) {
+ this.pathName = pathName;
+ return this;
+ }
+
+ public JsonTableColumnsClause getColumnsClause() {
+ return columnsClause;
+ }
+
+ public JsonTableNestedColumnDefinition setColumnsClause(
+ JsonTableColumnsClause columnsClause) {
+ this.columnsClause = columnsClause;
+ return this;
+ }
+
+ @Override
+ public void collectExpressions(List expressions) {
+ if (pathExpression != null) {
+ expressions.add(pathExpression);
+ }
+ if (columnsClause != null) {
+ columnsClause.collectExpressions(expressions);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("NESTED");
+ if (pathKeyword) {
+ builder.append(" PATH");
+ }
+ builder.append(" ").append(pathExpression);
+ if (pathName != null) {
+ builder.append(" AS ").append(pathName);
+ }
+ builder.append(" ").append(columnsClause);
+ return builder.toString();
+ }
+ }
+
+ public static class JsonTableValueColumnDefinition extends JsonTableColumnDefinition {
+ private String columnName;
+ private boolean forOrdinality;
+ private ColDataType dataType;
+ private boolean formatJson;
+ private boolean exists;
+ private boolean onEmptyAfterOnError;
+ private String encoding;
+ private Expression pathExpression;
+ private JsonTableWrapperClause wrapperClause;
+ private JsonTableQuotesClause quotesClause;
+ private JsonFunction.JsonOnResponseBehavior onEmptyBehavior;
+ private JsonFunction.JsonOnResponseBehavior onErrorBehavior;
+ private JsonFunction.ScalarsType scalarsType;
+
+ public String getColumnName() {
+ return columnName;
+ }
+
+ public JsonTableValueColumnDefinition setColumnName(String columnName) {
+ this.columnName = columnName;
+ return this;
+ }
+
+ public boolean isExists() {
+ return exists;
+ }
+
+ public JsonTableValueColumnDefinition setExistsKeyword(boolean exists) {
+ this.exists = exists;
+ return this;
+ }
+
+ public JsonTableValueColumnDefinition setOnEmptyAfterOnError(boolean b) {
+ this.onEmptyAfterOnError = b;
+ return this;
+ }
+
+ public boolean isForOrdinality() {
+ return forOrdinality;
+ }
+
+ public JsonTableValueColumnDefinition setForOrdinality(boolean forOrdinality) {
+ this.forOrdinality = forOrdinality;
+ return this;
+ }
+
+ public ColDataType getDataType() {
+ return dataType;
+ }
+
+ public JsonTableValueColumnDefinition setDataType(ColDataType dataType) {
+ this.dataType = dataType;
+ return this;
+ }
+
+ public boolean isFormatJson() {
+ return formatJson;
+ }
+
+ public JsonTableValueColumnDefinition setFormatJson(boolean formatJson) {
+ this.formatJson = formatJson;
+ return this;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public JsonTableValueColumnDefinition setEncoding(String encoding) {
+ this.encoding = encoding;
+ return this;
+ }
+
+ public Expression getPathExpression() {
+ return pathExpression;
+ }
+
+ public JsonTableValueColumnDefinition setPathExpression(Expression pathExpression) {
+ this.pathExpression = pathExpression;
+ return this;
+ }
+
+ public JsonTableWrapperClause getWrapperClause() {
+ return wrapperClause;
+ }
+
+ public JsonTableValueColumnDefinition setWrapperClause(
+ JsonTableWrapperClause wrapperClause) {
+ this.wrapperClause = wrapperClause;
+ return this;
+ }
+
+ public JsonTableQuotesClause getQuotesClause() {
+ return quotesClause;
+ }
+
+ public JsonTableValueColumnDefinition setQuotesClause(JsonTableQuotesClause quotesClause) {
+ this.quotesClause = quotesClause;
+ return this;
+ }
+
+ public JsonFunction.JsonOnResponseBehavior getOnEmptyBehavior() {
+ return onEmptyBehavior;
+ }
+
+ public JsonTableValueColumnDefinition setOnEmptyBehavior(
+ JsonFunction.JsonOnResponseBehavior onEmptyBehavior) {
+ this.onEmptyBehavior = onEmptyBehavior;
+ return this;
+ }
+
+ public JsonFunction.JsonOnResponseBehavior getOnErrorBehavior() {
+ return onErrorBehavior;
+ }
+
+ public JsonTableValueColumnDefinition setOnErrorBehavior(
+ JsonFunction.JsonOnResponseBehavior onErrorBehavior) {
+ this.onErrorBehavior = onErrorBehavior;
+ return this;
+ }
+
+ public void setScalarsType(JsonFunction.ScalarsType scalarsType) {
+ this.scalarsType = scalarsType;
+ }
+
+ public JsonFunction.ScalarsType getScalarsType() {
+ return scalarsType;
+ }
+
+ @Override
+ public void collectExpressions(List expressions) {
+ if (pathExpression != null) {
+ expressions.add(pathExpression);
+ }
+ if (onEmptyBehavior != null && onEmptyBehavior.getExpression() != null) {
+ expressions.add(onEmptyBehavior.getExpression());
+ }
+ if (onErrorBehavior != null && onErrorBehavior.getExpression() != null) {
+ expressions.add(onErrorBehavior.getExpression());
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(columnName);
+ if (forOrdinality) {
+ builder.append(" FOR ORDINALITY");
+ return builder.toString();
+ }
+ if (exists) {
+ builder.append(" EXISTS");
+ }
+ if (dataType != null) {
+ builder.append(" ").append(dataType);
+ }
+ if (formatJson) {
+ builder.append(" FORMAT JSON");
+ if (encoding != null) {
+ builder.append(" ENCODING ").append(encoding);
+ }
+ }
+ if (scalarsType != null) {
+ builder.append(" ");
+ builder.append(scalarsType);
+ builder.append(" SCALARS");
+ }
+ if (wrapperClause != null && wrapperClause.isBeforePathExpression()) {
+ builder.append(" ").append(wrapperClause);
+ }
+ if (pathExpression != null) {
+ builder.append(" PATH ").append(pathExpression);
+ }
+ if (wrapperClause != null && !wrapperClause.isBeforePathExpression()) {
+ builder.append(" ").append(wrapperClause);
+ }
+ if (quotesClause != null) {
+ builder.append(" ").append(quotesClause);
+ }
+ if (onEmptyBehavior != null && !onEmptyAfterOnError) {
+ builder.append(" ").append(onEmptyBehavior).append(" ON EMPTY");
+ }
+ if (onErrorBehavior != null) {
+ builder.append(" ").append(onErrorBehavior).append(" ON ERROR");
+ }
+ if (onEmptyBehavior != null && onEmptyAfterOnError) {
+ builder.append(" ").append(onEmptyBehavior).append(" ON EMPTY");
+ }
+ return builder.toString();
+ }
+ }
+
+ public static class JsonTableColumnsClause extends ASTNodeAccessImpl implements Serializable {
+ private final List columnDefinitions = new ArrayList<>();
+
+ public List getColumnDefinitions() {
+ return columnDefinitions;
+ }
+
+ public JsonTableColumnsClause addColumnDefinition(
+ JsonTableColumnDefinition columnDefinition) {
+ columnDefinitions.add(columnDefinition);
+ return this;
+ }
+
+ public void collectExpressions(List expressions) {
+ for (JsonTableColumnDefinition columnDefinition : columnDefinitions) {
+ if (columnDefinition != null) {
+ columnDefinition.collectExpressions(expressions);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("COLUMNS (");
+ boolean first = true;
+ for (JsonTableColumnDefinition columnDefinition : columnDefinitions) {
+ if (!first) {
+ builder.append(", ");
+ }
+ builder.append(columnDefinition);
+ first = false;
+ }
+ builder.append(")");
+ return builder.toString();
+ }
+ }
+
+ public JsonTableFunction() {
+ setName("JSON_TABLE");
+ }
+
+ public boolean getFormatJson() {
+ return formatJson;
+ }
+
+ public JsonTableFunction setFormatJson(boolean formatJson) {
+ this.formatJson = formatJson;
+ return this;
+ }
+
+ public Expression getJsonInputExpression() {
+ return jsonInputExpression;
+ }
+
+ public JsonTableFunction setJsonInputExpression(Expression jsonInputExpression) {
+ this.jsonInputExpression = jsonInputExpression;
+ return this;
+ }
+
+ public Expression getJsonPathExpression() {
+ return jsonPathExpression;
+ }
+
+ public JsonTableFunction setJsonPathExpression(Expression jsonPathExpression) {
+ this.jsonPathExpression = jsonPathExpression;
+ return this;
+ }
+
+ public String getPathName() {
+ return pathName;
+ }
+
+ public JsonTableFunction setPathName(String pathName) {
+ this.pathName = pathName;
+ return this;
+ }
+
+ public List getPassingClauses() {
+ return passingClauses;
+ }
+
+ public JsonTableFunction addPassingClause(JsonTablePassingClause passingClause) {
+ passingClauses.add(Objects.requireNonNull(passingClause, "passingClause"));
+ return this;
+ }
+
+ public JsonTableColumnsClause getColumnsClause() {
+ return columnsClause;
+ }
+
+ public JsonTableFunction setColumnsClause(JsonTableColumnsClause columnsClause) {
+ this.columnsClause = columnsClause;
+ return this;
+ }
+
+ public JsonTablePlanClause getPlanClause() {
+ return planClause;
+ }
+
+ public JsonTableFunction setPlanClause(JsonTablePlanClause planClause) {
+ this.planClause = planClause;
+ return this;
+ }
+
+ public JsonTableOnErrorClause getOnErrorClause() {
+ return onErrorClause;
+ }
+
+ public JsonTableFunction setOnErrorClause(JsonTableOnErrorClause onErrorClause) {
+ this.onErrorClause = onErrorClause;
+ return this;
+ }
+
+ public JsonTableParsingTypeClause getParsingTypeClause() {
+ return parsingTypeClause;
+ }
+
+ public JsonTableFunction setParsingTypeClause(JsonTableParsingTypeClause parsingTypeClause) {
+ this.parsingTypeClause = parsingTypeClause;
+ return this;
+ }
+
+ public JsonTableOnEmptyClause getOnEmptyClause() {
+ return onEmptyClause;
+ }
+
+ public JsonTableFunction setOnEmptyClause(JsonTableOnEmptyClause onEmptyClause) {
+ this.onEmptyClause = onEmptyClause;
+ return this;
+ }
+
+ public List getAllExpressions() {
+ List expressions = new ArrayList<>();
+ if (jsonInputExpression != null) {
+ expressions.add(jsonInputExpression);
+ }
+ if (jsonPathExpression != null) {
+ expressions.add(jsonPathExpression);
+ }
+ for (JsonTablePassingClause passingClause : passingClauses) {
+ passingClause.collectExpressions(expressions);
+ }
+ if (columnsClause != null) {
+ columnsClause.collectExpressions(expressions);
+ }
+ if (planClause != null) {
+ planClause.collectExpressions(expressions);
+ }
+ return expressions;
+ }
+
+ @Override
+ public T accept(ExpressionVisitor expressionVisitor, S context) {
+ return expressionVisitor.visit(this, context);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("JSON_TABLE(");
+ builder.append(jsonInputExpression);
+ if (formatJson) {
+ builder.append(" FORMAT JSON");
+ }
+ if (jsonPathExpression != null) {
+ builder.append(", ").append(jsonPathExpression);
+ }
+ if (pathName != null) {
+ builder.append(" AS ").append(pathName);
+ }
+ if (!passingClauses.isEmpty()) {
+ builder.append(" PASSING ");
+ boolean first = true;
+ for (JsonTablePassingClause passingClause : passingClauses) {
+ if (!first) {
+ builder.append(", ");
+ }
+ builder.append(passingClause);
+ first = false;
+ }
+ }
+ if (onErrorClause != null && onErrorClause.isBeforeColumns()) {
+ builder.append(" ").append(onErrorClause);
+ }
+ if (parsingTypeClause != null) {
+ builder.append(" ").append(parsingTypeClause);
+ }
+ if (onEmptyClause != null) {
+ builder.append(" ").append(onEmptyClause);
+ }
+ builder.append(" ").append(columnsClause);
+ if (planClause != null) {
+ builder.append(" ").append(planClause);
+ }
+ if (onErrorClause != null && !onErrorClause.isBeforeColumns()) {
+ builder.append(" ").append(onErrorClause);
+ }
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/KeyExpression.java b/src/main/java/net/sf/jsqlparser/expression/KeyExpression.java
new file mode 100644
index 000000000..2bb063875
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/KeyExpression.java
@@ -0,0 +1,44 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.util.Objects;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+/**
+ * Dialect specific expression for constructs such as {@code KEY chain.entity}.
+ */
+public class KeyExpression extends ASTNodeAccessImpl implements Expression {
+ private final Expression expression;
+
+ public KeyExpression(Expression expression) {
+ this.expression = Objects.requireNonNull(expression,
+ "The EXPRESSION of the KEY expression must not be null");
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ @Override
+ public T accept(ExpressionVisitor expressionVisitor, S context) {
+ return expressionVisitor.visit(this, context);
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("KEY ").append(expression);
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/MapExpression.java b/src/main/java/net/sf/jsqlparser/expression/MapExpression.java
new file mode 100644
index 000000000..1e64a7db8
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/MapExpression.java
@@ -0,0 +1,79 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+/**
+ * DuckDB's {@code MAP {key: value, ...}} literal expression.
+ *
+ * @see DuckDB MAP type
+ */
+public class MapExpression extends ASTNodeAccessImpl implements Expression {
+
+ private List> entries = new ArrayList<>();
+
+ public MapExpression() {}
+
+ public MapExpression(Collection extends Map.Entry> entries) {
+ this.entries.addAll(entries);
+ }
+
+ public List> getEntries() {
+ return entries;
+ }
+
+ public void setEntries(List> entries) {
+ this.entries = entries;
+ }
+
+ public MapExpression withEntries(List> entries) {
+ setEntries(entries);
+ return this;
+ }
+
+ public MapExpression addEntries(
+ Collection extends Map.Entry> entries) {
+ this.entries.addAll(entries);
+ return this;
+ }
+
+ public MapExpression addEntry(Expression key, Expression value) {
+ entries.add(new AbstractMap.SimpleEntry<>(key, value));
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("MAP {");
+ for (int i = 0; i < entries.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ Map.Entry entry = entries.get(i);
+ builder.append(entry.getKey()).append(": ").append(entry.getValue());
+ }
+ return builder.append("}");
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(ExpressionVisitor expressionVisitor, S context) {
+ return expressionVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/OracleHint.java b/src/main/java/net/sf/jsqlparser/expression/OracleHint.java
index 35ba8ad3b..166e19e08 100644
--- a/src/main/java/net/sf/jsqlparser/expression/OracleHint.java
+++ b/src/main/java/net/sf/jsqlparser/expression/OracleHint.java
@@ -24,7 +24,7 @@ public class OracleHint extends ASTNodeAccessImpl implements Expression {
private static final Pattern SINGLE_LINE = Pattern.compile("--\\+ *([^ ].*[^ ])");
private static final Pattern MULTI_LINE =
- Pattern.compile("\\/\\*\\+ *([^ ].*[^ ]) *\\*+\\/", Pattern.MULTILINE | Pattern.DOTALL);
+ Pattern.compile("\\A/\\*\\+ *([^ ].*[^ ]) *\\*+/", Pattern.MULTILINE | Pattern.DOTALL);
private String value;
private boolean singleLine = false;
diff --git a/src/main/java/net/sf/jsqlparser/expression/PostgresNamedFunctionParameter.java b/src/main/java/net/sf/jsqlparser/expression/PostgresNamedFunctionParameter.java
new file mode 100644
index 000000000..573ad60f2
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/PostgresNamedFunctionParameter.java
@@ -0,0 +1,55 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2021 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+import java.util.Objects;
+
+/**
+ * @author Andreas Reichel
+ */
+public class PostgresNamedFunctionParameter extends ASTNodeAccessImpl implements Expression {
+ private final String name;
+ private final Expression expression;
+
+ public PostgresNamedFunctionParameter(String name, Expression expression) {
+ this.name = Objects.requireNonNull(name,
+ "The NAME of the PostgresNamedFunctionParameter must not be null.");
+ this.expression = Objects.requireNonNull(expression,
+ "The EXPRESSION of the PostgresNamedFunctionParameter must not be null.");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ @Override
+ public T accept(ExpressionVisitor expressionVisitor, S context) {
+ return expressionVisitor.visit(this, context);
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append(name)
+ .append(" := ")
+ .append(expression);
+
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/RawFunction.java b/src/main/java/net/sf/jsqlparser/expression/RawFunction.java
new file mode 100644
index 000000000..1c2d5b874
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/RawFunction.java
@@ -0,0 +1,41 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+/**
+ * Function with a raw argument body preserved as-is for deparsing.
+ */
+public class RawFunction extends Function {
+ private String rawArguments;
+
+ public RawFunction() {}
+
+ public RawFunction(String name, String rawArguments) {
+ setName(name);
+ this.rawArguments = rawArguments;
+ }
+
+ public String getRawArguments() {
+ return rawArguments;
+ }
+
+ public void setRawArguments(String rawArguments) {
+ this.rawArguments = rawArguments;
+ }
+
+ @Override
+ public String toString() {
+ String name = getName();
+ if (rawArguments == null) {
+ return name + "()";
+ }
+ return name + "(" + rawArguments + ")";
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/SpannerInterleaveIn.java b/src/main/java/net/sf/jsqlparser/expression/SpannerInterleaveIn.java
index 0bf49925d..4dcb0aa7f 100644
--- a/src/main/java/net/sf/jsqlparser/expression/SpannerInterleaveIn.java
+++ b/src/main/java/net/sf/jsqlparser/expression/SpannerInterleaveIn.java
@@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.expression;
+import java.util.Locale;
import net.sf.jsqlparser.schema.Table;
import java.util.Collections;
@@ -74,7 +75,7 @@ public enum OnDelete {
CASCADE, NO_ACTION;
public static OnDelete from(String action) {
- return Enum.valueOf(OnDelete.class, action.toUpperCase());
+ return Enum.valueOf(OnDelete.class, action.toUpperCase(Locale.ROOT));
}
}
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/TernaryExpression.java b/src/main/java/net/sf/jsqlparser/expression/TernaryExpression.java
new file mode 100644
index 000000000..3d67d4ea3
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/TernaryExpression.java
@@ -0,0 +1,68 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2025 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+/**
+ * The C-style ternary conditional operator {@code condition ? thenExpression : elseExpression},
+ * supported for instance by ClickHouse as an alias for {@code if(condition, then, else)}.
+ */
+public class TernaryExpression extends ASTNodeAccessImpl implements Expression {
+ private Expression condition;
+ private Expression thenExpression;
+ private Expression elseExpression;
+
+ public TernaryExpression() {}
+
+ public TernaryExpression(Expression condition, Expression thenExpression,
+ Expression elseExpression) {
+ this.condition = condition;
+ this.thenExpression = thenExpression;
+ this.elseExpression = elseExpression;
+ }
+
+ public Expression getCondition() {
+ return condition;
+ }
+
+ public TernaryExpression setCondition(Expression condition) {
+ this.condition = condition;
+ return this;
+ }
+
+ public Expression getThenExpression() {
+ return thenExpression;
+ }
+
+ public TernaryExpression setThenExpression(Expression thenExpression) {
+ this.thenExpression = thenExpression;
+ return this;
+ }
+
+ public Expression getElseExpression() {
+ return elseExpression;
+ }
+
+ public TernaryExpression setElseExpression(Expression elseExpression) {
+ this.elseExpression = elseExpression;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return condition + " ? " + thenExpression + " : " + elseExpression;
+ }
+
+ @Override
+ public T accept(ExpressionVisitor expressionVisitor, S context) {
+ return expressionVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/TranscodingFunction.java b/src/main/java/net/sf/jsqlparser/expression/TranscodingFunction.java
index 343579e29..355e86305 100644
--- a/src/main/java/net/sf/jsqlparser/expression/TranscodingFunction.java
+++ b/src/main/java/net/sf/jsqlparser/expression/TranscodingFunction.java
@@ -9,20 +9,39 @@
*/
package net.sf.jsqlparser.expression;
+import java.util.Locale;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.statement.create.table.ColDataType;
+import java.util.Objects;
+
public class TranscodingFunction extends ASTNodeAccessImpl implements Expression {
+ private String keyword = "CONVERT";
private boolean isTranscodeStyle = true;
private ColDataType colDataType;
private Expression expression;
private String transcodingName;
+ public TranscodingFunction(String keyword, Expression expression, String transcodingName) {
+ this.keyword = Objects.requireNonNullElse(keyword, "CONVERT").toUpperCase(Locale.ROOT);
+ this.expression = expression;
+ this.transcodingName = transcodingName;
+ }
+
public TranscodingFunction(Expression expression, String transcodingName) {
this.expression = expression;
this.transcodingName = transcodingName;
}
+ public TranscodingFunction(String keyword, ColDataType colDataType, Expression expression,
+ String transcodingName) {
+ this.keyword = Objects.requireNonNullElse(keyword, "CONVERT").toUpperCase(Locale.ROOT);
+ this.colDataType = colDataType;
+ this.expression = expression;
+ this.transcodingName = transcodingName;
+ this.isTranscodeStyle = false;
+ }
+
public TranscodingFunction(ColDataType colDataType, Expression expression,
String transcodingName) {
this.colDataType = colDataType;
@@ -35,6 +54,15 @@ public TranscodingFunction() {
this(null, null);
}
+ public String getKeyword() {
+ return keyword;
+ }
+
+ public TranscodingFunction setKeyword(String keyword) {
+ this.keyword = Objects.requireNonNullElse(keyword, "CONVERT").toUpperCase(Locale.ROOT);
+ return this;
+ }
+
public Expression getExpression() {
return expression;
}
@@ -87,14 +115,16 @@ public T accept(ExpressionVisitor expressionVisitor, S context) {
public StringBuilder appendTo(StringBuilder builder) {
if (isTranscodeStyle) {
return builder
- .append("CONVERT( ")
+ .append(keyword)
+ .append("( ")
.append(expression)
.append(" USING ")
.append(transcodingName)
.append(" )");
} else {
return builder
- .append("CONVERT( ")
+ .append(keyword)
+ .append("( ")
.append(colDataType)
.append(", ")
.append(expression)
diff --git a/src/main/java/net/sf/jsqlparser/expression/WindowElement.java b/src/main/java/net/sf/jsqlparser/expression/WindowElement.java
index 97260ce97..01f77557c 100644
--- a/src/main/java/net/sf/jsqlparser/expression/WindowElement.java
+++ b/src/main/java/net/sf/jsqlparser/expression/WindowElement.java
@@ -10,12 +10,14 @@
package net.sf.jsqlparser.expression;
import java.io.Serializable;
+import java.util.Locale;
public class WindowElement implements Serializable {
private Type type;
private WindowOffset offset;
private WindowRange range;
+ private Exclusion exclusion;
public Type getType() {
return type;
@@ -41,6 +43,14 @@ public void setRange(WindowRange range) {
this.range = range;
}
+ public Exclusion getExclusion() {
+ return exclusion;
+ }
+
+ public void setExclusion(Exclusion exclusion) {
+ this.exclusion = exclusion;
+ }
+
@Override
public String toString() {
StringBuilder buffer = new StringBuilder(type.toString());
@@ -51,6 +61,10 @@ public String toString() {
buffer.append(range.toString());
}
+ if (exclusion != null) {
+ buffer.append(" EXCLUDE ").append(exclusion);
+ }
+
return buffer.toString();
}
@@ -69,11 +83,31 @@ public WindowElement withRange(WindowRange range) {
return this;
}
+ public WindowElement withExclusion(Exclusion exclusion) {
+ this.setExclusion(exclusion);
+ return this;
+ }
+
public enum Type {
- ROWS, RANGE;
+ ROWS, RANGE, GROUPS;
public static Type from(String type) {
- return Enum.valueOf(Type.class, type.toUpperCase());
+ return Enum.valueOf(Type.class, type.toUpperCase(Locale.ROOT));
+ }
+ }
+
+ public enum Exclusion {
+ CURRENT_ROW("CURRENT ROW"), GROUP("GROUP"), TIES("TIES"), NO_OTHERS("NO OTHERS");
+
+ private final String keyword;
+
+ Exclusion(String keyword) {
+ this.keyword = keyword;
+ }
+
+ @Override
+ public String toString() {
+ return keyword;
}
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/WindowOffset.java b/src/main/java/net/sf/jsqlparser/expression/WindowOffset.java
index 0303b4f06..079ec2eba 100644
--- a/src/main/java/net/sf/jsqlparser/expression/WindowOffset.java
+++ b/src/main/java/net/sf/jsqlparser/expression/WindowOffset.java
@@ -10,6 +10,7 @@
package net.sf.jsqlparser.expression;
import java.io.Serializable;
+import java.util.Locale;
public class WindowOffset implements Serializable {
@@ -79,7 +80,7 @@ public enum Type {
PRECEDING, FOLLOWING, CURRENT, EXPR;
public static Type from(String type) {
- return Enum.valueOf(Type.class, type.toUpperCase());
+ return Enum.valueOf(Type.class, type.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/XmlTableFunction.java b/src/main/java/net/sf/jsqlparser/expression/XmlTableFunction.java
new file mode 100644
index 000000000..9a4f4210b
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/XmlTableFunction.java
@@ -0,0 +1,231 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.create.table.ColDataType;
+
+/**
+ * Represents an {@code XMLTABLE} table function (SQL/XML standard, supported by PostgreSQL and
+ * Oracle), which turns an XML document into a relational row set, e.g.:
+ *
+ *
+ * XMLTABLE('//ROWS/ROW'
+ * PASSING data
+ * COLUMNS id int PATH '@id',
+ * ordinality FOR ORDINALITY,
+ * country_id text PATH 'COUNTRY_ID',
+ * size float PATH 'SIZE' DEFAULT 0)
+ *
+ *
+ * It is the XML counterpart of {@link JsonTableFunction} and mirrors its structure, without the
+ * JSON-specific wrapper/error/plan clauses.
+ */
+public class XmlTableFunction extends Function {
+
+ private Expression rowPathExpression;
+ private final List passingClauses = new ArrayList<>();
+ private final List columnDefinitions = new ArrayList<>();
+
+ public static class XmlTablePassingClause extends ASTNodeAccessImpl implements Serializable {
+ private Expression valueExpression;
+ private String name;
+
+ public Expression getValueExpression() {
+ return valueExpression;
+ }
+
+ public XmlTablePassingClause setValueExpression(Expression valueExpression) {
+ this.valueExpression = valueExpression;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public XmlTablePassingClause setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public void collectExpressions(List expressions) {
+ if (valueExpression != null) {
+ expressions.add(valueExpression);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return valueExpression + (name != null ? " AS " + name : "");
+ }
+ }
+
+ public static class XmlTableColumnDefinition extends ASTNodeAccessImpl implements Serializable {
+ private String columnName;
+ private boolean forOrdinality;
+ private ColDataType dataType;
+ private Expression pathExpression;
+ private Expression defaultExpression;
+
+ public String getColumnName() {
+ return columnName;
+ }
+
+ public XmlTableColumnDefinition setColumnName(String columnName) {
+ this.columnName = columnName;
+ return this;
+ }
+
+ public boolean isForOrdinality() {
+ return forOrdinality;
+ }
+
+ public XmlTableColumnDefinition setForOrdinality(boolean forOrdinality) {
+ this.forOrdinality = forOrdinality;
+ return this;
+ }
+
+ public ColDataType getDataType() {
+ return dataType;
+ }
+
+ public XmlTableColumnDefinition setDataType(ColDataType dataType) {
+ this.dataType = dataType;
+ return this;
+ }
+
+ public Expression getPathExpression() {
+ return pathExpression;
+ }
+
+ public XmlTableColumnDefinition setPathExpression(Expression pathExpression) {
+ this.pathExpression = pathExpression;
+ return this;
+ }
+
+ public Expression getDefaultExpression() {
+ return defaultExpression;
+ }
+
+ public XmlTableColumnDefinition setDefaultExpression(Expression defaultExpression) {
+ this.defaultExpression = defaultExpression;
+ return this;
+ }
+
+ public void collectExpressions(List expressions) {
+ if (pathExpression != null) {
+ expressions.add(pathExpression);
+ }
+ if (defaultExpression != null) {
+ expressions.add(defaultExpression);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(columnName);
+ if (forOrdinality) {
+ builder.append(" FOR ORDINALITY");
+ return builder.toString();
+ }
+ if (dataType != null) {
+ builder.append(" ").append(dataType);
+ }
+ if (pathExpression != null) {
+ builder.append(" PATH ").append(pathExpression);
+ }
+ if (defaultExpression != null) {
+ builder.append(" DEFAULT ").append(defaultExpression);
+ }
+ return builder.toString();
+ }
+ }
+
+ public Expression getRowPathExpression() {
+ return rowPathExpression;
+ }
+
+ public XmlTableFunction setRowPathExpression(Expression rowPathExpression) {
+ this.rowPathExpression = rowPathExpression;
+ return this;
+ }
+
+ public List getPassingClauses() {
+ return passingClauses;
+ }
+
+ public XmlTableFunction addPassingClause(XmlTablePassingClause passingClause) {
+ passingClauses.add(passingClause);
+ return this;
+ }
+
+ public List getColumnDefinitions() {
+ return columnDefinitions;
+ }
+
+ public XmlTableFunction addColumnDefinition(XmlTableColumnDefinition columnDefinition) {
+ columnDefinitions.add(columnDefinition);
+ return this;
+ }
+
+ public List getAllExpressions() {
+ List expressions = new ArrayList<>();
+ if (rowPathExpression != null) {
+ expressions.add(rowPathExpression);
+ }
+ for (XmlTablePassingClause passingClause : passingClauses) {
+ passingClause.collectExpressions(expressions);
+ }
+ for (XmlTableColumnDefinition columnDefinition : columnDefinitions) {
+ columnDefinition.collectExpressions(expressions);
+ }
+ return expressions;
+ }
+
+ @Override
+ public T accept(ExpressionVisitor expressionVisitor, S context) {
+ return expressionVisitor.visit(this, context);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("XMLTABLE(");
+ builder.append(rowPathExpression);
+ if (!passingClauses.isEmpty()) {
+ builder.append(" PASSING ");
+ boolean first = true;
+ for (XmlTablePassingClause passingClause : passingClauses) {
+ if (!first) {
+ builder.append(", ");
+ }
+ builder.append(passingClause);
+ first = false;
+ }
+ }
+ if (!columnDefinitions.isEmpty()) {
+ builder.append(" COLUMNS ");
+ boolean first = true;
+ for (XmlTableColumnDefinition columnDefinition : columnDefinitions) {
+ if (!first) {
+ builder.append(", ");
+ }
+ builder.append(columnDefinition);
+ first = false;
+ }
+ }
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java
index f191ae1a1..0bf79f0ec 100644
--- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java
+++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/FullTextSearch.java
@@ -9,6 +9,10 @@
*/
package net.sf.jsqlparser.expression.operators.relational;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Optional;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.expression.JdbcNamedParameter;
@@ -17,11 +21,6 @@
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.schema.Column;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Optional;
-
public class FullTextSearch extends ASTNodeAccessImpl implements Expression {
private ExpressionList _matchColumns;
@@ -44,16 +43,20 @@ public Expression getAgainstValue() {
return this._againstValue;
}
- public void setAgainstValue(StringValue val) {
+ public void setAgainstValue(Expression val) {
this._againstValue = val;
}
+ public void setAgainstValue(StringValue val) {
+ setAgainstValue((Expression) val);
+ }
+
public void setAgainstValue(JdbcNamedParameter val) {
- this._againstValue = val;
+ setAgainstValue((Expression) val);
}
public void setAgainstValue(JdbcParameter val) {
- this._againstValue = val;
+ setAgainstValue((Expression) val);
}
public String getSearchModifier() {
@@ -92,6 +95,10 @@ public FullTextSearch withMatchColumns(ExpressionList matchColumns) {
}
public FullTextSearch withAgainstValue(StringValue againstValue) {
+ return withAgainstValue((Expression) againstValue);
+ }
+
+ public FullTextSearch withAgainstValue(Expression againstValue) {
this.setAgainstValue(againstValue);
return this;
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java b/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java
index f450de1c1..770db3de3 100644
--- a/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java
+++ b/src/main/java/net/sf/jsqlparser/expression/operators/relational/LikeExpression.java
@@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.expression.operators.relational;
+import java.util.Locale;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitor;
@@ -120,7 +121,8 @@ public enum KeyWord {
LIKE, ILIKE, RLIKE, REGEXP_LIKE, REGEXP, SIMILAR_TO, MATCH_ANY, MATCH_ALL, MATCH_PHRASE, MATCH_PHRASE_PREFIX, MATCH_REGEXP;
public static KeyWord from(String keyword) {
- return Enum.valueOf(KeyWord.class, keyword.toUpperCase().replaceAll("\\s+", "_"));
+ return Enum.valueOf(KeyWord.class,
+ keyword.toUpperCase(Locale.ROOT).replaceAll("\\s+", "_"));
}
}
}
diff --git a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
index bdb25eeb4..31e070975 100644
--- a/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
+++ b/src/main/java/net/sf/jsqlparser/parser/CCJSqlParserUtil.java
@@ -88,6 +88,8 @@ public static Statement parse(String sql, Consumer consumer)
Statement statement;
try {
statement = parse(sql, executorService, consumer);
+ } catch (JSQLParserException ex) {
+ throw new JSQLParserException(sql, ex);
} finally {
executorService.shutdown();
}
@@ -218,7 +220,7 @@ public static Expression parseExpression(String expressionStr, boolean allowPart
"could only parse partial expression " + expression.toString());
}
} catch (ParseException ex) {
- throw new JSQLParserException(ex);
+ throw new JSQLParserException(expressionStr, ex);
}
} catch (JSQLParserException ex1) {
// when fast simple parsing fails, try complex parsing but only if it has a chance to
diff --git a/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java b/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java
index bfaa4a647..5bdb93830 100644
--- a/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java
+++ b/src/main/java/net/sf/jsqlparser/parser/ParserKeywordsUtils.java
@@ -10,425 +10,215 @@
package net.sf.jsqlparser.parser;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
-import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.*;
+import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * Utilities for querying the parser's reserved and non-reserved keyword sets.
+ *
+ *
+ * Non-reserved keywords are derived from the generated {@link CCJSqlParserConstants} token
+ * table using the {@code MIN_NON_RESERVED_WORD} / {@code MAX_NON_RESERVED_WORD} sentinels.
+ *
+ *
+ * Reserved keywords are determined by scanning the Grammar file for all simple string token
+ * definitions ({@code }) and subtracting the non-reserved set.
+ */
public class ParserKeywordsUtils {
- public final static CharsetEncoder CHARSET_ENCODER = StandardCharsets.US_ASCII.newEncoder();
-
- public final static int RESTRICTED_FUNCTION = 1;
- public final static int RESTRICTED_SCHEMA = 2;
- public final static int RESTRICTED_TABLE = 4;
- public final static int RESTRICTED_COLUMN = 8;
- public final static int RESTRICTED_EXPRESSION = 16;
- public final static int RESTRICTED_ALIAS = 32;
- public final static int RESTRICTED_SQL2016 = 64;
-
- public final static int RESTRICTED_JSQLPARSER = 128
- | RESTRICTED_FUNCTION
- | RESTRICTED_SCHEMA
- | RESTRICTED_TABLE
- | RESTRICTED_COLUMN
- | RESTRICTED_EXPRESSION
- | RESTRICTED_ALIAS
- | RESTRICTED_SQL2016;
+ private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder();
- // Classification follows http://www.h2database.com/html/advanced.html#keywords
- public final static Object[][] ALL_RESERVED_KEYWORDS = {
- {"ABSENT", RESTRICTED_JSQLPARSER},
- {"ALL", RESTRICTED_SQL2016},
- {"AND", RESTRICTED_SQL2016},
- {"ANY", RESTRICTED_JSQLPARSER},
- {"AS", RESTRICTED_SQL2016},
- {"BETWEEN", RESTRICTED_SQL2016},
- {"BOTH", RESTRICTED_SQL2016},
- {"CASEWHEN", RESTRICTED_ALIAS},
- {"CHECK", RESTRICTED_SQL2016},
- {"CONNECT", RESTRICTED_ALIAS},
- {"CONNECT_BY_ROOT", RESTRICTED_JSQLPARSER},
- {"CSV", RESTRICTED_JSQLPARSER},
- {"PRIOR", RESTRICTED_JSQLPARSER},
- {"CONSTRAINT", RESTRICTED_SQL2016},
- {"CREATE", RESTRICTED_ALIAS},
- {"CROSS", RESTRICTED_SQL2016},
- {"CURRENT", RESTRICTED_JSQLPARSER},
- {"DEFAULT", RESTRICTED_ALIAS},
- {"DISTINCT", RESTRICTED_SQL2016},
- {"DISTINCTROW", RESTRICTED_SQL2016},
- {"DOUBLE", RESTRICTED_ALIAS},
- {"ELSE", RESTRICTED_JSQLPARSER},
- {"ERRORS", RESTRICTED_JSQLPARSER},
- {"EXCEPT", RESTRICTED_SQL2016},
- {"EXCLUDES", RESTRICTED_JSQLPARSER},
- {"EXISTS", RESTRICTED_SQL2016},
- {"EXTEND", RESTRICTED_JSQLPARSER},
- {"FALSE", RESTRICTED_SQL2016},
- {"FBV", RESTRICTED_JSQLPARSER},
- {"FETCH", RESTRICTED_SQL2016},
- {"FILE", RESTRICTED_JSQLPARSER},
- {"FINAL", RESTRICTED_JSQLPARSER},
- {"FOR", RESTRICTED_SQL2016},
- {"FORCE", RESTRICTED_SQL2016},
- {"FOREIGN", RESTRICTED_SQL2016},
- {"FROM", RESTRICTED_SQL2016},
- {"FULL", RESTRICTED_SQL2016},
- {"GLOBAL", RESTRICTED_ALIAS},
- {"GROUP", RESTRICTED_SQL2016},
- {"GROUPING", RESTRICTED_ALIAS},
- {"QUALIFY", RESTRICTED_ALIAS},
- {"HAVING", RESTRICTED_SQL2016},
- {"IF", RESTRICTED_SQL2016},
- {"IIF", RESTRICTED_ALIAS},
- {"IGNORE", RESTRICTED_ALIAS},
- {"ILIKE", RESTRICTED_SQL2016},
- {"IMPORT", RESTRICTED_JSQLPARSER},
- {"IN", RESTRICTED_SQL2016},
- {"INCLUDES", RESTRICTED_JSQLPARSER},
- {"INNER", RESTRICTED_SQL2016},
- {"INTERSECT", RESTRICTED_SQL2016},
- {"INTERVAL", RESTRICTED_SQL2016},
- {"INTO", RESTRICTED_JSQLPARSER},
- {"IS", RESTRICTED_SQL2016},
- {"JOIN", RESTRICTED_JSQLPARSER},
- {"LATERAL", RESTRICTED_SQL2016},
- {"LEFT", RESTRICTED_SQL2016},
- {"LIKE", RESTRICTED_SQL2016},
- {"LIMIT", RESTRICTED_SQL2016},
- {"MINUS", RESTRICTED_SQL2016},
- {"NATURAL", RESTRICTED_SQL2016},
- {"NOCYCLE", RESTRICTED_JSQLPARSER},
- {"NOT", RESTRICTED_SQL2016},
- {"NULL", RESTRICTED_SQL2016},
- {"OFFSET", RESTRICTED_SQL2016},
- {"ON", RESTRICTED_SQL2016},
- {"ONLY", RESTRICTED_JSQLPARSER},
- {"OPTIMIZE", RESTRICTED_ALIAS},
- {"OR", RESTRICTED_SQL2016},
- {"ORDER", RESTRICTED_SQL2016},
- {"OUTER", RESTRICTED_JSQLPARSER},
- {"OUTPUT", RESTRICTED_JSQLPARSER},
- {"OPTIMIZE ", RESTRICTED_JSQLPARSER},
- {"OVERWRITE ", RESTRICTED_JSQLPARSER},
- {"PIVOT", RESTRICTED_JSQLPARSER},
- {"PREFERRING", RESTRICTED_JSQLPARSER},
- {"PRIOR", RESTRICTED_ALIAS},
- {"PROCEDURE", RESTRICTED_ALIAS},
- {"PUBLIC", RESTRICTED_ALIAS},
- {"RETURNING", RESTRICTED_JSQLPARSER},
- {"RIGHT", RESTRICTED_SQL2016},
- {"SAMPLE", RESTRICTED_ALIAS},
- {"SCRIPT", RESTRICTED_JSQLPARSER},
- {"SEL", RESTRICTED_ALIAS},
- {"SELECT", RESTRICTED_ALIAS},
- {"SEMI", RESTRICTED_JSQLPARSER},
- {"SET", RESTRICTED_JSQLPARSER},
- {"SOME", RESTRICTED_JSQLPARSER},
- {"START", RESTRICTED_JSQLPARSER},
- {"STATEMENT", RESTRICTED_JSQLPARSER},
- {"TABLES", RESTRICTED_ALIAS},
- {"TOP", RESTRICTED_SQL2016},
- {"TRAILING", RESTRICTED_SQL2016},
- {"TRUE", RESTRICTED_SQL2016},
- {"UNBOUNDED", RESTRICTED_JSQLPARSER},
- {"UNION", RESTRICTED_SQL2016},
- {"UNIQUE", RESTRICTED_SQL2016},
- {"UNKNOWN", RESTRICTED_SQL2016},
- {"UNPIVOT", RESTRICTED_JSQLPARSER},
- {"USE", RESTRICTED_JSQLPARSER},
- {"USING", RESTRICTED_SQL2016},
- {"SQL_CACHE", RESTRICTED_JSQLPARSER},
- {"SQL_CALC_FOUND_ROWS", RESTRICTED_JSQLPARSER},
- {"SQL_NO_CACHE", RESTRICTED_JSQLPARSER},
- {"STRAIGHT_JOIN", RESTRICTED_JSQLPARSER},
- {"TABLESAMPLE", RESTRICTED_ALIAS},
- {"VALUE", RESTRICTED_JSQLPARSER},
- {"VALUES", RESTRICTED_SQL2016},
- {"VARYING", RESTRICTED_JSQLPARSER},
- {"VERIFY", RESTRICTED_JSQLPARSER},
- {"WHEN", RESTRICTED_SQL2016},
- {"WHERE", RESTRICTED_SQL2016},
- {"WINDOW", RESTRICTED_SQL2016},
- {"WITH", RESTRICTED_SQL2016},
- {"XOR", RESTRICTED_JSQLPARSER},
- {"XMLSERIALIZE", RESTRICTED_JSQLPARSER},
+ /** Matches a pure keyword image: word characters, at least two characters, pure US-ASCII. */
+ private static final Pattern KEYWORD_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z_0-9]+");
- // add keywords from the composite token definitions:
- // tk= | tk= | tk=
- // we will use the composite tokens instead, which are always hit first before the
- // simple keywords
- // @todo: figure out a way to remove these composite tokens, as they do more harm than
- // good
- {"SEL", RESTRICTED_JSQLPARSER},
- {"SELECT", RESTRICTED_JSQLPARSER},
- {"DATE", RESTRICTED_JSQLPARSER},
- {"TIME", RESTRICTED_JSQLPARSER},
- {"TIMESTAMP", RESTRICTED_JSQLPARSER},
- {"YEAR", RESTRICTED_JSQLPARSER},
- {"MONTH", RESTRICTED_JSQLPARSER},
- {"DAY", RESTRICTED_JSQLPARSER},
- {"HOUR", RESTRICTED_JSQLPARSER},
- {"MINUTE", RESTRICTED_JSQLPARSER},
- {"SECOND", RESTRICTED_JSQLPARSER},
- {"SUBSTR", RESTRICTED_JSQLPARSER},
- {"SUBSTRING", RESTRICTED_JSQLPARSER},
- {"TRIM", RESTRICTED_JSQLPARSER},
- {"POSITION", RESTRICTED_JSQLPARSER},
- {"OVERLAY", RESTRICTED_JSQLPARSER},
- {"NEXTVAL", RESTRICTED_COLUMN},
-
- // @todo: Object Names should not start with Hex-Prefix, we shall not find that Token
- {"0x", RESTRICTED_JSQLPARSER}
- };
+ /**
+ * Matches simple token definitions in the grammar: {@code }. Group 1 captures
+ * the string value. Only matches definitions where the value is a plain quoted string —
+ * compound regex tokens like {@code } won't match.
+ */
+ private static final Pattern SIMPLE_TOKEN_PATTERN =
+ Pattern.compile("", Pattern.MULTILINE);
- @SuppressWarnings({"PMD.ExcessiveMethodLength"})
- public static List getReservedKeywords(int restriction) {
- ArrayList keywords = new ArrayList<>();
- for (Object[] data : ALL_RESERVED_KEYWORDS) {
- int value = (int) data[1];
+ private ParserKeywordsUtils() {
+ // utility class
+ }
- // test if bit is not set
- if ((value & restriction) == restriction || (restriction & value) == value) {
- keywords.add((String) data[0]);
+ /**
+ * Returns the set of non-reserved keywords, i.e. tokens whose kind lies between
+ * {@code MIN_NON_RESERVED_WORD} and {@code MAX_NON_RESERVED_WORD}. These keywords can be used
+ * as unquoted identifiers.
+ */
+ public static TreeSet getNonReservedKeywords() {
+ TreeSet keywords = new TreeSet<>();
+ String[] images = CCJSqlParserConstants.tokenImage;
+
+ for (int kind = CCJSqlParserConstants.MIN_NON_RESERVED_WORD
+ + 1; kind < CCJSqlParserConstants.MAX_NON_RESERVED_WORD; kind++) {
+ String image = extractKeyword(images[kind]);
+ if (image != null && isKeywordImage(image)) {
+ keywords.add(image);
}
}
-
return keywords;
}
/**
- * @param args with: Grammar File, Keyword Documentation File
- * @throws Exception
+ * Returns the set of reserved keywords by scanning the Grammar file for all simple
+ * string token definitions and subtracting the non-reserved keywords.
+ *
+ * @param grammarFile the {@code .jjt} grammar file
+ * @return reserved keyword strings
+ * @throws IOException if reading the grammar file fails
*/
- public static void main(String[] args) throws Exception {
- if (args.length < 2) {
- throw new IllegalArgumentException("No filename provided aS context ARGS[0]");
- }
-
- File grammarFile = new File(args[0]);
- if (grammarFile.exists() && grammarFile.canRead() && grammarFile.canWrite()) {
- buildGrammarForRelObjectName(grammarFile);
- buildGrammarForRelObjectNameWithoutValue(grammarFile);
- } else {
- throw new FileNotFoundException("Can't read file " + args[0]);
- }
-
- File keywordDocumentationFile = new File(args[1]);
- keywordDocumentationFile.createNewFile();
- if (keywordDocumentationFile.canWrite()) {
- writeKeywordsDocumentationFile(keywordDocumentationFile);
- } else {
- throw new FileNotFoundException("Can't read file " + args[1]);
- }
+ public static TreeSet getReservedKeywords(File grammarFile) throws IOException {
+ TreeSet allSimple = getAllSimpleKeywords(grammarFile);
+ allSimple.removeAll(getNonReservedKeywords());
+ return allSimple;
}
- public static TreeSet getAllKeywordsUsingRegex(File file) throws IOException {
- Pattern tokenBlockPattern = Pattern.compile(
- "TOKEN\\s*:\\s*/\\*.*\\*/*(?:\\r?\\n|\\r)\\{(?:[^}{]+|\\{(?:[^}{]+|\\{[^}{]*})*})*}",
- Pattern.MULTILINE);
- Pattern tokenStringValuePattern = Pattern.compile("\"(\\w{2,})\"", Pattern.MULTILINE);
-
- TreeSet allKeywords = new TreeSet<>();
-
- Path path = file.toPath();
- Charset charset = Charset.defaultCharset();
- String content = new String(Files.readAllBytes(path), charset);
-
- Matcher tokenBlockmatcher = tokenBlockPattern.matcher(content);
- while (tokenBlockmatcher.find()) {
- String tokenBlock = tokenBlockmatcher.group(0);
- // remove single and multiline comments
- tokenBlock = tokenBlock.replaceAll("(?sm)((\\/\\*.*?\\*\\/)|(\\/\\/.*?$))", "");
- for (String tokenDefinition : getTokenDefinitions(tokenBlock)) {
- // check if token definition is private
- if (tokenDefinition.matches("(?sm)^<\\s*[^#].*")) {
- Matcher tokenStringValueMatcher =
- tokenStringValuePattern.matcher(tokenDefinition);
- while (tokenStringValueMatcher.find()) {
- String tokenValue = tokenStringValueMatcher.group(1);
- // test if pure US-ASCII
- if (CHARSET_ENCODER.canEncode(tokenValue) && tokenValue.matches("\\w+")) {
- allKeywords.add(tokenValue);
- }
- }
- }
+ /**
+ * Returns all simple string keywords defined in the grammar file. Scans for
+ * {@code } patterns and collects the string values.
+ *
+ * @param grammarFile the {@code .jjt} grammar file
+ * @return all simple keyword strings
+ * @throws IOException if reading the grammar file fails
+ */
+ public static TreeSet getAllSimpleKeywords(File grammarFile) throws IOException {
+ TreeSet keywords = new TreeSet<>();
+ String content = Files.readString(grammarFile.toPath(), StandardCharsets.UTF_8);
+
+ Matcher matcher = SIMPLE_TOKEN_PATTERN.matcher(content);
+ while (matcher.find()) {
+ String value = matcher.group(1);
+ if (isKeywordImage(value) && ASCII_ENCODER.canEncode(value)) {
+ keywords.add(value);
}
}
- return allKeywords;
+ return keywords;
}
- @SuppressWarnings({"PMD.EmptyWhileStmt"})
- private static List getTokenDefinitions(String tokenBlock) {
- List tokenDefinitions = new ArrayList<>();
- int level = 0;
- char openChar = '<';
- char closeChar = '>';
- char[] tokenBlockChars = tokenBlock.toCharArray();
- int tokenDefinitionStart = -1;
- for (int i = 0; i < tokenBlockChars.length; ++i) {
- if (isQuotationMark(i, tokenBlockChars)) {
- // skip everything inside quotation marks
- while (!isQuotationMark(++i, tokenBlockChars)) {
- // skip until quotation ends
- }
- }
-
- char character = tokenBlockChars[i];
- if (character == openChar) {
- if (level == 0) {
- tokenDefinitionStart = i;
- }
+ /**
+ * Checks whether the given token kind is a non-reserved keyword that can be used as an unquoted
+ * identifier.
+ */
+ public static boolean isNonReservedKeyword(int tokenKind) {
+ return tokenKind > CCJSqlParserConstants.MIN_NON_RESERVED_WORD
+ && tokenKind < CCJSqlParserConstants.MAX_NON_RESERVED_WORD;
+ }
- ++level;
- } else if (character == closeChar) {
- --level;
+ /**
+ * Writes a reStructuredText documentation file listing all reserved keywords.
+ *
+ * @param grammarFile the {@code .jjt} grammar file
+ * @param rstFile the output {@code .rst} file to write
+ * @throws IOException if reading or writing fails
+ */
+ public static void writeKeywordsDocumentationFile(File grammarFile, File rstFile)
+ throws IOException {
+ TreeSet reserved = getReservedKeywords(grammarFile);
- if (level == 0 && tokenDefinitionStart >= 0) {
- tokenDefinitions.add(tokenBlock.substring(tokenDefinitionStart, i + 1));
- tokenDefinitionStart = -1;
- }
- }
- }
+ StringBuilder builder = new StringBuilder();
+ builder.append("***********************\n");
+ builder.append("Reserved Keywords\n");
+ builder.append("***********************\n");
+ builder.append("\n");
- return tokenDefinitions;
- }
+ builder.append(
+ "The following Keywords are **reserved** in JSQLParser-|JSQLPARSER_VERSION| and must not be used for **Naming Objects**: \n");
+ builder.append("\n");
- private static boolean isQuotationMark(int index, char[] str) {
- if (str[index] == '\"') {
- // check if quotation is escaped
- if (index > 0 && str[index - 1] == '\\') {
- return index > 1 && str[index - 2] == '\\';
- }
+ builder.append("+---------------------------+\n");
+ builder.append("| **Keyword** |\n");
+ builder.append("+---------------------------+\n");
- return true;
+ for (String keyword : reserved) {
+ builder.append("| ").append(rightPadding(keyword, ' ', 25)).append(" |\n");
+ builder.append("+---------------------------+\n");
}
- return false;
+ try (FileWriter fileWriter = new FileWriter(rstFile)) {
+ fileWriter.append(builder);
+ fileWriter.flush();
+ }
}
- public static void buildGrammarForRelObjectNameWithoutValue(File file) throws Exception {
- Pattern methodBlockPattern = Pattern.compile(
- "String\\W*RelObjectNameWithoutValue\\W*\\(\\W*\\)\\W*:\\s*\\{(?:[^}{]+|\\{(?:[^}{]+|\\{[^}{]*})*})*}\\s*\\{(?:[^}{]+|\\{(?:[^}{]+|\\{[^}{]*})*})*}",
- Pattern.MULTILINE);
-
- TreeSet allKeywords = getAllKeywords(file);
+ public static String rightPadding(String input, char ch, int length) {
+ return String.format("%" + (-length) + "s", input).replace(' ', ch);
+ }
- for (String reserved : getReservedKeywords(RESTRICTED_JSQLPARSER)) {
- allKeywords.remove(reserved);
+ /**
+ * Entry point for the {@code updateKeywords} Gradle/Maven task.
+ *
+ *
+ * Usage: {@code java net.sf.jsqlparser.parser.ParserKeywordsUtils }
+ *
+ * @param args {@code args[0]}: path to the grammar file, {@code args[1]}: path to the output
+ * RST file
+ * @throws Exception if reading or writing fails
+ */
+ public static void main(String[] args) throws Exception {
+ if (args.length < 2) {
+ throw new IllegalArgumentException(
+ "Usage: ParserKeywordsUtils ");
}
- StringBuilder builder = new StringBuilder();
- builder.append("String RelObjectNameWithoutValue() :\n"
- + "{ Token tk = null; }\n"
- + "{\n"
- // @todo: find a way to avoid those hardcoded compound tokens
- + " ( tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= \n"
- + " ");
-
- for (String keyword : allKeywords) {
- builder.append(" | tk=\"").append(keyword).append("\"");
+ File grammarFile = new File(args[0]);
+ if (!grammarFile.canRead()) {
+ throw new IOException("Cannot read grammar file: " + grammarFile);
}
- builder.append(" )\n" + " { return tk.image; }\n" + "}");
+ File rstFile = new File(args[1]);
+ rstFile.getParentFile().mkdirs();
+ writeKeywordsDocumentationFile(grammarFile, rstFile);
- replaceInFile(file, methodBlockPattern, builder.toString());
+ System.out.println("Reserved keywords: " + getReservedKeywords(grammarFile).size());
+ System.out.println("Non-reserved keywords: " + getNonReservedKeywords().size());
+ System.out.println("Written to: " + rstFile.getAbsolutePath());
}
- public static void buildGrammarForRelObjectName(File file) throws Exception {
- // Pattern pattern =
- // Pattern.compile("String\\W*RelObjectName\\W*\\(\\W*\\)\\W*:\\s*\\{(?:[^}{]+|\\{(?:[^}{]+|\\{[^}{]*})*})*}\\s*\\{(?:[^}{]+|\\{(?:[^}{]+|\\{[^}{]*})*})*}",
- // Pattern.MULTILINE);
- TreeSet allKeywords = new TreeSet<>();
- for (String reserved : getReservedKeywords(RESTRICTED_ALIAS)) {
- allKeywords.add(reserved);
+ /**
+ * Extracts the keyword string from a {@code tokenImage} entry.
+ *
+ *
+ * JavaCC renders inline BNF token declarations as {@code } in {@code tokenImage}.
+ * Stripping the {@code K_} prefix and angle brackets yields the keyword string.
+ *
+ * @return the keyword string, or {@code null} if the entry is not a {@code K_} token
+ */
+ private static String extractKeyword(String tokenImage) {
+ if (tokenImage == null || tokenImage.length() < 5) {
+ return null;
}
- for (String reserved : getReservedKeywords(RESTRICTED_JSQLPARSER & ~RESTRICTED_ALIAS)) {
- allKeywords.remove(reserved);
+ // Format: → ACTION
+ if (tokenImage.charAt(0) == '<'
+ && tokenImage.charAt(tokenImage.length() - 1) == '>'
+ && tokenImage.startsWith(" getAllKeywords(File file) throws Exception {
- return getAllKeywordsUsingRegex(file);
- }
-
- private static void replaceInFile(File file, Pattern pattern, String replacement)
- throws IOException {
- Path path = file.toPath();
- Charset charset = Charset.defaultCharset();
-
- String content = new String(Files.readAllBytes(path), charset);
- content = pattern.matcher(content).replaceAll(replacement);
- Files.write(file.toPath(), content.getBytes(charset));
+ return null;
}
- public static String rightPadding(String input, char ch, int length) {
- return String.format("%" + (-length) + "s", input).replace(' ', ch);
- }
-
- public static void writeKeywordsDocumentationFile(File file) throws IOException {
- StringBuilder builder = new StringBuilder();
- builder.append("***********************\n");
- builder.append("Restricted Keywords\n");
- builder.append("***********************\n");
- builder.append("\n");
-
- builder.append(
- "The following Keywords are **restricted** in JSQLParser-|JSQLPARSER_VERSION| and must not be used for **Naming Objects**: \n");
- builder.append("\n");
-
- builder.append("+----------------------+-------------+-----------+\n");
- builder.append("| **Keyword** | JSQL Parser | SQL:2016 |\n");
- builder.append("+----------------------+-------------+-----------+\n");
-
- for (Object[] keywordDefinition : ALL_RESERVED_KEYWORDS) {
- builder.append("| ").append(rightPadding(keywordDefinition[0].toString(), ' ', 20))
- .append(" | ");
-
- int value = (int) keywordDefinition[1];
- int restriction = RESTRICTED_JSQLPARSER;
- String s = (value & restriction) == restriction || (restriction & value) == value
- ? "Yes"
- : "";
- builder.append(rightPadding(s, ' ', 11)).append(" | ");
-
- restriction = RESTRICTED_SQL2016;
- s = (value & restriction) == restriction || (restriction & value) == value
- ? "Yes"
- : "";
- builder.append(rightPadding(s, ' ', 9)).append(" | ");
-
- builder.append("\n");
- builder.append("+----------------------+-------------+-----------+\n");
- }
- try (FileWriter fileWriter = new FileWriter(file)) {
- fileWriter.append(builder);
- fileWriter.flush();
- }
+ /**
+ * Returns {@code true} if the image looks like a SQL keyword: alphabetic start, word characters
+ * only, at least 2 characters, pure US-ASCII.
+ */
+ private static boolean isKeywordImage(String image) {
+ return KEYWORD_PATTERN.matcher(image).matches()
+ && ASCII_ENCODER.canEncode(image);
}
}
diff --git a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java
index c10c568a6..0e633ebbb 100644
--- a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java
+++ b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java
@@ -16,6 +16,7 @@
*/
public class SimpleCharStream {
+
/**
* Whether parser is static.
*/
@@ -24,8 +25,7 @@ public class SimpleCharStream {
* Position in buffer.
*/
public int bufpos = -1;
- protected int[] bufline;
- protected int[] bufcolumn;
+ protected long[] buflinecolumn;
protected int column = 0;
protected int line = 1;
protected boolean prevCharIsCR = false;
@@ -52,8 +52,7 @@ public SimpleCharStream(Provider dstream, int startline, int startcolumn, int bu
available = bufsize = buffersize;
buffer = new char[buffersize];
- bufline = new int[buffersize];
- bufcolumn = new int[buffersize];
+ buflinecolumn = new long[buffersize];
}
/**
@@ -83,43 +82,31 @@ public final int getAbsoluteTokenBegin() {
}
protected void ExpandBuff(boolean wrapAround) {
- char[] newbuffer = new char[bufsize + 2048];
- int[] newbufline = new int[bufsize + 2048];
- int[] newbufcolumn = new int[bufsize + 2048];
-
- try {
- if (wrapAround) {
- System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
- System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
- buffer = newbuffer;
+ final int newSize = bufsize * 2;
+ char[] newbuffer = new char[newSize];
+ long[] newbuflinecolumn = new long[newSize];
- System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
- System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
- bufline = newbufline;
+ if (wrapAround) {
+ System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+ System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
+ buffer = newbuffer;
- System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
- System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
- bufcolumn = newbufcolumn;
+ System.arraycopy(buflinecolumn, tokenBegin, newbuflinecolumn, 0, bufsize - tokenBegin);
+ System.arraycopy(buflinecolumn, 0, newbuflinecolumn, bufsize - tokenBegin, bufpos);
+ buflinecolumn = newbuflinecolumn;
- maxNextCharInd = bufpos += bufsize - tokenBegin;
- } else {
- System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
- buffer = newbuffer;
-
- System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
- bufline = newbufline;
+ maxNextCharInd = bufpos += bufsize - tokenBegin;
+ } else {
+ System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+ buffer = newbuffer;
- System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
- bufcolumn = newbufcolumn;
+ System.arraycopy(buflinecolumn, tokenBegin, newbuflinecolumn, 0, bufsize - tokenBegin);
+ buflinecolumn = newbuflinecolumn;
- maxNextCharInd = bufpos -= tokenBegin;
- }
- } catch (Throwable t) {
- throw new Error(t.getMessage());
+ maxNextCharInd = bufpos -= tokenBegin;
}
-
- bufsize += 2048;
+ bufsize = newSize;
available = bufsize;
tokenBegin = 0;
}
@@ -165,7 +152,7 @@ protected void FillBuff() throws java.io.IOException {
/**
* Start.
*/
- public char BeginToken() throws java.io.IOException {
+ public final char BeginToken() throws java.io.IOException {
tokenBegin = -1;
char c = readChar();
tokenBegin = bufpos;
@@ -175,9 +162,16 @@ public char BeginToken() throws java.io.IOException {
return c;
}
- protected void UpdateLineColumn(char c) {
+ protected final void UpdateLineColumn(char c) {
column++;
+ if (c >= ' ' && !prevCharIsLF && !prevCharIsCR) {
+ // Fast path: printable ASCII, no pending newline
+ buflinecolumn[bufpos] = ((long) line << 32) | (column & 0xFFFFFFFFL);
+ return;
+ }
+
+ // Slow path: newlines, carriage returns, tabs, control chars
if (prevCharIsLF) {
prevCharIsLF = false;
line += column = 1;
@@ -199,20 +193,19 @@ protected void UpdateLineColumn(char c) {
break;
case '\t':
column--;
- column += tabSize - column % tabSize;
+ column += tabSize - (column % tabSize);
break;
default:
break;
}
- bufline[bufpos] = line;
- bufcolumn[bufpos] = column;
+ buflinecolumn[bufpos] = ((long) line << 32) | (column & 0xFFFFFFFFL);
}
/**
* Read a character.
*/
- public char readChar() throws java.io.IOException {
+ public final char readChar() throws java.io.IOException {
if (inBuf > 0) {
--inBuf;
@@ -244,7 +237,7 @@ public char readChar() throws java.io.IOException {
*/
public int getColumn() {
- return bufcolumn[bufpos];
+ return (int) buflinecolumn[bufpos];
}
@Deprecated
@@ -254,42 +247,41 @@ public int getColumn() {
*/
public int getLine() {
- return bufline[bufpos];
+ return (int) (buflinecolumn[bufpos] >>> 32);
}
/**
* Get token end column number.
*/
public int getEndColumn() {
- return bufcolumn[bufpos];
+ return (int) buflinecolumn[bufpos];
}
/**
* Get token end line number.
*/
public int getEndLine() {
- return bufline[bufpos];
+ return (int) (buflinecolumn[bufpos] >>> 32);
}
/**
* Get token beginning column number.
*/
public int getBeginColumn() {
- return bufcolumn[tokenBegin];
+ return (int) buflinecolumn[tokenBegin];
}
/**
* Get token beginning line number.
*/
public int getBeginLine() {
- return bufline[tokenBegin];
+ return (int) (buflinecolumn[tokenBegin] >>> 32);
}
/**
* Backup a number of characters.
*/
- public void backup(int amount) {
-
+ public final void backup(int amount) {
inBuf += amount;
totalCharsRead -= amount;
if ((bufpos -= amount) < 0) {
@@ -308,8 +300,7 @@ public void ReInit(Provider dstream, int startline, int startcolumn, int buffers
if (buffer == null || buffersize != buffer.length) {
available = bufsize = buffersize;
buffer = new char[buffersize];
- bufline = new int[buffersize];
- bufcolumn = new int[buffersize];
+ buflinecolumn = new long[buffersize];
}
prevCharIsLF = prevCharIsCR = false;
tokenBegin = inBuf = maxNextCharInd = 0;
@@ -330,7 +321,6 @@ public void ReInit(Provider dstream) {
ReInit(dstream, 1, 1, 4096);
}
-
/**
* Get token literal value.
*/
@@ -364,8 +354,7 @@ public char[] GetSuffix(int len) {
*/
public void Done() {
buffer = null;
- bufline = null;
- bufcolumn = null;
+ buflinecolumn = null;
}
/**
@@ -387,29 +376,34 @@ public void adjustBeginLineColumn(int newLine, int newCol) {
int nextColDiff;
int columnDiff = 0;
- while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) {
- bufline[j] = newLine;
- nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
- bufcolumn[j] = newCol + columnDiff;
+ while (i < len &&
+ (int) (buflinecolumn[j = start % bufsize] >>> 32) == (int) (buflinecolumn[k =
+ ++start % bufsize] >>> 32)) {
+ int colJ = (int) buflinecolumn[j];
+ int colK = (int) buflinecolumn[k];
+ nextColDiff = columnDiff + colK - colJ;
+ buflinecolumn[j] = ((long) newLine << 32) | ((newCol + columnDiff) & 0xFFFFFFFFL);
columnDiff = nextColDiff;
i++;
}
if (i < len) {
- bufline[j] = newLine++;
- bufcolumn[j] = newCol + columnDiff;
+ buflinecolumn[j] = ((long) (newLine++) << 32) | ((newCol + columnDiff) & 0xFFFFFFFFL);
while (i++ < len) {
- if (bufline[j = start % bufsize] != bufline[++start % bufsize]) {
- bufline[j] = newLine++;
+ int lineJ = (int) (buflinecolumn[j = start % bufsize] >>> 32);
+ int lineNext = (int) (buflinecolumn[++start % bufsize] >>> 32);
+ if (lineJ != lineNext) {
+ buflinecolumn[j] =
+ ((long) (newLine++) << 32) | (buflinecolumn[j] & 0xFFFFFFFFL);
} else {
- bufline[j] = newLine;
+ buflinecolumn[j] = ((long) newLine << 32) | (buflinecolumn[j] & 0xFFFFFFFFL);
}
}
}
- line = bufline[j];
- column = bufcolumn[j];
+ line = (int) (buflinecolumn[j] >>> 32);
+ column = (int) buflinecolumn[j];
}
boolean getTrackLineColumn() {
diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
index 7f4cf2af0..4c1b6dd51 100644
--- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
+++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
@@ -30,6 +30,7 @@
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
import net.sf.jsqlparser.statement.analyze.Analyze;
import net.sf.jsqlparser.statement.comment.Comment;
+import net.sf.jsqlparser.statement.create.database.CreateDatabase;
import net.sf.jsqlparser.statement.create.function.CreateFunction;
import net.sf.jsqlparser.statement.create.index.CreateIndex;
import net.sf.jsqlparser.statement.create.procedure.CreateProcedure;
@@ -475,6 +476,12 @@ public enum Feature {
*/
drop, dropTable, dropIndex, dropView, dropSchema, dropSequence, dropTableIfExists, dropIndexIfExists, dropViewIfExists, dropSchemaIfExists, dropSequenceIfExists,
+ /**
+ * SQL "CREATE DATABASE" statement is allowed
+ *
+ * @see CreateDatabase
+ */
+ createDatabase,
/**
* SQL "CREATE SCHEMA" statement is allowed
*
@@ -809,6 +816,19 @@ public enum Feature {
* "EXPORT"
*/
export,
+
+ /**
+ * MySQL allows a ',' as a separator between key and value entries. We allow that by default,
+ * but it can be disabled here
+ */
+ allowCommaAsKeyValueSeparator(true),
+
+ /**
+ * DB2 and Oracle allow Expressions as JSON_OBJECT key values. This clashes with Informix and
+ * Snowflake Json-Extraction syntax
+ */
+ allowExpressionAsJsonObjectKey(false)
+
;
private final Object value;
diff --git a/src/main/java/net/sf/jsqlparser/schema/Column.java b/src/main/java/net/sf/jsqlparser/schema/Column.java
index 400d34c3a..33240ac52 100644
--- a/src/main/java/net/sf/jsqlparser/schema/Column.java
+++ b/src/main/java/net/sf/jsqlparser/schema/Column.java
@@ -12,11 +12,12 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-
import net.sf.jsqlparser.expression.ArrayConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitor;
+import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.ReturningReferenceType;
/**
* A column. It can have the table name it belongs to.
@@ -28,6 +29,9 @@ public class Column extends ASTNodeAccessImpl implements Expression, MultiPartNa
private String commentText;
private ArrayConstructor arrayConstructor;
private String tableDelimiter = ".";
+ private int oldOracleJoinSyntax = SupportsOldOracleJoinSyntax.NO_ORACLE_JOIN;
+ private ReturningReferenceType returningReferenceType = null;
+ private String returningQualifier = null;
// holds the physical table when resolved against an actual schema information
private Table resolvedTable = null;
@@ -53,7 +57,8 @@ public Column(List nameParts, List delimiters) {
}
public Column(String columnName) {
- this(null, columnName);
+ this();
+ setColumnName(columnName);
}
public ArrayConstructor getArrayConstructor() {
@@ -131,8 +136,56 @@ public String getUnquotedColumnName() {
return MultiPartName.unquote(columnName);
}
- public void setColumnName(String string) {
- columnName = string;
+ public void setColumnName(String name) {
+ // BigQuery seems to allow things like: `catalogName.schemaName.tableName` in only one pair
+ // of quotes
+ // however, some people believe that Dots in Names are a good idea, so provide a switch-off
+ boolean splitNamesOnDelimiter = System.getProperty("SPLIT_NAMES_ON_DELIMITER") == null ||
+ !List
+ .of("0", "N", "n", "FALSE", "false", "OFF", "off")
+ .contains(System.getProperty("SPLIT_NAMES_ON_DELIMITER"));
+
+ setName(name, splitNamesOnDelimiter);
+ }
+
+ public void setName(String name, boolean splitNamesOnDelimiter) {
+ if (MultiPartName.isQuoted(name) && name.contains(".") && splitNamesOnDelimiter) {
+ String[] parts = MultiPartName.unquote(name).split("\\.");
+ switch (parts.length) {
+ case 3:
+ this.table = new Table("\"" + parts[0] + "\".\"" + parts[1] + "\"");
+ this.columnName = "\"" + parts[2] + "\"";
+ break;
+ case 2:
+ this.table = new Table("\"" + parts[0] + "\"");
+ this.columnName = "\"" + parts[1] + "\"";
+ break;
+ case 1:
+ this.columnName = "\"" + parts[0] + "\"";
+ break;
+ default:
+ throw new RuntimeException("Invalid column name: " + name);
+ }
+ } else if (name.contains(".") && splitNamesOnDelimiter) {
+ String[] parts = MultiPartName.unquote(name).split("\\.");
+ switch (parts.length) {
+ case 3:
+ this.table = new Table(parts[0] + "." + parts[1]);
+ this.columnName = parts[2];
+ break;
+ case 2:
+ this.table = new Table(parts[0]);
+ this.columnName = parts[1];
+ break;
+ case 1:
+ this.columnName = parts[0];
+ break;
+ default:
+ throw new RuntimeException("Invalid column name: " + name);
+ }
+ } else {
+ this.columnName = name;
+ }
}
public String getTableDelimiter() {
@@ -143,6 +196,14 @@ public void setTableDelimiter(String tableDelimiter) {
this.tableDelimiter = tableDelimiter;
}
+ public int getOldOracleJoinSyntax() {
+ return oldOracleJoinSyntax;
+ }
+
+ public void setOldOracleJoinSyntax(int oldOracleJoinSyntax) {
+ this.oldOracleJoinSyntax = oldOracleJoinSyntax;
+ }
+
@Override
public String getFullyQualifiedName() {
return getFullyQualifiedName(false);
@@ -156,7 +217,9 @@ public String getUnquotedName() {
public String getFullyQualifiedName(boolean aliases) {
StringBuilder fqn = new StringBuilder();
- if (table != null) {
+ if (returningQualifier != null) {
+ fqn.append(returningQualifier);
+ } else if (table != null) {
if (table.getAlias() != null && aliases) {
fqn.append(table.getAlias().getName());
} else {
@@ -196,6 +259,7 @@ public T accept(ExpressionVisitor expressionVisitor, S context) {
@Override
public String toString() {
return getFullyQualifiedName(true)
+ + (oldOracleJoinSyntax != SupportsOldOracleJoinSyntax.NO_ORACLE_JOIN ? "(+)" : "")
+ (commentText != null ? " /* " + commentText + "*/ " : "");
}
@@ -219,6 +283,36 @@ public Column withTableDelimiter(String delimiter) {
return this;
}
+ public Column withOldOracleJoinSyntax(int oldOracleJoinSyntax) {
+ this.setOldOracleJoinSyntax(oldOracleJoinSyntax);
+ return this;
+ }
+
+ public ReturningReferenceType getReturningReferenceType() {
+ return returningReferenceType;
+ }
+
+ public Column setReturningReferenceType(ReturningReferenceType returningReferenceType) {
+ this.returningReferenceType = returningReferenceType;
+ return this;
+ }
+
+ public String getReturningQualifier() {
+ return returningQualifier;
+ }
+
+ public Column setReturningQualifier(String returningQualifier) {
+ this.returningQualifier = returningQualifier;
+ return this;
+ }
+
+ public Column withReturningReference(ReturningReferenceType returningReferenceType,
+ String returningQualifier) {
+ this.returningReferenceType = returningReferenceType;
+ this.returningQualifier = returningQualifier;
+ return this;
+ }
+
public String getCommentText() {
return commentText;
}
diff --git a/src/main/java/net/sf/jsqlparser/schema/MultiPartName.java b/src/main/java/net/sf/jsqlparser/schema/MultiPartName.java
index e2d985bc1..ce954780d 100644
--- a/src/main/java/net/sf/jsqlparser/schema/MultiPartName.java
+++ b/src/main/java/net/sf/jsqlparser/schema/MultiPartName.java
@@ -9,10 +9,12 @@
*/
package net.sf.jsqlparser.schema;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
public interface MultiPartName {
Pattern LEADING_TRAILING_QUOTES_PATTERN = Pattern.compile("^[\"\\[`]+|[\"\\]`]+$");
+ Pattern BACKTICK_PATTERN = Pattern.compile("`([^`]*)`");
/**
* Removes leading and trailing quotes from a SQL quoted identifier
@@ -27,10 +29,32 @@ static String unquote(String quotedIdentifier) {
}
static boolean isQuoted(String identifier) {
- return identifier!=null && LEADING_TRAILING_QUOTES_PATTERN.matcher(identifier).find();
+ return identifier != null && LEADING_TRAILING_QUOTES_PATTERN.matcher(identifier).find();
}
String getFullyQualifiedName();
String getUnquotedName();
+
+
+ static String replaceBackticksWithDoubleQuotes(String input) {
+ if (input == null || input.isEmpty()) {
+ return input;
+ }
+
+ Matcher matcher = BACKTICK_PATTERN.matcher(input);
+ StringBuilder sb = new StringBuilder();
+ int lastEnd = 0;
+
+ while (matcher.find()) {
+ sb.append(input, lastEnd, matcher.start()); // text before match
+ sb.append('"').append(matcher.group(1)).append('"'); // replace with double quotes
+ lastEnd = matcher.end();
+ }
+
+ sb.append(input.substring(lastEnd)); // append remaining text
+ return sb.toString();
+ }
+
+
}
diff --git a/src/main/java/net/sf/jsqlparser/schema/Sequence.java b/src/main/java/net/sf/jsqlparser/schema/Sequence.java
index 2f813c1d7..5d7b960cd 100644
--- a/src/main/java/net/sf/jsqlparser/schema/Sequence.java
+++ b/src/main/java/net/sf/jsqlparser/schema/Sequence.java
@@ -9,13 +9,13 @@
*/
package net.sf.jsqlparser.schema;
-import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Optional;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
/**
* Represents the database type for a {@code SEQUENCE}
@@ -29,6 +29,7 @@ public class Sequence extends ASTNodeAccessImpl implements MultiPartName {
private List partItems = new ArrayList<>();
private List parameters;
+ private String dataType;
public Sequence() {}
@@ -45,6 +46,19 @@ public void setParameters(List parameters) {
this.parameters = parameters;
}
+ public String getDataType() {
+ return dataType;
+ }
+
+ public void setDataType(String dataType) {
+ this.dataType = dataType;
+ }
+
+ public Sequence withDataType(String dataType) {
+ this.setDataType(dataType);
+ return this;
+ }
+
public Database getDatabase() {
return new Database(getIndex(DATABASE_IDX));
}
@@ -129,6 +143,9 @@ public String getUnquotedName() {
@Override
public String toString() {
StringBuilder sql = new StringBuilder(getFullyQualifiedName());
+ if (dataType != null) {
+ sql.append(" AS ").append(dataType);
+ }
if (parameters != null) {
for (Sequence.Parameter parameter : parameters) {
sql.append(" ").append(parameter.formatParameter());
@@ -158,10 +175,10 @@ public Sequence addParameters(Collection extends Parameter> parameters) {
* The available parameters to a sequence
*/
public enum ParameterType {
- INCREMENT_BY, START_WITH, RESTART_WITH, MAXVALUE, NOMAXVALUE, MINVALUE, NOMINVALUE, CYCLE, NOCYCLE, CACHE, NOCACHE, ORDER, NOORDER, KEEP, NOKEEP, SESSION, GLOBAL;
+ INCREMENT_BY, INCREMENT, START_WITH, START, RESTART_WITH, MAXVALUE, NOMAXVALUE, MINVALUE, NOMINVALUE, CYCLE, NOCYCLE, CACHE, NOCACHE, ORDER, NOORDER, KEEP, NOKEEP, SESSION, GLOBAL;
public static ParameterType from(String type) {
- return Enum.valueOf(ParameterType.class, type.toUpperCase());
+ return Enum.valueOf(ParameterType.class, type.toUpperCase(Locale.ROOT));
}
}
@@ -189,8 +206,12 @@ public String formatParameter() {
switch (option) {
case INCREMENT_BY:
return prefix("INCREMENT BY");
+ case INCREMENT:
+ return prefix("INCREMENT");
case START_WITH:
return prefix("START WITH");
+ case START:
+ return prefix("START");
case RESTART_WITH:
if (value != null) {
return prefix("RESTART WITH");
diff --git a/src/main/java/net/sf/jsqlparser/schema/Server.java b/src/main/java/net/sf/jsqlparser/schema/Server.java
index 9ac9bd2d2..7e4eaa51c 100644
--- a/src/main/java/net/sf/jsqlparser/schema/Server.java
+++ b/src/main/java/net/sf/jsqlparser/schema/Server.java
@@ -14,7 +14,7 @@
public final class Server implements MultiPartName {
public static final Pattern SERVER_PATTERN =
- Pattern.compile("\\[([^\\]]+?)(?:\\\\([^\\]]+))?\\]");
+ Pattern.compile("\\[([^\\]\\\\]+)(?:\\\\([^\\]]+))?\\]");
private String serverName;
diff --git a/src/main/java/net/sf/jsqlparser/schema/Table.java b/src/main/java/net/sf/jsqlparser/schema/Table.java
index cd0aa679d..1de14a8a5 100644
--- a/src/main/java/net/sf/jsqlparser/schema/Table.java
+++ b/src/main/java/net/sf/jsqlparser/schema/Table.java
@@ -170,7 +170,49 @@ public String getUnquotedSchemaName() {
}
public Table setSchemaName(String schemaName) {
- this.setIndex(SCHEMA_IDX, schemaName);
+ if (schemaName == null) {
+ setIndex(SCHEMA_IDX, null);
+ return this;
+ }
+
+ // BigQuery seems to allow things like: `catalogName.schemaName.tableName` in only one pair
+ // of quotes
+ // however, some people believe that Dots in Names are a good idea, so provide a switch-off
+ boolean splitNamesOnDelimiter = System.getProperty("SPLIT_NAMES_ON_DELIMITER") == null ||
+ !List
+ .of("0", "N", "n", "FALSE", "false", "OFF", "off")
+ .contains(System.getProperty("SPLIT_NAMES_ON_DELIMITER"));
+
+ if (MultiPartName.isQuoted(schemaName) && schemaName.contains(".")
+ && splitNamesOnDelimiter) {
+ String[] parts = MultiPartName.unquote(schemaName).split("\\.");
+ switch (parts.length) {
+ case 2:
+ setIndex(DATABASE_IDX, "\"" + parts[0] + "\"");
+ setIndex(SCHEMA_IDX, "\"" + parts[1] + "\"");
+ break;
+ case 1:
+ setIndex(SCHEMA_IDX, "\"" + parts[0] + "\"");
+ break;
+ default:
+ throw new RuntimeException("Invalid schema name: " + schemaName);
+ }
+ } else if (schemaName.contains(".") && splitNamesOnDelimiter) {
+ String[] parts = MultiPartName.unquote(schemaName).split("\\.");
+ switch (parts.length) {
+ case 2:
+ setIndex(DATABASE_IDX, parts[0]);
+ setIndex(SCHEMA_IDX, parts[1]);
+ break;
+ case 1:
+ setIndex(SCHEMA_IDX, parts[0]);
+ break;
+ default:
+ throw new RuntimeException("Invalid schema name: " + schemaName);
+ }
+ } else {
+ this.setIndex(SCHEMA_IDX, schemaName);
+ }
return this;
}
@@ -260,6 +302,10 @@ public Table setTimeTravelStrAfterAlias(String timeTravelStrAfterAlias) {
return this;
}
+ public void setNameParts(List nameParts) {
+ this.partItems = nameParts;
+ }
+
private void setIndex(int idx, String value) {
int size = partItems.size();
for (int i = 0; i < idx - size + 1; i++) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/DBMSType.java b/src/main/java/net/sf/jsqlparser/statement/DBMSType.java
index 4b3d667da..4f2500e9c 100644
--- a/src/main/java/net/sf/jsqlparser/statement/DBMSType.java
+++ b/src/main/java/net/sf/jsqlparser/statement/DBMSType.java
@@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.statement;
+import java.util.Locale;
import net.sf.jsqlparser.expression.StringValue;
public class DBMSType implements SourceDestinationType {
@@ -20,7 +21,7 @@ public DBMSType(String dbmsType) {
}
public DBMSType(String dbmsType, String jdbcDriverDefinition) {
- this.dbmsType = Kind.valueOf(dbmsType.toUpperCase());
+ this.dbmsType = Kind.valueOf(dbmsType.toUpperCase(Locale.ROOT));
if (jdbcDriverDefinition != null) {
this.jdbcDriverDefinition = new StringValue(jdbcDriverDefinition);
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/DeclareType.java b/src/main/java/net/sf/jsqlparser/statement/DeclareType.java
index 5208a333a..cb8cdbc46 100644
--- a/src/main/java/net/sf/jsqlparser/statement/DeclareType.java
+++ b/src/main/java/net/sf/jsqlparser/statement/DeclareType.java
@@ -9,6 +9,8 @@
*/
package net.sf.jsqlparser.statement;
+import java.util.Locale;
+
/**
* @author tobens
*/
@@ -16,6 +18,6 @@ public enum DeclareType {
TABLE, AS, TYPE;
public static DeclareType from(String type) {
- return Enum.valueOf(DeclareType.class, type.toUpperCase());
+ return Enum.valueOf(DeclareType.class, type.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java b/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java
index 048356425..fbc5e8503 100644
--- a/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java
+++ b/src/main/java/net/sf/jsqlparser/statement/ExplainStatement.java
@@ -12,17 +12,16 @@
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.stream.Collectors;
-
import net.sf.jsqlparser.schema.Table;
-import net.sf.jsqlparser.statement.select.Select;
/**
* An {@code EXPLAIN} statement
*/
public class ExplainStatement implements Statement {
private String keyword;
- private Select select;
+ private Statement statement;
private LinkedHashMap options;
private Table table;
@@ -37,24 +36,17 @@ public ExplainStatement() {
public ExplainStatement(String keyword, Table table) {
this.keyword = keyword;
this.table = table;
- this.select = null;
}
- public ExplainStatement(String keyword, Select select, List optionList) {
+ public ExplainStatement(String keyword, Statement statement, List optionList) {
this.keyword = keyword;
- this.select = select;
- this.table = null;
+ setStatement(statement);
- if (optionList != null && !optionList.isEmpty()) {
- options = new LinkedHashMap<>();
- for (Option o : optionList) {
- options.put(o.getType(), o);
- }
- }
+ initializeOptions(optionList);
}
- public ExplainStatement(Select select) {
- this("EXPLAIN", select, null);
+ public ExplainStatement(Statement statement) {
+ this("EXPLAIN", statement, null);
}
public Table getTable() {
@@ -63,15 +55,20 @@ public Table getTable() {
public ExplainStatement setTable(Table table) {
this.table = table;
+ if (table != null) {
+ this.statement = null;
+ }
return this;
}
- public Select getStatement() {
- return select;
+ public Statement getStatement() {
+ return statement;
}
- public void setStatement(Select select) {
- this.select = select;
+ public ExplainStatement setStatement(Statement statement) {
+ this.table = null;
+ this.statement = statement;
+ return this;
}
public LinkedHashMap getOptions() {
@@ -122,8 +119,8 @@ public String toString() {
}
builder.append(" ");
- if (select != null) {
- select.appendTo(builder);
+ if (statement != null) {
+ builder.append(statement);
}
}
@@ -135,11 +132,20 @@ public T accept(StatementVisitor statementVisitor, S context) {
return statementVisitor.visit(this, context);
}
+ private void initializeOptions(List optionList) {
+ if (optionList != null && !optionList.isEmpty()) {
+ options = new LinkedHashMap<>();
+ for (Option o : optionList) {
+ options.put(o.getType(), o);
+ }
+ }
+ }
+
public enum OptionType {
ANALYZE, VERBOSE, COSTS, BUFFERS, FORMAT, PLAN, PLAN_FOR;
public static OptionType from(String type) {
- return Enum.valueOf(OptionType.class, type.toUpperCase());
+ return Enum.valueOf(OptionType.class, type.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/FileType.java b/src/main/java/net/sf/jsqlparser/statement/FileType.java
index 7ea1535f9..6810b1e12 100644
--- a/src/main/java/net/sf/jsqlparser/statement/FileType.java
+++ b/src/main/java/net/sf/jsqlparser/statement/FileType.java
@@ -9,11 +9,13 @@
*/
package net.sf.jsqlparser.statement;
+import java.util.Locale;
+
public class FileType implements SourceDestinationType {
private final Kind fileType;
public FileType(String fileType) {
- this.fileType = Kind.valueOf(fileType.toUpperCase());
+ this.fileType = Kind.valueOf(fileType.toUpperCase(Locale.ROOT));
}
private enum Kind {
diff --git a/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java b/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java
index 4cb2755ba..7535d3467 100644
--- a/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java
+++ b/src/main/java/net/sf/jsqlparser/statement/PurgeObjectType.java
@@ -9,6 +9,8 @@
*/
package net.sf.jsqlparser.statement;
+import java.util.Locale;
+
/**
* @author Andreas Reichel
*/
@@ -16,6 +18,6 @@ public enum PurgeObjectType {
TABLE, INDEX, RECYCLEBIN, DBA_RECYCLEBIN, TABLESPACE;
public static PurgeObjectType from(String type) {
- return Enum.valueOf(PurgeObjectType.class, type.toUpperCase());
+ return Enum.valueOf(PurgeObjectType.class, type.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/ReferentialAction.java b/src/main/java/net/sf/jsqlparser/statement/ReferentialAction.java
index 5691aed25..6e441ee1c 100644
--- a/src/main/java/net/sf/jsqlparser/statement/ReferentialAction.java
+++ b/src/main/java/net/sf/jsqlparser/statement/ReferentialAction.java
@@ -10,6 +10,7 @@
package net.sf.jsqlparser.statement;
import java.io.Serializable;
+import java.util.Locale;
public class ReferentialAction implements Serializable {
@@ -90,7 +91,7 @@ public enum Type {
DELETE, UPDATE;
public static Type from(String name) {
- return Enum.valueOf(Type.class, name.toUpperCase());
+ return Enum.valueOf(Type.class, name.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java b/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java
index c84a07903..c37a6dfd5 100644
--- a/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java
+++ b/src/main/java/net/sf/jsqlparser/statement/ReturningClause.java
@@ -9,10 +9,18 @@
*/
package net.sf.jsqlparser.statement;
-import net.sf.jsqlparser.statement.select.SelectItem;
-
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.MultiPartName;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.select.AllTableColumns;
+import net.sf.jsqlparser.statement.select.SelectItem;
/**
* RETURNING clause according to > {
* List of output targets like Table or UserVariable
*/
private final List dataItems;
+ private final List outputAliases;
private Keyword keyword;
public ReturningClause(Keyword keyword, List> selectItems,
List dataItems) {
+ this(keyword, selectItems, null, dataItems);
+ }
+
+ public ReturningClause(Keyword keyword, List> selectItems,
+ List outputAliases, List dataItems) {
this.keyword = keyword;
this.addAll(selectItems);
+ this.outputAliases = outputAliases;
this.dataItems = dataItems;
+ normalizeReturningReferences();
}
public ReturningClause(String keyword, List> selectItems,
@@ -39,12 +55,17 @@ public ReturningClause(String keyword, List> selectItems,
this(Keyword.from(keyword), selectItems, dataItems);
}
+ public ReturningClause(String keyword, List> selectItems,
+ List outputAliases, List dataItems) {
+ this(Keyword.from(keyword), selectItems, outputAliases, dataItems);
+ }
+
public ReturningClause(Keyword keyword, List> selectItems) {
- this(keyword, selectItems, null);
+ this(keyword, selectItems, null, null);
}
public ReturningClause(String keyword, List> selectItems) {
- this(Keyword.valueOf(keyword), selectItems, null);
+ this(Keyword.from(keyword), selectItems, null, null);
}
public Keyword getKeyword() {
@@ -60,8 +81,22 @@ public List> getDataItems() {
return dataItems;
}
+ public List getOutputAliases() {
+ return outputAliases;
+ }
+
public StringBuilder appendTo(StringBuilder builder) {
builder.append(" ").append(keyword).append(" ");
+ if (outputAliases != null && !outputAliases.isEmpty()) {
+ builder.append("WITH (");
+ for (int i = 0; i < outputAliases.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(outputAliases.get(i));
+ }
+ builder.append(") ");
+ }
for (int i = 0; i < size(); i++) {
if (i > 0) {
builder.append(", ");
@@ -86,11 +121,131 @@ public String toString() {
return appendTo(new StringBuilder()).toString();
}
+ private void normalizeReturningReferences() {
+ Map qualifierMap = buildQualifierMap();
+ if (qualifierMap.isEmpty()) {
+ return;
+ }
+
+ ReturningReferenceNormalizer normalizer = new ReturningReferenceNormalizer(qualifierMap);
+ forEach(selectItem -> {
+ if (selectItem != null && selectItem.getExpression() != null) {
+ selectItem.getExpression().accept(normalizer, null);
+ }
+ });
+ }
+
+ private Map buildQualifierMap() {
+ LinkedHashMap qualifierMap = new LinkedHashMap<>();
+
+ if (outputAliases == null || outputAliases.isEmpty()) {
+ qualifierMap.put(QualifierKey.from("OLD"), ReturningReferenceType.OLD);
+ qualifierMap.put(QualifierKey.from("NEW"), ReturningReferenceType.NEW);
+ return qualifierMap;
+ }
+
+ for (ReturningOutputAlias outputAlias : outputAliases) {
+ if (outputAlias == null || outputAlias.getAlias() == null
+ || outputAlias.getReferenceType() == null) {
+ continue;
+ }
+ qualifierMap.put(QualifierKey.from(outputAlias.getAlias()),
+ outputAlias.getReferenceType());
+ }
+ return qualifierMap;
+ }
+
+ private static class ReturningReferenceNormalizer extends ExpressionVisitorAdapter {
+ private final Map qualifierMap;
+
+ ReturningReferenceNormalizer(Map qualifierMap) {
+ this.qualifierMap = qualifierMap;
+ }
+
+ @Override
+ public Void visit(Column column, S context) {
+ Table table = column.getTable();
+ String qualifier = extractSimpleQualifier(table);
+ if (qualifier == null) {
+ return null;
+ }
+ ReturningReferenceType referenceType = qualifierMap.get(QualifierKey.from(qualifier));
+ if (referenceType != null) {
+ column.withReturningReference(referenceType, qualifier);
+ column.setTable(null);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visit(AllTableColumns allTableColumns, S context) {
+ Table table = allTableColumns.getTable();
+ String qualifier = extractSimpleQualifier(table);
+ if (qualifier == null) {
+ return null;
+ }
+ ReturningReferenceType referenceType = qualifierMap.get(QualifierKey.from(qualifier));
+ if (referenceType != null) {
+ allTableColumns.withReturningReference(referenceType, qualifier);
+ allTableColumns.setTable(null);
+ }
+ return null;
+ }
+
+ private String extractSimpleQualifier(Table table) {
+ if (table == null || table.getSchemaName() != null || table.getDatabaseName() != null) {
+ return null;
+ }
+ String qualifier = table.getName();
+ if (qualifier == null || qualifier.contains("@")) {
+ return null;
+ }
+ return qualifier;
+ }
+ }
+
+ private static class QualifierKey {
+ private final boolean quoted;
+ private final String normalizedIdentifier;
+
+ private QualifierKey(boolean quoted, String normalizedIdentifier) {
+ this.quoted = quoted;
+ this.normalizedIdentifier = normalizedIdentifier;
+ }
+
+ static QualifierKey from(String identifier) {
+ boolean quoted = MultiPartName.isQuoted(identifier);
+ String unquoted = MultiPartName.unquote(identifier);
+ if (!quoted && unquoted != null) {
+ unquoted = unquoted.toUpperCase(Locale.ROOT);
+ }
+ return new QualifierKey(quoted, unquoted);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof QualifierKey)) {
+ return false;
+ }
+ QualifierKey that = (QualifierKey) o;
+ return quoted == that.quoted
+ && Objects.equals(normalizedIdentifier, that.normalizedIdentifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(quoted, normalizedIdentifier);
+ }
+ }
+
public enum Keyword {
RETURN, RETURNING;
public static Keyword from(String keyword) {
- return Enum.valueOf(Keyword.class, keyword.toUpperCase());
+ return Enum.valueOf(Keyword.class, keyword.toUpperCase(Locale.ROOT));
}
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/ReturningOutputAlias.java b/src/main/java/net/sf/jsqlparser/statement/ReturningOutputAlias.java
new file mode 100644
index 000000000..d0c42de34
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/ReturningOutputAlias.java
@@ -0,0 +1,66 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement;
+
+import java.util.Objects;
+
+public class ReturningOutputAlias {
+ private ReturningReferenceType referenceType;
+ private String alias;
+
+ public ReturningOutputAlias(ReturningReferenceType referenceType, String alias) {
+ this.referenceType = referenceType;
+ this.alias = alias;
+ }
+
+ public ReturningReferenceType getReferenceType() {
+ return referenceType;
+ }
+
+ public ReturningOutputAlias setReferenceType(ReturningReferenceType referenceType) {
+ this.referenceType = referenceType;
+ return this;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public ReturningOutputAlias setAlias(String alias) {
+ this.alias = alias;
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ return builder.append(referenceType).append(" AS ").append(alias);
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ReturningOutputAlias)) {
+ return false;
+ }
+ ReturningOutputAlias that = (ReturningOutputAlias) o;
+ return referenceType == that.referenceType && Objects.equals(alias, that.alias);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(referenceType, alias);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/ReturningReferenceType.java b/src/main/java/net/sf/jsqlparser/statement/ReturningReferenceType.java
new file mode 100644
index 000000000..06079904a
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/ReturningReferenceType.java
@@ -0,0 +1,30 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement;
+
+import net.sf.jsqlparser.schema.MultiPartName;
+
+public enum ReturningReferenceType {
+ OLD, NEW;
+
+ public static ReturningReferenceType from(String name) {
+ String unquoted = MultiPartName.unquote(name);
+ if (unquoted == null) {
+ return null;
+ }
+ if ("OLD".equalsIgnoreCase(unquoted)) {
+ return OLD;
+ }
+ if ("NEW".equalsIgnoreCase(unquoted)) {
+ return NEW;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/SessionStatement.java b/src/main/java/net/sf/jsqlparser/statement/SessionStatement.java
index 873c18245..1c791bb1a 100644
--- a/src/main/java/net/sf/jsqlparser/statement/SessionStatement.java
+++ b/src/main/java/net/sf/jsqlparser/statement/SessionStatement.java
@@ -9,17 +9,23 @@
*/
package net.sf.jsqlparser.statement;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
public class SessionStatement implements Statement {
public enum Action {
START, APPLY, DROP, SHOW, DESCRIBE;
public static Action from(String flag) {
- return Enum.valueOf(Action.class, flag.toUpperCase());
+ return Enum.valueOf(Action.class, flag.toUpperCase(Locale.ROOT));
}
}
final private Action action;
final private String id;
+ final private LinkedHashMap options = new LinkedHashMap<>();
public SessionStatement(Action action, String id) {
this.action = action;
@@ -43,6 +49,59 @@ public String getId() {
return id;
}
+ public int size() {
+ return options.size();
+ }
+
+ public String putOption(String key, String value) {
+ return options.put(key.replaceAll("[\"']", "").toLowerCase(Locale.ROOT),
+ value.toLowerCase(Locale.ROOT));
+ }
+
+ public boolean hasOptions() {
+ return !options.isEmpty();
+ }
+
+ public void clearOptions() {
+ options.clear();
+ }
+
+ public boolean removeOption(String key, String value) {
+ return options.remove(key, value);
+ }
+
+ public boolean containsOption(String value) {
+ return options.containsValue(value);
+ }
+
+ public String removeOption(String key) {
+ return options.remove(key);
+ }
+
+ public String getOption(String key) {
+ return options.get(key);
+ }
+
+ public Set getOptionKeySet() {
+ return options.keySet();
+ }
+
+ public Map getOptions() {
+ return options;
+ }
+
+ public Set> getOptionEntrySet() {
+ return options.entrySet();
+ }
+
+ public boolean hasOption(String key) {
+ return options.containsKey(key);
+ }
+
+ public String getOptionOrDefault(String key, String defaultValue) {
+ return options.getOrDefault(key, defaultValue);
+ }
+
@Override
public T accept(StatementVisitor statementVisitor, S context) {
return statementVisitor.visit(this, context);
@@ -55,6 +114,20 @@ public void accept(StatementVisitor> statementVisitor) {
@Override
public String toString() {
- return "SESSION " + action + " " + (id != null ? id : "") + ";";
+ StringBuilder builder =
+ new StringBuilder("SESSION " + action + " " + (id != null ? id : ""));
+ if (!options.isEmpty()) {
+ builder.append(" WITH ");
+ int i = 0;
+ for (Map.Entry e : options.entrySet()) {
+ if (i++ > 0) {
+ builder.append(", ");
+ }
+ builder.append(e.getKey()).append("=").append(e.getValue());
+ }
+ }
+ builder.append(";");
+
+ return builder.toString();
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
index 0068e0cf6..24a5ed6ab 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
@@ -16,7 +16,9 @@
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
import net.sf.jsqlparser.statement.analyze.Analyze;
import net.sf.jsqlparser.statement.comment.Comment;
+import net.sf.jsqlparser.statement.create.database.CreateDatabase;
import net.sf.jsqlparser.statement.create.index.CreateIndex;
+import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
import net.sf.jsqlparser.statement.create.synonym.CreateSynonym;
@@ -32,6 +34,7 @@
import net.sf.jsqlparser.statement.imprt.Import;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
+import net.sf.jsqlparser.statement.lock.LockStatement;
import net.sf.jsqlparser.statement.merge.Merge;
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
import net.sf.jsqlparser.statement.select.Select;
@@ -116,6 +119,12 @@ default void visit(CreateSchema createSchema) {
this.visit(createSchema, null);
}
+ T visit(CreateDatabase createDatabase, S context);
+
+ default void visit(CreateDatabase createDatabase) {
+ this.visit(createDatabase, null);
+ }
+
T visit(CreateTable createTable, S context);
default void visit(CreateTable createTable) {
@@ -343,4 +352,17 @@ default void visit(Import imprt) {
default void visit(Export export) {
this.visit(export, null);
}
+
+ T visit(LockStatement lock, S context);
+
+ default void visit(LockStatement lock) {
+ this.visit(lock, null);
+ }
+
+ T visit(CreatePolicy createPolicy, S context);
+
+ default void visit(CreatePolicy createPolicy) {
+ this.visit(createPolicy, null);
+ }
+
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
index 012abff28..38a32ea1f 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
@@ -20,7 +20,9 @@
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
import net.sf.jsqlparser.statement.analyze.Analyze;
import net.sf.jsqlparser.statement.comment.Comment;
+import net.sf.jsqlparser.statement.create.database.CreateDatabase;
import net.sf.jsqlparser.statement.create.index.CreateIndex;
+import net.sf.jsqlparser.statement.create.policy.CreatePolicy;
import net.sf.jsqlparser.statement.create.schema.CreateSchema;
import net.sf.jsqlparser.statement.create.sequence.CreateSequence;
import net.sf.jsqlparser.statement.create.synonym.CreateSynonym;
@@ -37,6 +39,7 @@
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.insert.InsertConflictAction;
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
+import net.sf.jsqlparser.statement.lock.LockStatement;
import net.sf.jsqlparser.statement.merge.Merge;
import net.sf.jsqlparser.statement.merge.MergeOperationVisitor;
import net.sf.jsqlparser.statement.merge.MergeOperationVisitorAdapter;
@@ -183,7 +186,6 @@ public T visit(Insert insert, S context) {
visitWithItems(insert.getWithItemsList(), context);
insert.getTable().accept(fromItemVisitor, context);
- fromItemVisitor.visitFromItem(insert.getTable(), context);
if (insert.getColumns() != null) {
for (Column column : insert.getColumns()) {
@@ -221,7 +223,7 @@ public T visit(Insert insert, S context) {
}
private T visitReturningClause(ReturningClause returningClause, S context) {
- if (returningClause!=null) {
+ if (returningClause != null) {
returningClause.forEach(selectItem -> selectItem.accept(selectItemVisitor, context));
// @todo: verify why this is a list of strings and not columns
}
@@ -260,6 +262,11 @@ public T visit(CreateSchema createSchema, S context) {
return null;
}
+ @Override
+ public T visit(CreateDatabase createDatabase, S context) {
+ return null;
+ }
+
@Override
public T visit(CreateTable createTable, S context) {
return createTable.getTable().accept(fromItemVisitor, context);
@@ -290,6 +297,18 @@ public T visit(Execute execute, S context) {
return null;
}
+ @Override
+ public T visit(LockStatement lock, S context) {
+
+ return null;
+ }
+
+ @Override
+ public T visit(CreatePolicy createPolicy, S context) {
+
+ return null;
+ }
+
@Override
public T visit(SetStatement set, S context) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java
index 372d9c790..dd4e07f52 100644
--- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java
@@ -19,7 +19,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
-
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.ReferentialAction;
import net.sf.jsqlparser.statement.ReferentialAction.Action;
@@ -53,12 +52,36 @@ public class AlterExpression implements Serializable {
private Index oldIndex = null;
private String constraintName;
private boolean usingIfExists;
+
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
private List fkColumns;
+
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
private String fkSourceSchema;
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
private String fkSourceTable;
+
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
private List fkSourceColumns;
private boolean uk;
+ private boolean ukTypeSpecified;
private boolean useEqual;
private List partitions;
@@ -135,10 +158,20 @@ public void hasColumns(boolean hasColumns) {
this.hasColumns = hasColumns;
}
+ /**
+ * @deprecated Use {@link #getIndex()} with
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public String getFkSourceSchema() {
return fkSourceSchema;
}
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public void setFkSourceSchema(String fkSourceSchema) {
this.fkSourceSchema = fkSourceSchema;
}
@@ -178,11 +211,19 @@ public void setOptionalSpecifier(String optionalSpecifier) {
/**
* @param type
* @param action
+ * @deprecated Standalone FK fields are deprecated. Use a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} via
+ * {@link #setIndex(Index)} instead.
*/
+ @Deprecated
public void setReferentialAction(Type type, Action action) {
setReferentialAction(type, action, true);
}
+ /**
+ * @deprecated Standalone FK fields are deprecated.
+ */
+ @Deprecated
public AlterExpression withReferentialAction(Type type, Action action) {
setReferentialAction(type, action);
return this;
@@ -190,7 +231,9 @@ public AlterExpression withReferentialAction(Type type, Action action) {
/**
* @param type
+ * @deprecated Standalone FK fields are deprecated.
*/
+ @Deprecated
public void removeReferentialAction(Type type) {
setReferentialAction(type, null, false);
}
@@ -198,7 +241,9 @@ public void removeReferentialAction(Type type) {
/**
* @param type
* @return
+ * @deprecated Standalone FK fields are deprecated.
*/
+ @Deprecated
public ReferentialAction getReferentialAction(Type type) {
return referentialActions.stream()
.filter(ra -> type.equals(ra.getType()))
@@ -279,18 +324,38 @@ public void setOnDeleteSetNull(boolean onDeleteSetNull) {
setReferentialAction(Type.DELETE, Action.SET_NULL, onDeleteSetNull);
}
+ /**
+ * @deprecated Use {@link #getIndex()} with
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public List getFkColumns() {
return fkColumns;
}
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public void setFkColumns(List fkColumns) {
this.fkColumns = fkColumns;
}
+ /**
+ * @deprecated Use {@link #getIndex()} with
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public String getFkSourceTable() {
return fkSourceTable;
}
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public void setFkSourceTable(String fkSourceTable) {
this.fkSourceTable = fkSourceTable;
}
@@ -350,10 +415,20 @@ public List getColumnSetVisibilityList() {
return columnSetVisibilityList;
}
+ /**
+ * @deprecated Use {@link #getIndex()} with
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public List getFkSourceColumns() {
return fkSourceColumns;
}
+ /**
+ * @deprecated Use {@link #setIndex(Index)} with a
+ * {@link net.sf.jsqlparser.statement.create.table.ForeignKeyIndex} instead.
+ */
+ @Deprecated
public void setFkSourceColumns(List fkSourceColumns) {
this.fkSourceColumns = fkSourceColumns;
}
@@ -533,6 +608,15 @@ public boolean getUk() {
public void setUk(boolean uk) {
this.uk = uk;
+ this.ukTypeSpecified = true;
+ }
+
+ public boolean isUkTypeSpecified() {
+ return ukTypeSpecified;
+ }
+
+ public void setUkTypeSpecified(boolean ukTypeSpecified) {
+ this.ukTypeSpecified = ukTypeSpecified;
}
public boolean isUseIfNotExists() {
@@ -645,19 +729,115 @@ public void setInvisible(boolean invisible) {
}
@Override
- @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity",
- "PMD.ExcessiveMethodLength", "PMD.SwitchStmtsShouldHaveDefault"})
- public String toString() {
-
+ public final String toString() {
StringBuilder b = new StringBuilder();
+ appendBody(b);
+ appendCommonTail(b);
+ return b.toString();
+ }
+ /**
+ * Appends the main body of this ALTER expression to the builder. Subclasses override this for
+ * type-specific rendering.
+ */
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity",
+ "PMD.ExcessiveMethodLength", "PMD.SwitchStmtsShouldHaveDefault"})
+ protected void appendBody(StringBuilder b) {
if (operation == AlterOperation.UNSPECIFIC) {
b.append(optionalSpecifier);
- } else if (operation == AlterOperation.ALTER && constraintType != null
- && constraintSymbol != null) {
- // This is for ALTER INDEX ... INVISIBLE
- b.append("ALTER ").append(constraintType).append(" ").append(constraintSymbol);
+ } else if (constraintType != null && constraintSymbol != null
+ && (operation == AlterOperation.ALTER || operation == AlterOperation.ADD)) {
+ toStringConstraintAlter(b);
+ } else if (operation == AlterOperation.ALTER
+ && (columnDropDefaultList != null && !columnDropDefaultList.isEmpty()
+ || columnSetDefaultList != null && !columnSetDefaultList.isEmpty()
+ || columnSetVisibilityList != null && !columnSetVisibilityList.isEmpty())) {
+ toStringAlterColumn(b);
+ } else if (isSimpleKeywordOperation()) {
+ toStringSimpleKeyword(b);
+ } else if (isRenameOperation()) {
+ toStringRename(b);
+ } else if (isDropSpecialOperation()) {
+ toStringDropSpecial(b);
+ } else if (operation == AlterOperation.CONVERT || operation == AlterOperation.COLLATE) {
+ toStringConvert(b);
+ } else if (isPartitionOperation()) {
+ toStringPartition(b);
+ } else {
+ toStringGeneral(b);
+ }
+ }
+
+ /**
+ * Appends the common tail (parameters, index comment) shared by all ALTER expressions.
+ */
+ protected void appendCommonTail(StringBuilder b) {
+ if (parameters != null && !parameters.isEmpty()) {
+ b.append(' ').append(PlainSelect.getStringList(parameters, false, false));
+ }
+ if (index != null && index.getCommentText() != null) {
+ b.append(" COMMENT ").append(index.getCommentText());
+ }
+ }
+
+ protected boolean isSimpleKeywordOperation() {
+ switch (operation) {
+ case SET_TABLE_OPTION:
+ case DISCARD_TABLESPACE:
+ case IMPORT_TABLESPACE:
+ case DISABLE_KEYS:
+ case ENABLE_KEYS:
+ case ENGINE:
+ case ALGORITHM:
+ case KEY_BLOCK_SIZE:
+ case LOCK:
+ return true;
+ default:
+ return false;
+ }
+ }
+ protected boolean isRenameOperation() {
+ return getOldIndex() != null || operation == AlterOperation.RENAME_TABLE;
+ }
+
+ protected boolean isDropSpecialOperation() {
+ switch (operation) {
+ case DROP_PRIMARY_KEY:
+ case DROP_UNIQUE:
+ case DROP_FOREIGN_KEY:
+ return true;
+ case DROP:
+ return columnName == null && pkColumns != null && !pkColumns.isEmpty();
+ default:
+ return false;
+ }
+ }
+
+ protected boolean isPartitionOperation() {
+ switch (operation) {
+ case DISCARD_PARTITION:
+ case IMPORT_PARTITION:
+ case TRUNCATE_PARTITION:
+ case COALESCE_PARTITION:
+ case REORGANIZE_PARTITION:
+ case EXCHANGE_PARTITION:
+ case ANALYZE_PARTITION:
+ case CHECK_PARTITION:
+ case OPTIMIZE_PARTITION:
+ case REBUILD_PARTITION:
+ case REPAIR_PARTITION:
+ case REMOVE_PARTITIONING:
+ case PARTITION_BY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected void toStringConstraintAlter(StringBuilder b) {
+ if (operation == AlterOperation.ALTER) {
+ b.append("ALTER ").append(constraintType).append(" ").append(constraintSymbol);
if (invisible) {
b.append(" INVISIBLE");
} else if (!isEnforced()) {
@@ -665,71 +845,80 @@ public String toString() {
} else if (enforced) {
b.append(" ENFORCED");
}
- } else if (operation == AlterOperation.ADD && constraintType != null
- && constraintSymbol != null) {
+ } else {
b.append("ADD CONSTRAINT ").append(constraintType).append(" ").append(constraintSymbol)
.append(" ");
-
if (index != null && index.getColumnsNames() != null) {
b.append(" ")
.append(PlainSelect.getStringList(index.getColumnsNames(), true, true));
}
- } else if (operation == AlterOperation.ALTER
- && columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) {
- b.append("ALTER ");
- if (hasColumn) {
- b.append("COLUMN ");
- }
+ }
+ }
+
+ protected void toStringAlterColumn(StringBuilder b) {
+ b.append("ALTER ");
+ if (hasColumn) {
+ b.append("COLUMN ");
+ }
+ if (columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) {
b.append(PlainSelect.getStringList(columnDropDefaultList));
- } else if (operation == AlterOperation.ALTER
- && columnSetDefaultList != null && !columnSetDefaultList.isEmpty()) {
- b.append("ALTER ");
- if (hasColumn) {
- b.append("COLUMN ");
- }
+ } else if (columnSetDefaultList != null && !columnSetDefaultList.isEmpty()) {
b.append(PlainSelect.getStringList(columnSetDefaultList));
- } else if (operation == AlterOperation.ALTER
- && columnSetVisibilityList != null && !columnSetVisibilityList.isEmpty()) {
- b.append("ALTER ");
- if (hasColumn) {
- b.append("COLUMN ");
- }
+ } else {
b.append(PlainSelect.getStringList(columnSetVisibilityList));
- } else if (operation == AlterOperation.SET_TABLE_OPTION) {
- b.append(tableOption);
- } else if (operation == AlterOperation.DISCARD_TABLESPACE) {
- b.append("DISCARD TABLESPACE");
- } else if (operation == AlterOperation.IMPORT_TABLESPACE) {
- b.append("IMPORT TABLESPACE");
- } else if (operation == AlterOperation.DISABLE_KEYS) {
- b.append("DISABLE KEYS");
- } else if (operation == AlterOperation.ENABLE_KEYS) {
- b.append("ENABLE KEYS");
- } else if (operation == AlterOperation.ENGINE) {
- b.append("ENGINE ");
- if (useEqual) {
- b.append("= ");
- }
- b.append(engineOption);
- } else if (operation == AlterOperation.ALGORITHM) {
- b.append("ALGORITHM ");
- if (useEqual) {
- b.append("= ");
- }
- b.append(algorithmOption);
- } else if (operation == AlterOperation.KEY_BLOCK_SIZE) {
- b.append("KEY_BLOCK_SIZE ");
- if (useEqual) {
- b.append("= ");
- }
- b.append(keyBlockSize);
- } else if (operation == AlterOperation.LOCK) {
- b.append("LOCK ");
- if (useEqual) {
- b.append("= ");
- }
- b.append(lockOption);
- } else if (getOldIndex() != null) {
+ }
+ }
+
+ protected void toStringSimpleKeyword(StringBuilder b) {
+ switch (operation) {
+ case SET_TABLE_OPTION:
+ b.append(tableOption);
+ break;
+ case DISCARD_TABLESPACE:
+ b.append("DISCARD TABLESPACE");
+ break;
+ case IMPORT_TABLESPACE:
+ b.append("IMPORT TABLESPACE");
+ break;
+ case DISABLE_KEYS:
+ b.append("DISABLE KEYS");
+ break;
+ case ENABLE_KEYS:
+ b.append("ENABLE KEYS");
+ break;
+ case ENGINE:
+ b.append("ENGINE ");
+ if (useEqual) {
+ b.append("= ");
+ }
+ b.append(engineOption);
+ break;
+ case ALGORITHM:
+ b.append("ALGORITHM ");
+ if (useEqual) {
+ b.append("= ");
+ }
+ b.append(algorithmOption);
+ break;
+ case KEY_BLOCK_SIZE:
+ b.append("KEY_BLOCK_SIZE ");
+ if (useEqual) {
+ b.append("= ");
+ }
+ b.append(keyBlockSize);
+ break;
+ case LOCK:
+ b.append("LOCK ");
+ if (useEqual) {
+ b.append("= ");
+ }
+ b.append(lockOption);
+ break;
+ }
+ }
+
+ protected void toStringRename(StringBuilder b) {
+ if (getOldIndex() != null) {
b.append("RENAME");
switch (operation) {
case RENAME_KEY:
@@ -743,13 +932,32 @@ public String toString() {
break;
}
b.append(getOldIndex().getName()).append(" TO ").append(getIndex().getName());
- } else if (operation == AlterOperation.RENAME_TABLE) {
-
+ } else {
b.append("RENAME TO ").append(newTableName);
- } else if (operation == AlterOperation.DROP_PRIMARY_KEY) {
+ }
+ }
- b.append("DROP PRIMARY KEY ");
- } else if (operation == AlterOperation.CONVERT) {
+ protected void toStringDropSpecial(StringBuilder b) {
+ switch (operation) {
+ case DROP_PRIMARY_KEY:
+ b.append("DROP PRIMARY KEY ");
+ break;
+ case DROP_UNIQUE:
+ b.append("DROP UNIQUE (").append(PlainSelect.getStringList(pkColumns)).append(')');
+ break;
+ case DROP_FOREIGN_KEY:
+ b.append("DROP FOREIGN KEY (").append(PlainSelect.getStringList(pkColumns))
+ .append(')');
+ break;
+ default:
+ // Oracle Multi Column Drop
+ b.append("DROP (").append(PlainSelect.getStringList(pkColumns)).append(')');
+ break;
+ }
+ }
+
+ protected void toStringConvert(StringBuilder b) {
+ if (operation == AlterOperation.CONVERT) {
if (convertType == ConvertType.CONVERT_TO) {
b.append("CONVERT TO CHARACTER SET ");
} else if (convertType == ConvertType.DEFAULT_CHARACTER_SET) {
@@ -763,11 +971,9 @@ public String toString() {
b.append("= ");
}
}
-
if (getCharacterSet() != null) {
b.append(getCharacterSet());
}
-
if (getCollation() != null) {
b.append(" COLLATE ");
if (hasEqualForCollate) {
@@ -775,7 +981,7 @@ public String toString() {
}
b.append(getCollation());
}
- } else if (operation == AlterOperation.COLLATE) {
+ } else {
if (isDefaultCollateSpecified()) {
b.append("DEFAULT ");
}
@@ -786,185 +992,202 @@ public String toString() {
if (getCollation() != null) {
b.append(getCollation());
}
- } else if (operation == AlterOperation.DROP_UNIQUE) {
-
- b.append("DROP UNIQUE (").append(PlainSelect.getStringList(pkColumns)).append(')');
- } else if (operation == AlterOperation.DROP_FOREIGN_KEY) {
-
- b.append("DROP FOREIGN KEY (").append(PlainSelect.getStringList(pkColumns)).append(')');
- } else if (operation == AlterOperation.DROP && columnName == null && pkColumns != null
- && !pkColumns.isEmpty()) {
- // Oracle Multi Column Drop
- b.append("DROP (").append(PlainSelect.getStringList(pkColumns)).append(')');
- } else if (operation == AlterOperation.DISCARD_PARTITION && partitions != null) {
- b.append("DISCARD PARTITION ").append(PlainSelect.getStringList(partitions));
- if (tableOption != null) {
- b.append(" ").append(tableOption);
- }
- } else if (operation == AlterOperation.IMPORT_PARTITION) {
- b.append("IMPORT PARTITION ").append(PlainSelect.getStringList(partitions));
- if (tableOption != null) {
- b.append(" ").append(tableOption);
+ }
+ }
+
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ protected void toStringPartition(StringBuilder b) {
+ switch (operation) {
+ case DISCARD_PARTITION:
+ b.append("DISCARD PARTITION ").append(PlainSelect.getStringList(partitions));
+ if (tableOption != null) {
+ b.append(" ").append(tableOption);
+ }
+ break;
+ case IMPORT_PARTITION:
+ b.append("IMPORT PARTITION ").append(PlainSelect.getStringList(partitions));
+ if (tableOption != null) {
+ b.append(" ").append(tableOption);
+ }
+ break;
+ case TRUNCATE_PARTITION:
+ b.append("TRUNCATE PARTITION ").append(PlainSelect.getStringList(partitions));
+ break;
+ case COALESCE_PARTITION:
+ b.append("COALESCE PARTITION ").append(coalescePartitionNumber);
+ break;
+ case REORGANIZE_PARTITION:
+ b.append("REORGANIZE PARTITION ")
+ .append(PlainSelect.getStringList(partitions))
+ .append(" INTO (")
+ .append(partitionDefinitions.stream()
+ .map(PartitionDefinition::toString)
+ .collect(Collectors.joining(", ")))
+ .append(")");
+ break;
+ case EXCHANGE_PARTITION:
+ b.append("EXCHANGE PARTITION ");
+ b.append(partitions.get(0)).append(" WITH TABLE ")
+ .append(exchangePartitionTableName);
+ if (exchangePartitionWithValidation) {
+ b.append(" WITH VALIDATION ");
+ } else if (exchangePartitionWithoutValidation) {
+ b.append(" WITHOUT VALIDATION ");
+ }
+ break;
+ case ANALYZE_PARTITION:
+ b.append("ANALYZE PARTITION ").append(PlainSelect.getStringList(partitions));
+ break;
+ case CHECK_PARTITION:
+ b.append("CHECK PARTITION ").append(PlainSelect.getStringList(partitions));
+ break;
+ case OPTIMIZE_PARTITION:
+ b.append("OPTIMIZE PARTITION ").append(PlainSelect.getStringList(partitions));
+ break;
+ case REBUILD_PARTITION:
+ b.append("REBUILD PARTITION ").append(PlainSelect.getStringList(partitions));
+ break;
+ case REPAIR_PARTITION:
+ b.append("REPAIR PARTITION ").append(PlainSelect.getStringList(partitions));
+ break;
+ case REMOVE_PARTITIONING:
+ b.append("REMOVE PARTITIONING");
+ break;
+ case PARTITION_BY:
+ b.append("PARTITION BY ").append(partitionType).append(" ");
+ if (partitionExpression != null) {
+ b.append("(").append(partitionExpression).append(") ");
+ } else if (partitionColumns != null && !partitionColumns.isEmpty()) {
+ b.append("COLUMNS(").append(String.join(", ", partitionColumns)).append(") ");
+ }
+ b.append("(").append(partitionDefinitions.stream()
+ .map(PartitionDefinition::toString)
+ .collect(Collectors.joining(", ")))
+ .append(")");
+ break;
+ }
+ }
+
+ /**
+ * Handles the general case for ADD, MODIFY, CHANGE, DROP (column), COMMENT, row-level security,
+ * and all field-based dispatch (columns, constraints, FK, UK, PK, index).
+ */
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity",
+ "PMD.ExcessiveMethodLength"})
+ protected void toStringGeneral(StringBuilder b) {
+ if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) {
+ b.append("COMMENT =").append(" ");
+ } else if (operation == AlterOperation.ENABLE_ROW_LEVEL_SECURITY) {
+ b.append("ENABLE ROW LEVEL SECURITY").append(" ");
+ } else if (operation == AlterOperation.DISABLE_ROW_LEVEL_SECURITY) {
+ b.append("DISABLE ROW LEVEL SECURITY").append(" ");
+ } else if (operation == AlterOperation.FORCE_ROW_LEVEL_SECURITY) {
+ b.append("FORCE ROW LEVEL SECURITY").append(" ");
+ } else if (operation == AlterOperation.NO_FORCE_ROW_LEVEL_SECURITY) {
+ b.append("NO FORCE ROW LEVEL SECURITY").append(" ");
+ } else {
+ b.append(operation).append(" ");
+ }
+ if (commentText != null) {
+ if (columnName != null) {
+ b.append(columnName).append(" COMMENT ");
}
- } else if (operation == AlterOperation.TRUNCATE_PARTITION
- && partitions != null) {
- b.append("TRUNCATE PARTITION ").append(PlainSelect.getStringList(partitions));
- } else if (operation == AlterOperation.COALESCE_PARTITION) {
- b.append("COALESCE PARTITION ").append(coalescePartitionNumber);
- } else if (operation == AlterOperation.REORGANIZE_PARTITION
- && partitions != null
- && partitionDefinitions != null) {
- b.append("REORGANIZE PARTITION ")
- .append(PlainSelect.getStringList(partitions))
- .append(" INTO (")
- .append(partitionDefinitions.stream()
- .map(PartitionDefinition::toString)
- .collect(Collectors.joining(", ")))
- .append(")");
- } else if (operation == AlterOperation.EXCHANGE_PARTITION) {
- b.append("EXCHANGE PARTITION ");
- b.append(partitions.get(0)).append(" WITH TABLE ").append(exchangePartitionTableName);
- if (exchangePartitionWithValidation) {
- b.append(" WITH VALIDATION ");
- } else if (exchangePartitionWithoutValidation) {
- b.append(" WITHOUT VALIDATION ");
+ b.append(commentText);
+ } else if (columnName != null) {
+ if (hasColumn) {
+ b.append("COLUMN ");
}
- } else if (operation == AlterOperation.ANALYZE_PARTITION && partitions != null) {
- b.append("ANALYZE PARTITION ").append(PlainSelect.getStringList(partitions));
- } else if (operation == AlterOperation.CHECK_PARTITION && partitions != null) {
- b.append("CHECK PARTITION ").append(PlainSelect.getStringList(partitions));
- } else if (operation == AlterOperation.OPTIMIZE_PARTITION && partitions != null) {
- b.append("OPTIMIZE PARTITION ").append(PlainSelect.getStringList(partitions));
- } else if (operation == AlterOperation.REBUILD_PARTITION && partitions != null) {
- b.append("REBUILD PARTITION ").append(PlainSelect.getStringList(partitions));
- } else if (operation == AlterOperation.REPAIR_PARTITION && partitions != null) {
- b.append("REPAIR PARTITION ").append(PlainSelect.getStringList(partitions));
- } else if (operation == AlterOperation.REMOVE_PARTITIONING) {
- b.append("REMOVE PARTITIONING");
- } else if (operation == AlterOperation.PARTITION_BY) {
- b.append("PARTITION BY ").append(partitionType).append(" ");
- if (partitionExpression != null) {
- b.append("(").append(partitionExpression).append(") ");
- } else if (partitionColumns != null && !partitionColumns.isEmpty()) {
- b.append("COLUMNS(").append(String.join(", ", partitionColumns)).append(") ");
+ if (usingIfExists) {
+ b.append("IF EXISTS ");
}
-
- b.append("(").append(partitionDefinitions.stream()
- .map(PartitionDefinition::toString)
- .collect(Collectors.joining(", ")))
- .append(")");
- } else {
- if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) {
- b.append("COMMENT =").append(" ");
- } else {
- b.append(operation).append(" ");
+ if (operation == AlterOperation.RENAME) {
+ b.append(columnOldName).append(" TO ");
}
- if (commentText != null) {
- if (columnName != null) {
- b.append(columnName).append(" COMMENT ");
+ b.append(columnName);
+ } else if (getColDataTypeList() != null) {
+ if (operation == AlterOperation.CHANGE) {
+ if (optionalSpecifier != null) {
+ b.append(optionalSpecifier).append(" ");
}
- b.append(commentText);
- } else if (columnName != null) {
+ b.append(columnOldName).append(" ");
+ } else if (colDataTypeList.size() > 1) {
+ b.append("(");
+ } else {
if (hasColumn) {
b.append("COLUMN ");
+ } else if (hasColumns) {
+ b.append("COLUMNS ");
}
if (usingIfExists) {
b.append("IF EXISTS ");
+ } else if (useIfNotExists) {
+ b.append("IF NOT EXISTS ");
}
- if (operation == AlterOperation.RENAME) {
- b.append(columnOldName).append(" TO ");
- }
- b.append(columnName);
- } else if (getColDataTypeList() != null) {
- if (operation == AlterOperation.CHANGE) {
- if (optionalSpecifier != null) {
- b.append(optionalSpecifier).append(" ");
- }
- b.append(columnOldName).append(" ");
- } else if (colDataTypeList.size() > 1) {
- b.append("(");
- } else {
- if (hasColumn) {
- b.append("COLUMN ");
- } else if (hasColumns) {
- b.append("COLUMNS ");
- }
- if (useIfNotExists
- && operation == AlterOperation.ADD) {
- b.append("IF NOT EXISTS ");
- }
- }
- if (useBrackets && colDataTypeList.size() == 1) {
- b.append(" ( ");
- }
- b.append(PlainSelect.getStringList(colDataTypeList));
- if (useBrackets && colDataTypeList.size() == 1) {
- b.append(" ) ");
- }
- if (colDataTypeList.size() > 1) {
- b.append(")");
- }
- } else if (getColumnDropNotNullList() != null) {
- b.append("COLUMN ");
- b.append(PlainSelect.getStringList(columnDropNotNullList));
- } else if (columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) {
- b.append("COLUMN ");
- b.append(PlainSelect.getStringList(columnDropDefaultList));
- } else if (constraintName != null) {
- b.append("CONSTRAINT ");
- if (usingIfExists) {
- b.append("IF EXISTS ");
- }
- b.append(constraintName);
- } else if (pkColumns != null) {
- b.append("PRIMARY KEY (").append(PlainSelect.getStringList(pkColumns)).append(')');
- } else if (ukColumns != null) {
- b.append("UNIQUE");
- if (ukName != null) {
+ }
+ if (useBrackets && colDataTypeList.size() == 1) {
+ b.append(" ( ");
+ }
+ b.append(PlainSelect.getStringList(colDataTypeList));
+ if (useBrackets && colDataTypeList.size() == 1) {
+ b.append(" ) ");
+ }
+ if (colDataTypeList.size() > 1) {
+ b.append(")");
+ }
+ } else if (getColumnDropNotNullList() != null) {
+ b.append("COLUMN ");
+ b.append(PlainSelect.getStringList(columnDropNotNullList));
+ } else if (columnDropDefaultList != null && !columnDropDefaultList.isEmpty()) {
+ b.append("COLUMN ");
+ b.append(PlainSelect.getStringList(columnDropDefaultList));
+ } else if (constraintName != null) {
+ b.append("CONSTRAINT ");
+ if (usingIfExists) {
+ b.append("IF EXISTS ");
+ }
+ b.append(constraintName);
+ } else if (pkColumns != null) {
+ b.append("PRIMARY KEY (").append(PlainSelect.getStringList(pkColumns)).append(')');
+ } else if (ukColumns != null) {
+ b.append("UNIQUE");
+ if (ukName != null) {
+ if (isUkTypeSpecified()) {
if (getUk()) {
b.append(" KEY ");
} else {
b.append(" INDEX ");
}
- b.append(ukName);
+ } else {
+ b.append(" ");
}
- b.append(" (").append(PlainSelect.getStringList(ukColumns)).append(")");
- } else if (fkColumns != null) {
- b.append("FOREIGN KEY (")
- .append(PlainSelect.getStringList(fkColumns))
- .append(") REFERENCES ")
- .append(
- fkSourceSchema != null && fkSourceSchema.trim().length() > 0
- ? fkSourceSchema + "."
- : "")
- .append(fkSourceTable)
- .append(" (")
- .append(PlainSelect.getStringList(fkSourceColumns))
- .append(")");
- referentialActions.forEach(b::append);
- } else if (index != null) {
- b.append(index);
- }
-
-
- if (getConstraints() != null && !getConstraints().isEmpty()) {
- b.append(' ').append(PlainSelect.getStringList(constraints, false, false));
- }
- if (getUseEqual()) {
- b.append('=');
+ b.append(ukName);
}
+ b.append(" (").append(PlainSelect.getStringList(ukColumns)).append(")");
+ } else if (fkColumns != null
+ && !(index instanceof net.sf.jsqlparser.statement.create.table.ForeignKeyIndex)) {
+ // @deprecated path - kept for backward compatibility when ForeignKeyIndex is not set
+ b.append("FOREIGN KEY (")
+ .append(PlainSelect.getStringList(fkColumns))
+ .append(") REFERENCES ")
+ .append(
+ fkSourceSchema != null && fkSourceSchema.trim().length() > 0
+ ? fkSourceSchema + "."
+ : "")
+ .append(fkSourceTable)
+ .append(" (")
+ .append(PlainSelect.getStringList(fkSourceColumns))
+ .append(")");
+ referentialActions.forEach(b::append);
+ } else if (index != null) {
+ b.append(index);
}
- if (parameters != null && !parameters.isEmpty()) {
- b.append(' ').append(PlainSelect.getStringList(parameters, false, false));
+ if (getConstraints() != null && !getConstraints().isEmpty()) {
+ b.append(' ').append(PlainSelect.getStringList(constraints, false, false));
}
-
- if (index != null && index.getCommentText() != null) {
- // `USING` is a parameters
- b.append(" COMMENT ").append(index.getCommentText());
+ if (getUseEqual()) {
+ b.append('=');
}
-
- return b.toString();
}
public AlterExpression withOperation(AlterOperation operation) {
@@ -1027,21 +1250,25 @@ public AlterExpression withOnDeleteCascade(boolean onDeleteCascade) {
return this;
}
+ @Deprecated
public AlterExpression withFkColumns(List fkColumns) {
this.setFkColumns(fkColumns);
return this;
}
+ @Deprecated
public AlterExpression withFkSourceSchema(String fkSourceSchema) {
this.setFkSourceTable(fkSourceSchema);
return this;
}
+ @Deprecated
public AlterExpression withFkSourceTable(String fkSourceTable) {
this.setFkSourceTable(fkSourceTable);
return this;
}
+ @Deprecated
public AlterExpression withFkSourceColumns(List fkSourceColumns) {
this.setFkSourceColumns(fkSourceColumns);
return this;
@@ -1096,18 +1323,21 @@ public AlterExpression addUkColumns(Collection ukColumns) {
return this.withUkColumns(collection);
}
+ @Deprecated
public AlterExpression addFkColumns(String... fkColumns) {
List collection = Optional.ofNullable(getFkColumns()).orElseGet(ArrayList::new);
Collections.addAll(collection, fkColumns);
return this.withFkColumns(collection);
}
+ @Deprecated
public AlterExpression addFkColumns(Collection fkColumns) {
List collection = Optional.ofNullable(getFkColumns()).orElseGet(ArrayList::new);
collection.addAll(fkColumns);
return this.withFkColumns(collection);
}
+ @Deprecated
public AlterExpression addFkSourceColumns(String... fkSourceColumns) {
List collection =
Optional.ofNullable(getFkSourceColumns()).orElseGet(ArrayList::new);
@@ -1115,6 +1345,7 @@ public AlterExpression addFkSourceColumns(String... fkSourceColumns) {
return this.withFkSourceColumns(collection);
}
+ @Deprecated
public AlterExpression addFkSourceColumns(Collection fkSourceColumns) {
List collection =
Optional.ofNullable(getFkSourceColumns()).orElseGet(ArrayList::new);
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionCharset.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionCharset.java
new file mode 100644
index 000000000..b78d5892e
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionCharset.java
@@ -0,0 +1,22 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.alter;
+
+/**
+ * Internal subclass for character set and collation operations within ALTER TABLE. Handles CONVERT
+ * TO CHARACTER SET, DEFAULT CHARACTER SET, CHARACTER SET, and COLLATE.
+ */
+public class AlterExpressionCharset extends AlterExpression {
+
+ @Override
+ protected void appendBody(StringBuilder b) {
+ toStringConvert(b);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionDrop.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionDrop.java
new file mode 100644
index 000000000..37e3b8587
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionDrop.java
@@ -0,0 +1,67 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.alter;
+
+import net.sf.jsqlparser.statement.select.PlainSelect;
+
+/**
+ * Internal subclass for DROP operations within ALTER TABLE. Handles DROP column, DROP CONSTRAINT,
+ * DROP INDEX/KEY, DROP PRIMARY KEY, DROP UNIQUE, DROP FOREIGN KEY, and DROP PARTITION.
+ */
+public class AlterExpressionDrop extends AlterExpression {
+
+ @Override
+ protected void appendBody(StringBuilder b) {
+ switch (getOperation()) {
+ case DROP_PRIMARY_KEY:
+ b.append("DROP PRIMARY KEY ");
+ break;
+ case DROP_UNIQUE:
+ b.append("DROP UNIQUE (")
+ .append(PlainSelect.getStringList(getPkColumns())).append(')');
+ break;
+ case DROP_FOREIGN_KEY:
+ b.append("DROP FOREIGN KEY (")
+ .append(PlainSelect.getStringList(getPkColumns())).append(')');
+ break;
+ case DROP_PARTITION:
+ b.append("DROP PARTITION ")
+ .append(PlainSelect.getStringList(getPartitions()));
+ break;
+ default:
+ toStringDropDefault(b);
+ break;
+ }
+ }
+
+ private void toStringDropDefault(StringBuilder b) {
+ b.append("DROP ");
+ if (getColumnName() == null && getPkColumns() != null && !getPkColumns().isEmpty()) {
+ // Oracle Multi Column Drop
+ b.append("(").append(PlainSelect.getStringList(getPkColumns())).append(')');
+ } else if (getConstraintName() != null) {
+ b.append("CONSTRAINT ");
+ if (isUsingIfExists()) {
+ b.append("IF EXISTS ");
+ }
+ b.append(getConstraintName());
+ } else if (getColumnName() != null) {
+ if (hasColumn()) {
+ b.append("COLUMN ");
+ }
+ if (isUsingIfExists()) {
+ b.append("IF EXISTS ");
+ }
+ b.append(getColumnName());
+ } else if (getIndex() != null) {
+ b.append(getIndex());
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java
new file mode 100644
index 000000000..dd9e37b6f
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java
@@ -0,0 +1,23 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.alter;
+
+/**
+ * Internal subclass for partition maintenance operations within ALTER TABLE. Handles TRUNCATE,
+ * COALESCE, REORGANIZE, EXCHANGE, ANALYZE, CHECK, OPTIMIZE, REBUILD, REPAIR PARTITION, PARTITION
+ * BY, and REMOVE PARTITIONING.
+ */
+public class AlterExpressionPartition extends AlterExpression {
+
+ @Override
+ protected void appendBody(StringBuilder b) {
+ toStringPartition(b);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java
new file mode 100644
index 000000000..703e20047
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionRename.java
@@ -0,0 +1,38 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.alter;
+
+/**
+ * Internal subclass for RENAME operations within ALTER TABLE. Handles RENAME COLUMN, RENAME TO
+ * (table), RENAME INDEX/KEY/CONSTRAINT.
+ */
+public class AlterExpressionRename extends AlterExpression {
+
+ @Override
+ protected void appendBody(StringBuilder b) {
+ switch (getOperation()) {
+ case RENAME:
+ b.append("RENAME ");
+ if (hasColumn()) {
+ b.append("COLUMN ");
+ }
+ b.append(getColumnOldName()).append(" TO ").append(getColumnName());
+ break;
+ case RENAME_TABLE:
+ b.append("RENAME TO ").append(getNewTableName());
+ break;
+ case RENAME_INDEX:
+ case RENAME_KEY:
+ case RENAME_CONSTRAINT:
+ toStringRename(b);
+ break;
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionTableOption.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionTableOption.java
new file mode 100644
index 000000000..06a1af136
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionTableOption.java
@@ -0,0 +1,35 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.alter;
+
+/**
+ * Internal subclass for table-level option operations within ALTER TABLE. Handles ENGINE,
+ * ALGORITHM, LOCK, KEY_BLOCK_SIZE, COMMENT, ENCRYPTION, AUTO_INCREMENT (SET_TABLE_OPTION),
+ * DISCARD/IMPORT TABLESPACE, DISABLE/ENABLE KEYS.
+ */
+public class AlterExpressionTableOption extends AlterExpression {
+
+ @Override
+ protected void appendBody(StringBuilder b) {
+ switch (getOperation()) {
+ case COMMENT:
+ b.append("COMMENT ");
+ b.append(getCommentText());
+ break;
+ case COMMENT_WITH_EQUAL_SIGN:
+ b.append("COMMENT = ");
+ b.append(getCommentText());
+ break;
+ default:
+ toStringSimpleKeyword(b);
+ break;
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java
index 839685b1a..a33a98536 100644
--- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java
@@ -9,10 +9,12 @@
*/
package net.sf.jsqlparser.statement.alter;
+import java.util.Locale;
+
public enum AlterOperation {
- ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS;
+ ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY;
public static AlterOperation from(String operation) {
- return Enum.valueOf(AlterOperation.class, operation.toUpperCase());
+ return Enum.valueOf(AlterOperation.class, operation.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterSessionOperation.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterSessionOperation.java
index 1b8e4ec2a..e22742b5c 100644
--- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterSessionOperation.java
+++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterSessionOperation.java
@@ -10,6 +10,8 @@
package net.sf.jsqlparser.statement.alter;
+import java.util.Locale;
+
/**
* @author are
*/
@@ -17,6 +19,6 @@ public enum AlterSessionOperation {
ADVISE_COMMIT, ADVISE_ROLLBACK, ADVISE_NOTHING, CLOSE_DATABASE_LINK, ENABLE_COMMIT_IN_PROCEDURE, DISABLE_COMMIT_IN_PROCEDURE, ENABLE_GUARD, DISABLE_GUARD, ENABLE_PARALLEL_DML, DISABLE_PARALLEL_DML, FORCE_PARALLEL_DML, ENABLE_PARALLEL_DDL, DISABLE_PARALLEL_DDL, FORCE_PARALLEL_DDL, ENABLE_PARALLEL_QUERY, DISABLE_PARALLEL_QUERY, FORCE_PARALLEL_QUERY, ENABLE_RESUMABLE, DISABLE_RESUMABLE, SET;
public static AlterSessionOperation from(String operation) {
- return Enum.valueOf(AlterSessionOperation.class, operation.toUpperCase());
+ return Enum.valueOf(AlterSessionOperation.class, operation.toUpperCase(Locale.ROOT));
}
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/database/CreateDatabase.java b/src/main/java/net/sf/jsqlparser/statement/create/database/CreateDatabase.java
new file mode 100644
index 000000000..9444d6bad
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/create/database/CreateDatabase.java
@@ -0,0 +1,113 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2026 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.create.database;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+
+/**
+ * A {@code CREATE DATABASE} statement. Modelled after MySQL, where the statement takes an optional
+ * {@code IF NOT EXISTS} and vendor-specific options, which are captured verbatim.
+ */
+public class CreateDatabase implements Statement {
+
+ private String databaseName;
+ private boolean hasIfNotExists = false;
+ private List databaseOptions = null;
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+
+ /**
+ * The name of the database.
+ *
+ * @return the database name
+ */
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ public CreateDatabase setDatabaseName(String databaseName) {
+ this.databaseName = databaseName;
+ return this;
+ }
+
+ public boolean hasIfNotExists() {
+ return hasIfNotExists;
+ }
+
+ public CreateDatabase setIfNotExists(boolean hasIfNotExists) {
+ this.hasIfNotExists = hasIfNotExists;
+ return this;
+ }
+
+ /**
+ * The vendor-specific options of the database, captured as raw tokens.
+ *
+ * @return the list of option tokens, {@code null} when no options were given
+ */
+ public List getDatabaseOptions() {
+ return databaseOptions;
+ }
+
+ public CreateDatabase setDatabaseOptions(List databaseOptions) {
+ this.databaseOptions = databaseOptions;
+ return this;
+ }
+
+ public CreateDatabase withDatabaseName(String databaseName) {
+ return this.setDatabaseName(databaseName);
+ }
+
+ public CreateDatabase withIfNotExists(boolean hasIfNotExists) {
+ return this.setIfNotExists(hasIfNotExists);
+ }
+
+ public CreateDatabase withDatabaseOptions(List databaseOptions) {
+ return this.setDatabaseOptions(databaseOptions);
+ }
+
+ public CreateDatabase addDatabaseOptions(String... databaseOptions) {
+ List collection =
+ Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new);
+ Collections.addAll(collection, databaseOptions);
+ return this.withDatabaseOptions(collection);
+ }
+
+ public CreateDatabase addDatabaseOptions(Collection databaseOptions) {
+ List collection =
+ Optional.ofNullable(getDatabaseOptions()).orElseGet(ArrayList::new);
+ collection.addAll(databaseOptions);
+ return this.withDatabaseOptions(collection);
+ }
+
+ @Override
+ public String toString() {
+ String sql = "CREATE DATABASE";
+ if (hasIfNotExists) {
+ sql += " IF NOT EXISTS";
+ }
+ if (databaseName != null) {
+ sql += " " + databaseName;
+ }
+ if (databaseOptions != null && !databaseOptions.isEmpty()) {
+ sql += " " + String.join(" ", databaseOptions);
+ }
+ return sql;
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java b/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java
index 87e8f1ee9..0da992dd6 100644
--- a/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java
+++ b/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java
@@ -12,7 +12,6 @@
import static java.util.stream.Collectors.joining;
import java.util.*;
-
import net.sf.jsqlparser.schema.*;
import net.sf.jsqlparser.statement.*;
import net.sf.jsqlparser.statement.create.table.*;
@@ -106,9 +105,7 @@ public String toString() {
buffer.append(
index.getColumns().stream()
- .map(cp -> cp.columnName + (cp.getParams() != null
- ? " " + String.join(" ", cp.getParams())
- : ""))
+ .map(Index.ColumnParams::toString)
.collect(joining(", ")));
buffer.append(")");
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/policy/CreatePolicy.java b/src/main/java/net/sf/jsqlparser/statement/create/policy/CreatePolicy.java
new file mode 100644
index 000000000..7c11636aa
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/create/policy/CreatePolicy.java
@@ -0,0 +1,131 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2025 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.create.policy;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * PostgreSQL CREATE POLICY statement for Row Level Security (RLS).
+ *
+ * Syntax: CREATE POLICY name ON table_name [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ] [ TO
+ * { role_name | PUBLIC | CURRENT_USER | SESSION_USER } [, ...] ] [ USING ( using_expression ) ] [
+ * WITH CHECK ( check_expression ) ]
+ */
+public class CreatePolicy implements Statement {
+
+ private String policyName;
+ private Table table;
+ private String command; // ALL, SELECT, INSERT, UPDATE, DELETE
+ private List roles = new ArrayList<>();
+ private Expression usingExpression;
+ private Expression withCheckExpression;
+
+ public String getPolicyName() {
+ return policyName;
+ }
+
+ public CreatePolicy setPolicyName(String policyName) {
+ this.policyName = policyName;
+ return this;
+ }
+
+ public Table getTable() {
+ return table;
+ }
+
+ public CreatePolicy setTable(Table table) {
+ this.table = table;
+ return this;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public CreatePolicy setCommand(String command) {
+ this.command = command;
+ return this;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public CreatePolicy setRoles(List roles) {
+ this.roles = roles;
+ return this;
+ }
+
+ public CreatePolicy addRole(String role) {
+ this.roles.add(role);
+ return this;
+ }
+
+ public Expression getUsingExpression() {
+ return usingExpression;
+ }
+
+ public CreatePolicy setUsingExpression(Expression usingExpression) {
+ this.usingExpression = usingExpression;
+ return this;
+ }
+
+ public Expression getWithCheckExpression() {
+ return withCheckExpression;
+ }
+
+ public CreatePolicy setWithCheckExpression(Expression withCheckExpression) {
+ this.withCheckExpression = withCheckExpression;
+ return this;
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("CREATE POLICY ");
+ builder.append(policyName);
+ builder.append(" ON ");
+ builder.append(table.toString());
+
+ if (command != null) {
+ builder.append(" FOR ").append(command);
+ }
+
+ if (roles != null && !roles.isEmpty()) {
+ builder.append(" TO ");
+ for (int i = 0; i < roles.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(roles.get(i));
+ }
+ }
+
+ if (usingExpression != null) {
+ builder.append(" USING (").append(usingExpression.toString()).append(")");
+ }
+
+ if (withCheckExpression != null) {
+ builder.append(" WITH CHECK (").append(withCheckExpression.toString()).append(")");
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java b/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java
index ea4cdc64d..e972c9c30 100644
--- a/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java
+++ b/src/main/java/net/sf/jsqlparser/statement/create/schema/CreateSchema.java
@@ -21,6 +21,7 @@
public class CreateSchema implements Statement {
private String authorization;
+ private String catalogName = null;
private String schemaName;
private List schemaPath;
private List statements = new ArrayList<>();
@@ -59,6 +60,15 @@ public void setAuthorization(String authorization) {
this.authorization = authorization;
}
+ public String getCatalogName() {
+ return catalogName;
+ }
+
+ public CreateSchema setCatalogName(String catalogName) {
+ this.catalogName = catalogName;
+ return this;
+ }
+
/**
* The name of the schema
*
@@ -119,7 +129,12 @@ public String toString() {
sql += " IF NOT EXISTS";
}
if (schemaName != null) {
- sql += " " + schemaName;
+ sql += " ";
+
+ if (catalogName!=null) {
+ sql += catalogName + ".";
+ }
+ sql += schemaName;
}
if (authorization != null) {
sql += " AUTHORIZATION " + authorization;
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java b/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java
index b803a1f7b..254fb3d01 100644
--- a/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java
+++ b/src/main/java/net/sf/jsqlparser/statement/create/table/CheckConstraint.java
@@ -39,7 +39,12 @@ public void setExpression(Expression expression) {
@Override
public String toString() {
- return "CONSTRAINT " + getName() + " CHECK (" + expression + ")";
+ StringBuilder b = new StringBuilder();
+ if (getName() != null) {
+ b.append("CONSTRAINT ").append(getName()).append(" ");
+ }
+ b.append("CHECK (").append(expression).append(")");
+ return b.toString();
}
public CheckConstraint withTable(Table table) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java
index b7b7a9ae9..a3ddcdb9f 100644
--- a/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java
+++ b/src/main/java/net/sf/jsqlparser/statement/create/table/ColDataType.java
@@ -9,6 +9,7 @@
*/
package net.sf.jsqlparser.statement.create.table;
+import net.sf.jsqlparser.expression.IntervalQualifier;
import net.sf.jsqlparser.statement.select.PlainSelect;
import java.io.Serializable;
@@ -26,6 +27,7 @@ public class ColDataType implements Serializable {
private String dataType;
private List argumentsStringList;
private String characterSet;
+ private IntervalQualifier intervalQualifier;
private List arrayData = new ArrayList