Skip to content

Commit 0f85ac2

Browse files
committed
Support SQL Server boolean SET options with shared statement rendering
1 parent 3774451 commit 0f85ac2

9 files changed

Lines changed: 321 additions & 47 deletions

File tree

src/main/java/net/sf/jsqlparser/parser/feature/Feature.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@ public enum Feature {
683683
* @see SetStatement
684684
*/
685685
set,
686+
/** SQL Server SET option [, option] ON | OFF. */
687+
sqlServerSetOptions,
686688
/**
687689
* @see ResetStatement
688690
*/

src/main/java/net/sf/jsqlparser/statement/SetStatement.java

Lines changed: 104 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,84 @@
1111

1212
import net.sf.jsqlparser.expression.Expression;
1313
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
14-
import net.sf.jsqlparser.statement.select.PlainSelect;
1514

1615
import java.io.Serializable;
1716
import java.util.ArrayList;
1817
import java.util.Arrays;
1918
import java.util.Collection;
2019
import java.util.List;
20+
import java.util.Objects;
21+
import java.util.function.Consumer;
2122

2223
public final class SetStatement implements Statement {
2324

2425
private final List<NameExpr> values = new ArrayList<>();
2526
private String effectParameter;
27+
private OnOffOptions onOffOptions;
28+
29+
/** SQL Server options that share the SET option [, option] ON | OFF syntax. */
30+
public enum OnOffOption {
31+
QUOTED_IDENTIFIER, CONCAT_NULL_YIELDS_NULL, CURSOR_CLOSE_ON_COMMIT, ARITHABORT, ARITHIGNORE, FMTONLY, NOCOUNT, NOEXEC, NUMERIC_ROUNDABORT, PARSEONLY, ANSI_DEFAULTS, ANSI_NULL_DFLT_OFF, ANSI_NULL_DFLT_ON, ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, FORCEPLAN, SHOWPLAN_ALL, SHOWPLAN_TEXT, SHOWPLAN_XML, IMPLICIT_TRANSACTIONS, REMOTE_PROC_TRANSACTIONS, XACT_ABORT;
32+
33+
public static OnOffOption fromName(String name) {
34+
for (OnOffOption option : values()) {
35+
if (option.name().equalsIgnoreCase(name)) {
36+
return option;
37+
}
38+
}
39+
return null;
40+
}
41+
}
42+
43+
/** A group of options sharing one ON/OFF value; separate from assignment expressions. */
44+
public static final class OnOffOptions implements Serializable {
45+
private final List<OnOffOption> options;
46+
private boolean on;
47+
48+
public OnOffOptions(Collection<OnOffOption> options, boolean on) {
49+
this.options = new ArrayList<>(options);
50+
if (this.options.isEmpty() || this.options.contains(null)) {
51+
throw new IllegalArgumentException("At least one non-null SET option is required");
52+
}
53+
this.on = on;
54+
}
55+
56+
public List<OnOffOption> getOptions() {
57+
return options;
58+
}
59+
60+
public boolean isOn() {
61+
return on;
62+
}
63+
64+
public void setOn(boolean on) {
65+
this.on = on;
66+
}
67+
68+
private StringBuilder appendTo(StringBuilder builder) {
69+
if (options.isEmpty() || options.contains(null)) {
70+
throw new IllegalStateException("Invalid SQL Server SET option group");
71+
}
72+
for (int i = 0; i < options.size(); i++) {
73+
if (i > 0) {
74+
builder.append(", ");
75+
}
76+
builder.append(options.get(i));
77+
}
78+
return builder.append(on ? " ON" : " OFF");
79+
}
80+
}
81+
82+
public OnOffOptions getOnOffOptions() {
83+
return onOffOptions;
84+
}
85+
86+
/** Selects SQL Server option syntax, clearing any existing assignments and scope. */
87+
public void setOnOffOptions(OnOffOptions onOffOptions) {
88+
Objects.requireNonNull(onOffOptions, "onOffOptions");
89+
clear();
90+
this.onOffOptions = onOffOptions;
91+
}
2692

2793
public SetStatement() {
2894
// empty constructor
@@ -33,6 +99,7 @@ public SetStatement(Object name, ExpressionList<?> value) {
3399
}
34100

35101
public void add(Object name, ExpressionList<?> value, boolean useEqual) {
102+
onOffOptions = null;
36103
values.add(new NameExpr(name, value, useEqual));
37104
}
38105

@@ -103,35 +170,40 @@ public void setExpressions(int idx, ExpressionList<?> expressions) {
103170
values.get(idx).expressions = expressions;
104171
}
105172

106-
private String toString(NameExpr ne) {
107-
return ne.name + (ne.useEqual ? " = " : " ")
108-
+ PlainSelect.getStringList(ne.expressions, true, false);
109-
}
110-
111-
@Override
112-
public String toString() {
113-
StringBuilder b = new StringBuilder("SET ");
173+
/** Shares statement punctuation with deparsers while allowing expression visitors. */
174+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionRenderer) {
175+
builder.append("SET ");
176+
if (onOffOptions != null) {
177+
if (!values.isEmpty() || effectParameter != null) {
178+
throw new IllegalStateException(
179+
"SET options cannot be combined with assignments or scope");
180+
}
181+
return onOffOptions.appendTo(builder);
182+
}
114183
if (effectParameter != null) {
115-
b.append(effectParameter).append(" ");
116-
}
117-
boolean addComma = false;
118-
for (NameExpr ne : values) {
119-
if (addComma) {
120-
b.append(", ");
121-
} else {
122-
addComma = true;
184+
builder.append(effectParameter).append(" ");
185+
}
186+
for (int i = 0; i < values.size(); i++) {
187+
if (i > 0) {
188+
builder.append(", ");
123189
}
124-
b.append(toString(ne));
190+
values.get(i).appendTo(builder, expressionRenderer);
125191
}
192+
return builder;
193+
}
126194

127-
return b.toString();
195+
@Override
196+
public String toString() {
197+
StringBuilder builder = new StringBuilder();
198+
return appendTo(builder, builder::append).toString();
128199
}
129200

130201
public List<NameExpr> getKeyValuePairs() {
131202
return values;
132203
}
133204

134205
public void addKeyValuePairs(Collection<NameExpr> keyValuePairs) {
206+
onOffOptions = null;
135207
values.addAll(keyValuePairs);
136208
}
137209

@@ -140,6 +212,7 @@ public void addKeyValuePairs(NameExpr... keyValuePairs) {
140212
}
141213

142214
public void clear() {
215+
onOffOptions = null;
143216
values.clear();
144217
effectParameter = null;
145218
}
@@ -173,6 +246,18 @@ public NameExpr(Object name, ExpressionList<?> expressions, boolean useEqual) {
173246
this.useEqual = useEqual;
174247
}
175248

249+
private void appendTo(StringBuilder builder, Consumer<Expression> expressionRenderer) {
250+
builder.append(name).append(useEqual ? " = " : " ");
251+
if (expressions != null) {
252+
for (int i = 0; i < expressions.size(); i++) {
253+
if (i > 0) {
254+
builder.append(", ");
255+
}
256+
expressionRenderer.accept((Expression) expressions.get(i));
257+
}
258+
}
259+
}
260+
176261
public Object getName() {
177262
return name;
178263
}

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1725,7 +1725,9 @@ public void visit(Execute execute) {
17251725

17261726
@Override
17271727
public <S> Void visit(SetStatement setStatement, S context) {
1728-
throwUnsupported(setStatement);
1728+
if (setStatement.getOnOffOptions() == null) {
1729+
throwUnsupported(setStatement);
1730+
}
17291731
return null;
17301732
}
17311733

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

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
*/
1010
package net.sf.jsqlparser.util.deparser;
1111

12-
import net.sf.jsqlparser.expression.Expression;
1312
import net.sf.jsqlparser.expression.ExpressionVisitor;
1413
import net.sf.jsqlparser.statement.SetStatement;
1514

16-
import java.util.List;
1715

1816
public class SetStatementDeParser extends AbstractDeParser<SetStatement> {
1917

@@ -27,28 +25,7 @@ public SetStatementDeParser(ExpressionVisitor<StringBuilder> expressionVisitor,
2725

2826
@Override
2927
public void deParse(SetStatement set) {
30-
builder.append("SET ");
31-
if (set.getEffectParameter() != null) {
32-
builder.append(set.getEffectParameter()).append(" ");
33-
}
34-
for (int i = 0; i < set.getCount(); i++) {
35-
if (i > 0) {
36-
builder.append(", ");
37-
}
38-
builder.append(set.getName(i));
39-
if (set.isUseEqual(i)) {
40-
builder.append(" =");
41-
}
42-
builder.append(" ");
43-
List<Expression> expressions = set.getExpressions(i);
44-
for (int j = 0; j < expressions.size(); j++) {
45-
if (j > 0) {
46-
builder.append(", ");
47-
}
48-
expressions.get(j).accept(expressionVisitor, null);
49-
}
50-
}
51-
28+
set.appendTo(builder, expression -> expression.accept(expressionVisitor, null));
5229
}
5330

5431
public ExpressionVisitor<StringBuilder> getExpressionVisitor() {

src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public enum SqlServerVersion implements Version {
8383
Feature.executeExec, Feature.executeExecute,
8484

8585
// https://docs.microsoft.com/en-us/sql/t-sql/language-elements/set-local-variable-transact-sql?view=sql-server-ver15
86-
Feature.set,
86+
Feature.set, Feature.sqlServerSetOptions,
8787

8888
// https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql?view=sql-server-ver15
8989
Feature.alterTable, // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-sequence-transact-sql?view=sql-server-ver15

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public class SetStatementValidator extends AbstractValidator<SetStatement> {
2323
public void validate(SetStatement set) {
2424
for (ValidationCapability c : getCapabilities()) {
2525
validateFeature(c, Feature.set);
26+
if (set.getOnOffOptions() != null) {
27+
validateFeature(c, Feature.sqlServerSetOptions);
28+
}
2629
}
2730
for (int i = 0; i < set.getCount(); i++) {
2831
validateOptionalExpressions(set.getExpressions(i));

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4064,7 +4064,22 @@ SessionStatement SessionStatement():
40644064
}
40654065

40664066
SetStatement Set(): {
4067-
String namePart;
4067+
SetStatement set;
4068+
}
4069+
{
4070+
<K_SET>
4071+
(
4072+
LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect))
4073+
&& getToken(1).kind == S_IDENTIFIER
4074+
&& SetStatement.OnOffOption.fromName(getToken(1).image) != null
4075+
&& !"=".equals(getToken(2).image) && !".".equals(getToken(2).image) })
4076+
set=SqlServerSetOnOffOptions()
4077+
| set=SetAssignments()
4078+
)
4079+
{ return set; }
4080+
}
4081+
4082+
SetStatement SetAssignments(): {
40684083
Object name;
40694084
ExpressionList expList;
40704085
boolean useEqual = false;
@@ -4074,7 +4089,6 @@ SetStatement Set(): {
40744089
String effectParameter = null;
40754090
}
40764091
{
4077-
<K_SET>
40784092
[LOOKAHEAD(3) (tk = <K_LOCAL> | tk = <K_SESSION>) {effectParameter = tk.image; } ]
40794093
(
40804094
LOOKAHEAD(2)
@@ -4122,6 +4136,33 @@ SetStatement Set(): {
41224136
{ return set; }
41234137
}
41244138

4139+
SetStatement SqlServerSetOnOffOptions(): {
4140+
SetStatement set = new SetStatement();
4141+
List<SetStatement.OnOffOption> options = new ArrayList<SetStatement.OnOffOption>();
4142+
SetStatement.OnOffOption option;
4143+
Token name;
4144+
boolean on;
4145+
}
4146+
{
4147+
name=<S_IDENTIFIER>
4148+
{
4149+
option = accessEnum(SetStatement.OnOffOption.class, name.image);
4150+
options.add(option);
4151+
}
4152+
(
4153+
"," name=<S_IDENTIFIER>
4154+
{
4155+
option = accessEnum(SetStatement.OnOffOption.class, name.image);
4156+
options.add(option);
4157+
}
4158+
)*
4159+
( <K_ON> { on = true; } | <K_OFF> { on = false; } )
4160+
{
4161+
set.setOnOffOptions(new SetStatement.OnOffOptions(options, on));
4162+
return set;
4163+
}
4164+
}
4165+
41254166
ResetStatement Reset(): {
41264167
String name;
41274168
ResetStatement reset;

src/site/sphinx/usage.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,13 @@ operators such as ``js#>>'{a}'`` and ``js#>'{a}'`` work without surrounding
788788
spaces. Quote identifiers containing ``#``, for example ``"js#"``. Other
789789
dialects retain their existing identifier and hash-comment rules.
790790

791+
With ``Dialect.SQLSERVER``, ``SET NOCOUNT ON`` and grouped boolean options such as
792+
``SET QUOTED_IDENTIFIER, ANSI_NULLS OFF`` use ``SetStatement.getOnOffOptions()``.
793+
The ordered ``OnOffOption`` list and shared ``isOn()`` value are editable;
794+
``setOnOffOptions()`` replaces generic assignments and their scope. Both SQL
795+
renderers share statement punctuation while generic assignments retain expression
796+
visitor support. Parsing a SET directive records it without changing lexer settings.
797+
791798
With ``Dialect.SQLSERVER``, ``PRIMARY KEY NONCLUSTERED (id)`` and
792799
``UNIQUE CLUSTERED (id)`` store their clustering option in ``Index.getClustering()``
793800
for both ``CREATE TABLE`` and ``ALTER TABLE``. Without that dialect, these words

0 commit comments

Comments
 (0)