Skip to content

Commit 3e23858

Browse files
author
Jules Ivanic
committed
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.
1 parent 066f581 commit 3e23858

4 files changed

Lines changed: 324 additions & 49 deletions

File tree

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

Lines changed: 168 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,33 @@
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.
2432
*
2533
* @see <a href="https://duckdb.org/docs/stable/sql/statements/create_macro">CREATE MACRO</a>
2634
*/
2735
public class CreateMacro implements Statement {
2836
private boolean orReplace;
2937
private boolean temporary;
38+
private boolean functionKeyword;
39+
private boolean ifNotExists;
3040
private String name;
31-
private List<Parameter> parameters;
32-
private Expression expression;
33-
private Select select;
41+
private List<Overload> overloads = new ArrayList<>();
3442

3543
public boolean isOrReplace() {
3644
return orReplace;
@@ -58,6 +66,33 @@ public CreateMacro withTemporary(boolean temporary) {
5866
return this;
5967
}
6068

69+
/** Whether it was written {@code CREATE FUNCTION}, DuckDB's alias of {@code CREATE MACRO}. */
70+
public boolean isFunctionKeyword() {
71+
return functionKeyword;
72+
}
73+
74+
public void setFunctionKeyword(boolean functionKeyword) {
75+
this.functionKeyword = functionKeyword;
76+
}
77+
78+
public CreateMacro withFunctionKeyword(boolean functionKeyword) {
79+
setFunctionKeyword(functionKeyword);
80+
return this;
81+
}
82+
83+
public boolean isIfNotExists() {
84+
return ifNotExists;
85+
}
86+
87+
public void setIfNotExists(boolean ifNotExists) {
88+
this.ifNotExists = ifNotExists;
89+
}
90+
91+
public CreateMacro withIfNotExists(boolean ifNotExists) {
92+
setIfNotExists(ifNotExists);
93+
return this;
94+
}
95+
6196
public String getName() {
6297
return name;
6398
}
@@ -71,12 +106,32 @@ public CreateMacro withName(String name) {
71106
return this;
72107
}
73108

109+
public List<Overload> getOverloads() {
110+
return overloads;
111+
}
112+
113+
public void setOverloads(List<Overload> overloads) {
114+
this.overloads = overloads;
115+
}
116+
117+
public CreateMacro withOverloads(List<Overload> overloads) {
118+
setOverloads(overloads);
119+
return this;
120+
}
121+
122+
private Overload firstOverload() {
123+
if (overloads.isEmpty()) {
124+
overloads.add(new Overload());
125+
}
126+
return overloads.get(0);
127+
}
128+
74129
public List<Parameter> getParameters() {
75-
return parameters;
130+
return overloads.isEmpty() ? null : overloads.get(0).getParameters();
76131
}
77132

78133
public void setParameters(List<Parameter> parameters) {
79-
this.parameters = parameters;
134+
firstOverload().setParameters(parameters);
80135
}
81136

82137
public CreateMacro withParameters(List<Parameter> parameters) {
@@ -85,11 +140,11 @@ public CreateMacro withParameters(List<Parameter> parameters) {
85140
}
86141

87142
public Expression getExpression() {
88-
return expression;
143+
return overloads.isEmpty() ? null : overloads.get(0).getExpression();
89144
}
90145

91146
public void setExpression(Expression expression) {
92-
this.expression = expression;
147+
firstOverload().setExpression(expression);
93148
}
94149

95150
public CreateMacro withExpression(Expression expression) {
@@ -98,11 +153,11 @@ public CreateMacro withExpression(Expression expression) {
98153
}
99154

100155
public Select getSelect() {
101-
return select;
156+
return overloads.isEmpty() ? null : overloads.get(0).getSelect();
102157
}
103158

104159
public void setSelect(Select select) {
105-
this.select = select;
160+
firstOverload().setSelect(select);
106161
}
107162

108163
public CreateMacro withSelect(Select select) {
@@ -112,7 +167,7 @@ public CreateMacro withSelect(Select select) {
112167

113168
/** A table macro returns a relation, a scalar macro an expression. */
114169
public boolean isTable() {
115-
return select != null;
170+
return getSelect() != null;
116171
}
117172

118173
public StringBuilder appendTo(StringBuilder builder) {
@@ -129,19 +184,16 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expres
129184
if (temporary) {
130185
builder.append("TEMPORARY ");
131186
}
132-
builder.append("MACRO ").append(name).append(" (");
133-
for (int i = 0; i < parameters.size(); i++) {
187+
builder.append(functionKeyword ? "FUNCTION " : "MACRO ");
188+
if (ifNotExists) {
189+
builder.append("IF NOT EXISTS ");
190+
}
191+
builder.append(name).append(" ");
192+
for (int i = 0; i < overloads.size(); i++) {
134193
if (i > 0) {
135194
builder.append(", ");
136195
}
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);
196+
overloads.get(i).appendTo(builder, expressionRenderer, selectRenderer);
145197
}
146198
return builder;
147199
}
@@ -156,24 +208,113 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
156208
return statementVisitor.visit(this, context);
157209
}
158210

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

164296
public Parameter(String name) {
165-
this(name, null);
297+
this(name, null, null);
166298
}
167299

168300
public Parameter(String name, Expression defaultValue) {
301+
this(name, null, defaultValue);
302+
}
303+
304+
public Parameter(String name, ColDataType type, Expression defaultValue) {
169305
this.name = name;
306+
this.type = type;
170307
this.defaultValue = defaultValue;
171308
}
172309

173310
public String getName() {
174311
return name;
175312
}
176313

314+
public ColDataType getType() {
315+
return type;
316+
}
317+
177318
public Expression getDefaultValue() {
178319
return defaultValue;
179320
}
@@ -187,6 +328,9 @@ public String toString() {
187328
private StringBuilder appendTo(StringBuilder builder,
188329
Consumer<Expression> expressionRenderer) {
189330
builder.append(name);
331+
if (type != null) {
332+
builder.append(" ").append(type);
333+
}
190334
if (defaultValue != null) {
191335
builder.append(" := ");
192336
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)