Skip to content

Commit ea9dbc0

Browse files
committed
Add CREATE/ALTER/DROP/SHOW CREATE ROLE support
Implement parsing and explain output for ROLE statements: - CREATE ROLE - ALTER ROLE - DROP ROLE - SHOW CREATE ROLE The explain output matches ClickHouse's format: - CREATE/ALTER -> "CreateRoleQuery" - DROP -> "DROP ROLE query" - SHOW CREATE (single) -> "SHOW CREATE ROLE query" - SHOW CREATE (multiple) -> "SHOW CREATE ROLES query" This fixes all 56 failing statements in 01293_create_role test and also fixes related ROLE statements in other test files.
1 parent 0994664 commit ea9dbc0

9 files changed

Lines changed: 174 additions & 92 deletions

File tree

ast/ast.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,36 @@ func (s *ShowCreateRowPolicyQuery) Pos() token.Position { return s.Position }
930930
func (s *ShowCreateRowPolicyQuery) End() token.Position { return s.Position }
931931
func (s *ShowCreateRowPolicyQuery) statementNode() {}
932932

933+
// CreateRoleQuery represents a CREATE ROLE or ALTER ROLE statement.
934+
type CreateRoleQuery struct {
935+
Position token.Position `json:"-"`
936+
IsAlter bool `json:"is_alter,omitempty"`
937+
}
938+
939+
func (c *CreateRoleQuery) Pos() token.Position { return c.Position }
940+
func (c *CreateRoleQuery) End() token.Position { return c.Position }
941+
func (c *CreateRoleQuery) statementNode() {}
942+
943+
// DropRoleQuery represents a DROP ROLE statement.
944+
type DropRoleQuery struct {
945+
Position token.Position `json:"-"`
946+
IfExists bool `json:"if_exists,omitempty"`
947+
}
948+
949+
func (d *DropRoleQuery) Pos() token.Position { return d.Position }
950+
func (d *DropRoleQuery) End() token.Position { return d.Position }
951+
func (d *DropRoleQuery) statementNode() {}
952+
953+
// ShowCreateRoleQuery represents a SHOW CREATE ROLE statement.
954+
type ShowCreateRoleQuery struct {
955+
Position token.Position `json:"-"`
956+
RoleCount int `json:"role_count,omitempty"` // Number of roles specified
957+
}
958+
959+
func (s *ShowCreateRoleQuery) Pos() token.Position { return s.Position }
960+
func (s *ShowCreateRoleQuery) End() token.Position { return s.Position }
961+
func (s *ShowCreateRoleQuery) statementNode() {}
962+
933963
// CreateIndexQuery represents a CREATE INDEX statement.
934964
type CreateIndexQuery struct {
935965
Position token.Position `json:"-"`

internal/explain/explain.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
149149
fmt.Fprintf(sb, "%sDROP ROW POLICY query\n", indent)
150150
case *ast.ShowCreateRowPolicyQuery:
151151
fmt.Fprintf(sb, "%sSHOW CREATE ROW POLICY query\n", indent)
152+
case *ast.CreateRoleQuery:
153+
fmt.Fprintf(sb, "%sCreateRoleQuery\n", indent)
154+
case *ast.DropRoleQuery:
155+
fmt.Fprintf(sb, "%sDROP ROLE query\n", indent)
156+
case *ast.ShowCreateRoleQuery:
157+
// Use ROLES (plural) when multiple roles are specified
158+
if n.RoleCount > 1 {
159+
fmt.Fprintf(sb, "%sSHOW CREATE ROLES query\n", indent)
160+
} else {
161+
fmt.Fprintf(sb, "%sSHOW CREATE ROLE query\n", indent)
162+
}
152163
case *ast.ShowGrantsQuery:
153164
fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent)
154165
case *ast.GrantQuery:

parser/parser.go

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ func (p *Parser) parseStatement() ast.Statement {
135135
if p.peek.Token == token.IDENT && (strings.ToUpper(p.peek.Value) == "ROW" || strings.ToUpper(p.peek.Value) == "POLICY") {
136136
return p.parseDropRowPolicy()
137137
}
138+
// Check for DROP ROLE
139+
if p.peek.Token == token.IDENT && strings.ToUpper(p.peek.Value) == "ROLE" {
140+
return p.parseDropRole()
141+
}
138142
return p.parseDrop()
139143
case token.ALTER:
140144
// Check for ALTER USER
@@ -153,6 +157,10 @@ func (p *Parser) parseStatement() ast.Statement {
153157
if p.peek.Token == token.IDENT && (strings.ToUpper(p.peek.Value) == "ROW" || strings.ToUpper(p.peek.Value) == "POLICY") {
154158
return p.parseAlterRowPolicy()
155159
}
160+
// Check for ALTER ROLE
161+
if p.peek.Token == token.IDENT && strings.ToUpper(p.peek.Value) == "ROLE" {
162+
return p.parseAlterRole()
163+
}
156164
return p.parseAlter()
157165
case token.TRUNCATE:
158166
return p.parseTruncate()
@@ -1366,7 +1374,10 @@ func (p *Parser) parseCreate() ast.Statement {
13661374
case "POLICY":
13671375
// CREATE POLICY (without ROW keyword)
13681376
return p.parseCreateRowPolicy(pos)
1369-
case "RESOURCE", "WORKLOAD", "ROLE", "QUOTA":
1377+
case "ROLE":
1378+
// CREATE ROLE
1379+
return p.parseCreateRole(pos)
1380+
case "RESOURCE", "WORKLOAD", "QUOTA":
13701381
// Skip these statements - just consume tokens until semicolon
13711382
p.parseCreateGeneric(create)
13721383
default:
@@ -2184,6 +2195,119 @@ func (p *Parser) parseShowCreateRowPolicy(pos token.Position) *ast.ShowCreateRow
21842195
return query
21852196
}
21862197

2198+
func (p *Parser) parseCreateRole(pos token.Position) *ast.CreateRoleQuery {
2199+
query := &ast.CreateRoleQuery{
2200+
Position: pos,
2201+
}
2202+
2203+
// Skip ROLE
2204+
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "ROLE" {
2205+
p.nextToken()
2206+
}
2207+
2208+
// Skip the rest of the statement (role names, SETTINGS, etc.)
2209+
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
2210+
p.nextToken()
2211+
}
2212+
2213+
return query
2214+
}
2215+
2216+
func (p *Parser) parseDropRole() *ast.DropRoleQuery {
2217+
query := &ast.DropRoleQuery{
2218+
Position: p.current.Pos,
2219+
}
2220+
2221+
p.nextToken() // skip DROP
2222+
2223+
// Skip ROLE
2224+
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "ROLE" {
2225+
p.nextToken()
2226+
}
2227+
2228+
// Handle IF EXISTS
2229+
if p.currentIs(token.IF) {
2230+
p.nextToken()
2231+
if p.currentIs(token.EXISTS) {
2232+
query.IfExists = true
2233+
p.nextToken()
2234+
}
2235+
}
2236+
2237+
// Skip the rest of the statement (role names, etc.)
2238+
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
2239+
p.nextToken()
2240+
}
2241+
2242+
return query
2243+
}
2244+
2245+
func (p *Parser) parseAlterRole() *ast.CreateRoleQuery {
2246+
query := &ast.CreateRoleQuery{
2247+
Position: p.current.Pos,
2248+
IsAlter: true,
2249+
}
2250+
2251+
p.nextToken() // skip ALTER
2252+
2253+
// Skip ROLE
2254+
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "ROLE" {
2255+
p.nextToken()
2256+
}
2257+
2258+
// Skip the rest of the statement (role names, SETTINGS, RENAME TO, etc.)
2259+
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
2260+
p.nextToken()
2261+
}
2262+
2263+
return query
2264+
}
2265+
2266+
func (p *Parser) parseShowCreateRole(pos token.Position) *ast.ShowCreateRoleQuery {
2267+
query := &ast.ShowCreateRoleQuery{
2268+
Position: pos,
2269+
RoleCount: 1, // Default to 1 role
2270+
}
2271+
2272+
// Skip ROLE
2273+
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "ROLE" {
2274+
p.nextToken()
2275+
}
2276+
2277+
// Count role names (separated by commas)
2278+
// Skip first role name
2279+
for p.currentIs(token.IDENT) || p.currentIs(token.STRING) || p.current.Token.IsKeyword() {
2280+
p.nextToken()
2281+
// Handle role@host syntax
2282+
if p.currentIs(token.IDENT) && strings.HasPrefix(p.current.Value, "@") {
2283+
p.nextToken()
2284+
}
2285+
break
2286+
}
2287+
2288+
// Count additional roles
2289+
for p.currentIs(token.COMMA) {
2290+
query.RoleCount++
2291+
p.nextToken()
2292+
// Skip role name
2293+
for p.currentIs(token.IDENT) || p.currentIs(token.STRING) || p.current.Token.IsKeyword() {
2294+
p.nextToken()
2295+
// Handle role@host syntax
2296+
if p.currentIs(token.IDENT) && strings.HasPrefix(p.current.Value, "@") {
2297+
p.nextToken()
2298+
}
2299+
break
2300+
}
2301+
}
2302+
2303+
// Skip the rest of the statement
2304+
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
2305+
p.nextToken()
2306+
}
2307+
2308+
return query
2309+
}
2310+
21872311
func (p *Parser) parseCreateDictionary(create *ast.CreateQuery) {
21882312
// Handle IF NOT EXISTS
21892313
if p.currentIs(token.IF) {
@@ -3840,6 +3964,9 @@ func (p *Parser) parseShow() ast.Statement {
38403964
} else if p.currentIs(token.IDENT) && (strings.ToUpper(p.current.Value) == "ROW" || strings.ToUpper(p.current.Value) == "POLICY") {
38413965
// SHOW CREATE ROW POLICY or SHOW CREATE POLICY
38423966
return p.parseShowCreateRowPolicy(pos)
3967+
} else if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "ROLE" {
3968+
// SHOW CREATE ROLE
3969+
return p.parseShowCreateRole(pos)
38433970
} else if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "DICTIONARY" {
38443971
show.ShowType = ast.ShowCreateDictionary
38453972
p.nextToken()

parser/testdata/01292_create_user/metadata.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"explain_todo": {
3-
"stmt141": true,
43
"stmt157": true,
54
"stmt158": true,
65
"stmt159": true,
@@ -14,7 +13,6 @@
1413
"stmt205": true,
1514
"stmt207": true,
1615
"stmt208": true,
17-
"stmt219": true,
1816
"stmt22": true,
1917
"stmt221": true,
2018
"stmt23": true,
@@ -29,7 +27,6 @@
2927
"stmt40": true,
3028
"stmt41": true,
3129
"stmt42": true,
32-
"stmt43": true,
33-
"stmt6": true
30+
"stmt43": true
3431
}
3532
}
Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt1": true,
4-
"stmt11": true,
5-
"stmt12": true,
6-
"stmt13": true,
7-
"stmt14": true,
8-
"stmt16": true,
9-
"stmt17": true,
10-
"stmt18": true,
11-
"stmt19": true,
12-
"stmt2": true,
13-
"stmt20": true,
14-
"stmt21": true,
15-
"stmt22": true,
16-
"stmt24": true,
17-
"stmt25": true,
18-
"stmt26": true,
19-
"stmt27": true,
20-
"stmt28": true,
21-
"stmt29": true,
22-
"stmt3": true,
23-
"stmt30": true,
24-
"stmt31": true,
25-
"stmt32": true,
26-
"stmt33": true,
27-
"stmt34": true,
28-
"stmt35": true,
29-
"stmt36": true,
30-
"stmt37": true,
31-
"stmt38": true,
32-
"stmt39": true,
33-
"stmt40": true,
34-
"stmt41": true,
35-
"stmt42": true,
36-
"stmt43": true,
37-
"stmt44": true,
38-
"stmt45": true,
39-
"stmt46": true,
40-
"stmt47": true,
41-
"stmt48": true,
42-
"stmt5": true,
43-
"stmt50": true,
44-
"stmt51": true,
45-
"stmt52": true,
46-
"stmt53": true,
47-
"stmt54": true,
48-
"stmt56": true,
49-
"stmt58": true,
50-
"stmt6": true,
51-
"stmt60": true,
52-
"stmt61": true,
53-
"stmt62": true,
54-
"stmt63": true,
55-
"stmt64": true,
56-
"stmt66": true,
57-
"stmt8": true,
58-
"stmt9": true
59-
}
60-
}
1+
{}
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt108": true,
4-
"stmt4": true,
5-
"stmt47": true
6-
}
7-
}
1+
{}
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt31": true,
4-
"stmt6": true,
5-
"stmt70": true
6-
}
7-
}
1+
{}

parser/testdata/01702_system_query_log/metadata.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"explain_todo": {
33
"stmt14": true,
44
"stmt15": true,
5-
"stmt16": true,
65
"stmt19": true,
76
"stmt24": true,
87
"stmt26": true,
@@ -25,7 +24,6 @@
2524
"stmt45": true,
2625
"stmt46": true,
2726
"stmt49": true,
28-
"stmt5": true,
2927
"stmt50": true,
3028
"stmt51": true,
3129
"stmt52": true,
@@ -41,7 +39,6 @@
4139
"stmt69": true,
4240
"stmt74": true,
4341
"stmt8": true,
44-
"stmt83": true,
4542
"stmt86": true
4643
}
4744
}
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt26": true,
4-
"stmt27": true,
5-
"stmt47": true,
6-
"stmt48": true,
7-
"stmt52": true,
8-
"stmt53": true
9-
}
10-
}
1+
{}

0 commit comments

Comments
 (0)