From c60600dfee2b6fe6304abb7cceab49647a93d1d6 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 3 Jun 2021 07:51:33 +0700 Subject: [PATCH 1/4] Nested WithItems, fixes issue #1186 --- README.md | 2 +- nb-configuration.xml | 12 +- .../jsqlparser/statement/select/WithItem.java | 110 ++++++++++++-- .../sf/jsqlparser/util/TablesNamesFinder.java | 2 +- .../util/deparser/SelectDeParser.java | 38 ++++- .../validation/validator/SelectValidator.java | 2 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 15 +- .../statement/select/SelectTest.java | 143 ++++++++++-------- .../util/deparser/StatementDeParserTest.java | 17 ++- 9 files changed, 246 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 8c260f6a6..b37bcb397 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Also I would like to know about needed examples or documentation stuff. * support for **with** (ctl) for **delete**, **update** and **merge** * introduce a max depth to allow parsing complex expression lists without performance loss (thx to @manticore-projects) * allow all functions to have complex expressions as parameters (thx to @manticore-projects) -** API change FunctionWithCondParams production removed +* API change FunctionWithCondParams production removed * API change in ValuesStatement: the expression list is now hold as a ItemList and not as a List * support for parser modification within **parseExpression** and **parseCondExpression** * support for table schema for foreign keys diff --git a/nb-configuration.xml b/nb-configuration.xml index 8771b5deb..b58f89deb 100644 --- a/nb-configuration.xml +++ b/nb-configuration.xml @@ -20,7 +20,6 @@ false true JDK_1.8 - false none 4 4 @@ -28,5 +27,16 @@ 120 true project + true + 4 + netbeans-formatter + netbeans-formatter + netbeans-formatter + netbeans-formatter + false + netbeans-formatter + false + true + true diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java index d1ac98385..3d9a02f90 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java @@ -12,16 +12,72 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Optional; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.ItemsList; +import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList; public class WithItem implements SelectBody { private String name; private List withItemList; - private SelectBody selectBody; + private ItemsList itemsList; + private boolean useValues = true; + private boolean useBracketsForValues = false; + + private SubSelect subSelect; private boolean recursive; + /** + * Get the values (as VALUES (...) or SELECT) + * + * @return the values of the insert + */ + public ItemsList getItemsList() { + return itemsList; + } + + public void setItemsList(ItemsList list) { + itemsList = list; + } + + public boolean isUseValues() { + return useValues; + } + + public void setUseValues(boolean useValues) { + this.useValues = useValues; + } + + public WithItem withItemsList(ItemsList itemsList) { + this.setItemsList(itemsList); + return this; + } + + public E getItemsList(Class type) { + return type.cast(getItemsList()); + } + + public WithItem withUseValues(boolean useValues) { + this.setUseValues(useValues); + return this; + } + + public boolean isUsingBracketsForValues() { + return useBracketsForValues; + } + + public void setUseBracketsForValues(boolean useBracketsForValues) { + this.useBracketsForValues = useBracketsForValues; + } + + public WithItem withUseBracketsForValues(boolean useBracketsForValues) { + this.setUseBracketsForValues(useBracketsForValues); + return this; + } + public String getName() { return name; } @@ -38,12 +94,12 @@ public void setRecursive(boolean recursive) { this.recursive = recursive; } - public SelectBody getSelectBody() { - return selectBody; + public SubSelect getSubSelect() { + return subSelect.withUseBrackets(false); } - public void setSelectBody(SelectBody selectBody) { - this.selectBody = selectBody; + public void setSubSelect(SubSelect subSelect) { + this.subSelect = subSelect.withUseBrackets(false); } /** @@ -62,9 +118,39 @@ public void setWithItemList(List withItemList) { @Override @SuppressWarnings({"PMD.CyclomaticComplexity"}) public String toString() { - return (recursive ? "RECURSIVE " : "") + name + ((withItemList != null) ? " " + PlainSelect. - getStringList(withItemList, true, true) : "") - + " AS (" + selectBody + ")"; + StringBuilder builder = new StringBuilder(); + builder.append(recursive ? "RECURSIVE " : ""); + builder.append(name); + builder.append( + (withItemList != null) ? " " + PlainSelect.getStringList(withItemList, true, true) : ""); + builder.append(" AS "); + + if (useValues) { + builder.append("(VALUES "); + + if (itemsList instanceof MultiExpressionList) { + MultiExpressionList multiExpressionList = (MultiExpressionList) itemsList; + for (Iterator it = multiExpressionList.getExprList().iterator(); + it.hasNext();) { + builder.append( + PlainSelect.getStringList(it.next().getExpressions(), true, true)); + if (it.hasNext()) { + builder.append(", "); + } + } + } else if (itemsList instanceof ExpressionList) { + ExpressionList expressionList = (ExpressionList) itemsList; + builder.append( + PlainSelect.getStringList(expressionList.getExpressions(), true, useBracketsForValues)); + } + builder.append(")"); + } else { + builder.append(subSelect.isUseBrackets() ? "" : "("); + builder.append(subSelect); + + builder.append(subSelect.isUseBrackets() ? "" : ")"); + } + return builder.toString(); } @Override @@ -82,8 +168,8 @@ public WithItem withWithItemList(List withItemList) { return this; } - public WithItem withSelectBody(SelectBody selectBody) { - this.setSelectBody(selectBody); + public WithItem withSubSelect(SubSelect subSelect) { + this.setSubSelect(subSelect); return this; } @@ -104,7 +190,7 @@ public WithItem addWithItemList(Collection withItemList) { return this.withWithItemList(collection); } - public E getSelectBody(Class type) { - return type.cast(getSelectBody()); + public E getSubSelect(Class type) { + return type.cast(getSubSelect()); } } diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index eee11272e..fd4f704d0 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -147,7 +147,7 @@ public List getTableList(Expression expr) { @Override public void visit(WithItem withItem) { otherItemNames.add(withItem.getName().toLowerCase()); - withItem.getSelectBody().accept(this); + withItem.getSubSelect().accept((ItemsListVisitor) this); } @Override diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java index aa12b3aa5..79e1c0986 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -19,6 +19,7 @@ import net.sf.jsqlparser.expression.OracleHint; import net.sf.jsqlparser.expression.SQLServerHints; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.ItemsList; import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitor; import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList; import net.sf.jsqlparser.expression.operators.relational.NamedExpressionList; @@ -240,7 +241,7 @@ public void visit(SelectExpressionItem selectExpressionItem) { @Override public void visit(SubSelect subSelect) { - buffer.append("("); + buffer.append(subSelect.isUseBrackets() ? "(" : ""); if (subSelect.getWithItemsList() != null && !subSelect.getWithItemsList().isEmpty()) { buffer.append("WITH "); for (Iterator iter = subSelect.getWithItemsList().iterator(); iter.hasNext();) { @@ -253,7 +254,7 @@ public void visit(SubSelect subSelect) { } } subSelect.getSelectBody().accept(this); - buffer.append(")"); + buffer.append(subSelect.isUseBrackets() ? ")" : ""); Alias alias = subSelect.getAlias(); if (alias != null) { buffer.append(alias.toString()); @@ -484,9 +485,38 @@ public void visit(WithItem withItem) { if (withItem.getWithItemList() != null) { buffer.append(" ").append(PlainSelect.getStringList(withItem.getWithItemList(), true, true)); } - buffer.append(" AS ("); - withItem.getSelectBody().accept(this); + buffer.append(" AS "); + + if (withItem.isUseValues()) { + ItemsList itemsList = withItem.getItemsList(); + boolean useBracketsForValues = withItem.isUsingBracketsForValues(); + buffer.append("(VALUES "); + + if (itemsList instanceof MultiExpressionList) { + MultiExpressionList multiExpressionList = (MultiExpressionList) itemsList; + for (Iterator it = multiExpressionList.getExprList().iterator(); + it.hasNext(); ) { + buffer.append(PlainSelect.getStringList(it.next().getExpressions(), true, true)); + if (it.hasNext()) { + buffer.append(", "); + } + } + } else if (itemsList instanceof ExpressionList) { + ExpressionList expressionList = (ExpressionList) itemsList; + buffer.append( + PlainSelect.getStringList(expressionList.getExpressions(), true, useBracketsForValues)); + } + buffer.append(")"); + } else { + SubSelect subSelect = withItem.getSubSelect(); + if (!subSelect.isUseBrackets()) { + buffer.append("("); + } + subSelect.accept((FromItemVisitor) this); + if (!subSelect.isUseBrackets()) { buffer.append(")"); + } + } } @Override diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java index 7d45ce5a2..807b9df08 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java @@ -287,7 +287,7 @@ public void visit(WithItem withItem) { if (isNotEmpty(withItem.getWithItemList())) { withItem.getWithItemList().forEach(wi -> wi.accept(this)); } - withItem.getSelectBody().accept(this); + withItem.getSubSelect().accept(this); } @Override diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index f0953db01..0fa05248a 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1691,13 +1691,24 @@ WithItem WithItem() #WithItem: WithItem with = new WithItem(); String name = null; List selectItems = null; - SelectBody selectBody = null; + SubSelect select = null; + + ExpressionList simpleExpressionList; } { [ { with.setRecursive(true); } ] name=RelObjectName() { with.setName(name); } [ "(" selectItems=SelectItemsList() ")" { with.setWithItemList(selectItems); } ] - "(" selectBody = SelectBody() { with.setSelectBody(selectBody); } ")" + // if the next block looks alike an ExpressionList without Brackets, then parse as List + ( LOOKAHEAD( "(" SimpleExpressionList(true) ")" ) + "(" + simpleExpressionList = SimpleExpressionList(true) { with.withUseBracketsForValues(false).setItemsList(simpleExpressionList); } + ")" + + // Otherwise parse it as a SubSelect + | "(" select = SubSelect() { with.setSubSelect(select.withUseBrackets(false)); with.setUseValues(false); } ")" + + ) { return with; } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 04ca3b36d..e3f040066 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -960,7 +960,7 @@ public void testDistinct() throws JSQLParserException { assertEquals("myid", ((Column) ((SelectExpressionItem) plainSelect.getDistinct().getOnSelectItems(). get(0)).getExpression()) - .getColumnName()); + .getColumnName()); assertEquals("mycol", ((Column) ((SelectExpressionItem) plainSelect.getSelectItems().get(1)). getExpression()).getColumnName()); @@ -975,7 +975,7 @@ public void testDistinctTop() throws JSQLParserException { assertEquals("myid", ((Column) ((SelectExpressionItem) plainSelect.getSelectItems().get(0)). getExpression()) - .getColumnName()); + .getColumnName()); assertEquals("mycol", ((Column) ((SelectExpressionItem) plainSelect.getSelectItems().get(1)). getExpression()).getColumnName()); @@ -1020,7 +1020,7 @@ public void testJoin() throws JSQLParserException { assertEquals("tab1.id", ((Column) ((EqualsTo) plainSelect.getJoins().get(0).getOnExpression()). getLeftExpression()) - .getFullyQualifiedName()); + .getFullyQualifiedName()); assertTrue(plainSelect.getJoins().get(0).isOuter()); assertStatementCanBeDeparsedAs(select, statement); @@ -1278,7 +1278,7 @@ public void testOrderBy() throws JSQLParserException { assertEquals(2, plainSelect.getOrderByElements().size()); assertEquals("tab1.a", ((Column) plainSelect.getOrderByElements().get(0).getExpression()) - .getFullyQualifiedName()); + .getFullyQualifiedName()); assertEquals("b", ((Column) plainSelect.getOrderByElements().get(1).getExpression()).getColumnName()); assertTrue(plainSelect.getOrderByElements().get(1).isAsc()); @@ -1316,7 +1316,7 @@ public void testTimestamp() throws JSQLParserException { PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); assertEquals("2004-04-30 04:05:34.56", ((TimestampValue) ((GreaterThan) plainSelect.getWhere()).getRightExpression()). - getValue().toString()); + getValue().toString()); assertStatementCanBeDeparsedAs(select, statement); } @@ -1370,12 +1370,12 @@ public void testCase() throws JSQLParserException { + // "WHEN (SELECT c FROM tab2 WHERE d = 2) = 3 THEN 'AAA' " + "END) FROM tab1"; assertSqlCanBeParsedAndDeparsed(statement); - } + } - @Test - public void testNestedCaseCondition() throws JSQLParserException { - assertSqlCanBeParsedAndDeparsed("SELECT CASE WHEN CASE WHEN 1 THEN 10 ELSE 20 END > 15 THEN 'BBB' END FROM tab1"); - assertSqlCanBeParsedAndDeparsed("SELECT (CASE WHEN (CASE a WHEN 1 THEN 10 ELSE 20 END) > 15 THEN 'BBB' END) FROM tab1"); + @Test + public void testNestedCaseCondition() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("SELECT CASE WHEN CASE WHEN 1 THEN 10 ELSE 20 END > 15 THEN 'BBB' END FROM tab1"); + assertSqlCanBeParsedAndDeparsed("SELECT (CASE WHEN (CASE a WHEN 1 THEN 10 ELSE 20 END) > 15 THEN 'BBB' END) FROM tab1"); } @Test @@ -2549,12 +2549,12 @@ public void testRegexpMySQL() throws JSQLParserException { String stmt = "SELECT * FROM mytable WHERE first_name REGEXP '^Ste(v|ph)en$'"; assertSqlCanBeParsedAndDeparsed(stmt); } - + @Test public void testNotRegexpMySQLIssue887() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE first_name NOT REGEXP '^Ste(v|ph)en$'"); } - + @Test public void testNotRegexpMySQLIssue887_2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE NOT first_name REGEXP '^Ste(v|ph)en$'"); @@ -2571,7 +2571,6 @@ public void testXorCondition() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE field = value XOR other_value"); } - @Test public void testRlike() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE first_name RLIKE '^Ste(v|ph)en$'"); @@ -2736,10 +2735,10 @@ public void testJsonExpression() throws JSQLParserException { //The following staments can be parsed but not deparsed for (String statement : new String[]{ - "SELECT doc->'site_name' FROM websites WHERE doc @> '{\"tags\":[{\"term\":\"paris\"}, {\"term\":\"food\"}]}'", - "SELECT * FROM sales where sale ->'items' @> '[{\"count\":0}]'", - "SELECT * FROM sales where sale ->'items' ? 'name'", - "SELECT * FROM sales where sale ->'items' -# 'name'" + "SELECT doc->'site_name' FROM websites WHERE doc @> '{\"tags\":[{\"term\":\"paris\"}, {\"term\":\"food\"}]}'", + "SELECT * FROM sales where sale ->'items' @> '[{\"count\":0}]'", + "SELECT * FROM sales where sale ->'items' ? 'name'", + "SELECT * FROM sales where sale ->'items' -# 'name'" }) { Select select = (Select) parserManager.parse(new StringReader(statement)); assertStatementCanBeDeparsedAs(select, statement, true); @@ -2809,12 +2808,11 @@ public void testSelectOracleColl() throws JSQLParserException { public void testSelectInnerWith() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM (WITH actor AS (SELECT 'a' aid FROM DUAL) SELECT aid FROM actor)"); } - + // @Test // public void testSelectInnerWithAndUnionIssue1084() throws JSQLParserException { // assertSqlCanBeParsedAndDeparsed("WITH actor AS (SELECT 'b' aid FROM DUAL) SELECT aid FROM actor UNION WITH actor2 AS (SELECT 'a' aid FROM DUAL) SELECT aid FROM actor2"); // } - @Test public void testSelectInnerWithAndUnionIssue1084_2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("WITH actor AS (SELECT 'b' aid FROM DUAL) SELECT aid FROM actor UNION SELECT aid FROM actor2"); @@ -2971,7 +2969,7 @@ public void testOracleHint() throws JSQLParserException { + " b.application_id\n" + "FROM jl_br_journals j,\n" + " po_vendors p", true, "ORDERED INDEX (b, jl_br_balances_n1) USE_NL (j b) \n" - + " USE_NL (glcc glf) USE_MERGE (gp gsb)"); + + " USE_NL (glcc glf) USE_MERGE (gp gsb)"); assertOracleHintExists("SELECT /*+ROWID(emp)*/ /*+ THIS IS NOT HINT! ***/ * \n" + "FROM emp \n" + "WHERE rowid > 'AAAAtkAABAAAFNTAAA' AND empno = 155", false, "ROWID(emp)"); @@ -3751,7 +3749,7 @@ public void testFuncConditionParameter2() throws JSQLParserException { public void testFuncConditionParameter3() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT CAST((MAX(CAST(IIF(isnumeric(license_no) = 1, license_no, 0) AS INT)) + 2) AS varchar) FROM lcps.t_license WHERE profession_id = 60 and license_type = 100 and YEAR(issue_date) % 2 = case when YEAR(issue_date) % 2 = 0 then 0 else 1 end and ISNUMERIC(license_no) = 1", true); } - + @Test public void testFuncConditionParameter4() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT IIF(isnumeric(license_no) = 1, license_no, 0) FROM mytable", true); @@ -3945,7 +3943,7 @@ public void visit(Select select) { @Override public void visit(PlainSelect plainSelect) { SelectExpressionItem typedExpression - = (SelectExpressionItem) plainSelect.getSelectItems().get(0); + = (SelectExpressionItem) plainSelect.getSelectItems().get(0); assertNotNull(typedExpression); assertNull(typedExpression.getAlias()); StringValue value = (StringValue) typedExpression.getExpression(); @@ -4203,13 +4201,13 @@ public void testWrongParseTreeIssue89() throws JSQLParserException { SetOperationList unionQueries = (SetOperationList) unionQuery.getSelectBody(); assertThat(unionQueries.getSelects()) - .extracting(select -> (PlainSelect) select).allSatisfy(ps -> assertNull(ps.getOrderByElements())); + .extracting(select -> (PlainSelect) select).allSatisfy(ps -> assertNull(ps.getOrderByElements())); assertThat(unionQueries.getOrderByElements()) - .isNotNull() - .hasSize(1) - .extracting(item -> item.toString()) - .contains("col"); + .isNotNull() + .hasSize(1) + .extracting(item -> item.toString()) + .contains("col"); } @Test @@ -4250,7 +4248,6 @@ public void testTableFunctionInExprIssue923() throws JSQLParserException { // .replace("@Prompt", "MyFunc"); // assertSqlCanBeParsedAndDeparsed(stmt, true); // } - @Test public void testTableFunctionInExprIssue923_3() throws JSQLParserException, IOException { String stmt = IOUtils.toString( @@ -4301,7 +4298,7 @@ public void testPreserveAndOperator() throws JSQLParserException { new Select().withSelectBody(new PlainSelect() .addSelectItems(Collections.singleton(new AllColumns())) .withFromItem(new Table("mytable")).withWhere( - new AndExpression().withUseOperator(true) + new AndExpression().withUseOperator(true) .withLeftExpression(new EqualsTo(new LongValue(1), new LongValue(2))) .withRightExpression(new EqualsTo(new LongValue(2), new LongValue(3))))), statement); @@ -4351,7 +4348,7 @@ public void testVariableAssignment3() throws JSQLParserException { public void testKeyWordOfIssue1029() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT of.Full_Name_c AS FullName FROM comdb.Offer_c AS of"); } - + @Test public void testKeyWordExceptIssue1026() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM xxx WHERE exclude = 1"); @@ -4368,32 +4365,32 @@ public void testSelectConditionsIssue720And991() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT 1 < 2 AS a, 0 IS NULL AS b"); // assertSqlCanBeParsedAndDeparsed("SELECT 1 < 2 AS a, (0 IS NULL) AS b"); } - + @Test public void testKeyWordExceptIssue1040() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT FORMAT(100000, 2)"); } - + @Test public void testKeyWordExceptIssue1044() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT SP_ID FROM ST_PR WHERE INSTR(',' || SP_OFF || ',', ',' || ? || ',') > 0"); } - + @Test public void testKeyWordExceptIssue1055() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT INTERVAL ? DAY"); } - + @Test public void testKeyWordExceptIssue1055_2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE A.end_time > now() AND A.end_time <= date_add(now(), INTERVAL ? DAY)"); } - + @Test public void testIssue1062() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE temperature.timestamp <= @to AND temperature.timestamp >= @from"); } - + @Test public void testIssue1062_2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM mytable WHERE temperature.timestamp <= @until AND temperature.timestamp >= @from"); @@ -4403,101 +4400,101 @@ public void testIssue1062_2() throws JSQLParserException { public void testIssue1068() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT t2.c AS div"); } - + @Test public void selectWithSingleIn() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT 1 FROM dual WHERE a IN 1"); } - + @Test public void testKeywordSequenceIssue1075() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT a.sequence FROM all_procedures a"); } - + @Test public void testKeywordSequenceIssue1074() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM t_user WITH (NOLOCK)"); } - + @Test public void testContionItemsSelectedIssue1077() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT 1 > 0"); } - + @Test public void testExistsKeywordIssue1076() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT EXISTS (4)"); } - + @Test public void testExistsKeywordIssue1076_1() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT mycol, EXISTS (SELECT mycol FROM mytable) mycol2 FROM mytable"); } - + @Test public void testFormatKeywordIssue1078() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT FORMAT(date, 'yyyy-MM') AS year_month FROM mine_table"); } - + @Test public void testConditionalParametersForFunctions() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT myFunc(SELECT mycol FROM mytable)"); } - + @Test public void testCreateTableWithParameterDefaultFalseIssue1088() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT p.*, rhp.house_id FROM rel_house_person rhp INNER JOIN person p ON rhp.person_id = p.if WHERE rhp.house_id IN (SELECT house_id FROM rel_house_person WHERE person_id = :personId AND current_occupant = :current) AND rhp.current_occupant = :currentOccupant"); } - + @Test public void testMissingLimitKeywordIssue1006() throws JSQLParserException { Statement stmt = CCJSqlParserUtil.parse("SELECT id, name FROM test OFFSET 20 LIMIT 10"); assertEquals("SELECT id, name FROM test LIMIT 10 OFFSET 20", stmt.toString()); } - + @Test public void testKeywordUnsignedIssue961() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT COLUMN1, COLUMN2, CASE WHEN COLUMN1.DATA NOT IN ('1', '3') THEN CASE WHEN CAST(COLUMN2 AS UNSIGNED) IN ('1', '2', '3') THEN 'Q1' ELSE 'Q2' END END AS YEAR FROM TESTTABLE"); } - + @Test public void testH2CaseWhenFunctionIssue1091() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT CASEWHEN(ID = 1, 'A', 'B') FROM mytable"); } - + @Test public void testMultiPartTypesIssue992() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT CAST('*' AS pg_catalog.text)"); } - + @Test public void testSetOperationWithParenthesisIssue1094() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM ((SELECT A FROM tbl) UNION DISTINCT (SELECT B FROM tbl2)) AS union1"); } - + @Test public void testSetOperationWithParenthesisIssue1094_2() throws JSQLParserException { Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM (((SELECT A FROM tbl)) UNION DISTINCT (SELECT B FROM tbl2)) AS union1"); assertEquals("SELECT * FROM ((SELECT A FROM tbl) UNION DISTINCT (SELECT B FROM tbl2)) AS union1", stmt.toString()); } - + @Test public void testSetOperationWithParenthesisIssue1094_3() throws JSQLParserException { Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM (((SELECT A FROM tbl)) UNION DISTINCT ((SELECT B FROM tbl2))) AS union1"); assertEquals("SELECT * FROM ((SELECT A FROM tbl) UNION DISTINCT ((SELECT B FROM tbl2))) AS union1", stmt.toString()); } - + @Test public void testSetOperationWithParenthesisIssue1094_4() throws JSQLParserException { Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM (((((SELECT A FROM tbl)))) UNION DISTINCT (((((((SELECT B FROM tbl2)))))))) AS union1"); assertEquals("SELECT * FROM ((SELECT A FROM tbl) UNION DISTINCT ((SELECT B FROM tbl2))) AS union1", stmt.toString()); } - + @Test public void testSignedKeywordIssue1100() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT signed, unsigned FROM mytable"); } - + @Test public void testSignedKeywordIssue995() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT leading FROM prd_reprint"); @@ -4518,17 +4515,17 @@ public void testColonDelimiterIssue1134() throws JSQLParserException { Statement stmt = CCJSqlParserUtil.parse("SELECT * FROM stores_demo:informix.accounts"); assertEquals("SELECT * FROM stores_demo.informix.accounts", stmt.toString()); } - + @Test public void testKeywordSkipIssue1136() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT skip"); } - + @Test public void testKeywordAlgorithmIssue1137() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT algorithm FROM tablename"); } - + @Test public void testKeywordAlgorithmIssue1138() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM in.tablename"); @@ -4538,22 +4535,22 @@ public void testKeywordAlgorithmIssue1138() throws JSQLParserException { public void testFunctionOrderBy() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT array_agg(DISTINCT s ORDER BY b)[1] FROM t"); } - + @Test public void testProblematicDeparsingIssue1183() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT ARRAY_AGG(NAME ORDER BY ID) FILTER (WHERE NAME IS NOT NULL)"); } - + @Test public void testProblematicDeparsingIssue1183_2() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID)"); } - + @Test public void testKeywordCostsIssue1185() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("WITH costs AS (SELECT * FROM MY_TABLE1 AS ALIAS_TABLE1) SELECT * FROM TESTSTMT"); } - + @Test public void testFunctionWithComplexParameters_Issue1190() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT to_char(a = '3') FROM dual", true); @@ -4563,16 +4560,32 @@ public void testFunctionWithComplexParameters_Issue1190() throws JSQLParserExcep public void testConditionsWithExtraBrackets_Issue1194() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT (col IS NULL) FROM tbl", true); } - + public void testWithValueListWithExtraBrackets1135() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("with sample_data(day, value) as (values ((0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16))) select day, value from sample_data", true); } - + @Test public void testWithValueListWithOutExtraBrackets1135() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("with sample_data(\"DAY\") as (values 0, 1, 2)\n" + + " select \"DAY\" from sample_data", true); assertSqlCanBeParsedAndDeparsed("with sample_data(day, value) as (values (0, 13), (1, 12), (2, 15), (3, 4), (4, 8), (5, 16)) select day, value from sample_data", true); } - + + @Test + public void testWithInsideWithIssue1186() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "WITH TESTSTMT1 AS ( WITH TESTSTMT2 AS (SELECT * FROM MY_TABLE2) SELECT col1, col2 FROM TESTSTMT2) SELECT * FROM TESTSTMT", + true); + } + + @Test + public void testWithWithValueList1135_SimpleExpressionListWithBrackets() + throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed("with sample_data(\"DAY\") as (values (0, 1, 2))\n" + + " select \"DAY\" from sample_data", true); + } + @Test public void testKeywordSynonymIssue1211() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("select businessDate as \"bd\", synonym as \"synonym\" from sc.tab", true); diff --git a/src/test/java/net/sf/jsqlparser/util/deparser/StatementDeParserTest.java b/src/test/java/net/sf/jsqlparser/util/deparser/StatementDeParserTest.java index 724e1f893..6273b6cc4 100644 --- a/src/test/java/net/sf/jsqlparser/util/deparser/StatementDeParserTest.java +++ b/src/test/java/net/sf/jsqlparser/util/deparser/StatementDeParserTest.java @@ -35,6 +35,7 @@ import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SelectBody; +import net.sf.jsqlparser.statement.select.SubSelect; import net.sf.jsqlparser.statement.select.WithItem; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.upsert.Upsert; @@ -96,8 +97,8 @@ public void shouldUseProvidedDeparsersWhenDeParsingInsert() throws JSQLParserExc List withItemsList = new ArrayList(); WithItem withItem1 = spy(new WithItem()); WithItem withItem2 = spy(new WithItem()); - SelectBody withItem1SelectBody = mock(SelectBody.class); - SelectBody withItem2SelectBody = mock(SelectBody.class); + SubSelect withItem1SubSelect = mock(SubSelect.class); + SubSelect withItem2SubSelect = mock(SubSelect.class); SelectBody selectBody = mock(SelectBody.class); insert.setSelect(select); @@ -114,8 +115,8 @@ public void shouldUseProvidedDeparsersWhenDeParsingInsert() throws JSQLParserExc select.setSelectBody(selectBody); withItemsList.add(withItem1); withItemsList.add(withItem2); - withItem1.setSelectBody(withItem1SelectBody); - withItem2.setSelectBody(withItem2SelectBody); + withItem1.setSubSelect(withItem1SubSelect); + withItem2.setSubSelect(withItem2SubSelect); statementDeParser.visit(insert); @@ -329,8 +330,8 @@ public void shouldUseProvidedDeparsersWhenDeParsingUpsertWithExpressionList() th List withItemsList = new ArrayList(); WithItem withItem1 = spy(new WithItem()); WithItem withItem2 = spy(new WithItem()); - SelectBody withItem1SelectBody = mock(SelectBody.class); - SelectBody withItem2SelectBody = mock(SelectBody.class); + SubSelect withItem1SubSelect = mock(SubSelect.class); + SubSelect withItem2SubSelect = mock(SubSelect.class); SelectBody selectBody = mock(SelectBody.class); upsert.setSelect(select); @@ -347,8 +348,8 @@ public void shouldUseProvidedDeparsersWhenDeParsingUpsertWithExpressionList() th select.setSelectBody(selectBody); withItemsList.add(withItem1); withItemsList.add(withItem2); - withItem1.setSelectBody(withItem1SelectBody); - withItem2.setSelectBody(withItem2SelectBody); + withItem1.setSubSelect(withItem1SubSelect); + withItem2.setSubSelect(withItem2SubSelect); statementDeParser.visit(upsert); From 345e395de4bf838a65ee6a6b4ba8ab2ba9b15c01 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 3 Jun 2021 08:10:11 +0700 Subject: [PATCH 2/4] Remove redundant Test Avoid altering the nb-configuration --- nb-configuration.xml | 12 +----------- .../sf/jsqlparser/statement/select/SelectTest.java | 7 ------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/nb-configuration.xml b/nb-configuration.xml index b58f89deb..8771b5deb 100644 --- a/nb-configuration.xml +++ b/nb-configuration.xml @@ -20,6 +20,7 @@ false true JDK_1.8 + false none 4 4 @@ -27,16 +28,5 @@ 120 true project - true - 4 - netbeans-formatter - netbeans-formatter - netbeans-formatter - netbeans-formatter - false - netbeans-formatter - false - true - true diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index e3f040066..5e8cff630 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -4579,13 +4579,6 @@ public void testWithInsideWithIssue1186() throws JSQLParserException { true); } - @Test - public void testWithWithValueList1135_SimpleExpressionListWithBrackets() - throws JSQLParserException { - assertSqlCanBeParsedAndDeparsed("with sample_data(\"DAY\") as (values (0, 1, 2))\n" - + " select \"DAY\" from sample_data", true); - } - @Test public void testKeywordSynonymIssue1211() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("select businessDate as \"bd\", synonym as \"synonym\" from sc.tab", true); From 1f9567fe730719133416d008354419c5183c45a8 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 3 Jun 2021 08:12:48 +0700 Subject: [PATCH 3/4] Mention Nested WITH CTEs in the readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b37bcb397..3eb909c14 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ Also I would like to know about needed examples or documentation stuff. ## Extensions in the latest SNAPSHOT version 4.1 -* support for **with** (ctl) for **delete**, **update** and **merge** +* support for nested `WITH` CTEs +* support for **with** (cte) for **delete**, **update** and **merge** * introduce a max depth to allow parsing complex expression lists without performance loss (thx to @manticore-projects) * allow all functions to have complex expressions as parameters (thx to @manticore-projects) * API change FunctionWithCondParams production removed From 5b27be31052ff830bd3aa479eae6318ad480d8ef Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 3 Jun 2021 08:35:03 +0700 Subject: [PATCH 4/4] Eliminate dead/unused MultiExpression Code --- .../jsqlparser/statement/select/WithItem.java | 29 ++--------- .../util/deparser/SelectDeParser.java | 51 ++++++++----------- 2 files changed, 23 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java index 3d9a02f90..e3cb63494 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/WithItem.java @@ -12,12 +12,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Optional; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ItemsList; -import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList; public class WithItem implements SelectBody { @@ -56,10 +54,6 @@ public WithItem withItemsList(ItemsList itemsList) { return this; } - public E getItemsList(Class type) { - return type.cast(getItemsList()); - } - public WithItem withUseValues(boolean useValues) { this.setUseValues(useValues); return this; @@ -127,22 +121,9 @@ public String toString() { if (useValues) { builder.append("(VALUES "); - - if (itemsList instanceof MultiExpressionList) { - MultiExpressionList multiExpressionList = (MultiExpressionList) itemsList; - for (Iterator it = multiExpressionList.getExprList().iterator(); - it.hasNext();) { - builder.append( - PlainSelect.getStringList(it.next().getExpressions(), true, true)); - if (it.hasNext()) { - builder.append(", "); - } - } - } else if (itemsList instanceof ExpressionList) { - ExpressionList expressionList = (ExpressionList) itemsList; - builder.append( - PlainSelect.getStringList(expressionList.getExpressions(), true, useBracketsForValues)); - } + ExpressionList expressionList = (ExpressionList) itemsList; + builder.append( + PlainSelect.getStringList(expressionList.getExpressions(), true, useBracketsForValues)); builder.append(")"); } else { builder.append(subSelect.isUseBrackets() ? "" : "("); @@ -189,8 +170,4 @@ public WithItem addWithItemList(Collection withItemList) { collection.addAll(withItemList); return this.withWithItemList(collection); } - - public E getSubSelect(Class type) { - return type.cast(getSubSelect()); - } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java index 79e1c0986..22afaac67 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -241,7 +241,7 @@ public void visit(SelectExpressionItem selectExpressionItem) { @Override public void visit(SubSelect subSelect) { - buffer.append(subSelect.isUseBrackets() ? "(" : ""); + buffer.append(subSelect.isUseBrackets() ? "(" : ""); if (subSelect.getWithItemsList() != null && !subSelect.getWithItemsList().isEmpty()) { buffer.append("WITH "); for (Iterator iter = subSelect.getWithItemsList().iterator(); iter.hasNext();) { @@ -254,7 +254,7 @@ public void visit(SubSelect subSelect) { } } subSelect.getSelectBody().accept(this); - buffer.append(subSelect.isUseBrackets() ? ")" : ""); + buffer.append(subSelect.isUseBrackets() ? ")" : ""); Alias alias = subSelect.getAlias(); if (alias != null) { buffer.append(alias.toString()); @@ -485,38 +485,27 @@ public void visit(WithItem withItem) { if (withItem.getWithItemList() != null) { buffer.append(" ").append(PlainSelect.getStringList(withItem.getWithItemList(), true, true)); } - buffer.append(" AS "); + buffer.append(" AS "); - if (withItem.isUseValues()) { - ItemsList itemsList = withItem.getItemsList(); - boolean useBracketsForValues = withItem.isUsingBracketsForValues(); - buffer.append("(VALUES "); + if (withItem.isUseValues()) { + ItemsList itemsList = withItem.getItemsList(); + boolean useBracketsForValues = withItem.isUsingBracketsForValues(); + buffer.append("(VALUES "); - if (itemsList instanceof MultiExpressionList) { - MultiExpressionList multiExpressionList = (MultiExpressionList) itemsList; - for (Iterator it = multiExpressionList.getExprList().iterator(); - it.hasNext(); ) { - buffer.append(PlainSelect.getStringList(it.next().getExpressions(), true, true)); - if (it.hasNext()) { - buffer.append(", "); - } + ExpressionList expressionList = (ExpressionList) itemsList; + buffer.append( + PlainSelect.getStringList(expressionList.getExpressions(), true, useBracketsForValues)); + buffer.append(")"); + } else { + SubSelect subSelect = withItem.getSubSelect(); + if (!subSelect.isUseBrackets()) { + buffer.append("("); + } + subSelect.accept((FromItemVisitor) this); + if (!subSelect.isUseBrackets()) { + buffer.append(")"); + } } - } else if (itemsList instanceof ExpressionList) { - ExpressionList expressionList = (ExpressionList) itemsList; - buffer.append( - PlainSelect.getStringList(expressionList.getExpressions(), true, useBracketsForValues)); - } - buffer.append(")"); - } else { - SubSelect subSelect = withItem.getSubSelect(); - if (!subSelect.isUseBrackets()) { - buffer.append("("); - } - subSelect.accept((FromItemVisitor) this); - if (!subSelect.isUseBrackets()) { - buffer.append(")"); - } - } } @Override