Skip to content

Commit eaa7db3

Browse files
kyleconroyclaude
andauthored
Add full CREATE EVENT NOTIFICATION parsing support (#39)
Parse complete CREATE EVENT NOTIFICATION syntax including: - ON SERVER/DATABASE/QUEUE scope with queue name support - WITH FAN_IN option - Event type and event group lists with PascalCase conversion - TO SERVICE with broker service and instance specifier Enable Baselines100_EventNotificationStatementTests100 and EventNotificationStatementTests100 tests. Co-authored-by: Claude <[email protected]>
1 parent a7e2b7a commit eaa7db3

File tree

6 files changed

+201
-5
lines changed

6 files changed

+201
-5
lines changed

ast/create_simple_statements.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,36 @@ type CreateXmlIndexStatement struct {
215215
func (s *CreateXmlIndexStatement) node() {}
216216
func (s *CreateXmlIndexStatement) statement() {}
217217

218+
// EventNotificationObjectScope represents the scope of an event notification (SERVER, DATABASE, or QUEUE).
219+
type EventNotificationObjectScope struct {
220+
Target string `json:"Target,omitempty"` // "Server", "Database", or "Queue"
221+
QueueName *SchemaObjectName `json:"QueueName,omitempty"`
222+
}
223+
224+
func (s *EventNotificationObjectScope) node() {}
225+
226+
// EventTypeGroupContainer is an interface for event type/group containers.
227+
type EventTypeGroupContainer interface {
228+
node()
229+
eventTypeGroupContainer()
230+
}
231+
232+
// EventGroupContainer represents a group of events.
233+
type EventGroupContainer struct {
234+
EventGroup string `json:"EventGroup,omitempty"`
235+
}
236+
237+
func (c *EventGroupContainer) node() {}
238+
func (c *EventGroupContainer) eventTypeGroupContainer() {}
239+
218240
// CreateEventNotificationStatement represents a CREATE EVENT NOTIFICATION statement.
219241
type CreateEventNotificationStatement struct {
220-
Name *Identifier `json:"Name,omitempty"`
242+
Name *Identifier `json:"Name,omitempty"`
243+
Scope *EventNotificationObjectScope `json:"Scope,omitempty"`
244+
WithFanIn bool `json:"WithFanIn,omitempty"`
245+
EventTypeGroups []EventTypeGroupContainer `json:"EventTypeGroups,omitempty"`
246+
BrokerService *StringLiteral `json:"BrokerService,omitempty"`
247+
BrokerInstanceSpecifier *StringLiteral `json:"BrokerInstanceSpecifier,omitempty"`
221248
}
222249

223250
func (s *CreateEventNotificationStatement) node() {}

ast/create_trigger_statement.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ func (s *CreateTriggerStatement) node() {}
1818

1919
// EventTypeContainer represents an event type container
2020
type EventTypeContainer struct {
21-
EventType string
21+
EventType string `json:"EventType,omitempty"`
2222
}
23+
24+
func (c *EventTypeContainer) node() {}
25+
func (c *EventTypeContainer) eventTypeGroupContainer() {}

parser/marshal.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7005,9 +7005,55 @@ func createEventNotificationStatementToJSON(s *ast.CreateEventNotificationStatem
70057005
if s.Name != nil {
70067006
node["Name"] = identifierToJSON(s.Name)
70077007
}
7008+
if s.Scope != nil {
7009+
node["Scope"] = eventNotificationObjectScopeToJSON(s.Scope)
7010+
// Include WithFanIn when Scope is present
7011+
node["WithFanIn"] = s.WithFanIn
7012+
}
7013+
if len(s.EventTypeGroups) > 0 {
7014+
groups := make([]jsonNode, len(s.EventTypeGroups))
7015+
for i, g := range s.EventTypeGroups {
7016+
groups[i] = eventTypeGroupContainerToJSON(g)
7017+
}
7018+
node["EventTypeGroups"] = groups
7019+
}
7020+
if s.BrokerService != nil {
7021+
node["BrokerService"] = stringLiteralToJSON(s.BrokerService)
7022+
}
7023+
if s.BrokerInstanceSpecifier != nil {
7024+
node["BrokerInstanceSpecifier"] = stringLiteralToJSON(s.BrokerInstanceSpecifier)
7025+
}
7026+
return node
7027+
}
7028+
7029+
func eventNotificationObjectScopeToJSON(s *ast.EventNotificationObjectScope) jsonNode {
7030+
node := jsonNode{
7031+
"$type": "EventNotificationObjectScope",
7032+
"Target": s.Target,
7033+
}
7034+
if s.QueueName != nil {
7035+
node["QueueName"] = schemaObjectNameToJSON(s.QueueName)
7036+
}
70087037
return node
70097038
}
70107039

7040+
func eventTypeGroupContainerToJSON(c ast.EventTypeGroupContainer) jsonNode {
7041+
switch v := c.(type) {
7042+
case *ast.EventTypeContainer:
7043+
return jsonNode{
7044+
"$type": "EventTypeContainer",
7045+
"EventType": v.EventType,
7046+
}
7047+
case *ast.EventGroupContainer:
7048+
return jsonNode{
7049+
"$type": "EventGroupContainer",
7050+
"EventGroup": v.EventGroup,
7051+
}
7052+
default:
7053+
return jsonNode{"$type": "Unknown"}
7054+
}
7055+
}
7056+
70117057
func alterDatabaseAddFileStatementToJSON(s *ast.AlterDatabaseAddFileStatement) jsonNode {
70127058
node := jsonNode{
70137059
"$type": "AlterDatabaseAddFileStatement",

parser/parse_statements.go

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4525,11 +4525,131 @@ func (p *Parser) parseCreateEventNotificationFromEvent() (*ast.CreateEventNotifi
45254525
Name: p.parseIdentifier(),
45264526
}
45274527

4528-
// Skip rest of statement
4528+
// Parse ON <scope>
4529+
if p.curTok.Type == TokenOn {
4530+
p.nextToken() // consume ON
4531+
stmt.Scope = &ast.EventNotificationObjectScope{}
4532+
4533+
scopeUpper := strings.ToUpper(p.curTok.Literal)
4534+
switch scopeUpper {
4535+
case "SERVER":
4536+
stmt.Scope.Target = "Server"
4537+
p.nextToken()
4538+
case "DATABASE":
4539+
stmt.Scope.Target = "Database"
4540+
p.nextToken()
4541+
case "QUEUE":
4542+
stmt.Scope.Target = "Queue"
4543+
p.nextToken()
4544+
// Parse queue name
4545+
stmt.Scope.QueueName, _ = p.parseSchemaObjectName()
4546+
}
4547+
}
4548+
4549+
// Parse optional WITH FAN_IN
4550+
if strings.ToUpper(p.curTok.Literal) == "WITH" {
4551+
p.nextToken() // consume WITH
4552+
if strings.ToUpper(p.curTok.Literal) == "FAN_IN" {
4553+
stmt.WithFanIn = true
4554+
p.nextToken() // consume FAN_IN
4555+
}
4556+
}
4557+
4558+
// Parse FOR <event_type_or_group_list>
4559+
if strings.ToUpper(p.curTok.Literal) == "FOR" {
4560+
p.nextToken() // consume FOR
4561+
4562+
// Parse comma-separated list of event types/groups
4563+
for {
4564+
eventName := p.curTok.Literal
4565+
p.nextToken()
4566+
4567+
// Convert event name to PascalCase and determine if it's a group or type
4568+
pascalName := eventNameToPascalCase(eventName)
4569+
4570+
// If name ends with "Events" (after conversion), it's a group
4571+
if strings.HasSuffix(strings.ToUpper(eventName), "_EVENTS") || strings.HasSuffix(strings.ToUpper(eventName), "EVENTS") {
4572+
stmt.EventTypeGroups = append(stmt.EventTypeGroups, &ast.EventGroupContainer{
4573+
EventGroup: pascalName,
4574+
})
4575+
} else {
4576+
stmt.EventTypeGroups = append(stmt.EventTypeGroups, &ast.EventTypeContainer{
4577+
EventType: pascalName,
4578+
})
4579+
}
4580+
4581+
if p.curTok.Type != TokenComma {
4582+
break
4583+
}
4584+
p.nextToken() // consume comma
4585+
}
4586+
}
4587+
4588+
// Parse TO SERVICE 'service_name', 'broker_instance'
4589+
if strings.ToUpper(p.curTok.Literal) == "TO" {
4590+
p.nextToken() // consume TO
4591+
if strings.ToUpper(p.curTok.Literal) == "SERVICE" {
4592+
p.nextToken() // consume SERVICE
4593+
4594+
// Parse broker service name (string literal)
4595+
if p.curTok.Type == TokenString {
4596+
litVal := p.curTok.Literal
4597+
// Strip surrounding quotes
4598+
if len(litVal) >= 2 && litVal[0] == '\'' && litVal[len(litVal)-1] == '\'' {
4599+
litVal = litVal[1 : len(litVal)-1]
4600+
}
4601+
stmt.BrokerService = &ast.StringLiteral{
4602+
LiteralType: "String",
4603+
IsNational: false,
4604+
IsLargeObject: false,
4605+
Value: litVal,
4606+
}
4607+
p.nextToken()
4608+
}
4609+
4610+
// Parse comma and broker instance specifier
4611+
if p.curTok.Type == TokenComma {
4612+
p.nextToken() // consume comma
4613+
4614+
if p.curTok.Type == TokenString {
4615+
litVal := p.curTok.Literal
4616+
// Strip surrounding quotes
4617+
if len(litVal) >= 2 && litVal[0] == '\'' && litVal[len(litVal)-1] == '\'' {
4618+
litVal = litVal[1 : len(litVal)-1]
4619+
}
4620+
stmt.BrokerInstanceSpecifier = &ast.StringLiteral{
4621+
LiteralType: "String",
4622+
IsNational: false,
4623+
IsLargeObject: false,
4624+
Value: litVal,
4625+
}
4626+
p.nextToken()
4627+
}
4628+
}
4629+
}
4630+
}
4631+
4632+
// Skip any remaining tokens
45294633
p.skipToEndOfStatement()
45304634
return stmt, nil
45314635
}
45324636

4637+
// eventNameToPascalCase converts an event name like "Object_Created" or "DDL_CREDENTIAL_EVENTS" to PascalCase.
4638+
func eventNameToPascalCase(name string) string {
4639+
// Split by underscore
4640+
parts := strings.Split(name, "_")
4641+
var result strings.Builder
4642+
for _, part := range parts {
4643+
if len(part) == 0 {
4644+
continue
4645+
}
4646+
// Capitalize first letter, lowercase rest
4647+
result.WriteString(strings.ToUpper(part[:1]))
4648+
result.WriteString(strings.ToLower(part[1:]))
4649+
}
4650+
return result.String()
4651+
}
4652+
45334653
func (p *Parser) parseCreatePartitionFunctionFromPartition() (*ast.CreatePartitionFunctionStatement, error) {
45344654
// PARTITION has already been consumed, curTok is FUNCTION
45354655
if strings.ToUpper(p.curTok.Literal) == "FUNCTION" {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"todo": true}
1+
{}

0 commit comments

Comments
 (0)