Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SelectVisitor;
import net.sf.jsqlparser.statement.select.Values;
import net.sf.jsqlparser.statement.select.WithItem;

@SuppressWarnings({"PMD.CyclomaticComplexity"})
Expand Down Expand Up @@ -726,7 +727,9 @@ protected <S> void deparse(BinaryExpression binaryExpression,
@Override
public <S> StringBuilder visit(Select select, S context) {
if (selectVisitor != null) {
if (select.getWithItemsList() != null) {
// ValuesStatementDeParser owns WITH rendering when using SelectDeParser.
if (select.getWithItemsList() != null
&& !(select instanceof Values && selectVisitor instanceof SelectDeParser)) {
builder.append("WITH ");
for (Iterator<WithItem<?>> iter = select.getWithItemsList().iterator(); iter
.hasNext();) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ public <S> StringBuilder visit(ParenthesedFromItem fromItem, S context) {

@Override
public <S> StringBuilder visit(Values values, S context) {
new ValuesStatementDeParser(expressionVisitor, builder).deParse(values);
new ValuesStatementDeParser(expressionVisitor, this, builder).deParse(values, context);
return builder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,30 @@
public class ValuesStatementDeParser extends AbstractDeParser<Values> {

private final ExpressionVisitor<StringBuilder> expressionVisitor;
private final SelectDeParser selectDeParser;

public ValuesStatementDeParser(ExpressionVisitor<StringBuilder> expressionVisitor,
StringBuilder buffer) {
this(expressionVisitor, null, buffer);
}

public ValuesStatementDeParser(ExpressionVisitor<StringBuilder> expressionVisitor,
SelectDeParser selectDeParser, StringBuilder buffer) {
super(buffer);
this.expressionVisitor = expressionVisitor;
this.selectDeParser = selectDeParser;
}

@Override
public void deParse(Values values) {
deParse(values, null);
}

public <S> void deParse(Values values, S context) {
new DmlDeParserSupport(expressionVisitor, selectDeParser, builder)
.deparseWithItems(values.getWithItemsList(), context);
builder.append("VALUES ");
values.getExpressions().accept(expressionVisitor, null);
values.getExpressions().accept(expressionVisitor, context);
if (values.getAlias() != null) {
builder.append(" ").append(values.getAlias());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2019 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.util.deparser;

import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
import static org.junit.jupiter.api.Assertions.*;

import java.util.ArrayList;
import java.util.List;
import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.SelectVisitor;
import net.sf.jsqlparser.statement.select.Values;
import net.sf.jsqlparser.statement.select.WithItem;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class ValuesWithDeParserTest {
private static Statement parse(String sql) throws Exception {
return CCJSqlParserUtil.parse(sql, parser -> parser.withDialect(Dialect.POSTGRESQL));
}

@ParameterizedTest
@ValueSource(strings = {
"WITH cte(foo) AS (VALUES (42)) VALUES ((SELECT foo FROM cte))",
"WITH a(n) AS (VALUES (1)), b(n) AS (VALUES (2)) "
+ "VALUES ((SELECT n FROM a), (SELECT n FROM b))",
"WITH RECURSIVE c(n) AS (VALUES (1) UNION ALL "
+ "SELECT n + 1 FROM c WHERE n < 3) VALUES ((SELECT max(n) FROM c))",
"WITH c(n) AS MATERIALIZED (VALUES (1)) VALUES ((SELECT n FROM c))",
"WITH c(n) AS NOT MATERIALIZED (VALUES (1)) VALUES ((SELECT n FROM c))",
"WITH c AS (DELETE FROM t RETURNING id) VALUES ((SELECT count(*) FROM c))"
})
void preservesWithItemsInStatementAndDirectValuesDeparsers(String sql) throws Exception {
Values values = assertInstanceOf(Values.class, assertSqlCanBeParsedAndDeparsed(sql, true,
parser -> parser.withDialect(Dialect.POSTGRESQL)));
String original = values.toString();
for (boolean direct : new boolean[] {false, true}) {
StringBuilder output = new StringBuilder();
SelectDeParser selects = new SelectDeParser();
ExpressionDeParser expressions = new ExpressionDeParser(selects, output);
if (direct) {
new ValuesStatementDeParser(expressions, output).deParse(values);
} else {
values.accept(new StatementDeParser(expressions, selects, output), null);
}
Values reparsed = assertInstanceOf(Values.class, parse(output.toString()));
assertEquals(original, reparsed.toString());
assertEquals(values.getWithItemsList().size(), reparsed.getWithItemsList().size());
assertEquals(original, values.toString());
}
}

@ParameterizedTest
@ValueSource(strings = {"statement", "select", "expression", "direct", "directWithSelect"})
void preservesCustomVisitorsAndContext(String entryPoint) throws Exception {
Values values = assertInstanceOf(Values.class,
parse("WITH first_cte AS (VALUES (7)), second_cte AS (VALUES (8)) VALUES (9)"));
String original = values.toString();
Object expectedContext = new Object();
StringBuilder output = new StringBuilder();
List<String> withItems = new ArrayList<>();
List<Long> numbers = new ArrayList<>();
SelectDeParser selects = new SelectDeParser() {
@Override
public <S> StringBuilder visit(WithItem<?> item, S context) {
assertSame(expectedContext, context);
withItems.add(item.getAlias().getName());
return super.visit(item, context);
}
};
ExpressionDeParser expressions = new ExpressionDeParser(selects, output) {
@Override
public <S> StringBuilder visit(LongValue value, S context) {
assertSame(expectedContext, context);
numbers.add(value.getValue());
return output.append(value.getValue() + 100);
}
};
selects.setExpressionVisitor(expressions);
selects.setBuilder(output);
switch (entryPoint) {
case "statement":
values.accept(new StatementDeParser(expressions, selects, output), expectedContext);
break;
case "select":
values.accept((SelectVisitor<StringBuilder>) selects, expectedContext);
break;
case "expression":
values.accept((ExpressionVisitor<StringBuilder>) expressions, expectedContext);
break;
case "direct":
new ValuesStatementDeParser(expressions, output).deParse(values, expectedContext);
break;
default:
new ValuesStatementDeParser(expressions, selects, output)
.deParse(values, expectedContext);
}
assertEquals(List.of("first_cte", "second_cte"), withItems);
assertEquals(List.of(7L, 8L, 9L), numbers);
assertEquals(parse("WITH first_cte AS (VALUES (107)), "
+ "second_cte AS (VALUES (108)) VALUES (109)").toString(),
parse(output.toString()).toString());
assertEquals(original, values.toString());
}

@ParameterizedTest
@ValueSource(strings = {
"SELECT (WITH c(n) AS (VALUES (1)) VALUES ((SELECT n FROM c)))",
"VALUES ((WITH c(n) AS (VALUES (1)) VALUES ((SELECT n FROM c))))",
"SELECT * FROM (WITH c(n) AS (VALUES (1)) VALUES ((SELECT n FROM c))) AS v(n)"
})
void rendersNestedValuesWithItemsOnce(String sql) throws Exception {
Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true,
parser -> parser.withDialect(Dialect.POSTGRESQL));
StringBuilder output = new StringBuilder();
statement.accept(new StatementDeParser(output), null);
assertEquals(statement.toString(), parse(output.toString()).toString());
}
}
Loading