From 236793aaeabc30f80857b3347ce8e4a6b767e849 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Sat, 30 Mar 2024 13:51:02 +0700 Subject: [PATCH] feat: DuckDB Lambda Functions - at the moment, only one lambda parameter is supported, sorry Signed-off-by: Andreas Reichel --- .../expression/ExpressionVisitor.java | 2 + .../expression/ExpressionVisitorAdapter.java | 5 ++ .../expression/LambdaExpression.java | 57 +++++++++++++++++++ .../sf/jsqlparser/util/TablesNamesFinder.java | 6 ++ .../util/deparser/ExpressionDeParser.java | 18 ++++++ .../validator/ExpressionValidator.java | 6 ++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 32 ++++++++++- .../expression/LambdaExpressionTest.java | 29 ++++++++++ 8 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/sf/jsqlparser/expression/LambdaExpression.java create mode 100644 src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java index 426194d25..70a3ef356 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java @@ -247,4 +247,6 @@ public interface ExpressionVisitor { void visit(TSQLRightJoin tsqlRightJoin); void visit(StructType structType); + + void visit(LambdaExpression lambdaExpression); } diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index ceed08989..13ae35e20 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -702,4 +702,9 @@ public void visit(StructType structType) { } } + @Override + public void visit(LambdaExpression lambdaExpression) { + lambdaExpression.getExpression().accept(this); + } + } diff --git a/src/main/java/net/sf/jsqlparser/expression/LambdaExpression.java b/src/main/java/net/sf/jsqlparser/expression/LambdaExpression.java new file mode 100644 index 000000000..77aa86db0 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/LambdaExpression.java @@ -0,0 +1,57 @@ +package net.sf.jsqlparser.expression; + +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +import java.util.List; + +public class LambdaExpression extends ASTNodeAccessImpl implements Expression { + private List identifiers; + private Expression expression; + + public LambdaExpression(List identifiers, Expression expression) { + this.identifiers = identifiers; + this.expression = expression; + } + + public List getIdentifiers() { + return identifiers; + } + + public LambdaExpression setIdentifiers(List identifiers) { + this.identifiers = identifiers; + return this; + } + + public Expression getExpression() { + return expression; + } + + public LambdaExpression setExpression(Expression expression) { + this.expression = expression; + return this; + } + + public StringBuilder appendTo(StringBuilder builder) { + if (identifiers.size() == 1) { + builder.append(identifiers.get(0)); + } else { + int i = 0; + builder.append("( "); + for (String s : identifiers) { + builder.append(i++ > 0 ? ", " : "").append(s); + } + builder.append(" )"); + } + return builder.append(" -> ").append(expression); + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } + + @Override + public void accept(ExpressionVisitor expressionVisitor) { + expressionVisitor.visit(this); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index f7a9a5b53..043ef2199 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -41,6 +41,7 @@ import net.sf.jsqlparser.expression.JsonFunction; import net.sf.jsqlparser.expression.JsonFunctionExpression; import net.sf.jsqlparser.expression.KeepExpression; +import net.sf.jsqlparser.expression.LambdaExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.MySQLGroupConcat; import net.sf.jsqlparser.expression.NextValExpression; @@ -1187,6 +1188,11 @@ public void visit(StructType structType) { } } + @Override + public void visit(LambdaExpression lambdaExpression) { + lambdaExpression.getExpression().accept(this); + } + @Override public void visit(VariableAssignment var) { var.getVariable().accept(this); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java index 0439b397f..068509d25 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -35,6 +35,7 @@ import net.sf.jsqlparser.expression.JsonExpression; import net.sf.jsqlparser.expression.JsonFunction; import net.sf.jsqlparser.expression.KeepExpression; +import net.sf.jsqlparser.expression.LambdaExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.MySQLGroupConcat; import net.sf.jsqlparser.expression.NextValExpression; @@ -1179,4 +1180,21 @@ public void visit(StructType structType) { } } + @Override + public void visit(LambdaExpression lambdaExpression) { + if (lambdaExpression.getIdentifiers().size() == 1) { + buffer.append(lambdaExpression.getIdentifiers().get(0)); + } else { + int i = 0; + buffer.append("( "); + for (String s : lambdaExpression.getIdentifiers()) { + buffer.append(i++ > 0 ? ", " : "").append(s); + } + buffer.append(" )"); + } + + buffer.append(" -> "); + lambdaExpression.getExpression().accept(this); + } + } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java index f3c4de330..76f6b5d58 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java @@ -34,6 +34,7 @@ import net.sf.jsqlparser.expression.JsonExpression; import net.sf.jsqlparser.expression.JsonFunction; import net.sf.jsqlparser.expression.KeepExpression; +import net.sf.jsqlparser.expression.LambdaExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.MySQLGroupConcat; import net.sf.jsqlparser.expression.NextValExpression; @@ -719,4 +720,9 @@ public void visit(StructType structType) { } } } + + @Override + public void visit(LambdaExpression lambdaExpression) { + lambdaExpression.getExpression().accept(this); + } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index ad4b568dd..c8755ac66 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3879,7 +3879,7 @@ ExpressionList SimpleExpressionList(): } { expr=SimpleExpression() { expressions.add(expr); } - ( LOOKAHEAD(2, {!interrupted} ) "," expr=SimpleExpression() { expressions.add(expr); } )* + ( LOOKAHEAD(2, {!interrupted} ) "," ( LOOKAHEAD(2) expr=LambdaExpression() | expr=SimpleExpression()) { expressions.add(expr); } )* { return expressions; } @@ -3928,6 +3928,7 @@ ExpressionList ComplexExpressionList(): LOOKAHEAD(2, {!interrupted}) "," ( LOOKAHEAD(2) expr=OracleNamedFunctionParameter() + | LOOKAHEAD(2) expr=LambdaExpression() | expr=Expression() ) { expressions.add(expr); } )* @@ -5202,6 +5203,35 @@ FullTextSearch FullTextSearch() : { } } +LambdaExpression LambdaExpression() #LambdaExpression: +{ + String s; + ArrayList identifiers = new ArrayList(); + Expression expression; + LambdaExpression lambdaExpression; +} +{ +// wip, right now the Grammar works but collides with Multi Value Lists +// ( +// LOOKAHEAD(3) "(" +// s = RelObjectName() { identifiers.add(s); } +// ( "," s = RelObjectName() { identifiers.add(s); } )* +// ")" +// ) +// | + ( + s = RelObjectName() { identifiers.add(s); } + ) + + "->" + expression = Expression() + { + lambdaExpression = new LambdaExpression(identifiers, expression); + linkAST(lambdaExpression,jjtThis); + return lambdaExpression; + } +} + Function Function() #Function: { Function function; diff --git a/src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java new file mode 100644 index 000000000..c0e3a11e9 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/LambdaExpressionTest.java @@ -0,0 +1,29 @@ +package net.sf.jsqlparser.expression; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class LambdaExpressionTest { + + @Test + void testLambdaFunctionSingleParameter() throws JSQLParserException { + String sqlStr = "select list_transform( split('test', ''), x -> unicode(x) )"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + + @Disabled + @Test + // wip, right now the Grammar works but collides with Multi Value Lists + void testLambdaFunctionMultipleParameter() throws JSQLParserException { + String sqlStr = "SELECT list_transform(\n" + + " [1, 2, 3],\n" + + " x -> list_reduce([4, 5, 6], (a, b) -> a + b) + x\n" + + " )"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + +}