Skip to content

Commit 5990c29

Browse files
authored
Add CREATE EXTERNAL FILE FORMAT FormatType and format options parsing (#32)
1 parent bf00667 commit 5990c29

5 files changed

Lines changed: 158 additions & 18 deletions

File tree

ast/external_statements.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,35 @@ type ExternalDataSourceOption struct {
1717

1818
// CreateExternalFileFormatStatement represents CREATE EXTERNAL FILE FORMAT statement
1919
type CreateExternalFileFormatStatement struct {
20-
Name *Identifier
21-
Options []*ExternalFileFormatOption
20+
Name *Identifier
21+
FormatType string
22+
ExternalFileFormatOptions []ExternalFileFormatOption
2223
}
2324

2425
func (s *CreateExternalFileFormatStatement) node() {}
2526
func (s *CreateExternalFileFormatStatement) statement() {}
2627

27-
// ExternalFileFormatOption represents an option for external file format
28-
type ExternalFileFormatOption struct {
28+
// ExternalFileFormatOption is an interface for external file format options
29+
type ExternalFileFormatOption interface {
30+
externalFileFormatOption()
31+
}
32+
33+
// ExternalFileFormatContainerOption represents a container option with suboptions
34+
type ExternalFileFormatContainerOption struct {
2935
OptionKind string
30-
Value ScalarExpression
31-
SubOptions []*ExternalFileFormatOption
36+
Suboptions []ExternalFileFormatOption
3237
}
3338

39+
func (o *ExternalFileFormatContainerOption) externalFileFormatOption() {}
40+
41+
// ExternalFileFormatLiteralOption represents a literal value option
42+
type ExternalFileFormatLiteralOption struct {
43+
OptionKind string
44+
Value *StringLiteral
45+
}
46+
47+
func (o *ExternalFileFormatLiteralOption) externalFileFormatOption() {}
48+
3449
// CreateExternalTableStatement represents CREATE EXTERNAL TABLE statement
3550
type CreateExternalTableStatement struct {
3651
SchemaObjectName *SchemaObjectName

parser/marshal.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6067,9 +6067,48 @@ func createExternalFileFormatStatementToJSON(s *ast.CreateExternalFileFormatStat
60676067
if s.Name != nil {
60686068
node["Name"] = identifierToJSON(s.Name)
60696069
}
6070+
if s.FormatType != "" {
6071+
node["FormatType"] = s.FormatType
6072+
}
6073+
if len(s.ExternalFileFormatOptions) > 0 {
6074+
var options []jsonNode
6075+
for _, opt := range s.ExternalFileFormatOptions {
6076+
options = append(options, externalFileFormatOptionToJSON(opt))
6077+
}
6078+
node["ExternalFileFormatOptions"] = options
6079+
}
60706080
return node
60716081
}
60726082

6083+
func externalFileFormatOptionToJSON(opt ast.ExternalFileFormatOption) jsonNode {
6084+
switch o := opt.(type) {
6085+
case *ast.ExternalFileFormatContainerOption:
6086+
node := jsonNode{
6087+
"$type": "ExternalFileFormatContainerOption",
6088+
"OptionKind": o.OptionKind,
6089+
}
6090+
if len(o.Suboptions) > 0 {
6091+
var subs []jsonNode
6092+
for _, sub := range o.Suboptions {
6093+
subs = append(subs, externalFileFormatOptionToJSON(sub))
6094+
}
6095+
node["Suboptions"] = subs
6096+
}
6097+
return node
6098+
case *ast.ExternalFileFormatLiteralOption:
6099+
node := jsonNode{
6100+
"$type": "ExternalFileFormatLiteralOption",
6101+
"OptionKind": o.OptionKind,
6102+
}
6103+
if o.Value != nil {
6104+
node["Value"] = stringLiteralToJSON(o.Value)
6105+
}
6106+
return node
6107+
default:
6108+
return jsonNode{"$type": "UnknownExternalFileFormatOption"}
6109+
}
6110+
}
6111+
60736112
func createExternalTableStatementToJSON(s *ast.CreateExternalTableStatement) jsonNode {
60746113
node := jsonNode{
60756114
"$type": "CreateExternalTableStatement",

parser/parse_statements.go

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4248,19 +4248,44 @@ func (p *Parser) parseCreateExternalFileFormatStatement() (*ast.CreateExternalFi
42484248
p.nextToken() // consume (
42494249

42504250
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
4251-
opt := &ast.ExternalFileFormatOption{
4252-
OptionKind: p.curTok.Literal,
4253-
}
4251+
optName := strings.ToUpper(p.curTok.Literal)
42544252
p.nextToken() // consume option name
4255-
if p.curTok.Type == TokenEquals {
4256-
p.nextToken()
4257-
val, err := p.parseScalarExpression()
4258-
if err != nil {
4259-
return nil, err
4253+
4254+
if optName == "FORMAT_TYPE" {
4255+
if p.curTok.Type == TokenEquals {
4256+
p.nextToken() // consume =
4257+
}
4258+
// Parse format type value and convert to PascalCase
4259+
stmt.FormatType = p.formatTypeToPascalCase(p.curTok.Literal)
4260+
p.nextToken() // consume value
4261+
} else if optName == "FORMAT_OPTIONS" {
4262+
// Parse container option with suboptions
4263+
opt := &ast.ExternalFileFormatContainerOption{
4264+
OptionKind: "FormatOptions",
4265+
}
4266+
if p.curTok.Type == TokenLParen {
4267+
p.nextToken() // consume (
4268+
for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF {
4269+
subOpt := p.parseExternalFileFormatSuboption()
4270+
if subOpt != nil {
4271+
opt.Suboptions = append(opt.Suboptions, subOpt)
4272+
}
4273+
if p.curTok.Type == TokenComma {
4274+
p.nextToken()
4275+
}
4276+
}
4277+
if p.curTok.Type == TokenRParen {
4278+
p.nextToken() // consume )
4279+
}
4280+
}
4281+
stmt.ExternalFileFormatOptions = append(stmt.ExternalFileFormatOptions, opt)
4282+
} else {
4283+
// Skip other options for now
4284+
if p.curTok.Type == TokenEquals {
4285+
p.nextToken() // consume =
4286+
p.nextToken() // consume value
42604287
}
4261-
opt.Value = val
42624288
}
4263-
stmt.Options = append(stmt.Options, opt)
42644289
if p.curTok.Type == TokenComma {
42654290
p.nextToken()
42664291
}
@@ -4276,6 +4301,67 @@ func (p *Parser) parseCreateExternalFileFormatStatement() (*ast.CreateExternalFi
42764301
return stmt, nil
42774302
}
42784303

4304+
func (p *Parser) formatTypeToPascalCase(s string) string {
4305+
upper := strings.ToUpper(s)
4306+
switch upper {
4307+
case "DELTA":
4308+
return "Delta"
4309+
case "DELIMITEDTEXT":
4310+
return "DelimitedText"
4311+
case "PARQUET":
4312+
return "Parquet"
4313+
case "ORC":
4314+
return "Orc"
4315+
case "RCFILE":
4316+
return "RcFile"
4317+
case "JSON":
4318+
return "Json"
4319+
default:
4320+
return s
4321+
}
4322+
}
4323+
4324+
func (p *Parser) parseExternalFileFormatSuboption() ast.ExternalFileFormatOption {
4325+
optName := strings.ToUpper(p.curTok.Literal)
4326+
p.nextToken() // consume option name
4327+
4328+
// Map to option kind
4329+
optionKind := p.externalFileFormatOptionKind(optName)
4330+
4331+
if p.curTok.Type == TokenEquals {
4332+
p.nextToken() // consume =
4333+
val, _ := p.parseStringLiteral()
4334+
return &ast.ExternalFileFormatLiteralOption{
4335+
OptionKind: optionKind,
4336+
Value: val,
4337+
}
4338+
}
4339+
return nil
4340+
}
4341+
4342+
func (p *Parser) externalFileFormatOptionKind(name string) string {
4343+
switch strings.ToUpper(name) {
4344+
case "PARSER_VERSION":
4345+
return "ParserVersion"
4346+
case "FIELD_TERMINATOR":
4347+
return "FieldTerminator"
4348+
case "STRING_DELIMITER":
4349+
return "StringDelimiter"
4350+
case "DATE_FORMAT":
4351+
return "DateFormat"
4352+
case "USE_TYPE_DEFAULT":
4353+
return "UseTypeDefault"
4354+
case "ENCODING":
4355+
return "Encoding"
4356+
case "DATA_COMPRESSION":
4357+
return "DataCompression"
4358+
case "FIRST_ROW":
4359+
return "FirstRow"
4360+
default:
4361+
return name
4362+
}
4363+
}
4364+
42794365
func (p *Parser) parseCreateExternalTableStatement() (*ast.CreateExternalTableStatement, error) {
42804366
// TABLE name - skip rest of statement for now
42814367
p.nextToken() // consume TABLE
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)