Skip to content

Commit 339d6ba

Browse files
feat: DuckDB STRUCT with curly brackets
Signed-off-by: Andreas Reichel <andreas@manticore-projects.com>
1 parent 4c187d5 commit 339d6ba

File tree

6 files changed

+114
-34
lines changed

6 files changed

+114
-34
lines changed

src/main/java/net/sf/jsqlparser/expression/StructType.java

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import net.sf.jsqlparser.statement.create.table.ColDataType;
55
import net.sf.jsqlparser.statement.select.SelectItem;
66

7+
import java.util.ArrayList;
78
import java.util.List;
89
import java.util.Map;
910

@@ -31,20 +32,33 @@
3132
*
3233
*/
3334
public class StructType extends ASTNodeAccessImpl implements Expression {
35+
public enum Dialect { BIG_QUERY, DUCKDB };
36+
37+
public Dialect getDialect() {
38+
return dialect;
39+
}
40+
41+
public StructType setDialect(Dialect dialect) {
42+
this.dialect = dialect;
43+
return this;
44+
}
45+
46+
private Dialect dialect = Dialect.BIG_QUERY;
3447
private String keyword;
3548
private List<Map.Entry<String, ColDataType>> parameters;
3649
private List<SelectItem<?>> arguments;
3750

38-
public StructType(String keyword, List<Map.Entry<String, ColDataType>> parameters,
51+
public StructType(Dialect dialect, String keyword, List<Map.Entry<String, ColDataType>> parameters,
3952
List<SelectItem<?>> arguments) {
53+
this.dialect = dialect;
4054
this.keyword = keyword;
4155
this.parameters = parameters;
4256
this.arguments = arguments;
4357
}
4458

45-
public StructType(List<Map.Entry<String, ColDataType>> parameters,
59+
public StructType(Dialect dialect, List<Map.Entry<String, ColDataType>> parameters,
4660
List<SelectItem<?>> arguments) {
47-
this.keyword = "STRUCT";
61+
this.dialect = dialect;
4862
this.parameters = parameters;
4963
this.arguments = arguments;
5064
}
@@ -76,6 +90,15 @@ public StructType setArguments(List<SelectItem<?>> arguments) {
7690
return this;
7791
}
7892

93+
public StructType add(Expression expression, String aliasName) {
94+
if (arguments==null) {
95+
arguments= new ArrayList<>();
96+
}
97+
arguments.add(new SelectItem<>(expression, aliasName));
98+
99+
return this;
100+
}
101+
79102
public StringBuilder appendTo(StringBuilder builder) {
80103
if (keyword != null) {
81104
builder.append(keyword);
@@ -102,17 +125,31 @@ public StringBuilder appendTo(StringBuilder builder) {
102125
}
103126

104127
if (arguments != null && !arguments.isEmpty()) {
105-
builder.append("(");
106-
int i = 0;
107128

108-
for (SelectItem<?> e : arguments) {
109-
if (0 < i++) {
110-
builder.append(",");
129+
if (dialect==Dialect.DUCKDB) {
130+
builder.append("{ ");
131+
int i = 0;
132+
for (SelectItem<?> e : arguments) {
133+
if (0 < i++) {
134+
builder.append(",");
135+
}
136+
builder.append(e.getAlias().getName());
137+
builder.append(":");
138+
builder.append(e.getExpression());
139+
}
140+
builder.append(" }");
141+
} else {
142+
builder.append("(");
143+
int i = 0;
144+
for (SelectItem<?> e : arguments) {
145+
if (0 < i++) {
146+
builder.append(",");
147+
}
148+
e.appendTo(builder);
111149
}
112-
e.appendTo(builder);
113-
}
114150

115-
builder.append(")");
151+
builder.append(")");
152+
}
116153
}
117154

118155
return builder;

src/main/java/net/sf/jsqlparser/parser/ASTNodeAccessImpl.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,19 @@ public StringBuilder appendTo(StringBuilder builder) {
3131
final Set<String> punctuation = new TreeSet<>(Set.of(".", "[", "]"));
3232

3333
SimpleNode simpleNode = getASTNode();
34-
Token token = simpleNode.jjtGetFirstToken();
35-
Token lastToken = simpleNode.jjtGetLastToken();
36-
Token prevToken = null;
37-
while (token.next != null && token.absoluteEnd <= lastToken.absoluteEnd) {
38-
if (!punctuation.contains(token.image)
39-
&& (prevToken == null || !punctuation.contains(prevToken.image))) {
40-
builder.append(" ");
34+
if (simpleNode!=null) {
35+
Token token = simpleNode.jjtGetFirstToken();
36+
Token lastToken = simpleNode.jjtGetLastToken();
37+
Token prevToken = null;
38+
while (token.next!=null && token.absoluteEnd <= lastToken.absoluteEnd) {
39+
if (!punctuation.contains(token.image)
40+
&& (prevToken==null || !punctuation.contains(prevToken.image))) {
41+
builder.append(" ");
42+
}
43+
builder.append(token.image);
44+
prevToken = token;
45+
token = token.next;
4146
}
42-
builder.append(token.image);
43-
prevToken = token;
44-
token = token.next;
4547
}
4648
return builder;
4749
}

src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,17 +1136,30 @@ public void visit(StructType structType) {
11361136
}
11371137

11381138
if (structType.getArguments() != null && !structType.getArguments().isEmpty()) {
1139-
buffer.append("(");
1140-
int i = 0;
1141-
1142-
for (SelectItem<?> e : structType.getArguments()) {
1143-
if (0 < i++) {
1144-
buffer.append(",");
1139+
if (structType.getDialect()==StructType.Dialect.DUCKDB) {
1140+
buffer.append("{ ");
1141+
int i = 0;
1142+
for (SelectItem<?> e : structType.getArguments()) {
1143+
if (0 < i++) {
1144+
buffer.append(",");
1145+
}
1146+
buffer.append(e.getAlias().getName());
1147+
buffer.append(":");
1148+
buffer.append(e.getExpression());
1149+
}
1150+
buffer.append(" }");
1151+
} else {
1152+
buffer.append("(");
1153+
int i = 0;
1154+
for (SelectItem<?> e : structType.getArguments()) {
1155+
if (0 < i++) {
1156+
buffer.append(",");
1157+
}
1158+
e.appendTo(buffer);
11451159
}
1146-
e.appendTo(buffer);
1147-
}
11481160

1149-
buffer.append(")");
1161+
buffer.append(")");
1162+
}
11501163
}
11511164
}
11521165

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,10 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
487487
| <K_YAML:"YAML">
488488
| <K_YES:"YES">
489489
| <K_ZONE:"ZONE">
490+
491+
| <OPENING_CURLY_BRACKET: "{">
492+
| <CLOSING_CURLY_BRACKET: "}">
493+
| <DOUBLE_COLON: ":">
490494
}
491495

492496
TOKEN : /* Statement Separators */
@@ -4516,11 +4520,13 @@ List<Map.Entry<String, ColDataType>> StructParameters():
45164520

45174521
StructType StructType() #StruckType:
45184522
{
4523+
StructType.Dialect dialect = StructType.Dialect.BIG_QUERY;
45194524
Token tk1;
45204525
String keyword = "";
45214526
List<Map.Entry<String, ColDataType>> parameters = null;
45224527
List<SelectItem<?>> arguments = null;
4523-
4528+
String id = null;
4529+
Expression expression = null;
45244530
StructType type;
45254531
}
45264532
{
@@ -4535,13 +4541,25 @@ StructType StructType() #StruckType:
45354541
tk1=<K_STRUCT> { keyword = tk1.image; }
45364542
"(" arguments = SelectItemsList() ")"
45374543
)
4544+
|
4545+
(
4546+
<OPENING_CURLY_BRACKET> { arguments= new ArrayList<SelectItem<?>>(); dialect = StructType.Dialect.DUCKDB;}
4547+
4548+
id = RelObjectName() <DOUBLE_COLON> expression = Expression() { arguments.add( new SelectItem( expression, id) ); }
4549+
4550+
(
4551+
","
4552+
id = RelObjectName() <DOUBLE_COLON> expression = Expression() { arguments.add( new SelectItem( expression, id) ); }
4553+
)*
4554+
<CLOSING_CURLY_BRACKET>
4555+
)
45384556

45394557
// don't parse this as an Struct, but rather use an Expressionlist
45404558
// |
45414559
// arguments = StructArguments()
45424560
)
45434561
{
4544-
type = new StructType(keyword, parameters, arguments);
4562+
type = new StructType(dialect, keyword, parameters, arguments);
45454563
linkAST(type,jjtThis);
45464564
return type;
45474565
}

src/test/java/net/sf/jsqlparser/expression/StructTypeTest.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
class StructTypeTest {
88
@Test
9-
void testStructType() throws JSQLParserException {
9+
void testStructTypeBigQuery() throws JSQLParserException {
1010
String sqlStr = "SELECT t, len, FORMAT('%T', LPAD(t, len)) AS LPAD FROM UNNEST([\n" +
1111
" STRUCT('abc' AS t, 5 AS len),\n" +
1212
" ('abc', 2),\n" +
@@ -23,4 +23,14 @@ void testStructType() throws JSQLParserException {
2323
sqlStr = "SELECT STRUCT<x int64, y string>(1, t.str_col)";
2424
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
2525
}
26+
27+
@Test
28+
void testStructTypeDuckDB() throws JSQLParserException {
29+
//@todo: check why the white-space after the "{" is needed?!
30+
String sqlStr = "SELECT { t:'abc',len:5}";
31+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
32+
33+
sqlStr = "SELECT UNNEST({ t:'abc', len:5 })";
34+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
35+
}
2636
}

src/test/java/net/sf/jsqlparser/test/TestUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public class TestUtils {
5959

6060
// Assure SPACE around Syntax Characters
6161
private static final Pattern SQL_SANITATION_PATTERN2 =
62-
Pattern.compile("\\s*([!/,()=+\\-*|\\]<>:\\[\\]])\\s*", Pattern.MULTILINE);
62+
Pattern.compile("\\s*([!/,()=+\\-*|\\]<>:\\[\\]\\{\\}])\\s*", Pattern.MULTILINE);
6363

6464
/**
6565
* @param statement

0 commit comments

Comments
 (0)