Skip to content

Commit f574d96

Browse files
author
Jules Ivanic
committed
Keep lambda a plain identifier outside DuckDB lambdas
As a keyword token, LAMBDA stopped parsing where only plain identifiers are accepted: `CAST(a AS lambda)`, `COLLATE lambda`, `CREATE TABLE t (a lambda)`, `ALTER TABLE t DROP COLUMN lambda`, `ALTER TABLE t RENAME lambda TO x`. `isDuckDBLambdaAhead()` recognises `lambda name [, name ...] :` contextually, as `isKeywordAhead()` does for other words, so no token is needed.
1 parent e86fa61 commit f574d96

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,33 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
15991599
catch (IllegalArgumentException exception) { throw new ParseException("Unknown " + type.getSimpleName() + ": " + value); }
16001600
}
16011601

1602+
/**
1603+
* DuckDB's lambda x, y : expr. LAMBDA stays a plain identifier, so a column, type or alias
1604+
* named lambda keeps parsing: only LAMBDA followed by names separated by commas and a ":" is
1605+
* a lambda.
1606+
*/
1607+
private boolean isDuckDBLambdaAhead() {
1608+
if (getToken(1).kind != S_IDENTIFIER || !isKeywordAhead("LAMBDA")) {
1609+
return false;
1610+
}
1611+
int offset = 2;
1612+
while (true) {
1613+
int kind = getToken(offset).kind;
1614+
if (kind != S_IDENTIFIER && kind != S_QUOTED_IDENTIFIER && kind != DATA_TYPE
1615+
&& (kind < MIN_NON_RESERVED_WORD || kind > MAX_NON_RESERVED_WORD)) {
1616+
return false;
1617+
}
1618+
String next = getToken(offset + 1).image;
1619+
if (":".equals(next)) {
1620+
return true;
1621+
}
1622+
if (!",".equals(next)) {
1623+
return false;
1624+
}
1625+
offset += 2;
1626+
}
1627+
}
1628+
16021629
private boolean isKeywordAhead(String keyword) {
16031630
Token token = getToken(1);
16041631
return token.kind != EOF && token.image != null && keyword.equalsIgnoreCase(token.image);
@@ -2601,7 +2628,6 @@ String NonReservedWord() :
26012628
| tk=<K_KEYS:"KEYS">
26022629
| tk=<K_KILL:"KILL">
26032630
| tk=<K_FN:"FN">
2604-
| tk=<K_LAMBDA: "LAMBDA">
26052631
| tk=<K_LAST: "LAST">
26062632
| tk=<K_LEADING:"LEADING">
26072633
| tk=<K_LESS:"LESS">
@@ -11432,8 +11458,7 @@ Expression PrimaryExpression() #PrimaryExpression:
1143211458
retval=CharacterSetIntroducedString()
1143311459

1143411460
// DuckDB's lambda x, y : expr, before Column() which would take LAMBDA as a column name
11435-
| LOOKAHEAD( <K_LAMBDA> RelObjectName() ( "," RelObjectName() )* ":", {!interrupted} )
11436-
retval=DuckDBLambdaExpression()
11461+
| LOOKAHEAD({ !interrupted && isDuckDBLambdaAhead() }) retval=DuckDBLambdaExpression()
1143711462

1143811463
| LOOKAHEAD(2, {!interrupted}) retval=Column()
1143911464
[
@@ -13279,7 +13304,7 @@ LambdaExpression DuckDBLambdaExpression() #LambdaExpression:
1327913304
LambdaExpression lambdaExpression;
1328013305
}
1328113306
{
13282-
<K_LAMBDA> s = RelObjectName() { identifiers.add(s); }
13307+
<S_IDENTIFIER> s = RelObjectName() { identifiers.add(s); }
1328313308
( "," s = RelObjectName() { identifiers.add(s); } )*
1328413309
":"
1328513310
expression = Expression()

‎src/test/java/net/sf/jsqlparser/statement/select/DuckDBTest.java‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,10 +723,26 @@ void testLambdaKeywordExpression() throws JSQLParserException {
723723
Assertions.assertEquals(Arrays.asList("acc", "x"), lambda.getIdentifiers());
724724
}
725725

726+
@Test
727+
void testLambdaKeywordFollowedByArguments() throws JSQLParserException {
728+
PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(
729+
"SELECT f(l, lambda x : x + 1, 2) FROM t", true);
730+
Function function = select.getSelectItem(0).getExpression(Function.class);
731+
732+
Assertions.assertEquals(3, function.getParameters().size());
733+
Assertions.assertEquals("x + 1",
734+
((LambdaExpression) function.getParameters().get(1)).getExpression().toString());
735+
}
736+
726737
@Test
727738
void testLambdaAsIdentifier() throws JSQLParserException {
728739
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT lambda FROM t", true);
729740
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT lambda x, y FROM t", true);
730741
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT t.lambda FROM lambda t", true);
742+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT CAST(a AS lambda) FROM t", true);
743+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT 'a' COLLATE lambda FROM t", true);
744+
TestUtils.assertSqlCanBeParsedAndDeparsed("CREATE TABLE t (a lambda)", true);
745+
TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t DROP COLUMN lambda", true);
746+
TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t RENAME lambda TO x", true);
731747
}
732748
}

0 commit comments

Comments
 (0)