Skip to content
Merged
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
22 changes: 22 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,13 @@ public String toString() {
b.append(outputClause);
}

if (option != null) {
if (outputClause != null) {
b.setLength(b.length() - 1);
}
b.append(option);
}

return b.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

/**
* Models the SQL Server (T-SQL) {@code OPTION (...)} query hint clause, which attaches a list of
* {@link OptionHint}s to the end of a {@code SELECT}, {@code UPDATE} or {@code DELETE} statement,
* see <a href="https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query">Hints
* {@link OptionHint}s to the end of a {@code SELECT}, {@code UPDATE}, {@code DELETE} or
* {@code MERGE} statement, 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 OptionClause implements Serializable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public void deParse(Merge merge) {
if (merge.getOutputClause() != null) {
merge.getOutputClause().appendTo(builder);
}

if (merge.getOption() != null) {
if (merge.getOutputClause() != null) {
builder.setLength(builder.length() - 1);
}
builder.append(merge.getOption());
}
}

@Override
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
26 changes: 23 additions & 3 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 @@ -4803,7 +4806,7 @@ Select Select() #Select:

[ LOOKAHEAD(<K_ORDER> <K_BY>) orderByElements = OrderByElements() { select.setOrderByElements(orderByElements); } ]
[ LOOKAHEAD(2) interpolateElements = InterpolateClause() { select.setInterpolate(interpolateElements); } ]
[ LOOKAHEAD(2) optionClause = OptionClause() { select.setOption(optionClause); } ]
[ LOOKAHEAD(2, { !(select instanceof Values) }) optionClause = OptionClause() { select.setOption(optionClause); } ]

[ LOOKAHEAD(<K_LIMIT>) limit=LimitWithOffset() {select.setLimit(limit);} ]
[ LOOKAHEAD(<K_OFFSET>) offset = Offset() { select.setOffset(offset);} ]
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 Column(userVariable + " UNKNOWN");
}
|
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 @@ -13,7 +13,10 @@

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
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,64 @@ 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());
Column parameter =
Assertions.assertInstanceOf(Column.class, optimizeFor.getParameters().get(0));
Assertions.assertEquals("@p UNKNOWN", parameter.getColumnName());
}

@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());
Column parameter =
Assertions.assertInstanceOf(Column.class, optimizeFor.getParameters().get(1));
Assertions.assertEquals("@q UNKNOWN", parameter.getColumnName());
}

@Test
public void testOptionAfterInsertValuesIsRejected() {
Assertions.assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse(
"INSERT INTO t (a) VALUES (1) OPTION (RECOMPILE)"));
}

@Test
public void testOptionAfterInsertSelect() throws JSQLParserException {
String sql = "INSERT INTO t (a) SELECT a FROM s 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)",
false);
}

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