Skip to content

Commit 4ecc8bc

Browse files
guizmaiiJules Ivanic
andauthored
Support the rest of DuckDB's CREATE MACRO syntax (#2783)
* 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. * Keep other dialects' CREATE FUNCTION off the macro route `AS` directly after the parameters with a non-string body was not enough to tell a DuckDB macro apart: PostgreSQL's `AS $function$ ... $function$ LANGUAGE ...` (a tagged dollar quote lexes as an identifier), `AS U&'...' LANGUAGE ...` and BigQuery's `AS (expr) OPTIONS (...)` became macros and failed. `isCreateFunctionMacroAhead()` also rejects a `$tag$` body and a LANGUAGE or OPTIONS clause outside parentheses before the end of the statement. - `setOverloads()` copies its argument, so a null or immutable list no longer breaks the first-overload setters. - The Javadoc notes that, as in DuckDB, a `,` after a TABLE body ending with a list continues that list. - `testCreateOverloadedMacro` no longer mixes scalar and table overloads, which DuckDB rejects; `testCreateOverloadedTableMacro` covers table overloads. --------- Co-authored-by: Jules Ivanic <jules.ivanic@feedzai.com>
1 parent 166f0a2 commit 4ecc8bc

4 files changed

Lines changed: 368 additions & 47 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/create/macro/CreateMacro.java‎

Lines changed: 173 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,37 @@
1212
import net.sf.jsqlparser.expression.Expression;
1313
import net.sf.jsqlparser.statement.Statement;
1414
import net.sf.jsqlparser.statement.StatementVisitor;
15+
import net.sf.jsqlparser.statement.create.table.ColDataType;
1516
import net.sf.jsqlparser.statement.select.Select;
1617

1718
import java.io.Serializable;
19+
import java.util.ArrayList;
1820
import java.util.List;
1921
import java.util.function.Consumer;
2022

2123
/**
22-
* DuckDB's {@code CREATE [OR REPLACE] [TEMPORARY] MACRO name (parameters) AS [TABLE] body}, a named
23-
* expression or query that is inlined at call sites.
24+
* DuckDB's
25+
* {@code CREATE [OR REPLACE] [TEMPORARY] {MACRO | FUNCTION} [IF NOT EXISTS] name (parameters) AS [TABLE] body [, (parameters) AS [TABLE] body ...]},
26+
* a named expression or query that is inlined at call sites, optionally overloaded on its
27+
* parameters.
28+
*
29+
* <p>
30+
* {@link #getParameters()}, {@link #getExpression()} and {@link #getSelect()} and their setters
31+
* address the first overload.
32+
*
33+
* <p>
34+
* As in DuckDB, a {@code ,} after a {@code TABLE} body that ends with a list (FROM, GROUP BY, ORDER
35+
* BY ...) continues that list rather than starting a new overload.
2436
*
2537
* @see <a href="https://duckdb.org/docs/stable/sql/statements/create_macro">CREATE MACRO</a>
2638
*/
2739
public class CreateMacro implements Statement {
2840
private boolean orReplace;
2941
private boolean temporary;
42+
private boolean functionKeyword;
43+
private boolean ifNotExists;
3044
private String name;
31-
private List<Parameter> parameters;
32-
private Expression expression;
33-
private Select select;
45+
private List<Overload> overloads = new ArrayList<>();
3446

3547
public boolean isOrReplace() {
3648
return orReplace;
@@ -58,6 +70,33 @@ public CreateMacro withTemporary(boolean temporary) {
5870
return this;
5971
}
6072

73+
/** Whether it was written {@code CREATE FUNCTION}, DuckDB's alias of {@code CREATE MACRO}. */
74+
public boolean isFunctionKeyword() {
75+
return functionKeyword;
76+
}
77+
78+
public void setFunctionKeyword(boolean functionKeyword) {
79+
this.functionKeyword = functionKeyword;
80+
}
81+
82+
public CreateMacro withFunctionKeyword(boolean functionKeyword) {
83+
setFunctionKeyword(functionKeyword);
84+
return this;
85+
}
86+
87+
public boolean isIfNotExists() {
88+
return ifNotExists;
89+
}
90+
91+
public void setIfNotExists(boolean ifNotExists) {
92+
this.ifNotExists = ifNotExists;
93+
}
94+
95+
public CreateMacro withIfNotExists(boolean ifNotExists) {
96+
setIfNotExists(ifNotExists);
97+
return this;
98+
}
99+
61100
public String getName() {
62101
return name;
63102
}
@@ -71,12 +110,33 @@ public CreateMacro withName(String name) {
71110
return this;
72111
}
73112

113+
public List<Overload> getOverloads() {
114+
return overloads;
115+
}
116+
117+
/** Copies {@code overloads}, so that the first-overload setters can add to an empty list. */
118+
public void setOverloads(List<Overload> overloads) {
119+
this.overloads = overloads == null ? new ArrayList<>() : new ArrayList<>(overloads);
120+
}
121+
122+
public CreateMacro withOverloads(List<Overload> overloads) {
123+
setOverloads(overloads);
124+
return this;
125+
}
126+
127+
private Overload firstOverload() {
128+
if (overloads.isEmpty()) {
129+
overloads.add(new Overload());
130+
}
131+
return overloads.get(0);
132+
}
133+
74134
public List<Parameter> getParameters() {
75-
return parameters;
135+
return overloads.isEmpty() ? null : overloads.get(0).getParameters();
76136
}
77137

78138
public void setParameters(List<Parameter> parameters) {
79-
this.parameters = parameters;
139+
firstOverload().setParameters(parameters);
80140
}
81141

82142
public CreateMacro withParameters(List<Parameter> parameters) {
@@ -85,11 +145,11 @@ public CreateMacro withParameters(List<Parameter> parameters) {
85145
}
86146

87147
public Expression getExpression() {
88-
return expression;
148+
return overloads.isEmpty() ? null : overloads.get(0).getExpression();
89149
}
90150

91151
public void setExpression(Expression expression) {
92-
this.expression = expression;
152+
firstOverload().setExpression(expression);
93153
}
94154

95155
public CreateMacro withExpression(Expression expression) {
@@ -98,11 +158,11 @@ public CreateMacro withExpression(Expression expression) {
98158
}
99159

100160
public Select getSelect() {
101-
return select;
161+
return overloads.isEmpty() ? null : overloads.get(0).getSelect();
102162
}
103163

104164
public void setSelect(Select select) {
105-
this.select = select;
165+
firstOverload().setSelect(select);
106166
}
107167

108168
public CreateMacro withSelect(Select select) {
@@ -112,7 +172,7 @@ public CreateMacro withSelect(Select select) {
112172

113173
/** A table macro returns a relation, a scalar macro an expression. */
114174
public boolean isTable() {
115-
return select != null;
175+
return getSelect() != null;
116176
}
117177

118178
public StringBuilder appendTo(StringBuilder builder) {
@@ -129,19 +189,16 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expres
129189
if (temporary) {
130190
builder.append("TEMPORARY ");
131191
}
132-
builder.append("MACRO ").append(name).append(" (");
133-
for (int i = 0; i < parameters.size(); i++) {
192+
builder.append(functionKeyword ? "FUNCTION " : "MACRO ");
193+
if (ifNotExists) {
194+
builder.append("IF NOT EXISTS ");
195+
}
196+
builder.append(name).append(" ");
197+
for (int i = 0; i < overloads.size(); i++) {
134198
if (i > 0) {
135199
builder.append(", ");
136200
}
137-
parameters.get(i).appendTo(builder, expressionRenderer);
138-
}
139-
builder.append(") AS ");
140-
if (select != null) {
141-
builder.append("TABLE ");
142-
selectRenderer.accept(select);
143-
} else {
144-
expressionRenderer.accept(expression);
201+
overloads.get(i).appendTo(builder, expressionRenderer, selectRenderer);
145202
}
146203
return builder;
147204
}
@@ -156,24 +213,113 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
156213
return statementVisitor.visit(this, context);
157214
}
158215

159-
/** A macro parameter, optionally carrying a default value ({@code b := 5}). */
216+
/** One {@code (parameters) AS [TABLE] body} of a macro. */
217+
public static class Overload implements Serializable {
218+
private List<Parameter> parameters = new ArrayList<>();
219+
private Expression expression;
220+
private Select select;
221+
222+
public List<Parameter> getParameters() {
223+
return parameters;
224+
}
225+
226+
public void setParameters(List<Parameter> parameters) {
227+
this.parameters = parameters;
228+
}
229+
230+
public Overload withParameters(List<Parameter> parameters) {
231+
setParameters(parameters);
232+
return this;
233+
}
234+
235+
public Expression getExpression() {
236+
return expression;
237+
}
238+
239+
public void setExpression(Expression expression) {
240+
this.expression = expression;
241+
}
242+
243+
public Overload withExpression(Expression expression) {
244+
setExpression(expression);
245+
return this;
246+
}
247+
248+
public Select getSelect() {
249+
return select;
250+
}
251+
252+
public void setSelect(Select select) {
253+
this.select = select;
254+
}
255+
256+
public Overload withSelect(Select select) {
257+
setSelect(select);
258+
return this;
259+
}
260+
261+
/** A table overload returns a relation, a scalar one an expression. */
262+
public boolean isTable() {
263+
return select != null;
264+
}
265+
266+
@Override
267+
public String toString() {
268+
StringBuilder builder = new StringBuilder();
269+
return appendTo(builder, builder::append, builder::append).toString();
270+
}
271+
272+
private StringBuilder appendTo(StringBuilder builder,
273+
Consumer<Expression> expressionRenderer, Consumer<Select> selectRenderer) {
274+
builder.append("(");
275+
for (int i = 0; i < parameters.size(); i++) {
276+
if (i > 0) {
277+
builder.append(", ");
278+
}
279+
parameters.get(i).appendTo(builder, expressionRenderer);
280+
}
281+
builder.append(") AS ");
282+
if (select != null) {
283+
builder.append("TABLE ");
284+
selectRenderer.accept(select);
285+
} else {
286+
expressionRenderer.accept(expression);
287+
}
288+
return builder;
289+
}
290+
}
291+
292+
/**
293+
* A macro parameter, optionally typed ({@code a INTEGER}) and carrying a default value
294+
* ({@code b := 5}).
295+
*/
160296
public static class Parameter implements Serializable {
161297
private final String name;
298+
private final ColDataType type;
162299
private final Expression defaultValue;
163300

164301
public Parameter(String name) {
165-
this(name, null);
302+
this(name, null, null);
166303
}
167304

168305
public Parameter(String name, Expression defaultValue) {
306+
this(name, null, defaultValue);
307+
}
308+
309+
public Parameter(String name, ColDataType type, Expression defaultValue) {
169310
this.name = name;
311+
this.type = type;
170312
this.defaultValue = defaultValue;
171313
}
172314

173315
public String getName() {
174316
return name;
175317
}
176318

319+
public ColDataType getType() {
320+
return type;
321+
}
322+
177323
public Expression getDefaultValue() {
178324
return defaultValue;
179325
}
@@ -187,6 +333,9 @@ public String toString() {
187333
private StringBuilder appendTo(StringBuilder builder,
188334
Consumer<Expression> expressionRenderer) {
189335
builder.append(name);
336+
if (type != null) {
337+
builder.append(" ").append(type);
338+
}
190339
if (defaultValue != null) {
191340
builder.append(" := ");
192341
expressionRenderer.accept(defaultValue);

‎src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2722,8 +2722,10 @@ public void visit(CopyStatement copyStatement) {
27222722

27232723
@Override
27242724
public <S> Void visit(CreateMacro createMacro, S context) {
2725-
if (createMacro.getSelect() != null) {
2726-
createMacro.getSelect().accept((SelectVisitor<?>) this, context);
2725+
for (CreateMacro.Overload overload : createMacro.getOverloads()) {
2726+
if (overload.getSelect() != null) {
2727+
overload.getSelect().accept((SelectVisitor<?>) this, context);
2728+
}
27272729
}
27282730
return null;
27292731
}

0 commit comments

Comments
 (0)