Skip to content

Commit acbbdd5

Browse files
kyleconroyclaude
andcommitted
Add IPv4 address parsing for CREATE ENDPOINT statements
- Add IPv4 and ListenerIPEndpointProtocolOption AST types - Expand CreateEndpointStatement to include all endpoint fields - Add parseIPv4Address to handle IP addresses (including float tokenization) - Update marshaling for new types - Enable IPv4AddressTests and Baselines90_IPv4AddressTests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8c359e2 commit acbbdd5

File tree

7 files changed

+376
-25
lines changed

7 files changed

+376
-25
lines changed

ast/alter_simple_statements.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,27 @@ type LiteralEndpointProtocolOption struct {
133133
func (l *LiteralEndpointProtocolOption) node() {}
134134
func (l *LiteralEndpointProtocolOption) endpointProtocolOption() {}
135135

136+
// IPv4 represents an IPv4 address with four octets.
137+
type IPv4 struct {
138+
OctetOne *IntegerLiteral
139+
OctetTwo *IntegerLiteral
140+
OctetThree *IntegerLiteral
141+
OctetFour *IntegerLiteral
142+
}
143+
144+
func (i *IPv4) node() {}
145+
146+
// ListenerIPEndpointProtocolOption represents an IP address endpoint protocol option.
147+
type ListenerIPEndpointProtocolOption struct {
148+
IsAll bool
149+
IPv4PartOne *IPv4
150+
IPv4PartTwo *IPv4
151+
Kind string // TcpListenerIP, HttpListenerIP, etc.
152+
}
153+
154+
func (l *ListenerIPEndpointProtocolOption) node() {}
155+
func (l *ListenerIPEndpointProtocolOption) endpointProtocolOption() {}
156+
136157
// PayloadOption is an interface for endpoint payload options.
137158
type PayloadOption interface {
138159
Node

ast/create_simple_statements.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,13 @@ func (r *RouteOption) node() {}
211211

212212
// CreateEndpointStatement represents a CREATE ENDPOINT statement.
213213
type CreateEndpointStatement struct {
214-
Name *Identifier `json:"Name,omitempty"`
214+
Name *Identifier
215+
State string
216+
Affinity *EndpointAffinity
217+
Protocol string
218+
ProtocolOptions []EndpointProtocolOption
219+
EndpointType string
220+
PayloadOptions []PayloadOption
215221
}
216222

217223
func (s *CreateEndpointStatement) node() {}

parser/marshal.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17467,11 +17467,45 @@ func endpointProtocolOptionToJSON(opt ast.EndpointProtocolOption) jsonNode {
1746717467
node["Kind"] = o.Kind
1746817468
}
1746917469
return node
17470+
case *ast.ListenerIPEndpointProtocolOption:
17471+
node := jsonNode{
17472+
"$type": "ListenerIPEndpointProtocolOption",
17473+
"IsAll": o.IsAll,
17474+
}
17475+
if o.IPv4PartOne != nil {
17476+
node["IPv4PartOne"] = ipv4ToJSON(o.IPv4PartOne)
17477+
}
17478+
if o.IPv4PartTwo != nil {
17479+
node["IPv4PartTwo"] = ipv4ToJSON(o.IPv4PartTwo)
17480+
}
17481+
if o.Kind != "" {
17482+
node["Kind"] = o.Kind
17483+
}
17484+
return node
1747017485
default:
1747117486
return jsonNode{"$type": "UnknownProtocolOption"}
1747217487
}
1747317488
}
1747417489

17490+
func ipv4ToJSON(ip *ast.IPv4) jsonNode {
17491+
node := jsonNode{
17492+
"$type": "IPv4",
17493+
}
17494+
if ip.OctetOne != nil {
17495+
node["OctetOne"] = scalarExpressionToJSON(ip.OctetOne)
17496+
}
17497+
if ip.OctetTwo != nil {
17498+
node["OctetTwo"] = scalarExpressionToJSON(ip.OctetTwo)
17499+
}
17500+
if ip.OctetThree != nil {
17501+
node["OctetThree"] = scalarExpressionToJSON(ip.OctetThree)
17502+
}
17503+
if ip.OctetFour != nil {
17504+
node["OctetFour"] = scalarExpressionToJSON(ip.OctetFour)
17505+
}
17506+
return node
17507+
}
17508+
1747517509
func payloadOptionToJSON(opt ast.PayloadOption) jsonNode {
1747617510
switch o := opt.(type) {
1747717511
case *ast.SoapMethod:
@@ -18765,6 +18799,32 @@ func createEndpointStatementToJSON(s *ast.CreateEndpointStatement) jsonNode {
1876518799
if s.Name != nil {
1876618800
node["Name"] = identifierToJSON(s.Name)
1876718801
}
18802+
if s.State != "" {
18803+
node["State"] = s.State
18804+
}
18805+
if s.Affinity != nil {
18806+
node["Affinity"] = endpointAffinityToJSON(s.Affinity)
18807+
}
18808+
if s.Protocol != "" {
18809+
node["Protocol"] = s.Protocol
18810+
}
18811+
if len(s.ProtocolOptions) > 0 {
18812+
opts := make([]jsonNode, len(s.ProtocolOptions))
18813+
for i, opt := range s.ProtocolOptions {
18814+
opts[i] = endpointProtocolOptionToJSON(opt)
18815+
}
18816+
node["ProtocolOptions"] = opts
18817+
}
18818+
if s.EndpointType != "" {
18819+
node["EndpointType"] = s.EndpointType
18820+
}
18821+
if len(s.PayloadOptions) > 0 {
18822+
opts := make([]jsonNode, len(s.PayloadOptions))
18823+
for i, opt := range s.PayloadOptions {
18824+
opts[i] = payloadOptionToJSON(opt)
18825+
}
18826+
node["PayloadOptions"] = opts
18827+
}
1876818828
return node
1876918829
}
1877018830

parser/parse_ddl.go

Lines changed: 97 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8070,29 +8070,46 @@ func (p *Parser) parseAlterEndpointStatement() (*ast.AlterEndpointStatement, err
80708070
if p.curTok.Type == TokenEquals {
80718071
p.nextToken() // consume =
80728072
}
8073-
opt := &ast.LiteralEndpointProtocolOption{}
8074-
switch optName {
8075-
case "LISTENER_PORT":
8076-
opt.Kind = "TcpListenerPort"
8077-
case "LISTENER_IP":
8078-
opt.Kind = "TcpListenerIP"
8079-
default:
8080-
opt.Kind = optName
8081-
}
8082-
if p.curTok.Type == TokenNumber {
8083-
opt.Value = &ast.IntegerLiteral{
8084-
LiteralType: "Integer",
8085-
Value: p.curTok.Literal,
8073+
if optName == "LISTENER_IP" {
8074+
// Parse IP address option specially
8075+
ipOpt := &ast.ListenerIPEndpointProtocolOption{
8076+
Kind: "TcpListenerIP",
80868077
}
8087-
p.nextToken()
8088-
} else if p.curTok.Type == TokenString {
8089-
opt.Value = &ast.StringLiteral{
8090-
LiteralType: "String",
8091-
Value: p.curTok.Literal,
8078+
// Check for ALL or IP address in parentheses
8079+
if strings.ToUpper(p.curTok.Literal) == "ALL" {
8080+
ipOpt.IsAll = true
8081+
p.nextToken()
8082+
} else if p.curTok.Type == TokenLParen {
8083+
p.nextToken() // consume (
8084+
ipOpt.IPv4PartOne = p.parseIPv4Address()
8085+
if p.curTok.Type == TokenRParen {
8086+
p.nextToken() // consume )
8087+
}
80928088
}
8093-
p.nextToken()
8089+
stmt.ProtocolOptions = append(stmt.ProtocolOptions, ipOpt)
8090+
} else {
8091+
opt := &ast.LiteralEndpointProtocolOption{}
8092+
switch optName {
8093+
case "LISTENER_PORT":
8094+
opt.Kind = "TcpListenerPort"
8095+
default:
8096+
opt.Kind = optName
8097+
}
8098+
if p.curTok.Type == TokenNumber {
8099+
opt.Value = &ast.IntegerLiteral{
8100+
LiteralType: "Integer",
8101+
Value: p.curTok.Literal,
8102+
}
8103+
p.nextToken()
8104+
} else if p.curTok.Type == TokenString {
8105+
opt.Value = &ast.StringLiteral{
8106+
LiteralType: "String",
8107+
Value: p.curTok.Literal,
8108+
}
8109+
p.nextToken()
8110+
}
8111+
stmt.ProtocolOptions = append(stmt.ProtocolOptions, opt)
80948112
}
8095-
stmt.ProtocolOptions = append(stmt.ProtocolOptions, opt)
80968113
if p.curTok.Type == TokenComma {
80978114
p.nextToken()
80988115
}
@@ -8250,6 +8267,66 @@ func (p *Parser) parseAlterEndpointStatement() (*ast.AlterEndpointStatement, err
82508267
return stmt, nil
82518268
}
82528269

8270+
// parseIPv4Address parses an IPv4 address like "1.2.3.4" or "1 . 2 . 3 . 4"
8271+
// The lexer may tokenize "1.2" as a single float token, so we need to handle that
8272+
func (p *Parser) parseIPv4Address() *ast.IPv4 {
8273+
ipv4 := &ast.IPv4{}
8274+
var octets []string
8275+
8276+
// Collect all octets from tokens
8277+
for len(octets) < 4 {
8278+
if p.curTok.Type == TokenNumber {
8279+
// Check if this is a float-like number containing dots
8280+
literal := p.curTok.Literal
8281+
if strings.Contains(literal, ".") {
8282+
// Split by dots and add each part as an octet
8283+
parts := strings.Split(literal, ".")
8284+
for _, part := range parts {
8285+
if part != "" && len(octets) < 4 {
8286+
octets = append(octets, part)
8287+
}
8288+
}
8289+
} else {
8290+
octets = append(octets, literal)
8291+
}
8292+
p.nextToken()
8293+
} else if p.curTok.Type == TokenDot {
8294+
// Skip standalone dots
8295+
p.nextToken()
8296+
} else {
8297+
break
8298+
}
8299+
}
8300+
8301+
// Assign octets to the IPv4 struct
8302+
if len(octets) >= 1 {
8303+
ipv4.OctetOne = &ast.IntegerLiteral{
8304+
LiteralType: "Integer",
8305+
Value: octets[0],
8306+
}
8307+
}
8308+
if len(octets) >= 2 {
8309+
ipv4.OctetTwo = &ast.IntegerLiteral{
8310+
LiteralType: "Integer",
8311+
Value: octets[1],
8312+
}
8313+
}
8314+
if len(octets) >= 3 {
8315+
ipv4.OctetThree = &ast.IntegerLiteral{
8316+
LiteralType: "Integer",
8317+
Value: octets[2],
8318+
}
8319+
}
8320+
if len(octets) >= 4 {
8321+
ipv4.OctetFour = &ast.IntegerLiteral{
8322+
LiteralType: "Integer",
8323+
Value: octets[3],
8324+
}
8325+
}
8326+
8327+
return ipv4
8328+
}
8329+
82538330
func (p *Parser) parseAlterServiceStatement() (ast.Statement, error) {
82548331
// Consume SERVICE
82558332
p.nextToken()

0 commit comments

Comments
 (0)