Skip to content

Commit eea37a8

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 f39bc41 commit eea37a8

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
@@ -145,6 +145,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
145145
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
146146
| **PostgreSQL ownership** | `DROP OWNED` · `REASSIGN OWNED` |
147147
| **MySQL table maintenance** | `ANALYZE` · `CHECK` · `OPTIMIZE` · `REPAIR TABLE` |
148+
| **MySQL account management** | `CREATE`/`ALTER`/`RENAME USER` · `CREATE ROLE` · `SET ROLE` · `SET DEFAULT ROLE` · `SET PASSWORD` |
148149
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
149150
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
150151
| **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
@@ -706,6 +706,17 @@ public enum Feature {
706706
* @see Grant
707707
*/
708708
grant, revoke, createRole, alterRole, alterDefaultPrivileges,
709+
/**
710+
* MySQL/MariaDB account management: "ALTER USER", "RENAME USER", "SET ROLE", "SET DEFAULT ROLE"
711+
* and "SET PASSWORD"
712+
*
713+
* @see net.sf.jsqlparser.statement.alter.AlterUser
714+
* @see net.sf.jsqlparser.statement.alter.RenameUser
715+
* @see net.sf.jsqlparser.statement.SetRoleStatement
716+
* @see net.sf.jsqlparser.statement.SetDefaultRoleStatement
717+
* @see net.sf.jsqlparser.statement.SetPasswordStatement
718+
*/
719+
alterUser, renameUser, setRole, setDefaultRole, setPassword,
709720
/**
710721
* @see Function
711722
*/
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;
@@ -873,6 +875,11 @@ public <S> Void visit(SetIdentityInsertStatement statement, S context) {
873875
return sessionOnly();
874876
}
875877

878+
@Override
879+
public <S> Void visit(SetRoleStatement statement, S context) {
880+
return sessionOnly();
881+
}
882+
876883
@Override
877884
public <S> Void visit(UseStatement use, S context) {
878885
return sessionOnly();
@@ -1222,6 +1229,26 @@ public <S> Void visit(CreateUser statement, S context) {
12221229
return schemaOnly();
12231230
}
12241231

1232+
@Override
1233+
public <S> Void visit(AlterUser statement, S context) {
1234+
return schemaOnly();
1235+
}
1236+
1237+
@Override
1238+
public <S> Void visit(RenameUser statement, S context) {
1239+
return schemaOnly();
1240+
}
1241+
1242+
@Override
1243+
public <S> Void visit(SetDefaultRoleStatement statement, S context) {
1244+
return schemaOnly();
1245+
}
1246+
1247+
@Override
1248+
public <S> Void visit(SetPasswordStatement statement, S context) {
1249+
return schemaOnly();
1250+
}
1251+
12251252
// These statements store definitions; visiting their children must not make a stored
12261253
// rule action or policy predicate appear to execute during the DDL statement itself.
12271254

0 commit comments

Comments
 (0)