diff --git a/parser/ast.go b/parser/ast.go index e3c78d4..774bde8 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -3446,6 +3446,39 @@ func (s *TypeWithParams) Type() string { return s.Name.Name } +type TypeWithNamedParams struct { + LeftParenPos Pos + RightParenPos Pos + Name *Ident + Params []*NamedParameterExpr +} + +func (s *TypeWithNamedParams) Pos() Pos { + return s.Name.NamePos +} + +func (s *TypeWithNamedParams) End() Pos { + return s.RightParenPos +} + +func (s *TypeWithNamedParams) Accept(visitor ASTVisitor) error { + visitor.Enter(s) + defer visitor.Leave(s) + if err := s.Name.Accept(visitor); err != nil { + return err + } + for _, param := range s.Params { + if err := param.Accept(visitor); err != nil { + return err + } + } + return visitor.VisitTypeWithNamedParams(s) +} + +func (s *TypeWithNamedParams) Type() string { + return s.Name.Name +} + type ComplexType struct { LeftParenPos Pos RightParenPos Pos diff --git a/parser/ast_visitor.go b/parser/ast_visitor.go index ab27d96..7729faf 100644 --- a/parser/ast_visitor.go +++ b/parser/ast_visitor.go @@ -99,6 +99,7 @@ type ASTVisitor interface { VisitJSONType(expr *JSONType) error VisitPropertyType(expr *PropertyType) error VisitTypeWithParams(expr *TypeWithParams) error + VisitTypeWithNamedParams(expr *TypeWithNamedParams) error VisitComplexType(expr *ComplexType) error VisitNestedType(expr *NestedType) error VisitCompressionCodec(expr *CompressionCodec) error @@ -901,6 +902,13 @@ func (v *DefaultASTVisitor) VisitTypeWithParams(expr *TypeWithParams) error { return nil } +func (v *DefaultASTVisitor) VisitTypeWithNamedParams(expr *TypeWithNamedParams) error { + if v.Visit != nil { + return v.Visit(expr) + } + return nil +} + func (v *DefaultASTVisitor) VisitComplexType(expr *ComplexType) error { if v.Visit != nil { return v.Visit(expr) diff --git a/parser/format.go b/parser/format.go index ed8bee2..2eb46cf 100644 --- a/parser/format.go +++ b/parser/format.go @@ -2769,6 +2769,18 @@ func (s *TypeWithParams) FormatSQL(formatter *Formatter) { formatter.WriteByte(')') } +func (s *TypeWithNamedParams) FormatSQL(formatter *Formatter) { + formatter.WriteExpr(s.Name) + formatter.WriteByte('(') + for i, param := range s.Params { + if i > 0 { + formatter.WriteString(", ") + } + formatter.WriteExpr(param) + } + formatter.WriteByte(')') +} + func (t *TypedPlaceholder) FormatSQL(formatter *Formatter) { formatter.WriteString("{") formatter.WriteExpr(t.Name) diff --git a/parser/parser_column.go b/parser/parser_column.go index 30de285..3d40189 100644 --- a/parser/parser_column.go +++ b/parser/parser_column.go @@ -1323,9 +1323,16 @@ func (p *Parser) parseColumnTypeArgs(ident *Ident) (ColumnType, error) { // noli return p.parseColumnTypeWithParams(ident, p.Pos()) case strings.EqualFold(ident.Name, "Tuple"): return p.parseNestedType(ident, p.Pos()) + case p.matchNamedTypeParam(): + return p.parseColumnTypeWithNamedParams(ident, lParen.Pos) default: return p.parseComplexType(ident, p.Pos()) } + case p.matchTokenKind(TokenKindKeyword): + if p.matchNamedTypeParam() { + return p.parseColumnTypeWithNamedParams(ident, lParen.Pos) + } + return nil, fmt.Errorf("unexpected token kind: %v", p.currentTokenKind()) case p.matchTokenKind(TokenKindString): if peekToken, err := p.lexer.peekToken(); err == nil && peekToken.Kind == TokenKindSingleEQ { // enum values @@ -1440,6 +1447,50 @@ func (p *Parser) parseColumnTypeWithParams(name *Ident, pos Pos) (*TypeWithParam }, nil } +func (p *Parser) matchNamedTypeParam() bool { + if !p.matchTokenKind(TokenKindIdent, TokenKindKeyword) { + return false + } + peekToken, err := p.lexer.peekToken() + return err == nil && peekToken != nil && peekToken.Kind == TokenKindSingleEQ +} + +func (p *Parser) parseColumnTypeWithNamedParams(name *Ident, leftParenPos Pos) (*TypeWithNamedParams, error) { + params := make([]*NamedParameterExpr, 0) + for !p.lexer.isEOF() { + paramName, err := p.parseAnyKeyword() + if err != nil { + return nil, err + } + if err := p.expectTokenKind(TokenKindSingleEQ); err != nil { + return nil, err + } + value, err := p.parseLiteral(p.Pos()) + if err != nil { + return nil, err + } + params = append(params, &NamedParameterExpr{ + NamePos: paramName.NamePos, + Name: paramName, + Value: value, + }) + if p.tryConsumeTokenKind(TokenKindComma) == nil { + break + } + } + + rightParenPos := p.Pos() + if err := p.expectTokenKind(TokenKindRParen); err != nil { + return nil, err + } + return &TypeWithNamedParams{ + Name: name, + LeftParenPos: leftParenPos, + RightParenPos: rightParenPos, + Params: params, + }, nil +} + func (p *Parser) parseJSONPath() (*JSONPath, error) { idents := make([]*Ident, 0) ident, err := p.parseIdent() diff --git a/parser/testdata/ddl/create_table_dynamic_type.sql b/parser/testdata/ddl/create_table_dynamic_type.sql new file mode 100644 index 0000000..d6fbbfd --- /dev/null +++ b/parser/testdata/ddl/create_table_dynamic_type.sql @@ -0,0 +1,6 @@ +CREATE TABLE t +( + `value_1` Dynamic(max_types = 16), + `value_2` Dynamic(max_types = 16) +) +ENGINE = Memory; diff --git a/parser/testdata/ddl/format/beautify/create_table_dynamic_type.sql b/parser/testdata/ddl/format/beautify/create_table_dynamic_type.sql new file mode 100644 index 0000000..f856a71 --- /dev/null +++ b/parser/testdata/ddl/format/beautify/create_table_dynamic_type.sql @@ -0,0 +1,16 @@ +-- Origin SQL: +CREATE TABLE t +( + `value_1` Dynamic(max_types = 16), + `value_2` Dynamic(max_types = 16) +) +ENGINE = Memory; + + +-- Beautify SQL: +CREATE TABLE t +( + `value_1` Dynamic(max_types=16), + `value_2` Dynamic(max_types=16) +) +ENGINE = Memory; diff --git a/parser/testdata/ddl/format/create_table_dynamic_type.sql b/parser/testdata/ddl/format/create_table_dynamic_type.sql new file mode 100644 index 0000000..77d8d63 --- /dev/null +++ b/parser/testdata/ddl/format/create_table_dynamic_type.sql @@ -0,0 +1,11 @@ +-- Origin SQL: +CREATE TABLE t +( + `value_1` Dynamic(max_types = 16), + `value_2` Dynamic(max_types = 16) +) +ENGINE = Memory; + + +-- Format SQL: +CREATE TABLE t (`value_1` Dynamic(max_types=16), `value_2` Dynamic(max_types=16)) ENGINE = Memory; diff --git a/parser/testdata/ddl/output/create_table_dynamic_type.sql.golden.json b/parser/testdata/ddl/output/create_table_dynamic_type.sql.golden.json new file mode 100644 index 0000000..620c1fb --- /dev/null +++ b/parser/testdata/ddl/output/create_table_dynamic_type.sql.golden.json @@ -0,0 +1,141 @@ +[ + { + "CreatePos": 0, + "StatementEnd": 111, + "OrReplace": false, + "Name": { + "Database": null, + "Table": { + "Name": "t", + "QuoteType": 1, + "NamePos": 13, + "NameEnd": 14 + } + }, + "IfNotExists": false, + "UUID": null, + "OnCluster": null, + "TableSchema": { + "SchemaPos": 15, + "SchemaEnd": 94, + "Columns": [ + { + "NamePos": 22, + "ColumnEnd": 53, + "Name": { + "Ident": { + "Name": "value_1", + "QuoteType": 3, + "NamePos": 22, + "NameEnd": 29 + }, + "DotIdent": null + }, + "Type": { + "LeftParenPos": 38, + "RightParenPos": 53, + "Name": { + "Name": "Dynamic", + "QuoteType": 1, + "NamePos": 31, + "NameEnd": 38 + }, + "Params": [ + { + "NamePos": 39, + "Name": { + "Name": "max_types", + "QuoteType": 1, + "NamePos": 39, + "NameEnd": 48 + }, + "Value": { + "NumPos": 51, + "NumEnd": 53, + "Literal": "16", + "Base": 10 + } + } + ] + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + }, + { + "NamePos": 61, + "ColumnEnd": 92, + "Name": { + "Ident": { + "Name": "value_2", + "QuoteType": 3, + "NamePos": 61, + "NameEnd": 68 + }, + "DotIdent": null + }, + "Type": { + "LeftParenPos": 77, + "RightParenPos": 92, + "Name": { + "Name": "Dynamic", + "QuoteType": 1, + "NamePos": 70, + "NameEnd": 77 + }, + "Params": [ + { + "NamePos": 78, + "Name": { + "Name": "max_types", + "QuoteType": 1, + "NamePos": 78, + "NameEnd": 87 + }, + "Value": { + "NumPos": 90, + "NumEnd": 92, + "Literal": "16", + "Base": 10 + } + } + ] + }, + "NotNull": null, + "Nullable": null, + "DefaultExpr": null, + "MaterializedExpr": null, + "AliasExpr": null, + "Codec": null, + "TTL": null, + "Comment": null, + "CompressionCodec": null + } + ], + "AliasTable": null, + "TableFunction": null + }, + "Engine": { + "EnginePos": 96, + "EngineEnd": 111, + "Name": "Memory", + "Params": null, + "PrimaryKey": null, + "PartitionBy": null, + "SampleBy": null, + "TTL": null, + "Settings": null, + "OrderBy": null + }, + "SubQuery": null, + "TableFunction": null, + "HasTemporary": false, + "Comment": null + } +] \ No newline at end of file diff --git a/parser/walk.go b/parser/walk.go index 4d0802a..e4b0785 100644 --- a/parser/walk.go +++ b/parser/walk.go @@ -1099,6 +1099,15 @@ func Walk(node Expr, fn WalkFunc) bool { return false } } + case *TypeWithNamedParams: + if !Walk(n.Name, fn) { + return false + } + for _, param := range n.Params { + if !Walk(param, fn) { + return false + } + } case *ComplexType: if !Walk(n.Name, fn) { return false