Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ast/alter_server_role_statement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ast

// AlterServerRoleStatement represents an ALTER SERVER ROLE statement
type AlterServerRoleStatement struct {
Name *Identifier
Action AlterRoleAction // Reuses the same action types as AlterRoleStatement
}

func (a *AlterServerRoleStatement) node() {}
func (a *AlterServerRoleStatement) statement() {}
10 changes: 10 additions & 0 deletions ast/create_server_role_statement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ast

// CreateServerRoleStatement represents a CREATE SERVER ROLE statement.
type CreateServerRoleStatement struct {
Name *Identifier
Owner *Identifier // via AUTHORIZATION
}

func (c *CreateServerRoleStatement) node() {}
func (c *CreateServerRoleStatement) statement() {}
7 changes: 4 additions & 3 deletions ast/grant_statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package ast

// GrantStatement represents a GRANT statement
type GrantStatement struct {
Permissions []*Permission
Principals []*SecurityPrincipal
WithGrantOption bool
Permissions []*Permission
Principals []*SecurityPrincipal
WithGrantOption bool
SecurityTargetObject *SecurityTargetObject
}

func (s *GrantStatement) node() {}
Expand Down
16 changes: 16 additions & 0 deletions ast/security_target_object.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ast

// SecurityTargetObject represents the target object in security statements (GRANT, REVOKE, DENY)
type SecurityTargetObject struct {
ObjectKind string // e.g., "ServerRole", "NotSpecified", "Type", etc.
ObjectName *SecurityTargetObjectName
}

func (s *SecurityTargetObject) node() {}

// SecurityTargetObjectName represents the name of a security target object
type SecurityTargetObjectName struct {
MultiPartIdentifier *MultiPartIdentifier
}

func (s *SecurityTargetObjectName) node() {}
204 changes: 202 additions & 2 deletions parser/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ func statementToJSON(stmt ast.Statement) jsonNode {
return alterSchemaStatementToJSON(s)
case *ast.AlterRoleStatement:
return alterRoleStatementToJSON(s)
case *ast.CreateServerRoleStatement:
return createServerRoleStatementToJSON(s)
case *ast.AlterServerRoleStatement:
return alterServerRoleStatementToJSON(s)
case *ast.AlterRemoteServiceBindingStatement:
return alterRemoteServiceBindingStatementToJSON(s)
case *ast.AlterXmlSchemaCollectionStatement:
Expand Down Expand Up @@ -2655,11 +2659,13 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) {

// Parse permission(s)
perm := &ast.Permission{}
for p.curTok.Type != TokenTo && p.curTok.Type != TokenEOF {
for p.curTok.Type != TokenTo && p.curTok.Type != TokenOn && p.curTok.Type != TokenEOF {
if p.curTok.Type == TokenIdent || p.curTok.Type == TokenCreate ||
p.curTok.Type == TokenProcedure || p.curTok.Type == TokenView ||
p.curTok.Type == TokenSelect || p.curTok.Type == TokenInsert ||
p.curTok.Type == TokenUpdate || p.curTok.Type == TokenDelete {
p.curTok.Type == TokenUpdate || p.curTok.Type == TokenDelete ||
p.curTok.Type == TokenAlter || p.curTok.Type == TokenExecute ||
p.curTok.Type == TokenDrop {
perm.Identifiers = append(perm.Identifiers, &ast.Identifier{
Value: p.curTok.Literal,
QuoteType: "NotQuoted",
Expand All @@ -2677,6 +2683,150 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) {
stmt.Permissions = append(stmt.Permissions, perm)
}

// Check for ON clause (SecurityTargetObject)
if p.curTok.Type == TokenOn {
p.nextToken() // consume ON

stmt.SecurityTargetObject = &ast.SecurityTargetObject{}
stmt.SecurityTargetObject.ObjectKind = "NotSpecified"

// Parse object kind and ::
// Object kinds can be: SERVER ROLE, APPLICATION ROLE, ASYMMETRIC KEY, SYMMETRIC KEY, etc.
objectKind := strings.ToUpper(p.curTok.Literal)
switch objectKind {
case "SERVER":
p.nextToken() // consume SERVER
if strings.ToUpper(p.curTok.Literal) == "ROLE" {
p.nextToken() // consume ROLE
stmt.SecurityTargetObject.ObjectKind = "ServerRole"
} else {
stmt.SecurityTargetObject.ObjectKind = "Server"
}
case "APPLICATION":
p.nextToken() // consume APPLICATION
if strings.ToUpper(p.curTok.Literal) == "ROLE" {
p.nextToken() // consume ROLE
}
stmt.SecurityTargetObject.ObjectKind = "ApplicationRole"
case "ASYMMETRIC":
p.nextToken() // consume ASYMMETRIC
if strings.ToUpper(p.curTok.Literal) == "KEY" {
p.nextToken() // consume KEY
}
stmt.SecurityTargetObject.ObjectKind = "AsymmetricKey"
case "SYMMETRIC":
p.nextToken() // consume SYMMETRIC
if strings.ToUpper(p.curTok.Literal) == "KEY" {
p.nextToken() // consume KEY
}
stmt.SecurityTargetObject.ObjectKind = "SymmetricKey"
case "REMOTE":
p.nextToken() // consume REMOTE
if strings.ToUpper(p.curTok.Literal) == "SERVICE" {
p.nextToken() // consume SERVICE
if strings.ToUpper(p.curTok.Literal) == "BINDING" {
p.nextToken() // consume BINDING
}
}
stmt.SecurityTargetObject.ObjectKind = "RemoteServiceBinding"
case "FULLTEXT":
p.nextToken() // consume FULLTEXT
if strings.ToUpper(p.curTok.Literal) == "CATALOG" {
p.nextToken() // consume CATALOG
}
stmt.SecurityTargetObject.ObjectKind = "FullTextCatalog"
case "MESSAGE":
p.nextToken() // consume MESSAGE
if strings.ToUpper(p.curTok.Literal) == "TYPE" {
p.nextToken() // consume TYPE
}
stmt.SecurityTargetObject.ObjectKind = "MessageType"
case "XML":
p.nextToken() // consume XML
if strings.ToUpper(p.curTok.Literal) == "SCHEMA" {
p.nextToken() // consume SCHEMA
if strings.ToUpper(p.curTok.Literal) == "COLLECTION" {
p.nextToken() // consume COLLECTION
}
}
stmt.SecurityTargetObject.ObjectKind = "XmlSchemaCollection"
case "SEARCH":
p.nextToken() // consume SEARCH
if strings.ToUpper(p.curTok.Literal) == "PROPERTY" {
p.nextToken() // consume PROPERTY
if strings.ToUpper(p.curTok.Literal) == "LIST" {
p.nextToken() // consume LIST
}
}
stmt.SecurityTargetObject.ObjectKind = "SearchPropertyList"
case "AVAILABILITY":
p.nextToken() // consume AVAILABILITY
if strings.ToUpper(p.curTok.Literal) == "GROUP" {
p.nextToken() // consume GROUP
}
stmt.SecurityTargetObject.ObjectKind = "AvailabilityGroup"
case "TYPE":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Type"
case "OBJECT":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Object"
case "ASSEMBLY":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Assembly"
case "CERTIFICATE":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Certificate"
case "CONTRACT":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Contract"
case "DATABASE":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Database"
case "ENDPOINT":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Endpoint"
case "LOGIN":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Login"
case "ROLE":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Role"
case "ROUTE":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Route"
case "SCHEMA":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Schema"
case "SERVICE":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "Service"
case "USER":
p.nextToken()
stmt.SecurityTargetObject.ObjectKind = "User"
}

// Expect ::
if p.curTok.Type == TokenColonColon {
p.nextToken() // consume ::

// Parse object name as multi-part identifier
stmt.SecurityTargetObject.ObjectName = &ast.SecurityTargetObjectName{}
multiPart := &ast.MultiPartIdentifier{}
for {
id := p.parseIdentifier()
multiPart.Identifiers = append(multiPart.Identifiers, id)
if p.curTok.Type == TokenDot {
p.nextToken() // consume .
} else {
break
}
}
multiPart.Count = len(multiPart.Identifiers)
stmt.SecurityTargetObject.ObjectName.MultiPartIdentifier = multiPart
}
}

// Expect TO
if p.curTok.Type == TokenTo {
p.nextToken()
Expand Down Expand Up @@ -2965,6 +3115,9 @@ func grantStatementToJSON(s *ast.GrantStatement) jsonNode {
}
node["Permissions"] = perms
}
if s.SecurityTargetObject != nil {
node["SecurityTargetObject"] = securityTargetObjectToJSON(s.SecurityTargetObject)
}
if len(s.Principals) > 0 {
principals := make([]jsonNode, len(s.Principals))
for i, p := range s.Principals {
Expand All @@ -2975,6 +3128,27 @@ func grantStatementToJSON(s *ast.GrantStatement) jsonNode {
return node
}

func securityTargetObjectToJSON(s *ast.SecurityTargetObject) jsonNode {
node := jsonNode{
"$type": "SecurityTargetObject",
"ObjectKind": s.ObjectKind,
}
if s.ObjectName != nil {
node["ObjectName"] = securityTargetObjectNameToJSON(s.ObjectName)
}
return node
}

func securityTargetObjectNameToJSON(s *ast.SecurityTargetObjectName) jsonNode {
node := jsonNode{
"$type": "SecurityTargetObjectName",
}
if s.MultiPartIdentifier != nil {
node["MultiPartIdentifier"] = multiPartIdentifierToJSON(s.MultiPartIdentifier)
}
return node
}

func permissionToJSON(p *ast.Permission) jsonNode {
node := jsonNode{
"$type": "Permission",
Expand Down Expand Up @@ -3578,6 +3752,32 @@ func alterRoleActionToJSON(a ast.AlterRoleAction) jsonNode {
}
}

func createServerRoleStatementToJSON(s *ast.CreateServerRoleStatement) jsonNode {
node := jsonNode{
"$type": "CreateServerRoleStatement",
}
if s.Owner != nil {
node["Owner"] = identifierToJSON(s.Owner)
}
if s.Name != nil {
node["Name"] = identifierToJSON(s.Name)
}
return node
}

func alterServerRoleStatementToJSON(s *ast.AlterServerRoleStatement) jsonNode {
node := jsonNode{
"$type": "AlterServerRoleStatement",
}
if s.Action != nil {
node["Action"] = alterRoleActionToJSON(s.Action)
}
if s.Name != nil {
node["Name"] = identifierToJSON(s.Name)
}
return node
}

func alterRemoteServiceBindingStatementToJSON(s *ast.AlterRemoteServiceBindingStatement) jsonNode {
node := jsonNode{
"$type": "AlterRemoteServiceBindingStatement",
Expand Down
66 changes: 65 additions & 1 deletion parser/parse_ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -1776,9 +1776,14 @@ func (p *Parser) parseAlterServerConfigurationStatement() (ast.Statement, error)
// Consume SERVER
p.nextToken()

// Check if it's ALTER SERVER ROLE or ALTER SERVER CONFIGURATION
if strings.ToUpper(p.curTok.Literal) == "ROLE" {
return p.parseAlterServerRoleStatement()
}

// Expect CONFIGURATION
if strings.ToUpper(p.curTok.Literal) != "CONFIGURATION" {
return nil, fmt.Errorf("expected CONFIGURATION after SERVER, got %s", p.curTok.Literal)
return nil, fmt.Errorf("expected CONFIGURATION or ROLE after SERVER, got %s", p.curTok.Literal)
}
p.nextToken()

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

func (p *Parser) parseAlterServerRoleStatement() (*ast.AlterServerRoleStatement, error) {
// Consume ROLE
p.nextToken()

stmt := &ast.AlterServerRoleStatement{}

// Parse role name
stmt.Name = p.parseIdentifier()

// Parse action: ADD MEMBER, DROP MEMBER, or WITH NAME =
switch strings.ToUpper(p.curTok.Literal) {
case "ADD":
p.nextToken() // consume ADD
if strings.ToUpper(p.curTok.Literal) != "MEMBER" {
return nil, fmt.Errorf("expected MEMBER after ADD, got %s", p.curTok.Literal)
}
p.nextToken() // consume MEMBER
action := &ast.AddMemberAlterRoleAction{}
action.Member = p.parseIdentifier()
stmt.Action = action

case "DROP":
p.nextToken() // consume DROP
if strings.ToUpper(p.curTok.Literal) != "MEMBER" {
return nil, fmt.Errorf("expected MEMBER after DROP, got %s", p.curTok.Literal)
}
p.nextToken() // consume MEMBER
action := &ast.DropMemberAlterRoleAction{}
action.Member = p.parseIdentifier()
stmt.Action = action

case "WITH":
p.nextToken() // consume WITH
if strings.ToUpper(p.curTok.Literal) != "NAME" {
return nil, fmt.Errorf("expected NAME after WITH, got %s", p.curTok.Literal)
}
p.nextToken() // consume NAME
if p.curTok.Type != TokenEquals {
return nil, fmt.Errorf("expected = after NAME, got %s", p.curTok.Literal)
}
p.nextToken() // consume =
action := &ast.RenameAlterRoleAction{}
action.NewName = p.parseIdentifier()
stmt.Action = action

default:
// Handle incomplete statement
p.skipToEndOfStatement()
return stmt, nil
}

// Skip optional semicolon
if p.curTok.Type == TokenSemicolon {
p.nextToken()
}

return stmt, nil
}

func (p *Parser) parseAlterRemoteServiceBindingStatement() (*ast.AlterRemoteServiceBindingStatement, error) {
// Consume REMOTE
p.nextToken()
Expand Down
Loading
Loading