Skip to content

Commit 3bca3dc

Browse files
committed
feat: support SQL Server OPTION query hints (#161)
Parse the T-SQL OPTION (...) clause at the end of SELECT (including after set operations and FOR XML), UPDATE and DELETE statements, following the Query Hints documentation: multi-word keyword hints (RECOMPILE, HASH JOIN, FORCE ORDER, OPTIMIZE FOR UNKNOWN, ...), single value arguments (FAST 100, MAXDOP 4, MAX_GRANT_PERCENT = 25) and parenthesized argument lists (USE HINT ('...'), OPTIMIZE FOR (@p = 1), TABLE HINT (t, INDEX (i))). Hints are kept in a generic OptionHint (keyword name + optional value or ExpressionList parameters) attached via a new OptionClause, mirroring how SQL Server keeps adding new hints without enum churn. OPTION is a non-reserved keyword and K_OPTION followed by ( is excluded from alias positions, so option remains usable as a column and table name. The union hoisting lifts the clause from the last PlainSelect onto the SetOperationList like ORDER BY and LIMIT. Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent 406a4d4 commit 3bca3dc

13 files changed

Lines changed: 556 additions & 0 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/delete/Delete.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import net.sf.jsqlparser.statement.select.FromItem;
2121
import net.sf.jsqlparser.statement.select.Join;
2222
import net.sf.jsqlparser.statement.select.Limit;
23+
import net.sf.jsqlparser.statement.select.OptionClause;
2324
import net.sf.jsqlparser.statement.select.OrderByElement;
2425
import net.sf.jsqlparser.statement.select.PlainSelect;
2526
import net.sf.jsqlparser.statement.select.WithItem;
@@ -45,6 +46,7 @@ public class Delete implements Statement {
4546
private Expression where;
4647
private PreferringClause preferringClause;
4748
private Limit limit;
49+
private OptionClause option;
4850
private List<OrderByElement> orderByElements;
4951
private boolean hasFrom = true;
5052
private DeleteModifierPriority modifierPriority;
@@ -147,6 +149,19 @@ public Limit getLimit() {
147149
return limit;
148150
}
149151

152+
public OptionClause getOption() {
153+
return option;
154+
}
155+
156+
public Delete setOption(OptionClause option) {
157+
this.option = option;
158+
return this;
159+
}
160+
161+
public Delete withOption(OptionClause option) {
162+
return setOption(option);
163+
}
164+
150165
public void setLimit(Limit limit) {
151166
this.limit = limit;
152167
}
@@ -280,6 +295,10 @@ public String toString() {
280295
b.append(limit);
281296
}
282297

298+
if (option != null) {
299+
b.append(option);
300+
}
301+
283302
if (returningClause != null) {
284303
returningClause.appendTo(b);
285304
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.select;
11+
12+
import java.io.Serializable;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
16+
/**
17+
* Models the SQL Server (T-SQL) {@code OPTION (...)} query hint clause, which attaches a list of
18+
* {@link OptionHint}s to the end of a {@code SELECT}, {@code UPDATE} or {@code DELETE} statement,
19+
* see <a href="https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query">Hints
20+
* (Transact-SQL) - Query Hints</a>.
21+
*/
22+
public class OptionClause implements Serializable {
23+
24+
private List<OptionHint> optionHints;
25+
26+
public OptionClause() {
27+
super();
28+
}
29+
30+
public OptionClause(List<OptionHint> optionHints) {
31+
this.optionHints = optionHints;
32+
}
33+
34+
public List<OptionHint> getOptionHints() {
35+
return optionHints;
36+
}
37+
38+
public void setOptionHints(List<OptionHint> optionHints) {
39+
this.optionHints = optionHints;
40+
}
41+
42+
public OptionClause withOptionHints(List<OptionHint> optionHints) {
43+
setOptionHints(optionHints);
44+
return this;
45+
}
46+
47+
public OptionClause addOptionHint(OptionHint optionHint) {
48+
if (optionHints == null) {
49+
optionHints = new ArrayList<>();
50+
}
51+
optionHints.add(optionHint);
52+
return this;
53+
}
54+
55+
@Override
56+
public String toString() {
57+
return " OPTION (" + Select.getStringList(optionHints, true, false) + ")";
58+
}
59+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.select;
11+
12+
import java.io.Serializable;
13+
14+
import net.sf.jsqlparser.expression.Expression;
15+
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
16+
17+
/**
18+
* Models one hint of the SQL Server (T-SQL) {@code OPTION (...)} query hint clause, see
19+
* <a href="https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query">Hints
20+
* (Transact-SQL) - Query Hints</a>.
21+
* <p>
22+
* A hint is a (possibly multi-word) keyword such as {@code RECOMPILE}, {@code HASH JOIN} or
23+
* {@code OPTIMIZE FOR UNKNOWN}, optionally followed by a single value argument ({@code FAST 100},
24+
* {@code MAXDOP 4}, {@code MAX_GRANT_PERCENT = 25}) or by a parenthesized argument list
25+
* ({@code USE HINT ('...')}, {@code OPTIMIZE FOR (@p = 1)}, {@code TABLE HINT (t, INDEX (i))}).
26+
*/
27+
public class OptionHint implements Serializable {
28+
29+
private String name;
30+
private Expression value;
31+
private ExpressionList<Expression> parameters;
32+
private boolean useEquals = false;
33+
34+
public OptionHint() {
35+
super();
36+
}
37+
38+
public OptionHint(String name) {
39+
this.name = name;
40+
}
41+
42+
public String getName() {
43+
return name;
44+
}
45+
46+
public void setName(String name) {
47+
this.name = name;
48+
}
49+
50+
public OptionHint withName(String name) {
51+
setName(name);
52+
return this;
53+
}
54+
55+
public Expression getValue() {
56+
return value;
57+
}
58+
59+
public void setValue(Expression value) {
60+
this.value = value;
61+
}
62+
63+
public OptionHint withValue(Expression value) {
64+
setValue(value);
65+
return this;
66+
}
67+
68+
public ExpressionList<Expression> getParameters() {
69+
return parameters;
70+
}
71+
72+
public void setParameters(ExpressionList<Expression> parameters) {
73+
this.parameters = parameters;
74+
}
75+
76+
public OptionHint withParameters(ExpressionList<Expression> parameters) {
77+
setParameters(parameters);
78+
return this;
79+
}
80+
81+
public OptionHint addParameter(Expression parameter) {
82+
if (parameters == null) {
83+
parameters = new ExpressionList<>();
84+
}
85+
parameters.add(parameter);
86+
return this;
87+
}
88+
89+
public boolean isUseEquals() {
90+
return useEquals;
91+
}
92+
93+
public void setUseEquals(boolean useEquals) {
94+
this.useEquals = useEquals;
95+
}
96+
97+
public OptionHint withUseEquals(boolean useEquals) {
98+
setUseEquals(useEquals);
99+
return this;
100+
}
101+
102+
@Override
103+
public String toString() {
104+
StringBuilder b = new StringBuilder(name);
105+
if (value != null) {
106+
b.append(useEquals ? " = " : " ").append(value);
107+
}
108+
if (parameters != null) {
109+
b.append(" (").append(Select.getStringList(parameters, true, false)).append(')');
110+
}
111+
return b.toString();
112+
}
113+
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public abstract class Select extends ASTNodeAccessImpl implements Statement, Exp
3434
boolean oracleSiblings = false;
3535

3636
ForClause forClause = null;
37+
OptionClause option = null;
3738

3839
List<OrderByElement> orderByElements;
3940
List<InterpolateElement> interpolate;
@@ -195,6 +196,19 @@ public Select setForClause(ForClause forClause) {
195196
return this;
196197
}
197198

199+
public OptionClause getOption() {
200+
return option;
201+
}
202+
203+
public Select setOption(OptionClause option) {
204+
this.option = option;
205+
return this;
206+
}
207+
208+
public Select withOption(OptionClause option) {
209+
return setOption(option);
210+
}
211+
198212
public List<OrderByElement> getOrderByElements() {
199213
return orderByElements;
200214
}
@@ -494,6 +508,10 @@ public StringBuilder appendTo(StringBuilder builder) {
494508
forClause.appendTo(builder);
495509
}
496510

511+
if (option != null) {
512+
builder.append(option);
513+
}
514+
497515
if (limitBy != null) {
498516
builder.append(limitBy);
499517
}

src/main/java/net/sf/jsqlparser/statement/update/Update.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import net.sf.jsqlparser.statement.select.FromItem;
2222
import net.sf.jsqlparser.statement.select.Join;
2323
import net.sf.jsqlparser.statement.select.Limit;
24+
import net.sf.jsqlparser.statement.select.OptionClause;
2425
import net.sf.jsqlparser.statement.select.OrderByElement;
2526
import net.sf.jsqlparser.statement.select.PlainSelect;
2627
import net.sf.jsqlparser.statement.select.Select;
@@ -48,6 +49,7 @@ public class Update implements Statement {
4849
private OracleHint oracleHint = null;
4950
private List<OrderByElement> orderByElements;
5051
private Limit limit;
52+
private OptionClause option;
5153
private ReturningClause returningClause;
5254
private UpdateModifierPriority modifierPriority;
5355
private boolean modifierIgnore;
@@ -255,6 +257,19 @@ public Limit getLimit() {
255257
return limit;
256258
}
257259

260+
public OptionClause getOption() {
261+
return option;
262+
}
263+
264+
public Update setOption(OptionClause option) {
265+
this.option = option;
266+
return this;
267+
}
268+
269+
public Update withOption(OptionClause option) {
270+
return setOption(option);
271+
}
272+
258273
public void setLimit(Limit limit) {
259274
this.limit = limit;
260275
}
@@ -356,6 +371,10 @@ public String toString() {
356371
b.append(limit);
357372
}
358373

374+
if (option != null) {
375+
b.append(option);
376+
}
377+
359378
if (returningClause != null) {
360379
returningClause.appendTo(b);
361380
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ public void deParse(Delete delete) {
105105
new LimitDeparser(expressionVisitor, builder).deParse(delete.getLimit());
106106
}
107107

108+
if (delete.getOption() != null) {
109+
builder.append(delete.getOption());
110+
}
111+
108112
if (delete.getReturningClause() != null) {
109113
delete.getReturningClause().appendTo(builder);
110114
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ public <S> StringBuilder visit(ParenthesedSelect select, S context) {
140140
deParseInterpolate(select.getInterpolate());
141141
}
142142

143+
if (select.getOption() != null) {
144+
builder.append(select.getOption());
145+
}
146+
143147
Alias alias = select.getAlias();
144148
if (alias != null) {
145149
builder.append(alias);
@@ -345,6 +349,10 @@ public <S> StringBuilder visit(PlainSelect plainSelect, S context) {
345349
plainSelect.getForClause().appendTo(builder);
346350
}
347351

352+
if (plainSelect.getOption() != null) {
353+
builder.append(plainSelect.getOption());
354+
}
355+
348356
if (plainSelect.isEmitChanges()) {
349357
builder.append(" EMIT CHANGES");
350358
}
@@ -758,6 +766,10 @@ public <S> StringBuilder visit(SetOperationList list, S context) {
758766
deParseInterpolate(list.getInterpolate());
759767
}
760768

769+
if (list.getOption() != null) {
770+
builder.append(list.getOption());
771+
}
772+
761773
if (list.getLimit() != null) {
762774
new LimitDeparser(expressionVisitor, builder).deParse(list.getLimit());
763775
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ public void deParse(Update update) {
103103
new LimitDeparser(expressionVisitor, builder).deParse(update.getLimit());
104104
}
105105

106+
if (update.getOption() != null) {
107+
builder.append(update.getOption());
108+
}
109+
106110
if (update.getReturningClause() != null) {
107111
update.getReturningClause().appendTo(builder);
108112
}

src/main/java/net/sf/jsqlparser/util/validation/validator/DeleteValidator.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import net.sf.jsqlparser.parser.feature.Feature;
1313
import net.sf.jsqlparser.statement.delete.Delete;
14+
import net.sf.jsqlparser.statement.select.OptionHint;
1415
import net.sf.jsqlparser.util.validation.ValidationCapability;
1516

1617
/**
@@ -48,6 +49,15 @@ public void validate(Delete delete) {
4849
getValidator(LimitValidator.class).validate(delete.getLimit());
4950
}
5051

52+
if (delete.getOption() != null) {
53+
for (OptionHint optionHint : delete.getOption().getOptionHints()) {
54+
validateOptionalExpression(optionHint.getValue());
55+
if (optionHint.getParameters() != null) {
56+
optionHint.getParameters().forEach(this::validateOptionalExpression);
57+
}
58+
}
59+
}
60+
5161
if (delete.getReturningClause() != null) {
5262
delete.getReturningClause().forEach(c -> c.accept(v, null));
5363
}

0 commit comments

Comments
 (0)