Skip to content

Commit f5b522a

Browse files
committed
feat: parse MySQL ALTER USER, RENAME USER and role statements with shared user options
Model the account options of CREATE USER and ALTER USER once in UserOptions (DEFAULT ROLE, REQUIRE, WITH resource limits, password and locking options, COMMENT/ATTRIBUTE), parsed by one shared production. Add ALTER USER, RENAME USER, MySQL CREATE ROLE names, SET ROLE, SET DEFAULT ROLE and SET PASSWORD, reading accounts and roles with the shared MySqlAccountName(), MySqlAccountNameList() and MySqlRoleSelection() productions. PostgreSQL ALTER USER and SET ROLE keep their existing AST. SET ROLE and SET PASSWORD without FOR become typed statements with the MySQL and MariaDB dialects. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent ce2cdaa commit f5b522a

32 files changed

Lines changed: 2928 additions & 7 deletions

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
144144
| **DML** | `INSERT` · `UPDATE` · `UPSERT` · `MERGE` · `DELETE` · `TRUNCATE TABLE` |
145145
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
146146
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `OPTIMIZE` · `REPAIR TABLE` |
147+
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
147148
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
148149
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
149150
| **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` |

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,17 @@ public enum Feature {
693693
* @see Grant
694694
*/
695695
grant, revoke, createRole, alterRole, alterDefaultPrivileges,
696+
/**
697+
* MySQL/MariaDB account management: "ALTER USER", "RENAME USER", "SET ROLE", "SET DEFAULT ROLE"
698+
* and "SET PASSWORD"
699+
*
700+
* @see net.sf.jsqlparser.statement.alter.AlterUser
701+
* @see net.sf.jsqlparser.statement.alter.RenameUser
702+
* @see net.sf.jsqlparser.statement.SetRoleStatement
703+
* @see net.sf.jsqlparser.statement.SetDefaultRoleStatement
704+
* @see net.sf.jsqlparser.statement.SetPasswordStatement
705+
*/
706+
alterUser, renameUser, setRole, setDefaultRole, setPassword,
696707
/**
697708
* @see Function
698709
*/
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import java.util.ArrayList;
13+
import java.util.Collection;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import java.util.stream.Collectors;
17+
import net.sf.jsqlparser.statement.create.user.RoleSelection;
18+
import net.sf.jsqlparser.statement.create.user.UserAccount;
19+
20+
/**
21+
* MySQL {@code SET DEFAULT ROLE {NONE | ALL | role, ...} TO user, ...}. MariaDB's
22+
* {@code SET DEFAULT ROLE {role | NONE} [FOR user]} is kept with {@link #isForKeyword()}; without
23+
* accounts the statement applies to the current user.
24+
*/
25+
public class SetDefaultRoleStatement implements Statement {
26+
27+
private RoleSelection roles;
28+
private final List<UserAccount> accounts = new ArrayList<>();
29+
private boolean forKeyword;
30+
31+
public RoleSelection getRoles() {
32+
return roles;
33+
}
34+
35+
public void setRoles(RoleSelection roles) {
36+
this.roles = roles;
37+
}
38+
39+
/** The accounts after {@code TO} or {@code FOR}; empty when omitted. */
40+
public List<UserAccount> getAccounts() {
41+
return Collections.unmodifiableList(accounts);
42+
}
43+
44+
public void setAccounts(Collection<? extends UserAccount> accounts) {
45+
this.accounts.clear();
46+
if (accounts != null) {
47+
this.accounts.addAll(accounts);
48+
}
49+
}
50+
51+
/** Whether the accounts follow MariaDB's {@code FOR} instead of {@code TO}. */
52+
public boolean isForKeyword() {
53+
return forKeyword;
54+
}
55+
56+
public void setForKeyword(boolean forKeyword) {
57+
this.forKeyword = forKeyword;
58+
}
59+
60+
@Override
61+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
62+
return statementVisitor.visit(this, context);
63+
}
64+
65+
@Override
66+
public String toString() {
67+
StringBuilder builder = new StringBuilder("SET DEFAULT ROLE ").append(roles);
68+
if (!accounts.isEmpty()) {
69+
builder.append(forKeyword ? " FOR " : " TO ").append(
70+
accounts.stream().map(Object::toString).collect(Collectors.joining(", ")));
71+
}
72+
return builder.toString();
73+
}
74+
75+
public SetDefaultRoleStatement withRoles(RoleSelection roles) {
76+
setRoles(roles);
77+
return this;
78+
}
79+
80+
public SetDefaultRoleStatement withAccounts(Collection<? extends UserAccount> accounts) {
81+
setAccounts(accounts);
82+
return this;
83+
}
84+
85+
public SetDefaultRoleStatement addAccounts(UserAccount... accounts) {
86+
Collections.addAll(this.accounts, accounts);
87+
return this;
88+
}
89+
90+
public SetDefaultRoleStatement withForKeyword(boolean forKeyword) {
91+
setForKeyword(forKeyword);
92+
return this;
93+
}
94+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import java.util.function.Consumer;
13+
import net.sf.jsqlparser.expression.Expression;
14+
import net.sf.jsqlparser.expression.StringValue;
15+
import net.sf.jsqlparser.statement.create.user.UserAccount;
16+
17+
/**
18+
* MySQL/MariaDB {@code SET PASSWORD [FOR user] {= password | TO RANDOM}
19+
* [REPLACE 'current'] [RETAIN CURRENT PASSWORD]}. The password is an expression, usually a string
20+
* literal; MariaDB also accepts {@code PASSWORD('...')}.
21+
*/
22+
public class SetPasswordStatement implements Statement {
23+
24+
private UserAccount account;
25+
private Expression password;
26+
private boolean random;
27+
private StringValue currentCredential;
28+
private boolean retainCurrentPassword;
29+
30+
/** The account after {@code FOR}, or null for the current user. */
31+
public UserAccount getAccount() {
32+
return account;
33+
}
34+
35+
public void setAccount(UserAccount account) {
36+
this.account = account;
37+
}
38+
39+
/** The password after {@code =}; null for {@code TO RANDOM}. */
40+
public Expression getPassword() {
41+
return password;
42+
}
43+
44+
public void setPassword(Expression password) {
45+
this.password = password;
46+
}
47+
48+
/** Whether the statement generates a password with {@code TO RANDOM}. */
49+
public boolean isRandom() {
50+
return random;
51+
}
52+
53+
public void setRandom(boolean random) {
54+
this.random = random;
55+
}
56+
57+
/** The current password of {@code REPLACE 'current'}, or null when omitted. */
58+
public StringValue getCurrentCredential() {
59+
return currentCredential;
60+
}
61+
62+
public void setCurrentCredential(StringValue currentCredential) {
63+
this.currentCredential = currentCredential;
64+
}
65+
66+
public boolean isRetainCurrentPassword() {
67+
return retainCurrentPassword;
68+
}
69+
70+
public void setRetainCurrentPassword(boolean retainCurrentPassword) {
71+
this.retainCurrentPassword = retainCurrentPassword;
72+
}
73+
74+
/** Shares statement rendering while letting deparsers render the password expression. */
75+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionRenderer) {
76+
builder.append("SET PASSWORD");
77+
if (account != null) {
78+
builder.append(" FOR ").append(account);
79+
}
80+
if (random) {
81+
builder.append(" TO RANDOM");
82+
} else {
83+
builder.append(" = ");
84+
expressionRenderer.accept(password);
85+
}
86+
if (currentCredential != null) {
87+
builder.append(" REPLACE ").append(currentCredential);
88+
}
89+
if (retainCurrentPassword) {
90+
builder.append(" RETAIN CURRENT PASSWORD");
91+
}
92+
return builder;
93+
}
94+
95+
@Override
96+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
97+
return statementVisitor.visit(this, context);
98+
}
99+
100+
@Override
101+
public String toString() {
102+
StringBuilder builder = new StringBuilder();
103+
return appendTo(builder, builder::append).toString();
104+
}
105+
106+
public SetPasswordStatement withAccount(UserAccount account) {
107+
setAccount(account);
108+
return this;
109+
}
110+
111+
public SetPasswordStatement withPassword(Expression password) {
112+
setPassword(password);
113+
return this;
114+
}
115+
116+
public SetPasswordStatement withRandom(boolean random) {
117+
setRandom(random);
118+
return this;
119+
}
120+
121+
public SetPasswordStatement withCurrentCredential(StringValue currentCredential) {
122+
setCurrentCredential(currentCredential);
123+
return this;
124+
}
125+
126+
public SetPasswordStatement withRetainCurrentPassword(boolean retainCurrentPassword) {
127+
setRetainCurrentPassword(retainCurrentPassword);
128+
return this;
129+
}
130+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import net.sf.jsqlparser.statement.create.user.RoleSelection;
13+
14+
/**
15+
* MySQL/MariaDB {@code SET ROLE {DEFAULT | NONE | ALL [EXCEPT role, ...] | role, ...}}. PostgreSQL
16+
* {@code SET [SESSION | LOCAL] ROLE name} remains a {@link SetStatement}.
17+
*/
18+
public class SetRoleStatement implements Statement {
19+
20+
private RoleSelection roles;
21+
22+
public SetRoleStatement() {
23+
// empty constructor
24+
}
25+
26+
public SetRoleStatement(RoleSelection roles) {
27+
this.roles = roles;
28+
}
29+
30+
public RoleSelection getRoles() {
31+
return roles;
32+
}
33+
34+
public void setRoles(RoleSelection roles) {
35+
this.roles = roles;
36+
}
37+
38+
@Override
39+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
40+
return statementVisitor.visit(this, context);
41+
}
42+
43+
@Override
44+
public String toString() {
45+
return "SET ROLE " + roles;
46+
}
47+
48+
public SetRoleStatement withRoles(RoleSelection roles) {
49+
setRoles(roles);
50+
return this;
51+
}
52+
}

‎src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import net.sf.jsqlparser.statement.alter.AlterType;
4343
import net.sf.jsqlparser.statement.alter.AlterUserMapping;
4444
import net.sf.jsqlparser.statement.alter.RenameTableStatement;
45+
import net.sf.jsqlparser.statement.alter.AlterUser;
46+
import net.sf.jsqlparser.statement.alter.RenameUser;
4547
import net.sf.jsqlparser.statement.alter.database.AlterDatabase;
4648
import net.sf.jsqlparser.statement.alter.schema.AlterSchema;
4749
import net.sf.jsqlparser.statement.alter.sequence.AlterSequence;
@@ -867,6 +869,11 @@ public <S> Void visit(SetIdentityInsertStatement statement, S context) {
867869
return sessionOnly();
868870
}
869871

872+
@Override
873+
public <S> Void visit(SetRoleStatement statement, S context) {
874+
return sessionOnly();
875+
}
876+
870877
@Override
871878
public <S> Void visit(UseStatement use, S context) {
872879
return sessionOnly();
@@ -1216,6 +1223,26 @@ public <S> Void visit(CreateUser statement, S context) {
12161223
return schemaOnly();
12171224
}
12181225

1226+
@Override
1227+
public <S> Void visit(AlterUser statement, S context) {
1228+
return schemaOnly();
1229+
}
1230+
1231+
@Override
1232+
public <S> Void visit(RenameUser statement, S context) {
1233+
return schemaOnly();
1234+
}
1235+
1236+
@Override
1237+
public <S> Void visit(SetDefaultRoleStatement statement, S context) {
1238+
return schemaOnly();
1239+
}
1240+
1241+
@Override
1242+
public <S> Void visit(SetPasswordStatement statement, S context) {
1243+
return schemaOnly();
1244+
}
1245+
12191246
// These statements store definitions; visiting their children must not make a stored
12201247
// rule action or policy predicate appear to execute during the DDL statement itself.
12211248

0 commit comments

Comments
 (0)