Skip to content

Commit bec6469

Browse files
akoclaude
andcommitted
Add CREATE OR MODIFY USER ROLE (additive upsert)
CREATE OR MODIFY USER ROLE creates the role if it doesn't exist, or additively adds the specified module roles to an existing role without removing any that are already assigned. Grammar: moved createUserRoleStatement into createStatement to share the CREATE (OR MODIFY)? prefix with other create statements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 62a6486 commit bec6469

6 files changed

Lines changed: 6753 additions & 6751 deletions

File tree

mdl/ast/ast_security.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ type DropModuleRoleStmt struct {
2121

2222
func (s *DropModuleRoleStmt) isStatement() {}
2323

24-
// CreateUserRoleStmt represents: CREATE USER ROLE Name (ModuleRole, ...) [MANAGE ALL ROLES]
24+
// CreateUserRoleStmt represents: CREATE [OR MODIFY] USER ROLE Name (ModuleRole, ...) [MANAGE ALL ROLES]
2525
type CreateUserRoleStmt struct {
2626
Name string
2727
ModuleRoles []QualifiedName
2828
ManageAllRoles bool
29+
CreateOrModify bool // If true, adds module roles to existing role instead of failing
2930
}
3031

3132
func (s *CreateUserRoleStmt) isStatement() {}

mdl/executor/cmd_security_write.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func (e *Executor) execDropModuleRole(s *ast.DropModuleRoleStmt) error {
158158
return nil
159159
}
160160

161-
// execCreateUserRole handles CREATE USER ROLE Name (ModuleRoles) [MANAGE ALL ROLES].
161+
// execCreateUserRole handles CREATE [OR MODIFY] USER ROLE Name (ModuleRoles) [MANAGE ALL ROLES].
162162
func (e *Executor) execCreateUserRole(s *ast.CreateUserRoleStmt) error {
163163
if e.writer == nil {
164164
return fmt.Errorf("not connected to a project in write mode")
@@ -169,20 +169,28 @@ func (e *Executor) execCreateUserRole(s *ast.CreateUserRoleStmt) error {
169169
return fmt.Errorf("failed to read project security: %w", err)
170170
}
171171

172-
// Check if role already exists
173-
for _, ur := range ps.UserRoles {
174-
if ur.Name == s.Name {
175-
return fmt.Errorf("user role already exists: %s", s.Name)
176-
}
177-
}
178-
179172
// Build qualified module role names
180173
var moduleRoleNames []string
181174
for _, mr := range s.ModuleRoles {
182175
qn := mr.Module + "." + mr.Name
183176
moduleRoleNames = append(moduleRoleNames, qn)
184177
}
185178

179+
// Check if role already exists
180+
for _, ur := range ps.UserRoles {
181+
if ur.Name == s.Name {
182+
if !s.CreateOrModify {
183+
return fmt.Errorf("user role already exists: %s", s.Name)
184+
}
185+
// Additive: ensure specified module roles are present
186+
if err := e.writer.AlterUserRoleModuleRoles(ps.ID, s.Name, true, moduleRoleNames); err != nil {
187+
return fmt.Errorf("failed to update user role: %w", err)
188+
}
189+
fmt.Fprintf(e.output, "Modified user role: %s\n", s.Name)
190+
return nil
191+
}
192+
}
193+
186194
if err := e.writer.AddUserRole(ps.ID, s.Name, moduleRoleNames, s.ManageAllRoles); err != nil {
187195
return fmt.Errorf("failed to create user role: %w", err)
188196
}

mdl/grammar/MDLParser.g4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ createStatement
9595
| createNavigationStatement
9696
| createBusinessEventServiceStatement
9797
| createWorkflowStatement
98+
| createUserRoleStatement
9899
)
99100
;
100101

@@ -284,7 +285,6 @@ moveStatement
284285
securityStatement
285286
: createModuleRoleStatement
286287
| dropModuleRoleStatement
287-
| createUserRoleStatement
288288
| alterUserRoleStatement
289289
| dropUserRoleStatement
290290
| grantEntityAccessStatement
@@ -312,7 +312,7 @@ dropModuleRoleStatement
312312
;
313313

314314
createUserRoleStatement
315-
: CREATE USER ROLE identifierOrKeyword
315+
: USER ROLE identifierOrKeyword
316316
LPAREN moduleRoleList RPAREN
317317
(MANAGE ALL ROLES)?
318318
;

mdl/grammar/parser/MDLParser.interp

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)