Skip to content

Commit ce2cdaa

Browse files
committed
feat: parse MySQL GRANT and REVOKE privilege levels, privileges and accounts
Parse MySQL privilege levels (*, *.*, db.*), static and dynamic privileges, user@host accounts, quoted role grants, PROXY, REVOKE ALL PRIVILEGES, GRANT OPTION, REVOKE IF EXISTS ... IGNORE UNKNOWN USER and GRANT ... AS user WITH ROLE. Grantees, granted roles and the grantor use a typed Grantee/UserAccount model shared with PostgreSQL, which also gains membership option lists such as WITH INHERIT TRUE, SET FALSE. Accounts are read with the shared MySqlAccountName() rule, and WITH ROLE uses a RoleSelection next to UserAccount. Role memberships no longer carry an empty table target. The string accessors remain as derived views. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 1af2ff3 commit ce2cdaa

15 files changed

Lines changed: 1420 additions & 108 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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.create.user;
11+
12+
import java.io.Serializable;
13+
import java.util.ArrayList;
14+
import java.util.Collection;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.stream.Collectors;
18+
19+
/**
20+
* The roles named by MySQL {@code SET ROLE}, {@code SET DEFAULT ROLE}, the {@code DEFAULT ROLE}
21+
* clause of {@code CREATE USER} and {@code ALTER USER}, and {@code GRANT ... AS user WITH ROLE}.
22+
* Role names use the account syntax, so each role is a {@link UserAccount} without authentication.
23+
*/
24+
public class RoleSelection implements Serializable {
25+
26+
public enum Kind {
27+
/**
28+
* {@code DEFAULT}: the account's default roles, only in {@code SET ROLE} and
29+
* {@code WITH ROLE}.
30+
*/
31+
DEFAULT,
32+
/** {@code NONE}. */
33+
NONE,
34+
/** {@code ALL}. */
35+
ALL,
36+
/** {@code ALL EXCEPT role [, role] ...}, only in {@code SET ROLE} and {@code WITH ROLE}. */
37+
ALL_EXCEPT,
38+
/** {@code role [, role] ...}. */
39+
ROLES
40+
}
41+
42+
private Kind kind;
43+
private final List<UserAccount> roles = new ArrayList<>();
44+
45+
public RoleSelection() {
46+
// empty constructor
47+
}
48+
49+
public RoleSelection(Kind kind) {
50+
this.kind = kind;
51+
}
52+
53+
public Kind getKind() {
54+
return kind;
55+
}
56+
57+
public void setKind(Kind kind) {
58+
this.kind = kind;
59+
}
60+
61+
/** Returns the listed roles of {@link Kind#ROLES} and {@link Kind#ALL_EXCEPT}. */
62+
public List<UserAccount> getRoles() {
63+
return Collections.unmodifiableList(roles);
64+
}
65+
66+
public void setRoles(Collection<? extends UserAccount> roles) {
67+
this.roles.clear();
68+
if (roles != null) {
69+
this.roles.addAll(roles);
70+
}
71+
}
72+
73+
@Override
74+
public String toString() {
75+
String names = roles.stream().map(Object::toString).collect(Collectors.joining(", "));
76+
switch (kind) {
77+
case ROLES:
78+
return names;
79+
case ALL_EXCEPT:
80+
return "ALL EXCEPT " + names;
81+
default:
82+
return kind.name();
83+
}
84+
}
85+
86+
public RoleSelection withKind(Kind kind) {
87+
setKind(kind);
88+
return this;
89+
}
90+
91+
public RoleSelection withRoles(Collection<? extends UserAccount> roles) {
92+
setRoles(roles);
93+
return this;
94+
}
95+
96+
public RoleSelection addRoles(UserAccount... roles) {
97+
Collections.addAll(this.roles, roles);
98+
return this;
99+
}
100+
}

‎src/main/java/net/sf/jsqlparser/statement/grant/Grant.java‎

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,23 @@
99
*/
1010
package net.sf.jsqlparser.statement.grant;
1111

12-
import java.util.AbstractList;
1312
import java.util.ArrayList;
1413
import java.util.Arrays;
1514
import java.util.Collection;
1615
import java.util.Collections;
1716
import java.util.List;
18-
import java.util.Optional;
1917
import java.util.function.Consumer;
2018
import java.util.stream.Collectors;
2119
import net.sf.jsqlparser.expression.Expression;
2220
import net.sf.jsqlparser.statement.Statement;
2321
import net.sf.jsqlparser.statement.StatementVisitor;
22+
import net.sf.jsqlparser.statement.create.user.RoleSelection;
2423

2524
public class Grant implements Statement {
2625
private PrivilegeClause clause = new PrivilegeClause();
27-
private GrantOption option;
26+
private final List<GrantOption> options = new ArrayList<>();
27+
private Grantee asUser;
28+
private RoleSelection withRole;
2829

2930
public PrivilegeClause getClause() {
3031
return clause;
@@ -34,12 +35,46 @@ public void setClause(PrivilegeClause clause) {
3435
this.clause = clause;
3536
}
3637

38+
/**
39+
* The {@code WITH} options as a mutable list: {@code GRANT OPTION}, {@code ADMIN OPTION} or
40+
* PostgreSQL membership options such as {@code INHERIT TRUE, SET FALSE}.
41+
*/
42+
public List<GrantOption> getOptions() {
43+
return options;
44+
}
45+
46+
public void setOptions(Collection<? extends GrantOption> options) {
47+
List<GrantOption> copy = options == null ? new ArrayList<>() : new ArrayList<>(options);
48+
this.options.clear();
49+
this.options.addAll(copy);
50+
}
51+
52+
/** The first {@code WITH} option, or null; see {@link #getOptions()}. */
3753
public GrantOption getOption() {
38-
return option;
54+
return options.isEmpty() ? null : options.get(0);
3955
}
4056

57+
/** Replaces all {@code WITH} options with the given option, or removes them for null. */
4158
public void setOption(GrantOption option) {
42-
this.option = option;
59+
setOptions(option == null ? null : Collections.singletonList(option));
60+
}
61+
62+
/** The MySQL {@code AS user} account, or null when omitted. */
63+
public Grantee getAsUser() {
64+
return asUser;
65+
}
66+
67+
public void setAsUser(Grantee asUser) {
68+
this.asUser = asUser;
69+
}
70+
71+
/** The MySQL {@code WITH ROLE} selection of the {@code AS user} clause, or null. */
72+
public RoleSelection getWithRole() {
73+
return withRole;
74+
}
75+
76+
public void setWithRole(RoleSelection withRole) {
77+
this.withRole = withRole;
4378
}
4479

4580
public List<Privilege> getPrivilegeItems() {
@@ -50,6 +85,7 @@ public void setPrivilegeItems(List<Privilege> privileges) {
5085
clause.setPrivileges(privileges);
5186
}
5287

88+
/** The target, or null for a role membership. */
5389
public PrivilegeTarget getTarget() {
5490
return clause.getTarget();
5591
}
@@ -67,14 +103,16 @@ public void setRoles(List<String> roles) {
67103
}
68104

69105
public String getRole() {
70-
return clause.getRoles().isEmpty() ? null : clause.getRoles().get(0);
106+
return clause.getRoleItems().isEmpty() ? null : clause.getRoles().get(0);
71107
}
72108

109+
/** Makes this a membership of the given role, which has neither privileges nor a target. */
73110
public void setRole(String role) {
74111
clause.setRoles(new ArrayList<>());
75112
if (role != null) {
76113
clause.getRoles().add(role);
77114
clause.setPrivileges(null);
115+
clause.setTarget(null);
78116
}
79117
}
80118

@@ -83,32 +121,7 @@ public List<String> getPrivileges() {
83121
if (clause.getPrivileges() == null) {
84122
return null;
85123
}
86-
return new AbstractList<String>() {
87-
@Override
88-
public int size() {
89-
return clause.getPrivileges().size();
90-
}
91-
92-
@Override
93-
public String get(int index) {
94-
return clause.getPrivileges().get(index).toString();
95-
}
96-
97-
@Override
98-
public String set(int index, String value) {
99-
return clause.getPrivileges().set(index, new Privilege(value)).toString();
100-
}
101-
102-
@Override
103-
public void add(int index, String value) {
104-
clause.getPrivileges().add(index, new Privilege(value));
105-
}
106-
107-
@Override
108-
public String remove(int index) {
109-
return clause.getPrivileges().remove(index).toString();
110-
}
111-
};
124+
return new StringListView<>(() -> clause.getPrivileges(), Privilege::new);
112125
}
113126

114127
public void setPrivileges(List<String> privileges) {
@@ -122,12 +135,22 @@ public void setPrivileges(List<String> privileges) {
122135
clause.setPrivileges(items);
123136
}
124137

138+
/**
139+
* The first named target as dotted text. MySQL wildcard levels keep their wildcard, for example
140+
* {@code db.*} or {@code *.*}.
141+
*/
125142
public String getObjectName() {
126-
if (clause.getTarget().getNames().isEmpty()
127-
|| clause.getTarget().getNames().get(0).isEmpty()) {
143+
PrivilegeTarget target = clause.getTarget();
144+
if (target == null) {
128145
return null;
129146
}
130-
List<String> parts = clause.getTarget().getNames().get(0);
147+
if (target.getWildcard() != null) {
148+
return target.getTargetText();
149+
}
150+
if (target.getNames().isEmpty() || target.getNames().get(0).isEmpty()) {
151+
return null;
152+
}
153+
List<String> parts = target.getNames().get(0);
131154
return parts.stream().map(part -> part == null ? "" : part)
132155
.collect(Collectors.joining("."));
133156
}
@@ -136,14 +159,19 @@ public void setObjectName(String objectName) {
136159
setObjectName(Collections.singletonList(objectName));
137160
}
138161

162+
/** Names the first target, replacing a MySQL wildcard level. */
139163
public void setObjectName(List<String> objectName) {
140164
List<String> parts = new ArrayList<>(objectName);
141165
getObjectNameParts().clear();
142166
getObjectNameParts().addAll(parts);
167+
clause.getTarget().setWildcard(null);
143168
}
144169

145170
/** Mutable parts of the first named target, retained for compatibility. */
146171
public List<String> getObjectNameParts() {
172+
if (clause.getTarget() == null) {
173+
clause.setTarget(new PrivilegeTarget());
174+
}
147175
if (clause.getTarget().getNames().isEmpty()) {
148176
clause.getTarget().getNames().add(new ArrayList<>());
149177
}
@@ -161,11 +189,18 @@ public void setUsers(List<String> users) {
161189
public void appendTo(StringBuilder sql, Consumer<Expression> visitor) {
162190
sql.append("GRANT ");
163191
clause.appendTo(sql, false, visitor);
164-
if (option != null) {
165-
sql.append(" WITH ").append(option);
192+
if (!options.isEmpty()) {
193+
sql.append(" WITH ").append(
194+
options.stream().map(Object::toString).collect(Collectors.joining(", ")));
195+
}
196+
if (asUser != null) {
197+
sql.append(" AS ").append(asUser);
198+
if (withRole != null) {
199+
sql.append(" WITH ROLE ").append(withRole);
200+
}
166201
}
167-
if (clause.getGrantedBy() != null) {
168-
sql.append(" GRANTED BY ").append(clause.getGrantedBy());
202+
if (clause.getGrantor() != null) {
203+
sql.append(" GRANTED BY ").append(clause.getGrantor());
169204
}
170205
}
171206

@@ -222,14 +257,17 @@ public Grant addPrivileges(Collection<String> privileges) {
222257
}
223258

224259
public Grant addUsers(String... users) {
225-
List<String> collection = Optional.ofNullable(getUsers()).orElseGet(ArrayList::new);
226-
Collections.addAll(collection, users);
227-
return this.withUsers(collection);
260+
return addUsers(Arrays.asList(users));
228261
}
229262

230263
public Grant addUsers(Collection<String> users) {
231-
List<String> collection = Optional.ofNullable(getUsers()).orElseGet(ArrayList::new);
232-
collection.addAll(users);
233-
return this.withUsers(collection);
264+
List<String> additions = new ArrayList<>(users);
265+
if (clause.getGranteeItems() == null) {
266+
clause.setGranteeItems(new ArrayList<>());
267+
}
268+
for (String user : additions) {
269+
clause.getGranteeItems().add(Grantee.valueOf(user));
270+
}
271+
return this;
234272
}
235273
}

0 commit comments

Comments
 (0)