Skip to content

Commit 14db886

Browse files
authored
Add support PostgreSQL ROWS FROM table functions (#2416)
1 parent 82b7c26 commit 14db886

5 files changed

Lines changed: 118 additions & 21 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/select/TableFunction.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
*/
1010
package net.sf.jsqlparser.statement.select;
1111

12+
import java.util.List;
1213
import net.sf.jsqlparser.expression.Alias;
1314
import net.sf.jsqlparser.expression.Expression;
1415
import net.sf.jsqlparser.expression.Function;
16+
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
1517

1618
@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
1719
public class TableFunction extends Function implements FromItem {
@@ -20,6 +22,7 @@ public class TableFunction extends Function implements FromItem {
2022
private Pivot pivot = null;
2123
private UnPivot unPivot = null;
2224
private Function function;
25+
private ParenthesedExpressionList<Function> rowsFromFunctions;
2326
private String withClause = null;
2427

2528
public TableFunction(Function function) {
@@ -42,6 +45,27 @@ public TableFunction(String prefix, Function function, String withClause) {
4245
this.withClause = withClause;
4346
}
4447

48+
public TableFunction(ParenthesedExpressionList<Function> rowsFromFunctions) {
49+
this.rowsFromFunctions = rowsFromFunctions;
50+
}
51+
52+
public TableFunction(String prefix, ParenthesedExpressionList<Function> rowsFromFunctions) {
53+
this.prefix = prefix;
54+
this.rowsFromFunctions = rowsFromFunctions;
55+
}
56+
57+
public TableFunction(ParenthesedExpressionList<Function> rowsFromFunctions, String withClause) {
58+
this.rowsFromFunctions = rowsFromFunctions;
59+
this.withClause = withClause;
60+
}
61+
62+
public TableFunction(String prefix, ParenthesedExpressionList<Function> rowsFromFunctions,
63+
String withClause) {
64+
this.prefix = prefix;
65+
this.rowsFromFunctions = rowsFromFunctions;
66+
this.withClause = withClause;
67+
}
68+
4569
public TableFunction(String prefix, String name, Expression... parameters) {
4670
this.prefix = prefix;
4771
this.function = new Function(name, parameters);
@@ -57,9 +81,32 @@ public Function getFunction() {
5781

5882
public TableFunction setFunction(Function function) {
5983
this.function = function;
84+
this.rowsFromFunctions = null;
6085
return this;
6186
}
6287

88+
public ParenthesedExpressionList<Function> getRowsFromFunctions() {
89+
return rowsFromFunctions;
90+
}
91+
92+
public TableFunction setRowsFromFunctions(
93+
ParenthesedExpressionList<Function> rowsFromFunctions) {
94+
this.rowsFromFunctions = rowsFromFunctions;
95+
this.function = null;
96+
return this;
97+
}
98+
99+
public boolean isRowsFrom() {
100+
return rowsFromFunctions != null;
101+
}
102+
103+
public List<Function> getFunctions() {
104+
if (rowsFromFunctions != null) {
105+
return rowsFromFunctions;
106+
}
107+
return function != null ? List.of(function) : null;
108+
}
109+
63110
@Deprecated
64111
public Function getExpression() {
65112
return getFunction();
@@ -151,7 +198,11 @@ public StringBuilder appendTo(StringBuilder builder) {
151198
if (prefix != null) {
152199
builder.append(prefix).append(" ");
153200
}
154-
builder.append(function.toString());
201+
if (rowsFromFunctions != null) {
202+
builder.append("ROWS FROM ").append(rowsFromFunctions);
203+
} else {
204+
builder.append(function);
205+
}
155206

156207
if (withClause != null) {
157208
builder.append(" WITH ").append(withClause);

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,11 @@ public <S> Void visit(OracleHint hint, S context) {
13071307

13081308
@Override
13091309
public <S> Void visit(TableFunction tableFunction, S context) {
1310-
visit(tableFunction.getFunction(), null);
1310+
if (tableFunction.getFunctions() != null) {
1311+
for (var function : tableFunction.getFunctions()) {
1312+
visit(function, null);
1313+
}
1314+
}
13111315
return null;
13121316
}
13131317

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -805,18 +805,7 @@ public <S> StringBuilder visit(TableStatement tableStatement, S context) {
805805

806806
@Override
807807
public <S> StringBuilder visit(TableFunction tableFunction, S context) {
808-
if (tableFunction.getPrefix() != null) {
809-
builder.append(tableFunction.getPrefix()).append(" ");
810-
}
811-
tableFunction.getFunction().accept(this.expressionVisitor, context);
812-
813-
if (tableFunction.getWithClause() != null) {
814-
builder.append(" WITH ").append(tableFunction.getWithClause());
815-
}
816-
817-
if (tableFunction.getAlias() != null) {
818-
builder.append(tableFunction.getAlias());
819-
}
808+
tableFunction.appendTo(builder);
820809
return builder;
821810
}
822811

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

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6095,7 +6095,11 @@ FromItem FromItem() #FromItem:
60956095
&& getToken(3).kind == OPENING_BRACKET
60966096
}) fromItem=TableFunction()
60976097
|
6098-
LOOKAHEAD({ (isFunctionAhead() && getToken(1).kind != K_LATERAL)
6098+
LOOKAHEAD({
6099+
(getToken(1).kind == K_ROWS
6100+
&& getToken(2).kind == K_FROM
6101+
&& getToken(3).kind == OPENING_BRACKET)
6102+
|| (isFunctionAhead() && getToken(1).kind != K_LATERAL)
60996103
|| (getToken(1).kind == K_LATERAL && getToken(2).kind != OPENING_BRACKET) })
61006104
fromItem=TableFunction()
61016105
|
@@ -10153,12 +10157,15 @@ JsonTableFunction JsonTableBody() : {
1015310157
TableFunction TableFunction():
1015410158
{
1015510159
Token prefix = null;
10156-
Function function;
10160+
Function function = null;
10161+
ParenthesedExpressionList<Function> rowsFromFunctions = null;
1015710162
Token withClause = null;
1015810163
}
1015910164
{
1016010165
[ prefix = <K_LATERAL> ]
1016110166
(
10167+
LOOKAHEAD(3) <K_ROWS> <K_FROM> rowsFromFunctions = RowsFromFunctionList()
10168+
|
1016210169
LOOKAHEAD({
1016310170
getToken(1).kind == S_IDENTIFIER
1016410171
&& getToken(1).image.equalsIgnoreCase("JSON_TABLE")
@@ -10170,16 +10177,44 @@ TableFunction TableFunction():
1017010177
)
1017110178
[ LOOKAHEAD(2) <K_WITH> ( withClause = <K_OFFSET> | withClause = <K_ORDINALITY> ) ]
1017210179
{
10173-
return prefix!=null
10174-
? withClause!=null
10180+
if (rowsFromFunctions != null) {
10181+
return prefix != null
10182+
? withClause != null
10183+
? new TableFunction(prefix.image, rowsFromFunctions, withClause.image)
10184+
: new TableFunction(prefix.image, rowsFromFunctions)
10185+
: withClause != null
10186+
? new TableFunction(rowsFromFunctions, withClause.image)
10187+
: new TableFunction(rowsFromFunctions);
10188+
}
10189+
10190+
return prefix != null
10191+
? withClause != null
1017510192
? new TableFunction(prefix.image, function, withClause.image)
1017610193
: new TableFunction(prefix.image, function)
10177-
: withClause!=null
10194+
: withClause != null
1017810195
? new TableFunction(function, withClause.image)
1017910196
: new TableFunction(function);
1018010197
}
1018110198
}
1018210199

10200+
ParenthesedExpressionList<Function> RowsFromFunctionList():
10201+
{
10202+
ParenthesedExpressionList<Function> functions = new ParenthesedExpressionList<Function>();
10203+
Function function;
10204+
}
10205+
{
10206+
"("
10207+
function = Function() { functions.add(function); }
10208+
(
10209+
","
10210+
function = Function() { functions.add(function); }
10211+
)*
10212+
")"
10213+
{
10214+
return functions;
10215+
}
10216+
}
10217+
1018310218
List<Index.ColumnParams> ColumnNamesWithParamsList() : {
1018410219
List<Index.ColumnParams> colNames = new ArrayList<Index.ColumnParams>();
1018510220
String columnName;

src/test/java/net/sf/jsqlparser/statement/select/TableFunctionTest.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
*/
1010
package net.sf.jsqlparser.statement.select;
1111

12+
import static org.junit.jupiter.api.Assertions.*;
13+
1214
import net.sf.jsqlparser.JSQLParserException;
1315
import net.sf.jsqlparser.test.TestUtils;
1416
import org.junit.jupiter.api.Test;
1517
import org.junit.jupiter.params.ParameterizedTest;
1618
import org.junit.jupiter.params.provider.ValueSource;
1719

18-
import static org.junit.jupiter.api.Assertions.*;
19-
2020
class TableFunctionTest {
2121

2222
@Test
@@ -59,4 +59,22 @@ void testTableFunctionWithSupportedWithClauses(String withClause) throws JSQLPar
5959
String sqlStr = "SELECT * FROM UNNEST(ARRAY[1, 2, 3]) WITH " + withClause + " AS t(a, b)";
6060
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
6161
}
62+
63+
@Test
64+
void testRowsFromTableFunction() throws JSQLParserException {
65+
String sqlStr = "SELECT *\n"
66+
+ "FROM ROWS FROM (\n"
67+
+ " generate_series(1,3),\n"
68+
+ " generate_series(10,12)\n"
69+
+ ") AS t(a,b)";
70+
71+
PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
72+
TableFunction tableFunction = select.getFromItem(TableFunction.class);
73+
74+
assertTrue(tableFunction.isRowsFrom());
75+
assertNotNull(tableFunction.getRowsFromFunctions());
76+
assertEquals(2, tableFunction.getRowsFromFunctions().size());
77+
assertEquals("generate_series", tableFunction.getRowsFromFunctions().get(0).getName());
78+
assertEquals("generate_series", tableFunction.getRowsFromFunctions().get(1).getName());
79+
}
6280
}

0 commit comments

Comments
 (0)