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 @@ -95,7 +95,7 @@
}

default <S> T visitLimit(Limit limit, S context) {
if (limit != null && !limit.isLimitNull() && !limit.isLimitAll()) {

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] isLimitAll() in Limit has been deprecated

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] isLimitNull() in Limit has been deprecated

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] isLimitAll() in Limit has been deprecated

Check warning on line 98 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] isLimitNull() in Limit has been deprecated
if (limit.getOffset() != null) {
limit.getOffset().accept(this, context);
}
Expand Down Expand Up @@ -531,6 +531,12 @@
this.visit(userVariable, null);
}

<S> T visit(UnknownVariable unknownVariable, S context);

default void visit(UnknownVariable unknownVariable) {
this.visit(unknownVariable, null);
}

<S> T visit(NumericBind numericBind, S context);

default void visit(NumericBind numericBind) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,11 @@
return applyExpression(userVariable, context);
}

@Override
public <S> T visit(UnknownVariable unknownVariable, S context) {
return applyExpression(unknownVariable, context);
}

@Override
public <S> T visit(NumericBind numericBind, S context) {
return applyExpression(numericBind, context);
Expand Down Expand Up @@ -765,12 +770,12 @@

@Override
public <S> T visit(ConnectByRootOperator connectByRootOperator, S context) {
return connectByRootOperator.getColumn().accept(this, context);

Check warning on line 773 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] getColumn() in ConnectByRootOperator has been deprecated

Check warning on line 773 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] getColumn() in ConnectByRootOperator has been deprecated
}

@Override
public <S> T visit(ConnectByPriorOperator connectByPriorOperator, S context) {
return connectByPriorOperator.getColumn().accept(this, context);

Check warning on line 778 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] getColumn() in ConnectByPriorOperator has been deprecated

Check warning on line 778 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] getColumn() in ConnectByPriorOperator has been deprecated
}

@Override
Expand Down Expand Up @@ -798,7 +803,7 @@
if (selectVisitor != null) {
if (select.getWithItemsList() != null) {
for (WithItem<?> item : select.getWithItemsList()) {
item.accept(selectVisitor, context);

Check warning on line 806 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (ubuntu-latest)

[deprecation] <T,S>accept(SelectVisitor<T>,S) in WithItem has been deprecated

Check warning on line 806 in src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

View workflow job for this annotation

GitHub Actions / Maven Verify (macos-latest)

[deprecation] <T,S>accept(SelectVisitor<T>,S) in WithItem has been deprecated
}
}
select.accept(selectVisitor, context);
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/net/sf/jsqlparser/expression/UnknownVariable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*-
* #%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.expression;

import net.sf.jsqlparser.parser.ASTNodeAccessImpl;

/**
* A user variable followed by the bare {@code UNKNOWN} keyword, as used in the SQL Server (T-SQL)
* {@code OPTIMIZE FOR (@variable_name UNKNOWN)} query hint parameter form, see
* <a href="https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query">Hints
* (Transact-SQL) - Query Hints</a>.
*/
public class UnknownVariable extends ASTNodeAccessImpl implements Expression {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need an extra class for this? Why not just carry a normal "Identifier" or "String" along? I would love to avoid especially the extra methods in the Visitors.


private UserVariable variable;

public UnknownVariable() {
// empty constructor
}

public UnknownVariable(UserVariable variable) {
this.variable = variable;
}

public UserVariable getVariable() {
return variable;
}

public void setVariable(UserVariable variable) {
this.variable = variable;
}

public UnknownVariable withVariable(UserVariable variable) {
this.setVariable(variable);
return this;
}

@Override
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
return expressionVisitor.visit(this, context);
}

@Override
public String toString() {
return variable.toString() + " UNKNOWN";
}
}
19 changes: 19 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/merge/Merge.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.StatementVisitor;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.OptionClause;
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
import net.sf.jsqlparser.statement.select.WithItem;

Expand All @@ -41,6 +42,7 @@ public class Merge implements Statement {
private List<MergeOperation> operations;

private OutputClause outputClause;
private OptionClause option;

private void deriveOperationsFromStandardClauses() {
List<MergeOperation> operations = new ArrayList<>();
Expand Down Expand Up @@ -214,6 +216,19 @@ public Merge setOutputClause(OutputClause outputClause) {
return this;
}

public OptionClause getOption() {
return option;
}

public Merge setOption(OptionClause option) {
this.option = option;
return this;
}

public Merge withOption(OptionClause option) {
return setOption(option);
}

@Override
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
public String toString() {
Expand Down Expand Up @@ -248,6 +263,10 @@ public String toString() {
b.append(outputClause);
}

if (option != null) {
b.append(option);
}

return b.toString();
}

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,12 @@ public <S> Void visit(UserVariable userVariable, S context) {
return null;
}

@Override
public <S> Void visit(UnknownVariable unknownVariable, S context) {

return null;
}

@Override
public <S> Void visit(NumericBind numericBind, S context) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import net.sf.jsqlparser.expression.TimezoneExpression;
import net.sf.jsqlparser.expression.TranscodingFunction;
import net.sf.jsqlparser.expression.TrimFunction;
import net.sf.jsqlparser.expression.UnknownVariable;
import net.sf.jsqlparser.expression.UserVariable;
import net.sf.jsqlparser.expression.VariableAssignment;
import net.sf.jsqlparser.expression.WhenClause;
Expand Down Expand Up @@ -1417,6 +1418,12 @@ public <S> StringBuilder visit(UserVariable var, S context) {
return builder;
}

@Override
public <S> StringBuilder visit(UnknownVariable var, S context) {
builder.append(var.toString());
return builder;
}

@Override
public <S> StringBuilder visit(NumericBind bind, S context) {
builder.append(bind.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ public void deParse(Merge merge) {
if (merge.getOutputClause() != null) {
merge.getOutputClause().appendTo(builder);
}

if (merge.getOption() != null) {
builder.append(merge.getOption());
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public ValuesStatementDeParser(ExpressionVisitor<StringBuilder> expressionVisito
public void deParse(Values values) {
builder.append("VALUES ");
values.getExpressions().accept(expressionVisitor, null);
if (values.getOption() != null) {
builder.append(values.getOption());
}
if (values.getAlias() != null) {
builder.append(" ").append(values.getAlias());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import net.sf.jsqlparser.expression.TimezoneExpression;
import net.sf.jsqlparser.expression.TranscodingFunction;
import net.sf.jsqlparser.expression.TrimFunction;
import net.sf.jsqlparser.expression.UnknownVariable;
import net.sf.jsqlparser.expression.UserVariable;
import net.sf.jsqlparser.expression.VariableAssignment;
import net.sf.jsqlparser.expression.WhenClause;
Expand Down Expand Up @@ -819,6 +820,12 @@ public <S> Void visit(UserVariable var, S context) {
return null;
}

@Override
public <S> Void visit(UnknownVariable var, S context) {
// nothing to validate
return null;
}

@Override
public <S> Void visit(NumericBind bind, S context) {
// nothing to validate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.statement.merge.*;
import net.sf.jsqlparser.statement.select.OptionHint;
import net.sf.jsqlparser.statement.update.UpdateSet;
import net.sf.jsqlparser.util.validation.ValidationCapability;

Expand All @@ -31,6 +32,14 @@ public void validate(Merge merge) {
merge.getOperations().forEach(operation -> operation.accept(this, null));
}
validateOptionalFromItems(merge.getFromItem());
if (merge.getOption() != null) {
for (OptionHint optionHint : merge.getOption().getOptionHints()) {
validateOptionalExpression(optionHint.getValue());
if (optionHint.getParameters() != null) {
optionHint.getParameters().forEach(this::validateOptionalExpression);
}
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package net.sf.jsqlparser.util.validation.validator;

import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.statement.select.OptionHint;
import net.sf.jsqlparser.statement.select.Values;

/**
Expand All @@ -21,5 +22,13 @@ public class ValuesStatementValidator extends AbstractValidator<Values> {
public void validate(Values values) {
validateFeature(Feature.values);
validateOptionalExpression(values.getExpressions());
if (values.getOption() != null) {
for (OptionHint optionHint : values.getOption().getOptionHints()) {
validateOptionalExpression(optionHint.getValue());
if (optionHint.getParameters() != null) {
optionHint.getParameters().forEach(this::validateOptionalExpression);
}
}
}
}
}
24 changes: 22 additions & 2 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -4388,6 +4388,7 @@ Statement Merge( List<WithItem<?>> with ) : {
Expression condition;
List<MergeOperation> operations;
OutputClause outputClause;
OptionClause optionClause = null;
}
{
<K_MERGE> { merge.setOracleHint(getOracleHint()); } <K_INTO> table=TableWithAlias() { merge.setTable(table); }
Expand All @@ -4398,6 +4399,8 @@ Statement Merge( List<WithItem<?>> with ) : {

[ outputClause = OutputClause() { merge.setOutputClause(outputClause); } ]

[ LOOKAHEAD(2) optionClause = OptionClause() { merge.setOption(optionClause); } ]

{ return merge.withWithItemsList(with); }
}

Expand Down Expand Up @@ -6952,6 +6955,23 @@ InterpolateElement InterpolateElement():
{ return interpolateElement; }
}

Expression OptionHintParameter():
{
UserVariable userVariable = null;
Expression parameter = null;
}
{
(
LOOKAHEAD({ getToken(1).kind == S_AT_IDENTIFIER && getToken(2).kind == K_UNKNOWN })
userVariable = UserVariable() <K_UNKNOWN>
{
return new UnknownVariable(userVariable);
}
|
parameter = Expression() { return parameter; }
)
}

OptionClause OptionClause():
{
OptionClause optionClause = new OptionClause();
Expand All @@ -6977,8 +6997,8 @@ OptionHint OptionHint():
(
LOOKAHEAD("=") "=" value = Expression() { optionHint.setValue(value); optionHint.setUseEquals(true); }
|
LOOKAHEAD("(") "(" parameter = Expression() { optionHint.addParameter(parameter); }
( "," parameter = Expression() { optionHint.addParameter(parameter); } )*
LOOKAHEAD("(") "(" parameter = OptionHintParameter() { optionHint.addParameter(parameter); }
( "," parameter = OptionHintParameter() { optionHint.addParameter(parameter); } )*
")"
|
LOOKAHEAD({ isOptionHintValueAhead() }) value = Expression() { optionHint.setValue(value); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.UnknownVariable;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.merge.Merge;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -147,6 +150,55 @@ public void testOptionInUpdateAndDelete() throws JSQLParserException {
Assertions.assertNotNull(((net.sf.jsqlparser.statement.update.Update) update).getOption());
}

@Test
public void testOptionOptimizeForUnknownParameter() throws JSQLParserException {
String sql = "SELECT * FROM t WHERE c = @p OPTION (OPTIMIZE FOR (@p UNKNOWN))";
PlainSelect plainSelect =
(PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
OptionHint optimizeFor = plainSelect.getOption().getOptionHints().get(0);
Assertions.assertEquals(1, optimizeFor.getParameters().size());
Assertions.assertTrue(optimizeFor.getParameters().get(0) instanceof UnknownVariable);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assertions should provide assertInstanceOf directly.

Assertions.assertEquals("@p UNKNOWN", optimizeFor.getParameters().get(0).toString());
}

@Test
public void testOptionOptimizeForMixedParameters() throws JSQLParserException {
String sql = "SELECT * FROM t WHERE c = @p OPTION (OPTIMIZE FOR (@p = 1, @q UNKNOWN))";
PlainSelect plainSelect =
(PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
OptionHint optimizeFor = plainSelect.getOption().getOptionHints().get(0);
Assertions.assertEquals(2, optimizeFor.getParameters().size());
Assertions.assertEquals("@p = 1", optimizeFor.getParameters().get(0).toString());
Assertions.assertTrue(optimizeFor.getParameters().get(1) instanceof UnknownVariable);
Assertions.assertEquals("@q UNKNOWN", optimizeFor.getParameters().get(1).toString());
}

@Test
public void testOptionAfterInsertValues() throws JSQLParserException {
String sql = "INSERT INTO t (a) VALUES (1) OPTION (RECOMPILE)";
Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true);
Insert insert = (Insert) statement;
Assertions.assertNotNull(insert.getSelect().getOption());
}

@Test
public void testOptionAfterMergeStatement() throws JSQLParserException {
String sql =
"MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET a = s.a OPTION (HASH JOIN)";
Statement statement = assertSqlCanBeParsedAndDeparsed(sql, true);
Merge merge = (Merge) statement;
Assertions.assertNotNull(merge.getOption());
Assertions.assertEquals("HASH JOIN", merge.getOption().getOptionHints().get(0).getName());
}

@Test
public void testOptionAfterMergeOutputClause() throws JSQLParserException {
assertSqlCanBeParsedAndDeparsed(
"MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET a = s.a "
+ "OUTPUT deleted.a OPTION (HASH JOIN)",
true);
}

@Test
public void testOptionAsIdentifierStillWorks() throws JSQLParserException {
// OPTION stays a non-reserved keyword usable as column and table name
Expand Down
Loading