Skip to content

Commit 32b396f

Browse files
authored
Add CREATE/ALTER SERVER ROLE and GRANT ON object_kind support (#45)
1 parent 5690b37 commit 32b396f

File tree

9 files changed

+340
-8
lines changed

9 files changed

+340
-8
lines changed

ast/alter_server_role_statement.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ast
2+
3+
// AlterServerRoleStatement represents an ALTER SERVER ROLE statement
4+
type AlterServerRoleStatement struct {
5+
Name *Identifier
6+
Action AlterRoleAction // Reuses the same action types as AlterRoleStatement
7+
}
8+
9+
func (a *AlterServerRoleStatement) node() {}
10+
func (a *AlterServerRoleStatement) statement() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ast
2+
3+
// CreateServerRoleStatement represents a CREATE SERVER ROLE statement.
4+
type CreateServerRoleStatement struct {
5+
Name *Identifier
6+
Owner *Identifier // via AUTHORIZATION
7+
}
8+
9+
func (c *CreateServerRoleStatement) node() {}
10+
func (c *CreateServerRoleStatement) statement() {}

ast/grant_statement.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package ast
22

33
// GrantStatement represents a GRANT statement
44
type GrantStatement struct {
5-
Permissions []*Permission
6-
Principals []*SecurityPrincipal
7-
WithGrantOption bool
5+
Permissions []*Permission
6+
Principals []*SecurityPrincipal
7+
WithGrantOption bool
8+
SecurityTargetObject *SecurityTargetObject
89
}
910

1011
func (s *GrantStatement) node() {}

ast/security_target_object.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package ast
2+
3+
// SecurityTargetObject represents the target object in security statements (GRANT, REVOKE, DENY)
4+
type SecurityTargetObject struct {
5+
ObjectKind string // e.g., "ServerRole", "NotSpecified", "Type", etc.
6+
ObjectName *SecurityTargetObjectName
7+
}
8+
9+
func (s *SecurityTargetObject) node() {}
10+
11+
// SecurityTargetObjectName represents the name of a security target object
12+
type SecurityTargetObjectName struct {
13+
MultiPartIdentifier *MultiPartIdentifier
14+
}
15+
16+
func (s *SecurityTargetObjectName) node() {}

parser/marshal.go

Lines changed: 202 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ func statementToJSON(stmt ast.Statement) jsonNode {
282282
return alterSchemaStatementToJSON(s)
283283
case *ast.AlterRoleStatement:
284284
return alterRoleStatementToJSON(s)
285+
case *ast.CreateServerRoleStatement:
286+
return createServerRoleStatementToJSON(s)
287+
case *ast.AlterServerRoleStatement:
288+
return alterServerRoleStatementToJSON(s)
285289
case *ast.AlterRemoteServiceBindingStatement:
286290
return alterRemoteServiceBindingStatementToJSON(s)
287291
case *ast.AlterXmlSchemaCollectionStatement:
@@ -2655,11 +2659,13 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) {
26552659

26562660
// Parse permission(s)
26572661
perm := &ast.Permission{}
2658-
for p.curTok.Type != TokenTo && p.curTok.Type != TokenEOF {
2662+
for p.curTok.Type != TokenTo && p.curTok.Type != TokenOn && p.curTok.Type != TokenEOF {
26592663
if p.curTok.Type == TokenIdent || p.curTok.Type == TokenCreate ||
26602664
p.curTok.Type == TokenProcedure || p.curTok.Type == TokenView ||
26612665
p.curTok.Type == TokenSelect || p.curTok.Type == TokenInsert ||
2662-
p.curTok.Type == TokenUpdate || p.curTok.Type == TokenDelete {
2666+
p.curTok.Type == TokenUpdate || p.curTok.Type == TokenDelete ||
2667+
p.curTok.Type == TokenAlter || p.curTok.Type == TokenExecute ||
2668+
p.curTok.Type == TokenDrop {
26632669
perm.Identifiers = append(perm.Identifiers, &ast.Identifier{
26642670
Value: p.curTok.Literal,
26652671
QuoteType: "NotQuoted",
@@ -2677,6 +2683,150 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) {
26772683
stmt.Permissions = append(stmt.Permissions, perm)
26782684
}
26792685

2686+
// Check for ON clause (SecurityTargetObject)
2687+
if p.curTok.Type == TokenOn {
2688+
p.nextToken() // consume ON
2689+
2690+
stmt.SecurityTargetObject = &ast.SecurityTargetObject{}
2691+
stmt.SecurityTargetObject.ObjectKind = "NotSpecified"
2692+
2693+
// Parse object kind and ::
2694+
// Object kinds can be: SERVER ROLE, APPLICATION ROLE, ASYMMETRIC KEY, SYMMETRIC KEY, etc.
2695+
objectKind := strings.ToUpper(p.curTok.Literal)
2696+
switch objectKind {
2697+
case "SERVER":
2698+
p.nextToken() // consume SERVER
2699+
if strings.ToUpper(p.curTok.Literal) == "ROLE" {
2700+
p.nextToken() // consume ROLE
2701+
stmt.SecurityTargetObject.ObjectKind = "ServerRole"
2702+
} else {
2703+
stmt.SecurityTargetObject.ObjectKind = "Server"
2704+
}
2705+
case "APPLICATION":
2706+
p.nextToken() // consume APPLICATION
2707+
if strings.ToUpper(p.curTok.Literal) == "ROLE" {
2708+
p.nextToken() // consume ROLE
2709+
}
2710+
stmt.SecurityTargetObject.ObjectKind = "ApplicationRole"
2711+
case "ASYMMETRIC":
2712+
p.nextToken() // consume ASYMMETRIC
2713+
if strings.ToUpper(p.curTok.Literal) == "KEY" {
2714+
p.nextToken() // consume KEY
2715+
}
2716+
stmt.SecurityTargetObject.ObjectKind = "AsymmetricKey"
2717+
case "SYMMETRIC":
2718+
p.nextToken() // consume SYMMETRIC
2719+
if strings.ToUpper(p.curTok.Literal) == "KEY" {
2720+
p.nextToken() // consume KEY
2721+
}
2722+
stmt.SecurityTargetObject.ObjectKind = "SymmetricKey"
2723+
case "REMOTE":
2724+
p.nextToken() // consume REMOTE
2725+
if strings.ToUpper(p.curTok.Literal) == "SERVICE" {
2726+
p.nextToken() // consume SERVICE
2727+
if strings.ToUpper(p.curTok.Literal) == "BINDING" {
2728+
p.nextToken() // consume BINDING
2729+
}
2730+
}
2731+
stmt.SecurityTargetObject.ObjectKind = "RemoteServiceBinding"
2732+
case "FULLTEXT":
2733+
p.nextToken() // consume FULLTEXT
2734+
if strings.ToUpper(p.curTok.Literal) == "CATALOG" {
2735+
p.nextToken() // consume CATALOG
2736+
}
2737+
stmt.SecurityTargetObject.ObjectKind = "FullTextCatalog"
2738+
case "MESSAGE":
2739+
p.nextToken() // consume MESSAGE
2740+
if strings.ToUpper(p.curTok.Literal) == "TYPE" {
2741+
p.nextToken() // consume TYPE
2742+
}
2743+
stmt.SecurityTargetObject.ObjectKind = "MessageType"
2744+
case "XML":
2745+
p.nextToken() // consume XML
2746+
if strings.ToUpper(p.curTok.Literal) == "SCHEMA" {
2747+
p.nextToken() // consume SCHEMA
2748+
if strings.ToUpper(p.curTok.Literal) == "COLLECTION" {
2749+
p.nextToken() // consume COLLECTION
2750+
}
2751+
}
2752+
stmt.SecurityTargetObject.ObjectKind = "XmlSchemaCollection"
2753+
case "SEARCH":
2754+
p.nextToken() // consume SEARCH
2755+
if strings.ToUpper(p.curTok.Literal) == "PROPERTY" {
2756+
p.nextToken() // consume PROPERTY
2757+
if strings.ToUpper(p.curTok.Literal) == "LIST" {
2758+
p.nextToken() // consume LIST
2759+
}
2760+
}
2761+
stmt.SecurityTargetObject.ObjectKind = "SearchPropertyList"
2762+
case "AVAILABILITY":
2763+
p.nextToken() // consume AVAILABILITY
2764+
if strings.ToUpper(p.curTok.Literal) == "GROUP" {
2765+
p.nextToken() // consume GROUP
2766+
}
2767+
stmt.SecurityTargetObject.ObjectKind = "AvailabilityGroup"
2768+
case "TYPE":
2769+
p.nextToken()
2770+
stmt.SecurityTargetObject.ObjectKind = "Type"
2771+
case "OBJECT":
2772+
p.nextToken()
2773+
stmt.SecurityTargetObject.ObjectKind = "Object"
2774+
case "ASSEMBLY":
2775+
p.nextToken()
2776+
stmt.SecurityTargetObject.ObjectKind = "Assembly"
2777+
case "CERTIFICATE":
2778+
p.nextToken()
2779+
stmt.SecurityTargetObject.ObjectKind = "Certificate"
2780+
case "CONTRACT":
2781+
p.nextToken()
2782+
stmt.SecurityTargetObject.ObjectKind = "Contract"
2783+
case "DATABASE":
2784+
p.nextToken()
2785+
stmt.SecurityTargetObject.ObjectKind = "Database"
2786+
case "ENDPOINT":
2787+
p.nextToken()
2788+
stmt.SecurityTargetObject.ObjectKind = "Endpoint"
2789+
case "LOGIN":
2790+
p.nextToken()
2791+
stmt.SecurityTargetObject.ObjectKind = "Login"
2792+
case "ROLE":
2793+
p.nextToken()
2794+
stmt.SecurityTargetObject.ObjectKind = "Role"
2795+
case "ROUTE":
2796+
p.nextToken()
2797+
stmt.SecurityTargetObject.ObjectKind = "Route"
2798+
case "SCHEMA":
2799+
p.nextToken()
2800+
stmt.SecurityTargetObject.ObjectKind = "Schema"
2801+
case "SERVICE":
2802+
p.nextToken()
2803+
stmt.SecurityTargetObject.ObjectKind = "Service"
2804+
case "USER":
2805+
p.nextToken()
2806+
stmt.SecurityTargetObject.ObjectKind = "User"
2807+
}
2808+
2809+
// Expect ::
2810+
if p.curTok.Type == TokenColonColon {
2811+
p.nextToken() // consume ::
2812+
2813+
// Parse object name as multi-part identifier
2814+
stmt.SecurityTargetObject.ObjectName = &ast.SecurityTargetObjectName{}
2815+
multiPart := &ast.MultiPartIdentifier{}
2816+
for {
2817+
id := p.parseIdentifier()
2818+
multiPart.Identifiers = append(multiPart.Identifiers, id)
2819+
if p.curTok.Type == TokenDot {
2820+
p.nextToken() // consume .
2821+
} else {
2822+
break
2823+
}
2824+
}
2825+
multiPart.Count = len(multiPart.Identifiers)
2826+
stmt.SecurityTargetObject.ObjectName.MultiPartIdentifier = multiPart
2827+
}
2828+
}
2829+
26802830
// Expect TO
26812831
if p.curTok.Type == TokenTo {
26822832
p.nextToken()
@@ -2965,6 +3115,9 @@ func grantStatementToJSON(s *ast.GrantStatement) jsonNode {
29653115
}
29663116
node["Permissions"] = perms
29673117
}
3118+
if s.SecurityTargetObject != nil {
3119+
node["SecurityTargetObject"] = securityTargetObjectToJSON(s.SecurityTargetObject)
3120+
}
29683121
if len(s.Principals) > 0 {
29693122
principals := make([]jsonNode, len(s.Principals))
29703123
for i, p := range s.Principals {
@@ -2975,6 +3128,27 @@ func grantStatementToJSON(s *ast.GrantStatement) jsonNode {
29753128
return node
29763129
}
29773130

3131+
func securityTargetObjectToJSON(s *ast.SecurityTargetObject) jsonNode {
3132+
node := jsonNode{
3133+
"$type": "SecurityTargetObject",
3134+
"ObjectKind": s.ObjectKind,
3135+
}
3136+
if s.ObjectName != nil {
3137+
node["ObjectName"] = securityTargetObjectNameToJSON(s.ObjectName)
3138+
}
3139+
return node
3140+
}
3141+
3142+
func securityTargetObjectNameToJSON(s *ast.SecurityTargetObjectName) jsonNode {
3143+
node := jsonNode{
3144+
"$type": "SecurityTargetObjectName",
3145+
}
3146+
if s.MultiPartIdentifier != nil {
3147+
node["MultiPartIdentifier"] = multiPartIdentifierToJSON(s.MultiPartIdentifier)
3148+
}
3149+
return node
3150+
}
3151+
29783152
func permissionToJSON(p *ast.Permission) jsonNode {
29793153
node := jsonNode{
29803154
"$type": "Permission",
@@ -3578,6 +3752,32 @@ func alterRoleActionToJSON(a ast.AlterRoleAction) jsonNode {
35783752
}
35793753
}
35803754

3755+
func createServerRoleStatementToJSON(s *ast.CreateServerRoleStatement) jsonNode {
3756+
node := jsonNode{
3757+
"$type": "CreateServerRoleStatement",
3758+
}
3759+
if s.Owner != nil {
3760+
node["Owner"] = identifierToJSON(s.Owner)
3761+
}
3762+
if s.Name != nil {
3763+
node["Name"] = identifierToJSON(s.Name)
3764+
}
3765+
return node
3766+
}
3767+
3768+
func alterServerRoleStatementToJSON(s *ast.AlterServerRoleStatement) jsonNode {
3769+
node := jsonNode{
3770+
"$type": "AlterServerRoleStatement",
3771+
}
3772+
if s.Action != nil {
3773+
node["Action"] = alterRoleActionToJSON(s.Action)
3774+
}
3775+
if s.Name != nil {
3776+
node["Name"] = identifierToJSON(s.Name)
3777+
}
3778+
return node
3779+
}
3780+
35813781
func alterRemoteServiceBindingStatementToJSON(s *ast.AlterRemoteServiceBindingStatement) jsonNode {
35823782
node := jsonNode{
35833783
"$type": "AlterRemoteServiceBindingStatement",

parser/parse_ddl.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1776,9 +1776,14 @@ func (p *Parser) parseAlterServerConfigurationStatement() (ast.Statement, error)
17761776
// Consume SERVER
17771777
p.nextToken()
17781778

1779+
// Check if it's ALTER SERVER ROLE or ALTER SERVER CONFIGURATION
1780+
if strings.ToUpper(p.curTok.Literal) == "ROLE" {
1781+
return p.parseAlterServerRoleStatement()
1782+
}
1783+
17791784
// Expect CONFIGURATION
17801785
if strings.ToUpper(p.curTok.Literal) != "CONFIGURATION" {
1781-
return nil, fmt.Errorf("expected CONFIGURATION after SERVER, got %s", p.curTok.Literal)
1786+
return nil, fmt.Errorf("expected CONFIGURATION or ROLE after SERVER, got %s", p.curTok.Literal)
17821787
}
17831788
p.nextToken()
17841789

@@ -3045,6 +3050,65 @@ func (p *Parser) parseAlterRoleStatement() (*ast.AlterRoleStatement, error) {
30453050
return stmt, nil
30463051
}
30473052

3053+
func (p *Parser) parseAlterServerRoleStatement() (*ast.AlterServerRoleStatement, error) {
3054+
// Consume ROLE
3055+
p.nextToken()
3056+
3057+
stmt := &ast.AlterServerRoleStatement{}
3058+
3059+
// Parse role name
3060+
stmt.Name = p.parseIdentifier()
3061+
3062+
// Parse action: ADD MEMBER, DROP MEMBER, or WITH NAME =
3063+
switch strings.ToUpper(p.curTok.Literal) {
3064+
case "ADD":
3065+
p.nextToken() // consume ADD
3066+
if strings.ToUpper(p.curTok.Literal) != "MEMBER" {
3067+
return nil, fmt.Errorf("expected MEMBER after ADD, got %s", p.curTok.Literal)
3068+
}
3069+
p.nextToken() // consume MEMBER
3070+
action := &ast.AddMemberAlterRoleAction{}
3071+
action.Member = p.parseIdentifier()
3072+
stmt.Action = action
3073+
3074+
case "DROP":
3075+
p.nextToken() // consume DROP
3076+
if strings.ToUpper(p.curTok.Literal) != "MEMBER" {
3077+
return nil, fmt.Errorf("expected MEMBER after DROP, got %s", p.curTok.Literal)
3078+
}
3079+
p.nextToken() // consume MEMBER
3080+
action := &ast.DropMemberAlterRoleAction{}
3081+
action.Member = p.parseIdentifier()
3082+
stmt.Action = action
3083+
3084+
case "WITH":
3085+
p.nextToken() // consume WITH
3086+
if strings.ToUpper(p.curTok.Literal) != "NAME" {
3087+
return nil, fmt.Errorf("expected NAME after WITH, got %s", p.curTok.Literal)
3088+
}
3089+
p.nextToken() // consume NAME
3090+
if p.curTok.Type != TokenEquals {
3091+
return nil, fmt.Errorf("expected = after NAME, got %s", p.curTok.Literal)
3092+
}
3093+
p.nextToken() // consume =
3094+
action := &ast.RenameAlterRoleAction{}
3095+
action.NewName = p.parseIdentifier()
3096+
stmt.Action = action
3097+
3098+
default:
3099+
// Handle incomplete statement
3100+
p.skipToEndOfStatement()
3101+
return stmt, nil
3102+
}
3103+
3104+
// Skip optional semicolon
3105+
if p.curTok.Type == TokenSemicolon {
3106+
p.nextToken()
3107+
}
3108+
3109+
return stmt, nil
3110+
}
3111+
30483112
func (p *Parser) parseAlterRemoteServiceBindingStatement() (*ast.AlterRemoteServiceBindingStatement, error) {
30493113
// Consume REMOTE
30503114
p.nextToken()

0 commit comments

Comments
 (0)