diff --git a/parser/ast.go b/parser/ast.go index cd2eb11..8092b29 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -4512,11 +4512,13 @@ func (c *CompressionCodec) String() string { builder.WriteByte(',') builder.WriteByte(' ') } - builder.WriteString(c.Name.String()) - if c.Level != nil { - builder.WriteByte('(') - builder.WriteString(c.Level.String()) - builder.WriteByte(')') + if c.Name != nil { + builder.WriteString(c.Name.String()) + if c.Level != nil { + builder.WriteByte('(') + builder.WriteString(c.Level.String()) + builder.WriteByte(')') + } } builder.WriteByte(')') return builder.String() @@ -4525,16 +4527,20 @@ func (c *CompressionCodec) String() string { func (c *CompressionCodec) Accept(visitor ASTVisitor) error { visitor.Enter(c) defer visitor.Leave(c) - if err := c.Type.Accept(visitor); err != nil { - return err + if c.Type != nil { + if err := c.Type.Accept(visitor); err != nil { + return err + } } if c.TypeLevel != nil { if err := c.TypeLevel.Accept(visitor); err != nil { return err } } - if err := c.Name.Accept(visitor); err != nil { - return err + if c.Name != nil { + if err := c.Name.Accept(visitor); err != nil { + return err + } } if c.Level != nil { if err := c.Level.Accept(visitor); err != nil { diff --git a/parser/parser_column.go b/parser/parser_column.go index 5312319..557bfbf 100644 --- a/parser/parser_column.go +++ b/parser/parser_column.go @@ -1126,27 +1126,34 @@ func (p *Parser) tryParseCompressionCodecs(pos Pos) (*CompressionCodec, error) { } // parse codec name - name, err := p.parseIdent() + codecType, err := p.parseIdent() if err != nil { return nil, err } // parse DELTA if CODEC(Delta, ZSTD(1)) // or CODEC(Delta(9), ZSTD(1)) or CODEC(T64, ZSTD(1)) - var codecType *Ident + var name *Ident var typeLevel *NumberLiteral - switch strings.ToUpper(name.Name) { + switch strings.ToUpper(codecType.Name) { case "DELTA", "DOUBLEDELTA", "T64", "GORILLA": - codecType = name // try parse delta level typeLevel, err = p.tryParseCompressionLevel(p.Pos()) if err != nil { return nil, err } - // consume comma - if err := p.expectTokenKind(TokenKindComma); err != nil { - return nil, err + + if p.matchTokenKind(TokenKindComma) { + if err := p.expectTokenKind(TokenKindComma); err != nil { + return nil, err + } + name, err = p.parseIdent() + if err != nil { + return nil, err + } } - name, err = p.parseIdent() + case "ZSTD", "LZ4HC", "LH4": + // For compression codecs, try to parse level + typeLevel, err = p.tryParseCompressionLevel(p.Pos()) if err != nil { return nil, err } @@ -1154,11 +1161,13 @@ func (p *Parser) tryParseCompressionCodecs(pos Pos) (*CompressionCodec, error) { var level *NumberLiteral // TODO: check if the codec name is valid - switch strings.ToUpper(name.Name) { - case "ZSTD", "LZ4HC", "LH4": - level, err = p.tryParseCompressionLevel(p.Pos()) - if err != nil { - return nil, err + if name != nil { + switch strings.ToUpper(name.Name) { + case "ZSTD", "LZ4HC", "LH4": + level, err = p.tryParseCompressionLevel(p.Pos()) + if err != nil { + return nil, err + } } } @@ -1167,6 +1176,21 @@ func (p *Parser) tryParseCompressionCodecs(pos Pos) (*CompressionCodec, error) { return nil, err } + // If there's only one codec (no second name), store it in Name field instead of Type + // This handles cases like CODEC(DoubleDelta) or CODEC(ZSTD(1)) + if name == nil { + return &CompressionCodec{ + CodecPos: pos, + RightParenPos: rightParenPos, + Type: nil, + TypeLevel: nil, + Name: codecType, + Level: typeLevel, + }, nil + } + + // Two codecs: Type is the preprocessing codec, Name is the compression codec + // e.g., CODEC(DoubleDelta, ZSTD(1)) return &CompressionCodec{ CodecPos: pos, RightParenPos: rightParenPos, diff --git a/parser/testdata/ddl/create_table_codec_no_args.sql b/parser/testdata/ddl/create_table_codec_no_args.sql new file mode 100644 index 0000000..6bee985 --- /dev/null +++ b/parser/testdata/ddl/create_table_codec_no_args.sql @@ -0,0 +1,3 @@ +CREATE TABLE shark_attacks ( + timestamp DateTime CODEC(DoubleDelta), +); \ No newline at end of file diff --git a/parser/testdata/ddl/format/create_table_codec_no_args.sql b/parser/testdata/ddl/format/create_table_codec_no_args.sql new file mode 100644 index 0000000..4d031cf --- /dev/null +++ b/parser/testdata/ddl/format/create_table_codec_no_args.sql @@ -0,0 +1,7 @@ +-- Origin SQL: +CREATE TABLE shark_attacks ( + timestamp DateTime CODEC(DoubleDelta), +); + +-- Format SQL: +CREATE TABLE shark_attacks (timestamp DateTime CODEC(DoubleDelta)); diff --git a/parser/testdata/ddl/output/create_table_codec_no_args.sql.golden.json b/parser/testdata/ddl/output/create_table_codec_no_args.sql.golden.json new file mode 100644 index 0000000..56f8c63 --- /dev/null +++ b/parser/testdata/ddl/output/create_table_codec_no_args.sql.golden.json @@ -0,0 +1,73 @@ +[ + { + "CreatePos": 0, + "StatementEnd": 0, + "OrReplace": false, + "Name": { + "Database": null, + "Table": { + "Name": "shark_attacks", + "QuoteType": 1, + "NamePos": 13, + "NameEnd": 26 + } + }, + "IfNotExists": false, + "UUID": null, + "OnCluster": null, + "TableSchema": { + "SchemaPos": 27, + "SchemaEnd": 72, + "Columns": [ + { + "NamePos": 33, + "ColumnEnd": 70, + "Name": { + "Ident": { + "Name": "timestamp", + "QuoteType": 1, + "NamePos": 33, + "NameEnd": 42 + }, + "DotIdent": null + }, + "Type": { + "Name": { + "Name": "DateTime", + "QuoteType": 1, + "NamePos": 43, + "NameEnd": 51 + } + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": { + "CodecPos": 52, + "RightParenPos": 70, + "Type": null, + "TypeLevel": null, + "Name": { + "Name": "DoubleDelta", + "QuoteType": 1, + "NamePos": 58, + "NameEnd": 69 + }, + "Level": null + }, + "TTL": null, + "Comment": null, + "CompressionCodec": null + } + ], + "AliasTable": null, + "TableFunction": null + }, + "Engine": null, + "SubQuery": null, + "HasTemporary": false, + "Comment": null + } +] \ No newline at end of file