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
100 changes: 100 additions & 0 deletions parser/column_modifiers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package parser

import (
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestParser_ColumnModifierOrder(t *testing.T) {
modifiers := map[string]string{
"comment": "cOmMeNt 'example'",
"codec": "cOdEc(ZSTD(1))",
"ttl": "tTl event_time + INTERVAL 1 DAY",
}
orders := [][]string{
{"comment", "codec", "ttl"},
{"comment", "ttl", "codec"},
{"codec", "comment", "ttl"},
{"codec", "ttl", "comment"},
{"ttl", "comment", "codec"},
{"ttl", "codec", "comment"},
}
forms := []struct{ name, query string }{
{"create", "CREATE TABLE db.example (event_time DateTime, %s) ENGINE = MergeTree ORDER BY event_time"},
{"add", "ALTER TABLE db.example ADD COLUMN %s"},
{"modify", "ALTER TABLE db.example MODIFY COLUMN %s"},
}
for _, form := range forms {
for _, order := range orders {
t.Run(form.name+"/"+strings.Join(order, "_"), func(t *testing.T) {
var clauses []string
for _, name := range order {
clauses = append(clauses, modifiers[name])
}
declaration := "value String " + strings.Join(clauses, " ")
query := fmt.Sprintf(form.query, declaration)
statements, err := NewParser(query).ParseStmts()
require.NoError(t, err)
require.Len(t, statements, 1)
column := modifierTestColumn(t, statements[0])
require.NotNil(t, column.Comment)
require.NotNil(t, column.Codec)
require.NotNil(t, column.TTL)
require.Equal(t, Pos(strings.Index(query, declaration)+len(declaration)), column.End())
canonical := "value String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY"
require.Equal(t, canonical, Format(column))
for _, beautify := range []bool{false, true} {
formatter := NewFormatter()
if beautify {
formatter.WithBeautify()
}
statements[0].FormatSQL(formatter)
reparsed, err := NewParser(formatter.String()).ParseStmts()
require.NoError(t, err)
require.Len(t, reparsed, 1)
require.Equal(t, canonical, Format(modifierTestColumn(t, reparsed[0])))
}
validFormatSQL(t, Format(statements[0])+";\n")
})
}
}
}

func TestParser_InvalidColumnModifiers(t *testing.T) {
for _, tc := range []struct{ name, modifiers, errorText string }{
{"comment twice", "COMMENT '' COMMENT 'example'", "duplicate COMMENT"},
{"separated comments", "COMMENT 'first' CODEC(ZSTD(1)) COMMENT 'second'", "duplicate COMMENT"},
{"codec twice", "CODEC(ZSTD(1)) CODEC(LZ4)", "duplicate CODEC"},
{"separated codecs", "CODEC(ZSTD(1)) COMMENT 'example' CODEC(LZ4)", "duplicate CODEC"},
{"ttl twice", "TTL event_time + INTERVAL 1 DAY TTL event_time + INTERVAL 2 DAY", "duplicate TTL"},
{"separated ttls", "TTL event_time + INTERVAL 1 DAY COMMENT 'example' TTL event_time + INTERVAL 2 DAY", "duplicate TTL"},
{"missing comment", "COMMENT", ""},
{"incomplete codec", "CODEC(", ""},
{"missing ttl", "TTL", ""},
} {
t.Run(tc.name, func(t *testing.T) {
query := "CREATE TABLE db.example (event_time DateTime, value String " + tc.modifiers + ") ENGINE = MergeTree ORDER BY event_time"
_, err := NewParser(query).ParseStmts()
require.Error(t, err)
if tc.errorText != "" {
require.ErrorContains(t, err, tc.errorText)
}
})
}
}

func modifierTestColumn(t *testing.T, expression Expr) *ColumnDef {
t.Helper()
var columns []*ColumnDef
Walk(expression, func(node Expr) bool {
if column, ok := node.(*ColumnDef); ok && Format(column.Name) == "value" {
columns = append(columns, column)
}
return true
})
require.Len(t, columns, 1)
return columns[0]
}
8 changes: 4 additions & 4 deletions parser/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,10 @@ func (c *ColumnDef) FormatSQL(formatter *Formatter) {
formatter.WriteString(" ALIAS ")
formatter.WriteExpr(c.AliasExpr)
}
if c.Comment != nil {
formatter.WriteString(" COMMENT ")
formatter.WriteExpr(c.Comment)
}
if c.Codec != nil {
formatter.WriteByte(whitespace)
formatter.WriteExpr(c.Codec)
Expand All @@ -681,10 +685,6 @@ func (c *ColumnDef) FormatSQL(formatter *Formatter) {
formatter.WriteByte(whitespace)
formatter.WriteExpr(c.TTL)
}
if c.Comment != nil {
formatter.WriteString(" COMMENT ")
formatter.WriteExpr(c.Comment)
}
}

func (c *ColumnExpr) FormatSQL(formatter *Formatter) {
Expand Down
62 changes: 36 additions & 26 deletions parser/parser_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -733,38 +733,48 @@ func (p *Parser) parseTableColumnExpr(pos Pos) (*ColumnDef, error) {
columnEnd = valueExpr.End()
}

comment, err := p.tryParseColumnComment(p.Pos())
if err != nil {
return nil, err
}
if comment != nil {
columnEnd = comment.End()
}

codec, err := p.tryParseCompressionCodecs(p.Pos())
if err != nil {
return nil, err
}
if codec != nil {
columnEnd = codec.End()
}
ttl, err := p.tryParseTTLClause(p.Pos(), false)
if err != nil {
return nil, err
}
if ttl != nil {
columnEnd = ttl.End()
}
column.TTL = ttl

column.ColumnEnd = columnEnd
column.Comment = comment
column.Codec = codec
column.Nullable = nullable
column.NotNull = notNull
if err := p.parseColumnModifiers(column); err != nil {
return nil, err
}
return column, nil
}

func (p *Parser) parseColumnModifiers(column *ColumnDef) error {
for {
var modifier Expr
var err error
switch {
case p.matchKeyword(KeywordComment):
if column.Comment != nil {
return fmt.Errorf("duplicate COMMENT clause")
}
column.Comment, err = p.tryParseColumnComment(p.Pos())
modifier = column.Comment
case p.matchKeyword(KeywordCodec):
if column.Codec != nil {
return fmt.Errorf("duplicate CODEC clause")
}
column.Codec, err = p.tryParseCompressionCodecs(p.Pos())
modifier = column.Codec
case p.matchKeyword(KeywordTtl):
if column.TTL != nil {
return fmt.Errorf("duplicate TTL clause")
}
column.TTL, err = p.tryParseTTLClause(p.Pos(), false)
modifier = column.TTL
default:
return nil
}
if err != nil {
return err
}
column.ColumnEnd = modifier.End()
}
}

func (p *Parser) parseTableArgExpr(pos Pos) (Expr, error) {
expr, err := p.parseTableArgPrimaryExpr(pos)
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions parser/testdata/ddl/column_modifiers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE TABLE db.example
(
event_time DateTime,
value_a String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_b String COMMENT 'example' TTL event_time + INTERVAL 1 DAY CODEC(ZSTD(1)),
value_c String CODEC(ZSTD(1)) COMMENT 'example' TTL event_time + INTERVAL 1 DAY,
value_d String CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY COMMENT 'example',
value_e String TTL event_time + INTERVAL 1 DAY COMMENT 'example' CODEC(ZSTD(1)),
value_f String TTL event_time + INTERVAL 1 DAY CODEC(ZSTD(1)) COMMENT 'example',
value_g String COMMENT 'example' CODEC(ZSTD(1)),
value_h String CODEC(ZSTD(1)) COMMENT 'example'
)
ENGINE = MergeTree
ORDER BY event_time;

ALTER TABLE db.example ADD COLUMN extra String DEFAULT 'value' TTL event_time + INTERVAL 1 DAY CODEC(LZ4) COMMENT 'example';
ALTER TABLE db.example MODIFY COLUMN extra String MATERIALIZED toString(event_time) CODEC(LZ4) COMMENT 'example' TTL event_time + INTERVAL 1 DAY;
40 changes: 40 additions & 0 deletions parser/testdata/ddl/format/beautify/column_modifiers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-- Origin SQL:
CREATE TABLE db.example
(
event_time DateTime,
value_a String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_b String COMMENT 'example' TTL event_time + INTERVAL 1 DAY CODEC(ZSTD(1)),
value_c String CODEC(ZSTD(1)) COMMENT 'example' TTL event_time + INTERVAL 1 DAY,
value_d String CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY COMMENT 'example',
value_e String TTL event_time + INTERVAL 1 DAY COMMENT 'example' CODEC(ZSTD(1)),
value_f String TTL event_time + INTERVAL 1 DAY CODEC(ZSTD(1)) COMMENT 'example',
value_g String COMMENT 'example' CODEC(ZSTD(1)),
value_h String CODEC(ZSTD(1)) COMMENT 'example'
)
ENGINE = MergeTree
ORDER BY event_time;

ALTER TABLE db.example ADD COLUMN extra String DEFAULT 'value' TTL event_time + INTERVAL 1 DAY CODEC(LZ4) COMMENT 'example';
ALTER TABLE db.example MODIFY COLUMN extra String MATERIALIZED toString(event_time) CODEC(LZ4) COMMENT 'example' TTL event_time + INTERVAL 1 DAY;


-- Beautify SQL:
CREATE TABLE db.example
(
event_time DateTime,
value_a String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_b String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_c String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_d String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_e String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_f String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_g String COMMENT 'example' CODEC(ZSTD(1)),
value_h String COMMENT 'example' CODEC(ZSTD(1))
)
ENGINE = MergeTree
ORDER BY
event_time;
ALTER TABLE db.example
ADD COLUMN extra String DEFAULT 'value' COMMENT 'example' CODEC(LZ4) TTL event_time + INTERVAL 1 DAY;
ALTER TABLE db.example
MODIFY COLUMN extra String MATERIALIZED toString(event_time) COMMENT 'example' CODEC(LZ4) TTL event_time + INTERVAL 1 DAY;
24 changes: 24 additions & 0 deletions parser/testdata/ddl/format/column_modifiers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- Origin SQL:
CREATE TABLE db.example
(
event_time DateTime,
value_a String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY,
value_b String COMMENT 'example' TTL event_time + INTERVAL 1 DAY CODEC(ZSTD(1)),
value_c String CODEC(ZSTD(1)) COMMENT 'example' TTL event_time + INTERVAL 1 DAY,
value_d String CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY COMMENT 'example',
value_e String TTL event_time + INTERVAL 1 DAY COMMENT 'example' CODEC(ZSTD(1)),
value_f String TTL event_time + INTERVAL 1 DAY CODEC(ZSTD(1)) COMMENT 'example',
value_g String COMMENT 'example' CODEC(ZSTD(1)),
value_h String CODEC(ZSTD(1)) COMMENT 'example'
)
ENGINE = MergeTree
ORDER BY event_time;

ALTER TABLE db.example ADD COLUMN extra String DEFAULT 'value' TTL event_time + INTERVAL 1 DAY CODEC(LZ4) COMMENT 'example';
ALTER TABLE db.example MODIFY COLUMN extra String MATERIALIZED toString(event_time) CODEC(LZ4) COMMENT 'example' TTL event_time + INTERVAL 1 DAY;


-- Format SQL:
CREATE TABLE db.example (event_time DateTime, value_a String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY, value_b String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY, value_c String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY, value_d String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY, value_e String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY, value_f String COMMENT 'example' CODEC(ZSTD(1)) TTL event_time + INTERVAL 1 DAY, value_g String COMMENT 'example' CODEC(ZSTD(1)), value_h String COMMENT 'example' CODEC(ZSTD(1))) ENGINE = MergeTree ORDER BY event_time;
ALTER TABLE db.example ADD COLUMN extra String DEFAULT 'value' COMMENT 'example' CODEC(LZ4) TTL event_time + INTERVAL 1 DAY;
ALTER TABLE db.example MODIFY COLUMN extra String MATERIALIZED toString(event_time) COMMENT 'example' CODEC(LZ4) TTL event_time + INTERVAL 1 DAY;
Loading
Loading