Skip to content

Commit

Permalink
feat: DuckDB STRUCT with curly brackets
Browse files Browse the repository at this point in the history
Signed-off-by: Andreas Reichel <andreas@manticore-projects.com>
  • Loading branch information
manticore-projects committed Mar 26, 2024
1 parent 4c187d5 commit 339d6ba
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 34 deletions.
59 changes: 48 additions & 11 deletions src/main/java/net/sf/jsqlparser/expression/StructType.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.sf.jsqlparser.statement.create.table.ColDataType;
import net.sf.jsqlparser.statement.select.SelectItem;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -31,20 +32,33 @@
*
*/
public class StructType extends ASTNodeAccessImpl implements Expression {
public enum Dialect { BIG_QUERY, DUCKDB };

public Dialect getDialect() {
return dialect;
}

public StructType setDialect(Dialect dialect) {
this.dialect = dialect;
return this;
}

private Dialect dialect = Dialect.BIG_QUERY;
private String keyword;
private List<Map.Entry<String, ColDataType>> parameters;
private List<SelectItem<?>> arguments;

public StructType(String keyword, List<Map.Entry<String, ColDataType>> parameters,
public StructType(Dialect dialect, String keyword, List<Map.Entry<String, ColDataType>> parameters,
List<SelectItem<?>> arguments) {
this.dialect = dialect;
this.keyword = keyword;
this.parameters = parameters;
this.arguments = arguments;
}

public StructType(List<Map.Entry<String, ColDataType>> parameters,
public StructType(Dialect dialect, List<Map.Entry<String, ColDataType>> parameters,
List<SelectItem<?>> arguments) {
this.keyword = "STRUCT";
this.dialect = dialect;
this.parameters = parameters;
this.arguments = arguments;
}
Expand Down Expand Up @@ -76,6 +90,15 @@ public StructType setArguments(List<SelectItem<?>> arguments) {
return this;
}

public StructType add(Expression expression, String aliasName) {
if (arguments==null) {
arguments= new ArrayList<>();
}
arguments.add(new SelectItem<>(expression, aliasName));

return this;
}

public StringBuilder appendTo(StringBuilder builder) {
if (keyword != null) {
builder.append(keyword);
Expand All @@ -102,17 +125,31 @@ public StringBuilder appendTo(StringBuilder builder) {
}

if (arguments != null && !arguments.isEmpty()) {
builder.append("(");
int i = 0;

for (SelectItem<?> e : arguments) {
if (0 < i++) {
builder.append(",");
if (dialect==Dialect.DUCKDB) {
builder.append("{ ");
int i = 0;
for (SelectItem<?> e : arguments) {
if (0 < i++) {
builder.append(",");
}
builder.append(e.getAlias().getName());
builder.append(":");
builder.append(e.getExpression());
}
builder.append(" }");
} else {
builder.append("(");
int i = 0;
for (SelectItem<?> e : arguments) {
if (0 < i++) {
builder.append(",");
}
e.appendTo(builder);
}
e.appendTo(builder);
}

builder.append(")");
builder.append(")");
}
}

return builder;
Expand Down
22 changes: 12 additions & 10 deletions src/main/java/net/sf/jsqlparser/parser/ASTNodeAccessImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ public StringBuilder appendTo(StringBuilder builder) {
final Set<String> punctuation = new TreeSet<>(Set.of(".", "[", "]"));

SimpleNode simpleNode = getASTNode();
Token token = simpleNode.jjtGetFirstToken();
Token lastToken = simpleNode.jjtGetLastToken();
Token prevToken = null;
while (token.next != null && token.absoluteEnd <= lastToken.absoluteEnd) {
if (!punctuation.contains(token.image)
&& (prevToken == null || !punctuation.contains(prevToken.image))) {
builder.append(" ");
if (simpleNode!=null) {
Token token = simpleNode.jjtGetFirstToken();
Token lastToken = simpleNode.jjtGetLastToken();
Token prevToken = null;
while (token.next!=null && token.absoluteEnd <= lastToken.absoluteEnd) {
if (!punctuation.contains(token.image)
&& (prevToken==null || !punctuation.contains(prevToken.image))) {
builder.append(" ");
}
builder.append(token.image);
prevToken = token;
token = token.next;
}
builder.append(token.image);
prevToken = token;
token = token.next;
}
return builder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1136,17 +1136,30 @@ public void visit(StructType structType) {
}

if (structType.getArguments() != null && !structType.getArguments().isEmpty()) {
buffer.append("(");
int i = 0;

for (SelectItem<?> e : structType.getArguments()) {
if (0 < i++) {
buffer.append(",");
if (structType.getDialect()==StructType.Dialect.DUCKDB) {
buffer.append("{ ");
int i = 0;
for (SelectItem<?> e : structType.getArguments()) {
if (0 < i++) {
buffer.append(",");
}
buffer.append(e.getAlias().getName());
buffer.append(":");
buffer.append(e.getExpression());
}
buffer.append(" }");
} else {
buffer.append("(");
int i = 0;
for (SelectItem<?> e : structType.getArguments()) {
if (0 < i++) {
buffer.append(",");
}
e.appendTo(buffer);
}
e.appendTo(buffer);
}

buffer.append(")");
buffer.append(")");
}
}
}

Expand Down
22 changes: 20 additions & 2 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,10 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
| <K_YAML:"YAML">
| <K_YES:"YES">
| <K_ZONE:"ZONE">

| <OPENING_CURLY_BRACKET: "{">
| <CLOSING_CURLY_BRACKET: "}">
| <DOUBLE_COLON: ":">
}

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

StructType StructType() #StruckType:
{
StructType.Dialect dialect = StructType.Dialect.BIG_QUERY;
Token tk1;
String keyword = "";
List<Map.Entry<String, ColDataType>> parameters = null;
List<SelectItem<?>> arguments = null;

String id = null;
Expression expression = null;
StructType type;
}
{
Expand All @@ -4535,13 +4541,25 @@ StructType StructType() #StruckType:
tk1=<K_STRUCT> { keyword = tk1.image; }
"(" arguments = SelectItemsList() ")"
)
|
(
<OPENING_CURLY_BRACKET> { arguments= new ArrayList<SelectItem<?>>(); dialect = StructType.Dialect.DUCKDB;}

id = RelObjectName() <DOUBLE_COLON> expression = Expression() { arguments.add( new SelectItem( expression, id) ); }

(
","
id = RelObjectName() <DOUBLE_COLON> expression = Expression() { arguments.add( new SelectItem( expression, id) ); }
)*
<CLOSING_CURLY_BRACKET>
)

// don't parse this as an Struct, but rather use an Expressionlist
// |
// arguments = StructArguments()
)
{
type = new StructType(keyword, parameters, arguments);
type = new StructType(dialect, keyword, parameters, arguments);
linkAST(type,jjtThis);
return type;
}
Expand Down
12 changes: 11 additions & 1 deletion src/test/java/net/sf/jsqlparser/expression/StructTypeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class StructTypeTest {
@Test
void testStructType() throws JSQLParserException {
void testStructTypeBigQuery() throws JSQLParserException {
String sqlStr = "SELECT t, len, FORMAT('%T', LPAD(t, len)) AS LPAD FROM UNNEST([\n" +
" STRUCT('abc' AS t, 5 AS len),\n" +
" ('abc', 2),\n" +
Expand All @@ -23,4 +23,14 @@ void testStructType() throws JSQLParserException {
sqlStr = "SELECT STRUCT<x int64, y string>(1, t.str_col)";
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}

@Test
void testStructTypeDuckDB() throws JSQLParserException {
//@todo: check why the white-space after the "{" is needed?!
String sqlStr = "SELECT { t:'abc',len:5}";
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);

sqlStr = "SELECT UNNEST({ t:'abc', len:5 })";
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}
}
2 changes: 1 addition & 1 deletion src/test/java/net/sf/jsqlparser/test/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class TestUtils {

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

/**
* @param statement
Expand Down

0 comments on commit 339d6ba

Please sign in to comment.