diff --git a/parser/column_modifiers_test.go b/parser/column_modifiers_test.go index 5daeae4..a12e086 100644 --- a/parser/column_modifiers_test.go +++ b/parser/column_modifiers_test.go @@ -22,10 +22,11 @@ func TestParser_ColumnModifierOrder(t *testing.T) { {"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"}, + forms := []struct{ name, query, columnType string }{ + {"create", "CREATE TABLE db.example (event_time DateTime, %s) ENGINE = MergeTree ORDER BY event_time", "String"}, + {"add", "ALTER TABLE db.example ADD COLUMN %s", "String"}, + {"modify", "ALTER TABLE db.example MODIFY COLUMN %s", "String"}, + {"modify without type", "ALTER TABLE db.example MODIFY COLUMN %s", ""}, } for _, form := range forms { for _, order := range orders { @@ -34,17 +35,22 @@ func TestParser_ColumnModifierOrder(t *testing.T) { for _, name := range order { clauses = append(clauses, modifiers[name]) } - declaration := "value String " + strings.Join(clauses, " ") + nameAndType := strings.TrimSpace("value " + form.columnType) + declaration := nameAndType + " " + 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.Equal(t, form.columnType, Format(column.Type)) + if form.columnType == "" { + require.Nil(t, column.Type) + } 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" + canonical := nameAndType + " 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() @@ -55,7 +61,13 @@ func TestParser_ColumnModifierOrder(t *testing.T) { reparsed, err := NewParser(formatter.String()).ParseStmts() require.NoError(t, err) require.Len(t, reparsed, 1) - require.Equal(t, canonical, Format(modifierTestColumn(t, reparsed[0]))) + reparsedColumn := modifierTestColumn(t, reparsed[0]) + require.Equal(t, column.Type == nil, reparsedColumn.Type == nil) + require.Equal(t, form.columnType, Format(reparsedColumn.Type)) + require.NotNil(t, reparsedColumn.Comment) + require.NotNil(t, reparsedColumn.Codec) + require.NotNil(t, reparsedColumn.TTL) + require.Equal(t, canonical, Format(reparsedColumn)) } validFormatSQL(t, Format(statements[0])+";\n") }) @@ -76,11 +88,15 @@ func TestParser_InvalidColumnModifiers(t *testing.T) { {"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) + for _, query := range []string{ + "CREATE TABLE db.example (event_time DateTime, value String " + tc.modifiers + ") ENGINE = MergeTree ORDER BY event_time", + "ALTER TABLE db.example MODIFY COLUMN value " + tc.modifiers, + } { + _, err := NewParser(query).ParseStmts() + require.Error(t, err, query) + if tc.errorText != "" { + require.ErrorContains(t, err, tc.errorText, query) + } } }) } diff --git a/parser/parser_table.go b/parser/parser_table.go index 28db5f7..0894b4b 100644 --- a/parser/parser_table.go +++ b/parser/parser_table.go @@ -693,7 +693,7 @@ func (p *Parser) parseTableColumnExpr(pos Pos) (*ColumnDef, error) { column.Name = name columnEnd := name.End() - if p.matchTokenKind(TokenKindIdent) && !p.matchKeyword(KeywordRemove) { + if p.matchTokenKind(TokenKindIdent) && !p.matchOneOfKeywords(KeywordRemove, KeywordComment, KeywordCodec, KeywordTtl) { columnType, err := p.parseColumnType(p.Pos()) if err != nil { return nil, err diff --git a/parser/testdata/ddl/alter_modify_column_modifiers.sql b/parser/testdata/ddl/alter_modify_column_modifiers.sql new file mode 100644 index 0000000..132ed54 --- /dev/null +++ b/parser/testdata/ddl/alter_modify_column_modifiers.sql @@ -0,0 +1,3 @@ +ALTER TABLE t MODIFY COLUMN x CODEC(LZ4) COMMENT 'example' TTL ts; +ALTER TABLE t MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; +ALTER TABLE t MODIFY COLUMN x CODEC(LZ4) TTL ts COMMENT 'example'; diff --git a/parser/testdata/ddl/format/alter_modify_column_modifiers.sql b/parser/testdata/ddl/format/alter_modify_column_modifiers.sql new file mode 100644 index 0000000..cc22eed --- /dev/null +++ b/parser/testdata/ddl/format/alter_modify_column_modifiers.sql @@ -0,0 +1,10 @@ +-- Origin SQL: +ALTER TABLE t MODIFY COLUMN x CODEC(LZ4) COMMENT 'example' TTL ts; +ALTER TABLE t MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; +ALTER TABLE t MODIFY COLUMN x CODEC(LZ4) TTL ts COMMENT 'example'; + + +-- Format SQL: +ALTER TABLE t MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; +ALTER TABLE t MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; +ALTER TABLE t MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; diff --git a/parser/testdata/ddl/format/beautify/alter_modify_column_modifiers.sql b/parser/testdata/ddl/format/beautify/alter_modify_column_modifiers.sql new file mode 100644 index 0000000..d197680 --- /dev/null +++ b/parser/testdata/ddl/format/beautify/alter_modify_column_modifiers.sql @@ -0,0 +1,13 @@ +-- Origin SQL: +ALTER TABLE t MODIFY COLUMN x CODEC(LZ4) COMMENT 'example' TTL ts; +ALTER TABLE t MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; +ALTER TABLE t MODIFY COLUMN x CODEC(LZ4) TTL ts COMMENT 'example'; + + +-- Beautify SQL: +ALTER TABLE t +MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; +ALTER TABLE t +MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; +ALTER TABLE t +MODIFY COLUMN x COMMENT 'example' CODEC(LZ4) TTL ts; diff --git a/parser/testdata/ddl/output/alter_modify_column_modifiers.sql.golden.json b/parser/testdata/ddl/output/alter_modify_column_modifiers.sql.golden.json new file mode 100644 index 0000000..8a2bd79 --- /dev/null +++ b/parser/testdata/ddl/output/alter_modify_column_modifiers.sql.golden.json @@ -0,0 +1,233 @@ +[ + { + "AlterPos": 0, + "StatementEnd": 65, + "TableIdentifier": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 12, + "NameEnd": 13 + } + }, + "OnCluster": null, + "AlterExprs": [ + { + "ModifyPos": 14, + "StatementEnd": 65, + "IfExists": false, + "Column": { + "NamePos": 28, + "ColumnEnd": 65, + "Name": { + "Ident": { + "Name": "x", + "QuoteType": 1, + "NamePos": 28, + "NameEnd": 29 + }, + "DotIdent": null + }, + "Type": null, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": { + "CodecPos": 30, + "RightParenPos": 40, + "Type": null, + "TypeLevel": null, + "Name": { + "Name": "LZ4", + "QuoteType": 1, + "NamePos": 36, + "NameEnd": 39 + }, + "Level": null + }, + "TTL": { + "TTLPos": 59, + "ListEnd": 65, + "Items": [ + { + "TTLPos": 59, + "Expr": { + "Name": "ts", + "QuoteType": 1, + "NamePos": 63, + "NameEnd": 65 + }, + "Policy": null + } + ] + }, + "Comment": { + "LiteralPos": 41, + "LiteralEnd": 58, + "Literal": "example" + }, + "CompressionCodec": null + }, + "RemovePropertyType": null + } + ], + "Settings": null + }, + { + "AlterPos": 67, + "StatementEnd": 132, + "TableIdentifier": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 79, + "NameEnd": 80 + } + }, + "OnCluster": null, + "AlterExprs": [ + { + "ModifyPos": 81, + "StatementEnd": 132, + "IfExists": false, + "Column": { + "NamePos": 95, + "ColumnEnd": 132, + "Name": { + "Ident": { + "Name": "x", + "QuoteType": 1, + "NamePos": 95, + "NameEnd": 96 + }, + "DotIdent": null + }, + "Type": null, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": { + "CodecPos": 115, + "RightParenPos": 125, + "Type": null, + "TypeLevel": null, + "Name": { + "Name": "LZ4", + "QuoteType": 1, + "NamePos": 121, + "NameEnd": 124 + }, + "Level": null + }, + "TTL": { + "TTLPos": 126, + "ListEnd": 132, + "Items": [ + { + "TTLPos": 126, + "Expr": { + "Name": "ts", + "QuoteType": 1, + "NamePos": 130, + "NameEnd": 132 + }, + "Policy": null + } + ] + }, + "Comment": { + "LiteralPos": 97, + "LiteralEnd": 114, + "Literal": "example" + }, + "CompressionCodec": null + }, + "RemovePropertyType": null + } + ], + "Settings": null + }, + { + "AlterPos": 134, + "StatementEnd": 199, + "TableIdentifier": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 146, + "NameEnd": 147 + } + }, + "OnCluster": null, + "AlterExprs": [ + { + "ModifyPos": 148, + "StatementEnd": 199, + "IfExists": false, + "Column": { + "NamePos": 162, + "ColumnEnd": 199, + "Name": { + "Ident": { + "Name": "x", + "QuoteType": 1, + "NamePos": 162, + "NameEnd": 163 + }, + "DotIdent": null + }, + "Type": null, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": { + "CodecPos": 164, + "RightParenPos": 174, + "Type": null, + "TypeLevel": null, + "Name": { + "Name": "LZ4", + "QuoteType": 1, + "NamePos": 170, + "NameEnd": 173 + }, + "Level": null + }, + "TTL": { + "TTLPos": 175, + "ListEnd": 181, + "Items": [ + { + "TTLPos": 175, + "Expr": { + "Name": "ts", + "QuoteType": 1, + "NamePos": 179, + "NameEnd": 181 + }, + "Policy": null + } + ] + }, + "Comment": { + "LiteralPos": 182, + "LiteralEnd": 199, + "Literal": "example" + }, + "CompressionCodec": null + }, + "RemovePropertyType": null + } + ], + "Settings": null + } +] \ No newline at end of file