From f1763ddd456c41e78dd67b9e25b50c5fed8842a7 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Tue, 13 Oct 2026 11:06:41 +1100 Subject: [PATCH 1/2] Support the rest of DuckDB's CREATE MACRO syntax DuckDB's CREATE MACRO also takes `IF NOT EXISTS`, typed parameters (`a INTEGER`), several overloads (`name (a TINYINT) AS ..., (a INT) AS ...`) and `FUNCTION` as an alias of `MACRO`. All of these were rejected, and `CREATE FUNCTION f(a) AS a + 1` was parsed as an opaque `CreateFunction`. - `CreateMacro` keeps a list of `Overload`s, each with its parameters and body; `getParameters()`, `getExpression()`, `getSelect()` and their setters address the first one, as before. It also records `IF NOT EXISTS` and the `FUNCTION` keyword, and `Parameter` an optional type. - `CREATE FUNCTION` is routed to `CreateMacro` only when `AS` directly follows the parameters and the body is not a string, so PostgreSQL functions (`... RETURNS ...`, `... AS $$ ... $$ LANGUAGE ...`) keep their current parse. - `TablesNamesFinder` visits the queries of every overload. --- .../statement/create/macro/CreateMacro.java | 192 +++++++++++++++--- .../sf/jsqlparser/util/TablesNamesFinder.java | 6 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 107 +++++++--- .../statement/select/DuckDBTest.java | 68 +++++++ 4 files changed, 324 insertions(+), 49 deletions(-) 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 index 11aa3f5b1..5cf1f6697 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/macro/CreateMacro.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/macro/CreateMacro.java @@ -12,25 +12,33 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; +import net.sf.jsqlparser.statement.create.table.ColDataType; import net.sf.jsqlparser.statement.select.Select; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; /** - * DuckDB's {@code CREATE [OR REPLACE] [TEMPORARY] MACRO name (parameters) AS [TABLE] body}, a named - * expression or query that is inlined at call sites. + * DuckDB's + * {@code CREATE [OR REPLACE] [TEMPORARY] {MACRO | FUNCTION} [IF NOT EXISTS] name (parameters) AS [TABLE] body [, (parameters) AS [TABLE] body ...]}, + * a named expression or query that is inlined at call sites, optionally overloaded on its + * parameters. + * + *

+ * {@link #getParameters()}, {@link #getExpression()} and {@link #getSelect()} and their setters + * address the first overload. * * @see CREATE MACRO */ public class CreateMacro implements Statement { private boolean orReplace; private boolean temporary; + private boolean functionKeyword; + private boolean ifNotExists; private String name; - private List parameters; - private Expression expression; - private Select select; + private List overloads = new ArrayList<>(); public boolean isOrReplace() { return orReplace; @@ -58,6 +66,33 @@ public CreateMacro withTemporary(boolean temporary) { return this; } + /** Whether it was written {@code CREATE FUNCTION}, DuckDB's alias of {@code CREATE MACRO}. */ + public boolean isFunctionKeyword() { + return functionKeyword; + } + + public void setFunctionKeyword(boolean functionKeyword) { + this.functionKeyword = functionKeyword; + } + + public CreateMacro withFunctionKeyword(boolean functionKeyword) { + setFunctionKeyword(functionKeyword); + return this; + } + + public boolean isIfNotExists() { + return ifNotExists; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public CreateMacro withIfNotExists(boolean ifNotExists) { + setIfNotExists(ifNotExists); + return this; + } + public String getName() { return name; } @@ -71,12 +106,32 @@ public CreateMacro withName(String name) { return this; } + public List getOverloads() { + return overloads; + } + + public void setOverloads(List overloads) { + this.overloads = overloads; + } + + public CreateMacro withOverloads(List overloads) { + setOverloads(overloads); + return this; + } + + private Overload firstOverload() { + if (overloads.isEmpty()) { + overloads.add(new Overload()); + } + return overloads.get(0); + } + public List getParameters() { - return parameters; + return overloads.isEmpty() ? null : overloads.get(0).getParameters(); } public void setParameters(List parameters) { - this.parameters = parameters; + firstOverload().setParameters(parameters); } public CreateMacro withParameters(List parameters) { @@ -85,11 +140,11 @@ public CreateMacro withParameters(List parameters) { } public Expression getExpression() { - return expression; + return overloads.isEmpty() ? null : overloads.get(0).getExpression(); } public void setExpression(Expression expression) { - this.expression = expression; + firstOverload().setExpression(expression); } public CreateMacro withExpression(Expression expression) { @@ -98,11 +153,11 @@ public CreateMacro withExpression(Expression expression) { } public Select getSelect() { - return select; + return overloads.isEmpty() ? null : overloads.get(0).getSelect(); } public void setSelect(Select select) { - this.select = select; + firstOverload().setSelect(select); } public CreateMacro withSelect(Select select) { @@ -112,7 +167,7 @@ public CreateMacro withSelect(Select select) { /** A table macro returns a relation, a scalar macro an expression. */ public boolean isTable() { - return select != null; + return getSelect() != null; } public StringBuilder appendTo(StringBuilder builder) { @@ -129,19 +184,16 @@ public StringBuilder appendTo(StringBuilder builder, Consumer expres if (temporary) { builder.append("TEMPORARY "); } - builder.append("MACRO ").append(name).append(" ("); - for (int i = 0; i < parameters.size(); i++) { + builder.append(functionKeyword ? "FUNCTION " : "MACRO "); + if (ifNotExists) { + builder.append("IF NOT EXISTS "); + } + builder.append(name).append(" "); + for (int i = 0; i < overloads.size(); i++) { if (i > 0) { builder.append(", "); } - parameters.get(i).appendTo(builder, expressionRenderer); - } - builder.append(") AS "); - if (select != null) { - builder.append("TABLE "); - selectRenderer.accept(select); - } else { - expressionRenderer.accept(expression); + overloads.get(i).appendTo(builder, expressionRenderer, selectRenderer); } return builder; } @@ -156,17 +208,102 @@ public T accept(StatementVisitor statementVisitor, S context) { return statementVisitor.visit(this, context); } - /** A macro parameter, optionally carrying a default value ({@code b := 5}). */ + /** One {@code (parameters) AS [TABLE] body} of a macro. */ + public static class Overload implements Serializable { + private List parameters = new ArrayList<>(); + private Expression expression; + private Select select; + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + public Overload withParameters(List parameters) { + setParameters(parameters); + return this; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public Overload withExpression(Expression expression) { + setExpression(expression); + return this; + } + + public Select getSelect() { + return select; + } + + public void setSelect(Select select) { + this.select = select; + } + + public Overload withSelect(Select select) { + setSelect(select); + return this; + } + + /** A table overload returns a relation, a scalar one an expression. */ + public boolean isTable() { + return select != null; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + return appendTo(builder, builder::append, builder::append).toString(); + } + + private StringBuilder appendTo(StringBuilder builder, + Consumer expressionRenderer, Consumer