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 4d9201e5e..92563d7d8 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
@@ -119,7 +119,7 @@ public LikeExpression withRightExpression(Expression arg0) {
}
public enum KeyWord {
- LIKE, ILIKE, RLIKE, REGEXP_LIKE, REGEXP, SIMILAR_TO, MATCH_ANY, MATCH_ALL, MATCH_PHRASE, MATCH_PHRASE_PREFIX, MATCH_REGEXP;
+ LIKE, ILIKE, RLIKE, REGEXP_LIKE, REGEXP, GLOB, SIMILAR_TO, MATCH_ANY, MATCH_ALL, MATCH_PHRASE, MATCH_PHRASE_PREFIX, MATCH_REGEXP;
public static KeyWord from(String keyword) {
return Enum.valueOf(KeyWord.class,
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 21ae94db5..0653d81fa 100644
--- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
+++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
@@ -182,6 +182,10 @@ public enum Feature {
* "SEMI" join
*/
joinSemi,
+ /**
+ * @see net.sf.jsqlparser.statement.select.Join#isAnti()
+ */
+ joinAnti,
/**
* "INNER" join
*/
diff --git a/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java b/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java
new file mode 100644
index 000000000..7ad71dd54
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/AttachStatement.java
@@ -0,0 +1,121 @@
+/*-
+ * #%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.statement.select.PlainSelect;
+
+import java.util.List;
+
+/**
+ * DuckDB's {@code ATTACH [DATABASE] [IF NOT EXISTS] path [AS alias] [(options)]}, which adds
+ * another database file or remote database to the session.
+ *
+ * @see ATTACH
+ */
+public class AttachStatement implements Statement {
+ private String databasePath;
+ private String alias;
+ private boolean usingDatabaseKeyword;
+ private boolean ifNotExists;
+ private List options;
+
+ public String getDatabasePath() {
+ return databasePath;
+ }
+
+ public void setDatabasePath(String databasePath) {
+ this.databasePath = databasePath;
+ }
+
+ public AttachStatement withDatabasePath(String databasePath) {
+ setDatabasePath(databasePath);
+ return this;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public AttachStatement withAlias(String alias) {
+ setAlias(alias);
+ return this;
+ }
+
+ public boolean isUsingDatabaseKeyword() {
+ return usingDatabaseKeyword;
+ }
+
+ public void setUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
+ this.usingDatabaseKeyword = usingDatabaseKeyword;
+ }
+
+ public AttachStatement withUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
+ setUsingDatabaseKeyword(usingDatabaseKeyword);
+ return this;
+ }
+
+ public boolean isIfNotExists() {
+ return ifNotExists;
+ }
+
+ public void setIfNotExists(boolean ifNotExists) {
+ this.ifNotExists = ifNotExists;
+ }
+
+ public AttachStatement withIfNotExists(boolean ifNotExists) {
+ setIfNotExists(ifNotExists);
+ return this;
+ }
+
+ public List getOptions() {
+ return options;
+ }
+
+ public void setOptions(List options) {
+ this.options = options;
+ }
+
+ public AttachStatement withOptions(List options) {
+ setOptions(options);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("ATTACH ");
+ if (usingDatabaseKeyword) {
+ builder.append("DATABASE ");
+ }
+ if (ifNotExists) {
+ builder.append("IF NOT EXISTS ");
+ }
+ builder.append(databasePath);
+ if (alias != null) {
+ builder.append(" AS ").append(alias);
+ }
+ if (options != null && !options.isEmpty()) {
+ builder.append(" ").append(PlainSelect.getStringList(options, true, true));
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java b/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java
new file mode 100644
index 000000000..a5ae350c6
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/ConnectStatement.java
@@ -0,0 +1,69 @@
+/*-
+ * #%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;
+
+/**
+ * DuckDB's {@code CONNECT target [AS alias]}, which routes the session's queries to a remote
+ * database.
+ */
+public class ConnectStatement implements Statement {
+ private String target;
+ private String alias;
+
+ public ConnectStatement() {}
+
+ public ConnectStatement(String target) {
+ this.target = target;
+ }
+
+ public String getTarget() {
+ return target;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ public ConnectStatement withTarget(String target) {
+ setTarget(target);
+ return this;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public ConnectStatement withAlias(String alias) {
+ setAlias(alias);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("CONNECT ").append(target);
+ if (alias != null) {
+ builder.append(" AS ").append(alias);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java b/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java
new file mode 100644
index 000000000..0dbc2892b
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/CopyStatement.java
@@ -0,0 +1,139 @@
+/*-
+ * #%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.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.Select;
+
+import java.util.List;
+
+/**
+ * {@code COPY table [(columns)] FROM path [(options)]} and
+ * {@code COPY {table | (query)} TO path [(options)]}, DuckDB's bulk import and export statement.
+ *
+ * @see COPY
+ */
+public class CopyStatement implements Statement {
+ private Table table;
+ private ExpressionList columns;
+ private Select select;
+ private boolean from;
+ private String path;
+ private List options;
+
+ public Table getTable() {
+ return table;
+ }
+
+ public void setTable(Table table) {
+ this.table = table;
+ }
+
+ public CopyStatement withTable(Table table) {
+ setTable(table);
+ return this;
+ }
+
+ public ExpressionList getColumns() {
+ return columns;
+ }
+
+ public void setColumns(ExpressionList columns) {
+ this.columns = columns;
+ }
+
+ public CopyStatement withColumns(ExpressionList columns) {
+ setColumns(columns);
+ return this;
+ }
+
+ public Select getSelect() {
+ return select;
+ }
+
+ public void setSelect(Select select) {
+ this.select = select;
+ }
+
+ public CopyStatement withSelect(Select select) {
+ setSelect(select);
+ return this;
+ }
+
+ /** {@code true} for COPY ... FROM (import), {@code false} for COPY ... TO (export). */
+ public boolean isFrom() {
+ return from;
+ }
+
+ public void setFrom(boolean from) {
+ this.from = from;
+ }
+
+ public CopyStatement withFrom(boolean from) {
+ setFrom(from);
+ return this;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public CopyStatement withPath(String path) {
+ setPath(path);
+ return this;
+ }
+
+ public List getOptions() {
+ return options;
+ }
+
+ public void setOptions(List options) {
+ this.options = options;
+ }
+
+ public CopyStatement withOptions(List options) {
+ setOptions(options);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("COPY ");
+ if (select != null) {
+ builder.append("(").append(select).append(")");
+ } else {
+ builder.append(table);
+ if (columns != null && !columns.isEmpty()) {
+ builder.append(" (").append(columns).append(")");
+ }
+ }
+ builder.append(from ? " FROM " : " TO ").append(path);
+ if (options != null && !options.isEmpty()) {
+ builder.append(" ").append(PlainSelect.getStringList(options, true, true));
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java b/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java
new file mode 100644
index 000000000..25d375e38
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/DeallocateStatement.java
@@ -0,0 +1,69 @@
+/*-
+ * #%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;
+
+/**
+ * {@code DEALLOCATE [PREPARE] name}, which drops a statement prepared by {@link PrepareStatement}.
+ */
+public class DeallocateStatement implements Statement {
+ private String name;
+ private boolean usingPrepareKeyword;
+
+ public DeallocateStatement() {}
+
+ public DeallocateStatement(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public DeallocateStatement withName(String name) {
+ setName(name);
+ return this;
+ }
+
+ public boolean isUsingPrepareKeyword() {
+ return usingPrepareKeyword;
+ }
+
+ public void setUsingPrepareKeyword(boolean usingPrepareKeyword) {
+ this.usingPrepareKeyword = usingPrepareKeyword;
+ }
+
+ public DeallocateStatement withUsingPrepareKeyword(boolean usingPrepareKeyword) {
+ setUsingPrepareKeyword(usingPrepareKeyword);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("DEALLOCATE ");
+ if (usingPrepareKeyword) {
+ builder.append("PREPARE ");
+ }
+ builder.append(name);
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java b/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java
index 10a92764c..054b64843 100644
--- a/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java
+++ b/src/main/java/net/sf/jsqlparser/statement/DescribeStatement.java
@@ -10,10 +10,12 @@
package net.sf.jsqlparser.statement;
import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.select.Select;
public class DescribeStatement implements Statement {
private Table table;
+ private Select select;
private String describeType;
public DescribeStatement() {
@@ -32,9 +34,24 @@ public void setTable(Table table) {
this.table = table;
}
+ /** DuckDB also describes a query: {@code DESCRIBE SELECT ...}. */
+ public Select getSelect() {
+ return select;
+ }
+
+ public void setSelect(Select select) {
+ this.select = select;
+ }
+
+ public DescribeStatement withSelect(Select select) {
+ setSelect(select);
+ return this;
+ }
+
@Override
public String toString() {
- return this.describeType + " " + table.getFullyQualifiedName();
+ return this.describeType + " "
+ + (select != null ? select.toString() : table.getFullyQualifiedName());
}
@Override
diff --git a/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java b/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java
new file mode 100644
index 000000000..80fe5780a
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/DetachStatement.java
@@ -0,0 +1,88 @@
+/*-
+ * #%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;
+
+/**
+ * DuckDB's {@code DETACH [DATABASE] [IF EXISTS] name}, the counterpart of {@link AttachStatement}.
+ *
+ * @see DETACH
+ */
+public class DetachStatement implements Statement {
+ private String databaseName;
+ private boolean usingDatabaseKeyword;
+ private boolean ifExists;
+
+ public DetachStatement() {}
+
+ public DetachStatement(String databaseName) {
+ this.databaseName = databaseName;
+ }
+
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ public void setDatabaseName(String databaseName) {
+ this.databaseName = databaseName;
+ }
+
+ public DetachStatement withDatabaseName(String databaseName) {
+ setDatabaseName(databaseName);
+ return this;
+ }
+
+ public boolean isUsingDatabaseKeyword() {
+ return usingDatabaseKeyword;
+ }
+
+ public void setUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
+ this.usingDatabaseKeyword = usingDatabaseKeyword;
+ }
+
+ public DetachStatement withUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
+ setUsingDatabaseKeyword(usingDatabaseKeyword);
+ return this;
+ }
+
+ public boolean isIfExists() {
+ return ifExists;
+ }
+
+ public void setIfExists(boolean ifExists) {
+ this.ifExists = ifExists;
+ }
+
+ public DetachStatement withIfExists(boolean ifExists) {
+ setIfExists(ifExists);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("DETACH ");
+ if (usingDatabaseKeyword) {
+ builder.append("DATABASE ");
+ }
+ if (ifExists) {
+ builder.append("IF EXISTS ");
+ }
+ builder.append(databaseName);
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java b/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java
new file mode 100644
index 000000000..fdbe6f4e7
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/DisconnectStatement.java
@@ -0,0 +1,54 @@
+/*-
+ * #%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;
+
+/**
+ * DuckDB's {@code DISCONNECT [name]}, the counterpart of {@link ConnectStatement}.
+ */
+public class DisconnectStatement implements Statement {
+ private String name;
+
+ public DisconnectStatement() {}
+
+ public DisconnectStatement(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public DisconnectStatement withName(String name) {
+ setName(name);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("DISCONNECT");
+ if (name != null) {
+ builder.append(" ").append(name);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/ExtensionStatement.java b/src/main/java/net/sf/jsqlparser/statement/ExtensionStatement.java
new file mode 100644
index 000000000..621927366
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/ExtensionStatement.java
@@ -0,0 +1,107 @@
+/*-
+ * #%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;
+
+/**
+ * DuckDB's extension management statements {@code [FORCE] INSTALL extension [FROM repository]} and
+ * {@code LOAD extension}.
+ *
+ * @see Extensions
+ */
+public class ExtensionStatement implements Statement {
+ private Operation operation;
+ private boolean force;
+ private String extensionName;
+ private String repository;
+
+ public ExtensionStatement() {}
+
+ public ExtensionStatement(Operation operation, String extensionName) {
+ this.operation = operation;
+ this.extensionName = extensionName;
+ }
+
+ public Operation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(Operation operation) {
+ this.operation = operation;
+ }
+
+ public ExtensionStatement withOperation(Operation operation) {
+ setOperation(operation);
+ return this;
+ }
+
+ public boolean isForce() {
+ return force;
+ }
+
+ public void setForce(boolean force) {
+ this.force = force;
+ }
+
+ public ExtensionStatement withForce(boolean force) {
+ setForce(force);
+ return this;
+ }
+
+ public String getExtensionName() {
+ return extensionName;
+ }
+
+ public void setExtensionName(String extensionName) {
+ this.extensionName = extensionName;
+ }
+
+ public ExtensionStatement withExtensionName(String extensionName) {
+ setExtensionName(extensionName);
+ return this;
+ }
+
+ public String getRepository() {
+ return repository;
+ }
+
+ public void setRepository(String repository) {
+ this.repository = repository;
+ }
+
+ public ExtensionStatement withRepository(String repository) {
+ setRepository(repository);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ if (force) {
+ builder.append("FORCE ");
+ }
+ builder.append(operation).append(" ").append(extensionName);
+ if (repository != null) {
+ builder.append(" FROM ").append(repository);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+
+ public enum Operation {
+ INSTALL, LOAD
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/PragmaStatement.java b/src/main/java/net/sf/jsqlparser/statement/PragmaStatement.java
new file mode 100644
index 000000000..c1692e6df
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/PragmaStatement.java
@@ -0,0 +1,91 @@
+/*-
+ * #%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.expression.Expression;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+
+/**
+ * {@code PRAGMA name}, {@code PRAGMA name(arguments)} and {@code PRAGMA name = value}, used by
+ * DuckDB and SQLite to read or set database settings.
+ *
+ * @see PRAGMA statements
+ */
+public class PragmaStatement implements Statement {
+ private String name;
+ private ExpressionList> parameters;
+ private Expression value;
+
+ public PragmaStatement() {}
+
+ public PragmaStatement(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public PragmaStatement withName(String name) {
+ setName(name);
+ return this;
+ }
+
+ public ExpressionList> getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(ExpressionList> parameters) {
+ this.parameters = parameters;
+ }
+
+ public PragmaStatement withParameters(ExpressionList> parameters) {
+ setParameters(parameters);
+ return this;
+ }
+
+ public Expression getValue() {
+ return value;
+ }
+
+ public void setValue(Expression value) {
+ this.value = value;
+ }
+
+ public PragmaStatement withValue(Expression value) {
+ setValue(value);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("PRAGMA ").append(name);
+ if (parameters != null) {
+ builder.append("(").append(parameters).append(")");
+ }
+ if (value != null) {
+ builder.append(" = ").append(value);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/PrepareStatement.java b/src/main/java/net/sf/jsqlparser/statement/PrepareStatement.java
new file mode 100644
index 000000000..e4675b340
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/PrepareStatement.java
@@ -0,0 +1,70 @@
+/*-
+ * #%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;
+
+/**
+ * {@code PREPARE name AS statement}, which stores a parameterised statement for later
+ * {@code EXECUTE}.
+ *
+ * @see Prepared
+ * statements
+ */
+public class PrepareStatement implements Statement {
+ private String name;
+ private Statement statement;
+
+ public PrepareStatement() {}
+
+ public PrepareStatement(String name, Statement statement) {
+ this.name = name;
+ this.statement = statement;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public PrepareStatement withName(String name) {
+ setName(name);
+ return this;
+ }
+
+ public Statement getStatement() {
+ return statement;
+ }
+
+ public void setStatement(Statement statement) {
+ this.statement = statement;
+ }
+
+ public PrepareStatement withStatement(Statement statement) {
+ setStatement(statement);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("PREPARE ").append(name).append(" AS ").append(statement);
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
index 861612fec..305b91a9c 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java
@@ -93,6 +93,8 @@
import java.util.Locale;
import java.util.Set;
import java.util.function.Predicate;
+import net.sf.jsqlparser.statement.create.macro.CreateMacro;
+import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
/**
* Derives a {@link StatementFeatures} verdict from a statement tree.
@@ -521,6 +523,83 @@ public Void visit(Drop drop, S context) {
return null;
}
+ @Override
+ public Void visit(PragmaStatement pragmaStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(ExtensionStatement extensionStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(AttachStatement attachStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(DetachStatement detachStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(ConnectStatement connectStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(DisconnectStatement disconnectStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(PrepareStatement prepareStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(DeallocateStatement deallocateStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SESSION);
+ return null;
+ }
+
+ @Override
+ public Void visit(CopyStatement copyStatement, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.READS_DATA, StmtFeature.MODIFIES_DATA);
+ return null;
+ }
+
+ @Override
+ public Void visit(CreateMacro createMacro, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SCHEMA);
+ return null;
+ }
+
+ @Override
+ public Void visit(CreateExtensionRepository createExtensionRepository, S context) {
+ analysis.claimTopLevel();
+ analysis.certain(StmtFeature.MODIFIES_SCHEMA);
+ return null;
+ }
+
@Override
public Void visit(PurgeStatement purgeStatement, S context) {
analysis.claimTopLevel();
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
index 8ec9dc1bc..d0a01a4a1 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java
@@ -68,6 +68,8 @@
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.upsert.Upsert;
+import net.sf.jsqlparser.statement.create.macro.CreateMacro;
+import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
public interface StatementVisitor {
@@ -371,6 +373,72 @@ default void visit(RenameTableStatement renameTableStatement) {
this.visit(renameTableStatement, null);
}
+ T visit(PragmaStatement pragmaStatement, S context);
+
+ default void visit(PragmaStatement pragmaStatement) {
+ this.visit(pragmaStatement, null);
+ }
+
+ T visit(ExtensionStatement extensionStatement, S context);
+
+ default void visit(ExtensionStatement extensionStatement) {
+ this.visit(extensionStatement, null);
+ }
+
+ T visit(AttachStatement attachStatement, S context);
+
+ default void visit(AttachStatement attachStatement) {
+ this.visit(attachStatement, null);
+ }
+
+ T visit(DetachStatement detachStatement, S context);
+
+ default void visit(DetachStatement detachStatement) {
+ this.visit(detachStatement, null);
+ }
+
+ T visit(ConnectStatement connectStatement, S context);
+
+ default void visit(ConnectStatement connectStatement) {
+ this.visit(connectStatement, null);
+ }
+
+ T visit(DisconnectStatement disconnectStatement, S context);
+
+ default void visit(DisconnectStatement disconnectStatement) {
+ this.visit(disconnectStatement, null);
+ }
+
+ T visit(PrepareStatement prepareStatement, S context);
+
+ default void visit(PrepareStatement prepareStatement) {
+ this.visit(prepareStatement, null);
+ }
+
+ T visit(DeallocateStatement deallocateStatement, S context);
+
+ default void visit(DeallocateStatement deallocateStatement) {
+ this.visit(deallocateStatement, null);
+ }
+
+ T visit(CopyStatement copyStatement, S context);
+
+ default void visit(CopyStatement copyStatement) {
+ this.visit(copyStatement, null);
+ }
+
+ T visit(CreateMacro createMacro, S context);
+
+ default void visit(CreateMacro createMacro) {
+ this.visit(createMacro, null);
+ }
+
+ T visit(CreateExtensionRepository createExtensionRepository, S context);
+
+ default void visit(CreateExtensionRepository createExtensionRepository) {
+ this.visit(createExtensionRepository, null);
+ }
+
T visit(PurgeStatement purgeStatement, S context);
default void visit(PurgeStatement purgeStatement) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
index ab05725ad..a64b21960 100644
--- a/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java
@@ -89,6 +89,8 @@
import java.util.List;
import net.sf.jsqlparser.util.TableDefinitionTraversal;
+import net.sf.jsqlparser.statement.create.macro.CreateMacro;
+import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
public class StatementVisitorAdapter implements StatementVisitor {
@@ -618,6 +620,61 @@ public T visit(RenameTableStatement renameTableStatement, S context) {
return null;
}
+ @Override
+ public T visit(PragmaStatement pragmaStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(ExtensionStatement extensionStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(AttachStatement attachStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(DetachStatement detachStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(ConnectStatement connectStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(DisconnectStatement disconnectStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(PrepareStatement prepareStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(DeallocateStatement deallocateStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(CopyStatement copyStatement, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(CreateMacro createMacro, S context) {
+ return null;
+ }
+
+ @Override
+ public T visit(CreateExtensionRepository createExtensionRepository, S context) {
+ return null;
+ }
+
@Override
public T visit(PurgeStatement purgeStatement, S context) {
return null;
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/extension/CreateExtensionRepository.java b/src/main/java/net/sf/jsqlparser/statement/create/extension/CreateExtensionRepository.java
new file mode 100644
index 000000000..f3b85ccee
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/create/extension/CreateExtensionRepository.java
@@ -0,0 +1,85 @@
+/*-
+ * #%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.extension;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+
+/**
+ * DuckDB 2.0's {@code CREATE EXTENSION REPOSITORY [IF NOT EXISTS] name [WITH PREFIX prefix]}, which
+ * registers a custom repository extensions may be installed from.
+ */
+public class CreateExtensionRepository implements Statement {
+ private String name;
+ private boolean ifNotExists;
+ private Expression prefix;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public CreateExtensionRepository withName(String name) {
+ setName(name);
+ return this;
+ }
+
+ public boolean isIfNotExists() {
+ return ifNotExists;
+ }
+
+ public void setIfNotExists(boolean ifNotExists) {
+ this.ifNotExists = ifNotExists;
+ }
+
+ public CreateExtensionRepository withIfNotExists(boolean ifNotExists) {
+ setIfNotExists(ifNotExists);
+ return this;
+ }
+
+ public Expression getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(Expression prefix) {
+ this.prefix = prefix;
+ }
+
+ public CreateExtensionRepository withPrefix(Expression prefix) {
+ setPrefix(prefix);
+ return this;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("CREATE EXTENSION REPOSITORY ");
+ if (ifNotExists) {
+ builder.append("IF NOT EXISTS ");
+ }
+ builder.append(name);
+ if (prefix != null) {
+ builder.append(" WITH PREFIX ").append(prefix);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/macro/CreateMacro.java b/src/main/java/net/sf/jsqlparser/statement/create/macro/CreateMacro.java
new file mode 100644
index 000000000..c08b866bb
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/create/macro/CreateMacro.java
@@ -0,0 +1,178 @@
+/*-
+ * #%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.macro;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.StatementVisitor;
+import net.sf.jsqlparser.statement.select.Select;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * DuckDB's {@code CREATE [OR REPLACE] [TEMPORARY] MACRO name (parameters) AS [TABLE] body}, a named
+ * expression or query that is inlined at call sites.
+ *
+ * @see CREATE MACRO
+ */
+public class CreateMacro implements Statement {
+ private boolean orReplace;
+ private boolean temporary;
+ private String name;
+ private List parameters;
+ private Expression expression;
+ private Select select;
+
+ public boolean isOrReplace() {
+ return orReplace;
+ }
+
+ public void setOrReplace(boolean orReplace) {
+ this.orReplace = orReplace;
+ }
+
+ public CreateMacro withOrReplace(boolean orReplace) {
+ setOrReplace(orReplace);
+ return this;
+ }
+
+ public boolean isTemporary() {
+ return temporary;
+ }
+
+ public void setTemporary(boolean temporary) {
+ this.temporary = temporary;
+ }
+
+ public CreateMacro withTemporary(boolean temporary) {
+ setTemporary(temporary);
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public CreateMacro withName(String name) {
+ setName(name);
+ return this;
+ }
+
+ public List getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(List parameters) {
+ this.parameters = parameters;
+ }
+
+ public CreateMacro withParameters(List parameters) {
+ setParameters(parameters);
+ return this;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public void setExpression(Expression expression) {
+ this.expression = expression;
+ }
+
+ public CreateMacro withExpression(Expression expression) {
+ setExpression(expression);
+ return this;
+ }
+
+ public Select getSelect() {
+ return select;
+ }
+
+ public void setSelect(Select select) {
+ this.select = select;
+ }
+
+ public CreateMacro withSelect(Select select) {
+ setSelect(select);
+ return this;
+ }
+
+ /** A table macro returns a relation, a scalar macro an expression. */
+ public boolean isTable() {
+ return select != null;
+ }
+
+ public StringBuilder appendTo(StringBuilder builder) {
+ builder.append("CREATE ");
+ if (orReplace) {
+ builder.append("OR REPLACE ");
+ }
+ if (temporary) {
+ builder.append("TEMPORARY ");
+ }
+ builder.append("MACRO ").append(name).append(" (");
+ for (int i = 0; i < parameters.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(parameters.get(i));
+ }
+ builder.append(") AS ");
+ if (select != null) {
+ builder.append("TABLE ").append(select);
+ } else {
+ builder.append(expression);
+ }
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(StatementVisitor statementVisitor, S context) {
+ return statementVisitor.visit(this, context);
+ }
+
+ /** A macro parameter, optionally carrying a default value ({@code b := 5}). */
+ public static class Parameter implements Serializable {
+ private final String name;
+ private final Expression defaultValue;
+
+ public Parameter(String name) {
+ this(name, null);
+ }
+
+ public Parameter(String name, Expression defaultValue) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Expression getDefaultValue() {
+ return defaultValue;
+ }
+
+ @Override
+ public String toString() {
+ return defaultValue == null ? name : name + " := " + defaultValue;
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/create/trigger/CreateTrigger.java b/src/main/java/net/sf/jsqlparser/statement/create/trigger/CreateTrigger.java
index 105115683..4c3b8f3f8 100644
--- a/src/main/java/net/sf/jsqlparser/statement/create/trigger/CreateTrigger.java
+++ b/src/main/java/net/sf/jsqlparser/statement/create/trigger/CreateTrigger.java
@@ -241,7 +241,9 @@ public void visit(Consumer tables, Consumer expressions) {
}
public void appendTo(StringBuilder sql, Consumer visitor) {
- if (routine == null) {
+ // MySQL's shorter form has neither a routine nor any of the standard clauses
+ if (routine == null && transitionRelations.isEmpty() && !constraint
+ && orientation != Orientation.STATEMENT) {
sql.append(mysqlSql());
return;
}
@@ -252,8 +254,13 @@ public void appendTo(StringBuilder sql, Consumer visitor) {
visitor.accept(whenExpression);
sql.append(')');
}
- sql.append(" EXECUTE ").append(executeKeyword).append(' ');
- visitor.accept(routine);
+ if (routine != null) {
+ sql.append(" EXECUTE ").append(executeKeyword).append(' ');
+ visitor.accept(routine);
+ } else {
+ // DuckDB 2.0 runs a statement rather than a function
+ sql.append(' ').append(body);
+ }
}
private void appendPostgreSqlHeader(StringBuilder sql, Consumer visitor) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java
index 307d0384b..3541c1022 100644
--- a/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java
+++ b/src/main/java/net/sf/jsqlparser/statement/insert/Insert.java
@@ -59,6 +59,7 @@ public enum OverridingMode {
private InsertConflictAction conflictAction;
private InsertDuplicateAction duplicateAction;
private Alias rowAlias;
+ private ColumnMatching columnMatching;
private boolean oracleMultiInsert = false;
private boolean oracleMultiInsertFirst = false;
private List oracleMultiInsertBranches;
@@ -231,6 +232,38 @@ public void setWithItemsList(List> withItemsList) {
this.withItemsList = withItemsList;
}
+ /**
+ * DuckDB matches the source columns of an INSERT either positionally or by name:
+ * {@code INSERT INTO tbl BY NAME SELECT ...}.
+ */
+ public enum ColumnMatching {
+ BY_NAME("BY NAME"), BY_POSITION("BY POSITION");
+
+ private final String keywords;
+
+ ColumnMatching(String keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public String toString() {
+ return keywords;
+ }
+ }
+
+ public ColumnMatching getColumnMatching() {
+ return columnMatching;
+ }
+
+ public void setColumnMatching(ColumnMatching columnMatching) {
+ this.columnMatching = columnMatching;
+ }
+
+ public Insert withColumnMatching(ColumnMatching columnMatching) {
+ setColumnMatching(columnMatching);
+ return this;
+ }
+
public OverridingMode getOverridingMode() {
return overridingMode;
}
@@ -406,6 +439,10 @@ private void appendInsertTargetAndValues(StringBuilder sql) {
sql.append(") ");
}
+ if (columnMatching != null) {
+ sql.append(columnMatching).append(" ");
+ }
+
if (isOverriding()) {
sql.append("OVERRIDING ").append(overridingMode).append(" VALUE ");
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java
index 69af24f55..f5c623577 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java
@@ -92,6 +92,14 @@ default void visit(PivotQuery pivotQuery) {
this.visit(pivotQuery, null);
}
+ default T visit(UnPivotQuery unPivotQuery, S context) {
+ return null;
+ }
+
+ default void visit(UnPivotQuery unPivotQuery) {
+ this.visit(unPivotQuery, null);
+ }
+
T visit(SetOperationList setOperationList, S context);
default void visit(SetOperationList setOperationList) {
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java
index 3c88d63c4..c7ce99901 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java
@@ -129,6 +129,11 @@ public T visit(PivotQuery pivotQuery, S context) {
return pivotQuery.accept(selectVisitor, context);
}
+ @Override
+ public T visit(UnPivotQuery unPivotQuery, S context) {
+ return unPivotQuery.accept(selectVisitor, context);
+ }
+
@Override
public T visit(SetOperationList setOperationList, S context) {
ArrayList results = new ArrayList<>();
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Join.java b/src/main/java/net/sf/jsqlparser/statement/select/Join.java
index d134689f6..d06a5df1f 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/Join.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/Join.java
@@ -36,6 +36,9 @@ public class Join extends ASTNodeAccessImpl {
private boolean simple = false;
private boolean cross = false;
private boolean semi = false;
+ private boolean anti = false;
+ private Long approxNearest;
+ private Expression similarity;
private boolean any = false;
private boolean all = false;
private boolean straight = false;
@@ -194,6 +197,49 @@ public Join withFetch(boolean b) {
*
* @return true if is a "SEMI" join
*/
+ /**
+ * DuckDB 2.0's similarity join:
+ * {@code JOIN products APPROX NEAREST 2 BY SIMILARITY array_cosine_similarity(...)}.
+ */
+ public Long getApproxNearest() {
+ return approxNearest;
+ }
+
+ public void setApproxNearest(Long approxNearest) {
+ this.approxNearest = approxNearest;
+ }
+
+ public Join withApproxNearest(Long approxNearest) {
+ setApproxNearest(approxNearest);
+ return this;
+ }
+
+ public Expression getSimilarity() {
+ return similarity;
+ }
+
+ public void setSimilarity(Expression similarity) {
+ this.similarity = similarity;
+ }
+
+ public Join withSimilarity(Expression similarity) {
+ setSimilarity(similarity);
+ return this;
+ }
+
+ public boolean isAnti() {
+ return anti;
+ }
+
+ public void setAnti(boolean b) {
+ anti = b;
+ }
+
+ public Join withAnti(boolean b) {
+ this.setAnti(b);
+ return this;
+ }
+
public boolean isSemi() {
return semi;
}
@@ -511,6 +557,8 @@ public StringBuilder appendJoinTypeTo(StringBuilder builder) {
builder.append("INNER ");
} else if (isSemi()) {
builder.append("SEMI ");
+ } else if (isAnti()) {
+ builder.append("ANTI ");
}
if (isArray()) {
@@ -561,6 +609,11 @@ public String toString() {
builder.append(fromItem).append((joinWindow != null) ? " WITHIN " + joinWindow : "");
}
+ if (approxNearest != null) {
+ builder.append(" APPROX NEAREST ").append(approxNearest)
+ .append(" BY SIMILARITY ").append(similarity);
+ }
+
for (Expression onExpression : onExpressions) {
builder.append(" ON ").append(onExpression);
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java
index f84bf6ea9..58510e04f 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java
@@ -66,6 +66,7 @@ public class PlainSelect extends Select {
private boolean isUsingFinal = false;
private boolean isUsingOnly = false;
private boolean useWithNoLog = false;
+ private boolean fromFirst = false;
private Table intoTempTable = null;
private List settings = null;
@@ -543,9 +544,83 @@ public void setWindowDefinitions(List windowDefinitions) {
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength",
"PMD.NPathComplexity"})
+ /**
+ * DuckDB allows the FROM clause to come first, with the SELECT clause optional:
+ * {@code FROM tbl SELECT a} and {@code FROM tbl WHERE a > 1}.
+ */
+ public boolean isFromFirst() {
+ return fromFirst;
+ }
+
+ public void setFromFirst(boolean fromFirst) {
+ this.fromFirst = fromFirst;
+ }
+
+ public PlainSelect withFromFirst(boolean fromFirst) {
+ setFromFirst(fromFirst);
+ return this;
+ }
+
public StringBuilder appendSelectBodyTo(StringBuilder builder) {
+ if (fromFirst) {
+ // DuckDB allows the FROM clause to lead: FROM tbl [SELECT ...] [WHERE ...]
+ appendFromClauseTo(builder);
+ if (selectItems != null && !selectItems.isEmpty()) {
+ builder.append(" ");
+ appendSelectClauseTo(builder);
+ appendIntoClausesTo(builder);
+ }
+ } else {
+ appendSelectClauseTo(builder);
+ appendIntoClausesTo(builder);
+ appendFromClauseTo(builder);
+ }
+
+ if (preWhere != null) {
+ builder.append(" PREWHERE ").append(preWhere);
+ }
+ if (where != null) {
+ builder.append(" WHERE ").append(where);
+ }
+ if (oracleHierarchical != null) {
+ builder.append(oracleHierarchical);
+ }
+ if (preferringClause != null) {
+ builder.append(" ").append(preferringClause);
+ }
+ if (groupBy != null) {
+ builder.append(" ").append(groupBy);
+ }
+ if (having != null) {
+ builder.append(" HAVING ").append(having);
+ }
+ if (qualify != null) {
+ builder.append(" QUALIFY ").append(qualify);
+ }
+ if (windowDefinitions != null) {
+ builder.append(" WINDOW ");
+ builder.append(windowDefinitions.stream().map(WindowDefinition::toString)
+ .collect(joining(", ")));
+ }
+ appendEmitClauseTo(builder);
+ if (intoTempTable != null) {
+ builder.append(" INTO TEMP ").append(intoTempTable);
+ }
+ if (useWithNoLog) {
+ builder.append(" WITH NO LOG");
+ }
+ return builder;
+ }
+
+ private void appendSelectClauseTo(StringBuilder builder) {
builder.append("SELECT ");
+ appendSelectHintsTo(builder);
+ appendSelectQualifiersTo(builder);
+ appendMySqlSelectFlagsTo(builder);
+ builder.append(getStringList(selectItems));
+ }
+ private void appendSelectHintsTo(StringBuilder builder) {
if (this.mySqlHintStraightJoin) {
builder.append("STRAIGHT_JOIN ");
}
@@ -561,7 +636,9 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
if (first != null) {
builder.append(first).append(" ");
}
+ }
+ private void appendSelectQualifiersTo(StringBuilder builder) {
if (distinct != null) {
builder.append(distinct).append(" ");
}
@@ -580,14 +657,19 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
if (top != null) {
builder.append(top).append(" ");
}
+ }
+
+ private void appendMySqlSelectFlagsTo(StringBuilder builder) {
if (mySqlCacheFlag != null) {
builder.append(mySqlCacheFlag.name()).append(" ");
}
+
if (mySqlSqlCalcFoundRows) {
builder.append("SQL_CALC_FOUND_ROWS").append(" ");
}
- builder.append(getStringList(selectItems));
+ }
+ private void appendIntoClausesTo(StringBuilder builder) {
if (intoTables != null) {
builder.append(" INTO ");
for (Iterator iter = intoTables.iterator(); iter.hasNext();) {
@@ -603,9 +685,11 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
.getPosition() == MySqlSelectIntoClause.Position.BEFORE_FROM) {
builder.append(" ").append(mySqlSelectIntoClause);
}
+ }
+ private void appendFromClauseTo(StringBuilder builder) {
if (fromItem != null) {
- builder.append(" FROM ");
+ builder.append(fromFirst ? "FROM " : " FROM ");
if (isUsingOnly) {
builder.append("ONLY ");
}
@@ -633,42 +717,9 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
builder.append(" WINDOW ").append(ksqlWindow);
}
}
- if (preWhere != null) {
- builder.append(" PREWHERE ").append(preWhere);
- }
- if (where != null) {
- builder.append(" WHERE ").append(where);
- }
- if (oracleHierarchical != null) {
- builder.append(oracleHierarchical);
- }
- if (preferringClause != null) {
- builder.append(" ").append(preferringClause);
- }
- if (groupBy != null) {
- builder.append(" ").append(groupBy);
- }
- if (having != null) {
- builder.append(" HAVING ").append(having);
- }
- if (qualify != null) {
- builder.append(" QUALIFY ").append(qualify);
- }
- if (windowDefinitions != null) {
- builder.append(" WINDOW ");
- builder.append(windowDefinitions.stream().map(WindowDefinition::toString)
- .collect(joining(", ")));
- }
- appendEmitClauseTo(builder);
- if (intoTempTable != null) {
- builder.append(" INTO TEMP ").append(intoTempTable);
- }
- if (useWithNoLog) {
- builder.append(" WITH NO LOG");
- }
- return builder;
}
+
@Override
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength",
"PMD.NPathComplexity"})
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java b/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java
index e6bc8f21d..a3c0203df 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/SampleClause.java
@@ -17,6 +17,7 @@ public class SampleClause {
private Number percentageArgument;
private String percentageUnit;
private boolean argumentInBrackets = true;
+ private boolean methodInBrackets = false;
// ClickHouse specific
private Number offsetArgument;
private Number repeatArgument;
@@ -130,10 +131,23 @@ public SampleClause setMethod(String method) {
return this;
}
+ /**
+ * DuckDB writes the sampling method behind the sample size:
+ * {@code USING SAMPLE 10 PERCENT (bernoulli)}.
+ */
+ public boolean isMethodInBrackets() {
+ return methodInBrackets;
+ }
+
+ public SampleClause setMethodInBrackets(boolean methodInBrackets) {
+ this.methodInBrackets = methodInBrackets;
+ return this;
+ }
+
public StringBuilder appendTo(StringBuilder builder) {
builder.append(" ").append(keyword);
- if (method != null) {
+ if (method != null && !methodInBrackets) {
builder.append(" ").append(method);
}
@@ -144,11 +158,20 @@ public StringBuilder appendTo(StringBuilder builder) {
} else {
builder.append(" ").append(percentageArgument);
if (percentageUnit != null) {
- builder.append(" ").append(percentageUnit);
+ // "10%" has no blank, "10 PERCENT" and "10 ROWS" do
+ builder.append("%".equals(percentageUnit) ? "" : " ").append(percentageUnit);
}
}
}
+ if (methodInBrackets) {
+ builder.append(" (").append(method);
+ if (seedArgument != null) {
+ builder.append(", ").append(seedArgument);
+ }
+ builder.append(")");
+ }
+
if (offsetArgument != null) {
builder.append(" OFFSET ").append(offsetArgument);
}
@@ -157,7 +180,7 @@ public StringBuilder appendTo(StringBuilder builder) {
builder.append(" REPEATABLE (").append(repeatArgument).append(")");
}
- if (seedArgument != null) {
+ if (seedArgument != null && !methodInBrackets) {
builder.append(" SEED (").append(seedArgument).append(")");
}
@@ -189,7 +212,7 @@ public String toString() {
}
public enum SampleMethod {
- BERNOULLI, SYSTEM, BLOCK;
+ BERNOULLI, SYSTEM, BLOCK, RESERVOIR;
public static SampleMethod from(String sampleMethod) {
return Enum.valueOf(SampleMethod.class,
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java
index f5b88bd35..5591f4bf8 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java
@@ -47,6 +47,14 @@ default void visit(PivotQuery pivotQuery) {
this.visit(pivotQuery, null);
}
+ default T visit(UnPivotQuery unPivotQuery, S context) {
+ return null;
+ }
+
+ default void visit(UnPivotQuery unPivotQuery) {
+ this.visit(unPivotQuery, null);
+ }
+
T visit(FromQuery fromQuery, S context);
T visit(SetOperationList setOpList, S context);
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java
index 051f3929e..28e9525bc 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java
@@ -272,6 +272,16 @@ public T visit(PivotQuery pivotQuery, S context) {
return null;
}
+ @Override
+ public T visit(UnPivotQuery unPivotQuery, S context) {
+ visitWithItems(unPivotQuery.getWithItemsList(), context);
+
+ fromItemVisitor.visitFromItem(unPivotQuery.getFromItem(), context);
+ expressionVisitor.visitExpressions(unPivotQuery.getOnExpressions(), context);
+ expressionVisitor.visitExpressions(unPivotQuery.getValueColumns(), context);
+ return null;
+ }
+
@Override
public T visit(FromQuery fromQuery, S context) {
return null;
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/UnPivotQuery.java b/src/main/java/net/sf/jsqlparser/statement/select/UnPivotQuery.java
new file mode 100644
index 000000000..799e6b612
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/select/UnPivotQuery.java
@@ -0,0 +1,115 @@
+/*-
+ * #%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.select;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.schema.Column;
+
+/**
+ * DuckDB's simplified UNPIVOT statement:
+ * {@code UNPIVOT tbl ON columns [INTO NAME name VALUE values]}, the counterpart of
+ * {@link PivotQuery}.
+ *
+ * @see UNPIVOT
+ */
+public class UnPivotQuery extends Select {
+ private FromItem fromItem;
+ private ExpressionList onExpressions;
+ private String nameColumn;
+ private ExpressionList valueColumns;
+
+ public FromItem getFromItem() {
+ return fromItem;
+ }
+
+ public void setFromItem(FromItem fromItem) {
+ this.fromItem = fromItem;
+ }
+
+ public UnPivotQuery withFromItem(FromItem fromItem) {
+ setFromItem(fromItem);
+ return this;
+ }
+
+ public ExpressionList getOnExpressions() {
+ return onExpressions;
+ }
+
+ public void setOnExpressions(ExpressionList onExpressions) {
+ this.onExpressions = onExpressions;
+ }
+
+ public UnPivotQuery withOnExpressions(ExpressionList onExpressions) {
+ setOnExpressions(onExpressions);
+ return this;
+ }
+
+ public String getNameColumn() {
+ return nameColumn;
+ }
+
+ public void setNameColumn(String nameColumn) {
+ this.nameColumn = nameColumn;
+ }
+
+ public UnPivotQuery withNameColumn(String nameColumn) {
+ setNameColumn(nameColumn);
+ return this;
+ }
+
+ public ExpressionList getValueColumns() {
+ return valueColumns;
+ }
+
+ public void setValueColumns(ExpressionList valueColumns) {
+ this.valueColumns = valueColumns;
+ }
+
+ public UnPivotQuery withValueColumns(ExpressionList valueColumns) {
+ setValueColumns(valueColumns);
+ return this;
+ }
+
+ @Override
+ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
+ builder.append("UNPIVOT ").append(fromItem);
+ if (onExpressions != null) {
+ builder.append(" ON ").append(onExpressions);
+ }
+ if (nameColumn != null) {
+ builder.append(" INTO NAME ").append(nameColumn);
+ if (valueColumns != null) {
+ builder.append(" VALUE ").append(valueColumns);
+ }
+ }
+ return builder;
+ }
+
+ @Override
+ public T accept(SelectVisitor selectVisitor, S context) {
+ return selectVisitor.visit(this, context);
+ }
+
+ @Override
+ public T accept(FromItemVisitor fromItemVisitor, S context) {
+ return fromItemVisitor.visit(this, context);
+ }
+
+ @Override
+ public SampleClause getSampleClause() {
+ return null;
+ }
+
+ @Override
+ public FromItem setSampleClause(SampleClause sampleClause) {
+ return null;
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java
index 767f96846..49fcff0cb 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java
@@ -11,6 +11,7 @@
import java.util.function.Consumer;
import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import java.io.Serializable;
import java.util.ArrayList;
@@ -35,6 +36,7 @@ public class WithItem implements Serializable {
private boolean recursive = false;
private boolean usingNot = false;
private boolean materialized = false;
+ private ExpressionList usingKeyExpressions;
public WithItem(K statement, Alias alias) {
this.statement = statement;
@@ -166,6 +168,23 @@ public WithItem withCycleClause(WithCycleClause cycleClause) {
return this;
}
+ /**
+ * DuckDB 2.0 aggregates a recursive CTE by key:
+ * {@code WITH RECURSIVE tbl (a, b) USING KEY (a, avg(b)) AS (...)}.
+ */
+ public ExpressionList getUsingKeyExpressions() {
+ return usingKeyExpressions;
+ }
+
+ public void setUsingKeyExpressions(ExpressionList usingKeyExpressions) {
+ this.usingKeyExpressions = usingKeyExpressions;
+ }
+
+ public WithItem withUsingKeyExpressions(ExpressionList usingKeyExpressions) {
+ setUsingKeyExpressions(usingKeyExpressions);
+ return this;
+ }
+
public StringBuilder appendRecursiveClausesTo(StringBuilder builder,
Consumer expressionPrinter) {
if (searchClause != null) {
@@ -197,6 +216,9 @@ public String toString() {
}
builder.append(")");
}
+ if (usingKeyExpressions != null) {
+ builder.append(" USING KEY (").append(usingKeyExpressions).append(")");
+ }
builder.append(" AS ");
if (materialized) {
builder.append(usingNot
diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
index 3bf8df2e0..489297ce7 100644
--- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
+++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
@@ -189,6 +189,7 @@
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.Pivot;
import net.sf.jsqlparser.statement.select.PivotQuery;
+import net.sf.jsqlparser.statement.select.UnPivotQuery;
import net.sf.jsqlparser.statement.select.PivotVisitor;
import net.sf.jsqlparser.statement.select.PivotXml;
import net.sf.jsqlparser.statement.select.PlainSelect;
@@ -209,6 +210,17 @@
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import net.sf.jsqlparser.statement.upsert.Upsert;
+import net.sf.jsqlparser.statement.PragmaStatement;
+import net.sf.jsqlparser.statement.ExtensionStatement;
+import net.sf.jsqlparser.statement.AttachStatement;
+import net.sf.jsqlparser.statement.DetachStatement;
+import net.sf.jsqlparser.statement.ConnectStatement;
+import net.sf.jsqlparser.statement.DisconnectStatement;
+import net.sf.jsqlparser.statement.PrepareStatement;
+import net.sf.jsqlparser.statement.DeallocateStatement;
+import net.sf.jsqlparser.statement.CopyStatement;
+import net.sf.jsqlparser.statement.create.macro.CreateMacro;
+import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
/**
@@ -483,6 +495,20 @@ public Void visit(PivotQuery pivotQuery, S context) {
return null;
}
+ @Override
+ public Void visit(UnPivotQuery unPivotQuery, S context) {
+ visitWithItems(unPivotQuery.getWithItemsList(), context);
+ visitFromItem(unPivotQuery.getFromItem(), context);
+ visitExpressions(unPivotQuery.getOnExpressions(), context);
+ visitExpressions(unPivotQuery.getValueColumns(), context);
+ return null;
+ }
+
+ @Override
+ public void visit(UnPivotQuery unPivotQuery) {
+ SelectVisitor.super.visit(unPivotQuery);
+ }
+
private void visitSelectItems(List extends SelectItem>> selectItems, S context) {
if (selectItems != null) {
for (SelectItem> item : selectItems) {
@@ -2044,7 +2070,11 @@ public void visit(Values values) {
@Override
public Void visit(DescribeStatement describe, S context) {
- describe.getTable().accept(this, context);
+ if (describe.getSelect() != null) {
+ describe.getSelect().accept((SelectVisitor>) this, context);
+ } else {
+ describe.getTable().accept(this, context);
+ }
return null;
}
@@ -2464,6 +2494,133 @@ public void visit(RenameTableStatement renameTableStatement) {
StatementVisitor.super.visit(renameTableStatement);
}
+ @Override
+ public Void visit(PragmaStatement pragmaStatement, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(PragmaStatement pragmaStatement) {
+ StatementVisitor.super.visit(pragmaStatement);
+ }
+
+ @Override
+ public Void visit(ExtensionStatement extensionStatement, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(ExtensionStatement extensionStatement) {
+ StatementVisitor.super.visit(extensionStatement);
+ }
+
+ @Override
+ public Void visit(AttachStatement attachStatement, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(AttachStatement attachStatement) {
+ StatementVisitor.super.visit(attachStatement);
+ }
+
+ @Override
+ public Void visit(DetachStatement detachStatement, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(DetachStatement detachStatement) {
+ StatementVisitor.super.visit(detachStatement);
+ }
+
+ @Override
+ public Void visit(ConnectStatement connectStatement, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(ConnectStatement connectStatement) {
+ StatementVisitor.super.visit(connectStatement);
+ }
+
+ @Override
+ public Void visit(DisconnectStatement disconnectStatement, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(DisconnectStatement disconnectStatement) {
+ StatementVisitor.super.visit(disconnectStatement);
+ }
+
+ @Override
+ public Void visit(PrepareStatement prepareStatement, S context) {
+ prepareStatement.getStatement().accept(this, context);
+ return null;
+ }
+
+ @Override
+ public void visit(PrepareStatement prepareStatement) {
+ StatementVisitor.super.visit(prepareStatement);
+ }
+
+ @Override
+ public Void visit(DeallocateStatement deallocateStatement, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(DeallocateStatement deallocateStatement) {
+ StatementVisitor.super.visit(deallocateStatement);
+ }
+
+ @Override
+ public Void visit(CopyStatement copyStatement, S context) {
+ if (copyStatement.getSelect() != null) {
+ copyStatement.getSelect().accept((SelectVisitor>) this, context);
+ } else if (copyStatement.getTable() != null) {
+ copyStatement.getTable().accept(this, context);
+ }
+ return null;
+ }
+
+ @Override
+ public void visit(CopyStatement copyStatement) {
+ StatementVisitor.super.visit(copyStatement);
+ }
+
+ @Override
+ public Void visit(CreateMacro createMacro, S context) {
+ if (createMacro.getSelect() != null) {
+ createMacro.getSelect().accept((SelectVisitor>) this, context);
+ }
+ return null;
+ }
+
+ @Override
+ public void visit(CreateMacro createMacro) {
+ StatementVisitor.super.visit(createMacro);
+ }
+
+ @Override
+ public Void visit(CreateExtensionRepository createExtensionRepository, S context) {
+ // no tables involved in this statement
+ return null;
+ }
+
+ @Override
+ public void visit(CreateExtensionRepository createExtensionRepository) {
+ StatementVisitor.super.visit(createExtensionRepository);
+ }
+
@Override
public Void visit(PurgeStatement purgeStatement, S context) {
if (purgeStatement.getPurgeObjectType() == PurgeObjectType.TABLE) {
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DmlDeParserSupport.java b/src/main/java/net/sf/jsqlparser/util/deparser/DmlDeParserSupport.java
index 9231d2b0e..e11b26586 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/DmlDeParserSupport.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/DmlDeParserSupport.java
@@ -93,6 +93,9 @@ StringBuilder deparseWithItem(WithItem> item, S context) {
builder.append(' ')
.append(PlainSelect.getStringList(item.getWithItemList(), true, true));
}
+ if (item.getUsingKeyExpressions() != null) {
+ builder.append(" USING KEY (").append(item.getUsingKeyExpressions()).append(")");
+ }
builder.append(" AS ");
if (item.isMaterialized()) {
builder.append(item.isUsingNot() ? "NOT MATERIALIZED " : "MATERIALIZED ");
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java
index 596af0456..7abcbc0c5 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java
@@ -104,6 +104,10 @@ public void deParse(Insert insert) {
builder.append(")");
}
+ if (insert.getColumnMatching() != null) {
+ builder.append(" ").append(insert.getColumnMatching());
+ }
+
if (insert.isOverriding()) {
builder.append(" OVERRIDING ").append(insert.getOverridingMode()).append(" VALUE");
}
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
index 0dc7d84b8..da0f5fcec 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
@@ -63,6 +63,7 @@
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.Pivot;
import net.sf.jsqlparser.statement.select.PivotQuery;
+import net.sf.jsqlparser.statement.select.UnPivotQuery;
import net.sf.jsqlparser.statement.select.PivotVisitor;
import net.sf.jsqlparser.statement.select.PivotXml;
import net.sf.jsqlparser.statement.select.PlainSelect;
@@ -198,101 +199,16 @@ public StringBuilder visit(PlainSelect plainSelect, S context) {
}
}
- builder.append("SELECT ");
-
- if (plainSelect.getMySqlHintStraightJoin()) {
- builder.append("STRAIGHT_JOIN ");
- }
-
- OracleHint hint = plainSelect.getOracleHint();
- if (hint != null) {
- builder.append(hint).append(" ");
- }
-
- Skip skip = plainSelect.getSkip();
- if (skip != null) {
- builder.append(skip).append(" ");
- }
-
- First first = plainSelect.getFirst();
- if (first != null) {
- builder.append(first).append(" ");
- }
-
- deparseDistinctClause(plainSelect.getDistinct());
-
- if (plainSelect.getBigQuerySelectQualifier() != null) {
- switch (plainSelect.getBigQuerySelectQualifier()) {
- case AS_STRUCT:
- builder.append("AS STRUCT ");
- break;
- case AS_VALUE:
- builder.append("AS VALUE ");
- break;
- }
- }
-
- Top top = plainSelect.getTop();
- if (top != null) {
- visit(top);
- }
-
- if (plainSelect.getMySqlSqlCacheFlag() != null) {
- builder.append(plainSelect.getMySqlSqlCacheFlag().name()).append(" ");
- }
-
- if (plainSelect.getMySqlSqlCalcFoundRows()) {
- builder.append("SQL_CALC_FOUND_ROWS").append(" ");
- }
-
- deparseSelectItemsClause(plainSelect.getSelectItems());
-
- if (plainSelect.getIntoTables() != null) {
- builder.append(" INTO ");
- for (Iterator iter = plainSelect.getIntoTables().iterator(); iter.hasNext();) {
- visit(iter.next(), context);
- if (iter.hasNext()) {
- builder.append(", ");
- }
- }
- }
-
- deparseMySqlSelectInto(plainSelect, MySqlSelectIntoClause.Position.BEFORE_FROM, context);
-
- if (plainSelect.getFromItem() != null) {
- builder.append(" FROM ");
- if (plainSelect.isUsingOnly()) {
- builder.append("ONLY ");
- }
- plainSelect.getFromItem().accept(this, context);
-
- if (plainSelect.getFromItem() instanceof Table) {
- Table table = (Table) plainSelect.getFromItem();
- if (table.getSampleClause() != null) {
- table.getSampleClause().appendTo(builder);
- }
- }
- }
-
- if (plainSelect.getLateralViews() != null) {
- for (LateralView lateralView : plainSelect.getLateralViews()) {
- deparseLateralView(lateralView);
- }
- }
-
- if (plainSelect.getJoins() != null) {
- for (Join join : plainSelect.getJoins()) {
- deparseJoin(join);
+ if (plainSelect.isFromFirst()) {
+ // DuckDB allows the FROM clause to lead, with an optional SELECT clause behind it
+ deparseFromClause(plainSelect, context);
+ if (plainSelect.getSelectItems() != null && !plainSelect.getSelectItems().isEmpty()) {
+ builder.append(" ");
+ deparseSelectClause(plainSelect, context);
}
- }
-
- if (plainSelect.isUsingFinal()) {
- builder.append(" FINAL");
- }
-
- if (plainSelect.getKsqlWindow() != null) {
- builder.append(" WINDOW ");
- builder.append(plainSelect.getKsqlWindow().toString());
+ } else {
+ deparseSelectClause(plainSelect, context);
+ deparseFromClause(plainSelect, context);
}
deparsePreWhereClause(plainSelect);
@@ -419,6 +335,119 @@ public StringBuilder visit(PlainSelect plainSelect, S context) {
return builder;
}
+ private void deparseSelectClause(PlainSelect plainSelect, S context) {
+ builder.append("SELECT ");
+
+ if (plainSelect.getMySqlHintStraightJoin()) {
+ builder.append("STRAIGHT_JOIN ");
+ }
+
+ OracleHint hint = plainSelect.getOracleHint();
+ if (hint != null) {
+ builder.append(hint).append(" ");
+ }
+
+ Skip skip = plainSelect.getSkip();
+ if (skip != null) {
+ builder.append(skip).append(" ");
+ }
+
+ First first = plainSelect.getFirst();
+ if (first != null) {
+ builder.append(first).append(" ");
+ }
+
+ deparseDistinctClause(plainSelect.getDistinct());
+
+ if (plainSelect.getBigQuerySelectQualifier() != null) {
+ switch (plainSelect.getBigQuerySelectQualifier()) {
+ case AS_STRUCT:
+ builder.append("AS STRUCT ");
+ break;
+ case AS_VALUE:
+ builder.append("AS VALUE ");
+ break;
+ }
+ }
+
+ Top top = plainSelect.getTop();
+ if (top != null) {
+ visit(top);
+ }
+
+ if (plainSelect.getMySqlSqlCacheFlag() != null) {
+ builder.append(plainSelect.getMySqlSqlCacheFlag().name()).append(" ");
+ }
+
+ if (plainSelect.getMySqlSqlCalcFoundRows()) {
+ builder.append("SQL_CALC_FOUND_ROWS").append(" ");
+ }
+
+ deparseSelectItemsClause(plainSelect.getSelectItems());
+
+ if (plainSelect.getIntoTables() != null) {
+ builder.append(" INTO ");
+ for (Iterator iter = plainSelect.getIntoTables().iterator(); iter.hasNext();) {
+ visit(iter.next(), context);
+ if (iter.hasNext()) {
+ builder.append(", ");
+ }
+ }
+ }
+
+ deparseMySqlSelectInto(plainSelect, MySqlSelectIntoClause.Position.BEFORE_FROM, context);
+ }
+
+ private void deparseFromClause(PlainSelect plainSelect, S context) {
+ if (plainSelect.getFromItem() != null) {
+ builder.append(plainSelect.isFromFirst() ? "FROM " : " FROM ");
+ if (plainSelect.isUsingOnly()) {
+ builder.append("ONLY ");
+ }
+ plainSelect.getFromItem().accept(this, context);
+
+ if (plainSelect.getFromItem() instanceof Table) {
+ Table table = (Table) plainSelect.getFromItem();
+ if (table.getSampleClause() != null) {
+ table.getSampleClause().appendTo(builder);
+ }
+ }
+ }
+
+ if (plainSelect.getLateralViews() != null) {
+ for (LateralView lateralView : plainSelect.getLateralViews()) {
+ deparseLateralView(lateralView);
+ }
+ }
+
+ if (plainSelect.getJoins() != null) {
+ for (Join join : plainSelect.getJoins()) {
+ deparseJoin(join);
+ }
+ }
+
+ if (plainSelect.isUsingFinal()) {
+ builder.append(" FINAL");
+ }
+
+ if (plainSelect.getKsqlWindow() != null) {
+ builder.append(" WINDOW ");
+ builder.append(plainSelect.getKsqlWindow().toString());
+ }
+ }
+
+
+ @Override
+ public StringBuilder visit(UnPivotQuery unPivotQuery, S context) {
+ unPivotQuery.appendSelectBodyTo(builder);
+ return builder;
+ }
+
+ @Override
+ public void visit(UnPivotQuery unPivotQuery) {
+ SelectVisitor.super.visit(unPivotQuery);
+ }
+
@Override
public StringBuilder visit(PivotQuery pivotQuery, S context) {
List> withItemsList = pivotQuery.getWithItemsList();
@@ -744,6 +773,12 @@ public void deparseJoin(Join join) {
builder.append(" WITHIN ");
builder.append(join.getJoinWindow().toString());
}
+ if (join.getApproxNearest() != null) {
+ builder.append(" APPROX NEAREST ").append(join.getApproxNearest())
+ .append(" BY SIMILARITY ");
+ join.getSimilarity().accept(expressionVisitor, null);
+ }
+
for (Expression onExpression : join.getOnExpressions()) {
builder.append(" ON ");
onExpression.accept(expressionVisitor, null);
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java
index 69cb9770c..63e33a444 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java
@@ -97,6 +97,17 @@
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.upsert.Upsert;
+import net.sf.jsqlparser.statement.PragmaStatement;
+import net.sf.jsqlparser.statement.ExtensionStatement;
+import net.sf.jsqlparser.statement.AttachStatement;
+import net.sf.jsqlparser.statement.DetachStatement;
+import net.sf.jsqlparser.statement.ConnectStatement;
+import net.sf.jsqlparser.statement.DisconnectStatement;
+import net.sf.jsqlparser.statement.PrepareStatement;
+import net.sf.jsqlparser.statement.DeallocateStatement;
+import net.sf.jsqlparser.statement.CopyStatement;
+import net.sf.jsqlparser.statement.create.macro.CreateMacro;
+import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
public class StatementDeParser extends AbstractDeParser
implements StatementVisitor {
@@ -444,7 +455,11 @@ public StringBuilder visit(Comment comment, S context) {
public StringBuilder visit(DescribeStatement describe, S context) {
builder.append(describe.getDescribeType());
builder.append(" ");
- builder.append(describe.getTable());
+ if (describe.getSelect() != null) {
+ describe.getSelect().accept(this, context);
+ } else {
+ builder.append(describe.getTable());
+ }
return builder;
}
@@ -552,6 +567,72 @@ public StringBuilder visit(RenameTableStatement renameTableStatement, S cont
return builder;
}
+ @Override
+ public StringBuilder visit(PragmaStatement pragmaStatement, S context) {
+ pragmaStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(ExtensionStatement extensionStatement, S context) {
+ extensionStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(AttachStatement attachStatement, S context) {
+ attachStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(DetachStatement detachStatement, S context) {
+ detachStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(ConnectStatement connectStatement, S context) {
+ connectStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(DisconnectStatement disconnectStatement, S context) {
+ disconnectStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(PrepareStatement prepareStatement, S context) {
+ prepareStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(DeallocateStatement deallocateStatement, S context) {
+ deallocateStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(CopyStatement copyStatement, S context) {
+ copyStatement.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(CreateMacro createMacro, S context) {
+ createMacro.appendTo(builder);
+ return builder;
+ }
+
+ @Override
+ public StringBuilder visit(CreateExtensionRepository createExtensionRepository, S context) {
+ createExtensionRepository.appendTo(builder);
+ return builder;
+ }
+
@Override
public StringBuilder visit(PurgeStatement purgeStatement, S context) {
purgeStatement.appendTo(builder);
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java b/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java
index 0048f8db8..f8c5694aa 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/feature/FeaturesAllowed.java
@@ -59,6 +59,7 @@ public class FeaturesAllowed implements FeatureSetValidation, ModifyableFeatureS
Feature.joinCross,
Feature.joinOuter,
Feature.joinSemi,
+ Feature.joinAnti,
Feature.joinInner,
Feature.joinStraight,
Feature.joinApply,
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
index 71c33f902..a5295db77 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
@@ -34,6 +34,7 @@
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.Pivot;
import net.sf.jsqlparser.statement.select.PivotQuery;
+import net.sf.jsqlparser.statement.select.UnPivotQuery;
import net.sf.jsqlparser.statement.select.PivotVisitor;
import net.sf.jsqlparser.statement.select.PivotXml;
import net.sf.jsqlparser.statement.select.PlainSelect;
@@ -160,6 +161,14 @@ public Void visit(PlainSelect plainSelect, S context) {
return null;
}
+ @Override
+ public Void visit(UnPivotQuery unPivotQuery, S context) {
+ validateFeature(Feature.pivot);
+ validateOptionalFromItem(unPivotQuery.getFromItem());
+ validateOptionalExpressions(unPivotQuery.getOnExpressions());
+ return null;
+ }
+
@Override
public Void visit(PivotQuery pivotQuery, S context) {
if (isNotEmpty(pivotQuery.getWithItemsList())) {
@@ -328,6 +337,7 @@ public void validateOptionalJoin(Join join) {
validateFeature(c, join.isOuter(), Feature.joinOuter);
validateFeature(c, join.isInner(), Feature.joinInner);
validateFeature(c, join.isSemi(), Feature.joinSemi);
+ validateFeature(c, join.isAnti(), Feature.joinAnti);
validateFeature(c, join.isStraight(), Feature.joinStraight);
validateFeature(c, join.isApply(), Feature.joinApply);
validateFeature(c, join.isAsOf(), Feature.joinAsOf);
@@ -463,6 +473,11 @@ public void visit(PivotQuery pivotQuery) {
visit(pivotQuery, null);
}
+ @Override
+ public void visit(UnPivotQuery unPivotQuery) {
+ visit(unPivotQuery, null);
+ }
+
public void visit(SelectItem> selectExpressionItem) {
visit(selectExpressionItem, null);
}
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java
index 6764ef740..13d48fd18 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java
@@ -99,6 +99,17 @@
import net.sf.jsqlparser.statement.upsert.Upsert;
import net.sf.jsqlparser.util.validation.ValidationCapability;
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
+import net.sf.jsqlparser.statement.PragmaStatement;
+import net.sf.jsqlparser.statement.ExtensionStatement;
+import net.sf.jsqlparser.statement.AttachStatement;
+import net.sf.jsqlparser.statement.DetachStatement;
+import net.sf.jsqlparser.statement.ConnectStatement;
+import net.sf.jsqlparser.statement.DisconnectStatement;
+import net.sf.jsqlparser.statement.PrepareStatement;
+import net.sf.jsqlparser.statement.DeallocateStatement;
+import net.sf.jsqlparser.statement.CopyStatement;
+import net.sf.jsqlparser.statement.create.macro.CreateMacro;
+import net.sf.jsqlparser.statement.create.extension.CreateExtensionRepository;
/**
* @author gitmotte
@@ -477,6 +488,72 @@ public Void visit(RenameTableStatement renameTableStatement, S context) {
return null;
}
+ @Override
+ public Void visit(PragmaStatement pragmaStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(ExtensionStatement extensionStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(AttachStatement attachStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(DetachStatement detachStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(ConnectStatement connectStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(DisconnectStatement disconnectStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(PrepareStatement prepareStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(DeallocateStatement deallocateStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(CopyStatement copyStatement, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(CreateMacro createMacro, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
+ @Override
+ public Void visit(CreateExtensionRepository createExtensionRepository, S context) {
+ // TODO: not yet implemented
+ return null;
+ }
+
@Override
public Void visit(PurgeStatement purgeStatement, S context) {
// TODO: not yet implemented
@@ -693,6 +770,50 @@ public void visit(RenameTableStatement renameTableStatement) {
visit(renameTableStatement, null);
}
+ public void visit(PragmaStatement pragmaStatement) {
+ visit(pragmaStatement, null);
+ }
+
+ public void visit(ExtensionStatement extensionStatement) {
+ visit(extensionStatement, null);
+ }
+
+ public void visit(AttachStatement attachStatement) {
+ visit(attachStatement, null);
+ }
+
+ public void visit(DetachStatement detachStatement) {
+ visit(detachStatement, null);
+ }
+
+ public void visit(ConnectStatement connectStatement) {
+ visit(connectStatement, null);
+ }
+
+ public void visit(DisconnectStatement disconnectStatement) {
+ visit(disconnectStatement, null);
+ }
+
+ public void visit(PrepareStatement prepareStatement) {
+ visit(prepareStatement, null);
+ }
+
+ public void visit(DeallocateStatement deallocateStatement) {
+ visit(deallocateStatement, null);
+ }
+
+ public void visit(CopyStatement copyStatement) {
+ visit(copyStatement, null);
+ }
+
+ public void visit(CreateMacro createMacro) {
+ visit(createMacro, null);
+ }
+
+ public void visit(CreateExtensionRepository createExtensionRepository) {
+ visit(createExtensionRepository, null);
+ }
+
public void visit(PurgeStatement purgeStatement) {
visit(purgeStatement, null);
}
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index 4ab8d1505..1d4e9cea8 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -60,6 +60,7 @@ import net.sf.jsqlparser.statement.create.extension.*;
import net.sf.jsqlparser.statement.create.event.*;
import net.sf.jsqlparser.statement.create.function.*;
import net.sf.jsqlparser.statement.create.index.*;
+import net.sf.jsqlparser.statement.create.macro.*;
import net.sf.jsqlparser.statement.create.policy.*;
import net.sf.jsqlparser.statement.create.procedure.*;
import net.sf.jsqlparser.statement.create.schema.*;
@@ -1063,9 +1064,11 @@ public class CCJSqlParser extends AbstractJSqlParser {
case K_CONNECT: return nextKind != K_BY;
case K_START: return nextKind != K_WITH;
case K_LEFT: return nextKind != K_JOIN && nextKind != K_OUTER
- && nextKind != K_SEMI && nextKind != K_ARRAY_LITERAL;
+ && nextKind != K_SEMI && nextKind != K_ANTI
+ && nextKind != K_ARRAY_LITERAL;
case K_RIGHT: return nextKind != K_JOIN && nextKind != K_OUTER
- && nextKind != K_SEMI && nextKind != K_ARRAY_LITERAL;
+ && nextKind != K_SEMI && nextKind != K_ANTI
+ && nextKind != K_ARRAY_LITERAL;
case K_ARRAY_LITERAL:
return nextKind != K_JOIN;
case K_ALL: return nextKind != K_JOIN;
@@ -1209,6 +1212,81 @@ public class CCJSqlParser extends AbstractJSqlParser {
return isReservedKeywordSafeByFollower();
}
+ /**
+ * DuckDB's FROM-first query: a FROM clause followed by query clauses the pipe syntax cannot
+ * consume. Scanning stops at a pipe operator or the end of the statement, and clause keywords
+ * nested in brackets (a sub-select, for instance) do not count.
+ */
+ private boolean isFromFirstSelectAhead() {
+ if (getToken(1).kind != K_FROM) {
+ return false;
+ }
+ int depth = 0;
+ for (int i = 2;; i++) {
+ Token token = getToken(i);
+ if (token == null || token.kind == EOF || ";".equals(token.image)
+ || "|>".equals(token.image)) {
+ return false;
+ }
+ if ("(".equals(token.image)) {
+ depth++;
+ } else if (")".equals(token.image)) {
+ depth--;
+ } else if (depth == 0) {
+ switch (token.kind) {
+ case K_SELECT:
+ case K_WHERE:
+ case K_GROUP:
+ case K_HAVING:
+ case K_QUALIFY:
+ case K_ORDER:
+ case K_LIMIT:
+ case K_OFFSET:
+ return true;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ /** CREATE [TEMP|TEMPORARY] MACRO, DuckDB's inlined function definition. */
+ private boolean isCreateMacroAhead() {
+ int offset = 1;
+ int kind = getToken(offset).kind;
+ if (kind == K_TEMP || kind == K_TEMPORARY) {
+ offset++;
+ }
+ return getToken(offset).kind == K_MACRO;
+ }
+
+ /** DuckDB sampling methods, which are ordinary identifiers except for SYSTEM/BERNOULLI. */
+ private boolean isSampleMethod(Token token) {
+ return token.kind == K_SYSTEM || token.kind == K_BERNOULLI
+ || (token.image != null && "RESERVOIR".equalsIgnoreCase(token.image));
+ }
+
+ /** DuckDB's INSERT ... BY NAME|POSITION column matching, never a table alias. */
+ private boolean isColumnMatchingAhead() {
+ return getToken(1).kind == K_BY
+ && ("NAME".equalsIgnoreCase(getToken(2).image)
+ || "POSITION".equalsIgnoreCase(getToken(2).image));
+ }
+
+ /** DuckDB 2.0's APPROX NEAREST similarity join, never a table alias. */
+ private boolean isApproxNearestAhead() {
+ return getToken(1).kind == K_APPROX && getToken(2).kind == K_NEAREST;
+ }
+
+ /** SEMI/ANTI stay contextual identifiers unless they prefix a JOIN. */
+ private boolean isSemiAntiJoinAhead() {
+ int kind = getToken(1).kind;
+ if (kind != K_SEMI && kind != K_ANTI) {
+ return false;
+ }
+ return getToken(2).kind == K_JOIN;
+ }
+
/** ASOF stays a contextual identifier outside a join prefix. */
private boolean isAsOfJoinAhead() {
if (!isKeywordAhead("ASOF")) {
@@ -1238,7 +1316,7 @@ public class CCJSqlParser extends AbstractJSqlParser {
* reserved keywords.
*/
private boolean isAliasAhead() {
- if (isAsOfJoinAhead()) {
+ if (isAsOfJoinAhead() || isSemiAntiJoinAhead() || isApproxNearestAhead()) {
return false;
}
Token t = getToken(1);
@@ -1480,6 +1558,8 @@ public class CCJSqlParser extends AbstractJSqlParser {
Token token = getToken(i);
if (token.kind == EOF || ";".equals(token.image) || token.kind == K_BEGIN) { return false; }
if (token.kind == K_EXECUTE && (getToken(i + 1).kind == K_FUNCTION || getToken(i + 1).kind == K_PROCEDURE)) { return true; }
+ // DuckDB 2.0 uses the standard header with a statement body instead of EXECUTE
+ if ("REFERENCING".equalsIgnoreCase(token.image) || token.kind == K_STATEMENT) { return true; }
}
}
@@ -1713,6 +1793,7 @@ public class CCJSqlParser extends AbstractJSqlParser {
case K_RLIKE: // RLIKE
case K_REGEXP_LIKE: // REGEXP_LIKE
case K_REGEXP: // REGEXP
+ case K_GLOB: // GLOB (DuckDB/SQLite)
case K_SIMILAR_TO: // SIMILAR TO (in LikeExpression)
case K_SIMILAR: // SIMILAR TO (in SimilarToExpression)
case K_MATCH_ANY: // MATCH_ANY
@@ -1929,8 +2010,10 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -1977,6 +2060,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -1994,6 +2078,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -2055,6 +2140,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -2072,6 +2158,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -2100,6 +2187,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -2116,6 +2204,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -2133,6 +2222,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -2174,6 +2264,8 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -2236,6 +2328,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=