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
59 changes: 51 additions & 8 deletions src/main/java/net/sf/jsqlparser/statement/SetStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ public final class SetStatement implements Statement {
private String effectParameter;
private OnOffOptions onOffOptions;

/** Assignment punctuation; NONE also supports SET options and PostgreSQL value lists. */
public enum AssignmentOperator {
NONE(""), EQUALS("="), COLON_EQUALS(":=");

private final String token;

AssignmentOperator(String token) {
this.token = token;
}

public String getToken() {
return token;
}
}

/** SQL Server options that share the SET option [, option] ON | OFF syntax. */
public enum OnOffOption {
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;
Expand Down Expand Up @@ -99,8 +114,12 @@ public SetStatement(Object name, ExpressionList<?> value) {
}

public void add(Object name, ExpressionList<?> value, boolean useEqual) {
add(name, value, useEqual ? AssignmentOperator.EQUALS : AssignmentOperator.NONE);
}

public void add(Object name, ExpressionList<?> value, AssignmentOperator operator) {
onOffOptions = null;
values.add(new NameExpr(name, value, useEqual));
values.add(new NameExpr(name, value, operator));
}

public void remove(int idx) {
Expand All @@ -112,7 +131,7 @@ public int getCount() {
}

public boolean isUseEqual(int idx) {
return values.get(idx).useEqual;
return values.get(idx).isUseEqual();
}

public boolean isUseEqual() {
Expand All @@ -129,10 +148,27 @@ public SetStatement withUseEqual(int idx, boolean useEqual) {
}

public SetStatement setUseEqual(int idx, boolean useEqual) {
values.get(idx).useEqual = useEqual;
values.get(idx).setUseEqual(useEqual);
return this;
}

public AssignmentOperator getAssignmentOperator(int idx) {
return values.get(idx).operator;
}

public AssignmentOperator getAssignmentOperator() {
return getAssignmentOperator(0);
}

public SetStatement setAssignmentOperator(int idx, AssignmentOperator operator) {
values.get(idx).operator = Objects.requireNonNull(operator, "operator");
return this;
}

public SetStatement setAssignmentOperator(AssignmentOperator operator) {
return setAssignmentOperator(0, operator);
}

public SetStatement withUseEqual(boolean useEqual) {
this.setUseEqual(useEqual);
return this;
Expand Down Expand Up @@ -194,7 +230,10 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expres

private static void appendAssignment(StringBuilder builder, NameExpr value,
Consumer<Expression> expressionRenderer) {
builder.append(value.name).append(value.useEqual ? " = " : " ");
builder.append(value.name).append(" ");
if (value.operator != AssignmentOperator.NONE) {
builder.append(value.operator.getToken()).append(" ");
}
if (value.expressions != null) {
for (int i = 0; i < value.expressions.size(); i++) {
if (i > 0) {
Expand Down Expand Up @@ -251,12 +290,16 @@ public SetStatement withEffectParameter(String effectParameter) {
static class NameExpr implements Serializable {
Object name;
ExpressionList expressions;
boolean useEqual;
AssignmentOperator operator;

public NameExpr(Object name, ExpressionList<?> expressions, boolean useEqual) {
this(name, expressions, useEqual ? AssignmentOperator.EQUALS : AssignmentOperator.NONE);
}

public NameExpr(Object name, ExpressionList<?> expressions, AssignmentOperator operator) {
this.name = name;
this.expressions = expressions;
this.useEqual = useEqual;
this.operator = Objects.requireNonNull(operator, "operator");
}

public Object getName() {
Expand All @@ -276,11 +319,11 @@ public void setExpressions(ExpressionList<?> expressions) {
}

public boolean isUseEqual() {
return useEqual;
return operator == AssignmentOperator.EQUALS;
}

public void setUseEqual(boolean useEqual) {
this.useEqual = useEqual;
operator = useEqual ? AssignmentOperator.EQUALS : AssignmentOperator.NONE;
}
}
}
92 changes: 45 additions & 47 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -4481,62 +4481,60 @@ SetStatement Set(): {
}

SetStatement SetAssignments(): {
Object name;
ExpressionList expList;
boolean useEqual = false;
SetStatement set;
Expression exp = null;
SetStatement set = new SetStatement();
Expression exp;
Token tk = null;
String effectParameter = null;
}
{
[LOOKAHEAD(3) (tk = <K_LOCAL> | tk = <K_SESSION>) {effectParameter = tk.image; } ]
[LOOKAHEAD(3) (tk = <K_LOCAL> | tk = <K_SESSION>) { set.setEffectParameter(tk.image); } ]
SetAssignment(set)
(
LOOKAHEAD(2)
<K_DATETIMELITERAL> <K_ZONE> { name = "Time Zone"; useEqual=false; }
|
(
name = UserVariable() ["=" { useEqual=true; } ]
)
|
(
name = IdentifierChain()
["=" { useEqual=true; } ]
)
)
exp=Expression()
{
expList = new ExpressionList();
expList.add(exp);
set = new SetStatement(name, expList)
.withUseEqual(useEqual)
.withEffectParameter(effectParameter);
}

(
{ useEqual=false; }
","
(LOOKAHEAD(3)
(
( LOOKAHEAD(2)
<K_DATETIMELITERAL> <K_ZONE> { name = "Time Zone"; useEqual=false; }
|
(name = RelObjectName() ["=" { useEqual=true; } ])
)
exp=Expression()
{
expList = new ExpressionList();
expList.add(exp);
set.add(name, expList, useEqual);
}
)
|
exp=Expression() { expList.add(exp); }
)
( LOOKAHEAD(3) SetAssignment(set)
| exp=Expression() { set.getExpressions(set.getCount() - 1).add(exp); } )
)*
{ return set; }
}

/** The first and subsequent assignments use identical names and operators. */
void SetAssignment(SetStatement set):
{
Object name;
Expression expression;
SetStatement.AssignmentOperator operator = SetStatement.AssignmentOperator.NONE;
}
{
(
LOOKAHEAD({ (getToken(1).kind == K_CHARACTER && getToken(2).kind == K_SET)
|| (isKeywordAhead("CHARSET") && !"=".equals(getToken(2).image)
&& !":=".equals(getToken(2).image)) }) SetCharacterSet(set)
|
( LOOKAHEAD(2) <K_DATETIMELITERAL> <K_ZONE> { name = "Time Zone"; }
| name=UserVariable()
| name=IdentifierChain() )
[ "=" { operator = SetStatement.AssignmentOperator.EQUALS; }
| ":=" { operator = SetStatement.AssignmentOperator.COLON_EQUALS; } ]
expression=Expression()
{ set.add(name, new ExpressionList<Expression>(expression), operator); }
)
}

/** Character set selection has a single name or DEFAULT, not an arbitrary expression. */
void SetCharacterSet(SetStatement set):
{
String name;
Token value;
Expression expression;
}
{
( <K_CHARACTER> <K_SET> { name = "CHARACTER SET"; }
| <S_IDENTIFIER> { name = "CHARSET"; } )
( value=<S_CHAR_LITERAL> { expression = new StringValue(value.image); }
| (value=<S_IDENTIFIER> | value=<S_QUOTED_IDENTIFIER> | value=<K_DEFAULT>)
{ expression = new Column(value.image); } )
{ set.add(name, new ExpressionList<Expression>(expression), false); }
}

SetStatement SqlServerSetOnOffOptions(): {
SetStatement set = new SetStatement();
List<SetStatement.OnOffOption> options = new ArrayList<SetStatement.OnOffOption>();
Expand Down
122 changes: 122 additions & 0 deletions src/test/java/net/sf/jsqlparser/statement/MySqlSetStatementTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement;

import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.SetStatement.AssignmentOperator;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class MySqlSetStatementTest {
private SetStatement parse(String sql) throws Exception {
return (SetStatement) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
}

@ParameterizedTest
@ValueSource(strings = {"SET @a := 1", "SET @a=1, @b:=2", "SET @a:=1, @b=2, @c:=3",
"SET @@session.sql_mode := ''", "SET SESSION sql_mode = ''",
"SET @a = (@b := 2), @c := 3", "SET CHARACTER SET utf8mb4",
"SET CHARACTER SET 'utf8mb4'", "SET CHARACTER SET DEFAULT", "SET CHARSET utf8mb4",
"SET CHARSET DEFAULT", "SET NAMES utf8mb4 COLLATE utf8mb4_bin",
"SET CHARSET DEFAULT, @a=1", "SET @a=1, CHARACTER SET utf8mb4"})
void preservesSetSyntax(String sql) throws Exception {
SetStatement statement = parse(sql);
StringBuilder output = new StringBuilder();
statement.accept(new StatementDeParser(output), null);
assertEquals(statement.toString(), output.toString());
assertEquals(statement.toString(), parse(output.toString()).toString());
}

@Test
void exposesEachAssignmentAndOperator() throws Exception {
SetStatement statement = parse("SET @a=1, @b:=2, @c=3");
assertEquals(3, statement.getCount());
assertEquals("@b", statement.getName(1).toString());
assertEquals(AssignmentOperator.COLON_EQUALS, statement.getAssignmentOperator(1));
assertEquals(1, statement.getExpressions(1).size());
assertInstanceOf(LongValue.class, statement.getExpressions(1).get(0));
statement.setExpressions(1, new ExpressionList<>(new LongValue(9)));
statement.setAssignmentOperator(2, AssignmentOperator.COLON_EQUALS);
assertEquals("SET @a = 1, @b := 9, @c := 3", statement.toString());
statement.setUseEqual(1, true);
assertEquals(AssignmentOperator.EQUALS, statement.getAssignmentOperator(1));
assertTrue(statement.isUseEqual(1));
statement.remove(0);
assertEquals("SET @b = 9, @c := 3", statement.toString());
}

@Test
void exposesCharsetSelectionAlongsideAssignments() throws Exception {
SetStatement statement = parse("SET @a=1, CHARACTER SET utf8mb4, @b:=2");
assertEquals(3, statement.getCount());
assertEquals("CHARACTER SET", statement.getName(1));
assertEquals(AssignmentOperator.NONE, statement.getAssignmentOperator(1));
assertEquals(AssignmentOperator.COLON_EQUALS, statement.getAssignmentOperator(2));
}

@Test
void keepsNestedAssignmentInItsValue() throws Exception {
SetStatement statement = parse("SET @a = (@b := 2), @c := 3");
assertEquals(2, statement.getCount());
assertEquals("@a", statement.getName().toString());
assertEquals(1, statement.getExpressions().size());
assertEquals("(@b := 2)", statement.getExpressions().get(0).toString());
}

@Test
void retainsPostgresqlValueListsAndLegacyApi() throws Exception {
SetStatement statement = (SetStatement) CCJSqlParserUtil.parse(
"SET search_path = public, extensions", p -> p.withDialect(Dialect.POSTGRESQL));
assertEquals(1, statement.getCount());
assertEquals(2, statement.getExpressions().size());
assertEquals("SET search_path = public, extensions", statement.toString());
SetStatement legacy = new SetStatement("x", new ExpressionList<>(new LongValue(1)));
assertEquals(AssignmentOperator.EQUALS, legacy.getAssignmentOperator());
legacy.setUseEqual(false);
assertEquals("SET x 1", legacy.toString());
legacy.setAssignmentOperator(AssignmentOperator.COLON_EQUALS);
assertEquals("SET x := 1", legacy.toString());
}

@Test
void visitsAllAssignmentValues() throws Exception {
List<Long> values = new java.util.ArrayList<>();
ExpressionDeParser expressions = new ExpressionDeParser() {
@Override
public <S> StringBuilder visit(LongValue value, S context) {
values.add(value.getValue());
return getBuilder().append(value.getValue() + 10);
}
};
StringBuilder output = new StringBuilder();
parse("SET @a:=1, @b=2").accept(
new StatementDeParser(expressions, new SelectDeParser(), output), null);
assertEquals(List.of(1L, 2L), values);
assertEquals("SET @a := 11, @b = 12", output.toString());
}

@ParameterizedTest
@ValueSource(strings = {"SET @a :=", "SET @a := 1,", "SET CHARACTER SET",
"SET CHARACTER SET utf8mb4 COLLATE utf8mb4_bin", "SET CHARSET DEFAULT,"})
void rejectsIncompleteAssignmentsAndInvalidCharsetClauses(String sql) {
assertThrows(Exception.class, () -> parse(sql));
}
}
Loading