@@ -73,32 +73,18 @@ func (p *Parser) parseColumnDef() (*ast.ColumnDef, error) {
7373
7474 dataTypeStr := dataType .Name
7575
76- // Check for type parameters like VARCHAR(100) or DECIMAL(10,2)
76+ // Check for type parameters. The simple form is VARCHAR(100) or
77+ // DECIMAL(10,2), but ClickHouse also has nested/parameterised types like
78+ // Array(Nullable(String)), Map(String, Array(UInt32)), Tuple(a UInt8, b String),
79+ // FixedString(16), DateTime64(3, 'UTC'), LowCardinality(String), Decimal(38, 18),
80+ // and engines like ReplicatedMergeTree('/path', '{replica}'). Use a depth-tracking
81+ // token collector that round-trips the type string.
7782 if p .isType (models .TokenTypeLParen ) {
78- dataTypeStr += "("
79- p .advance () // Consume (
80-
81- // Parse first parameter (can be number or identifier like MAX)
82- if p .isType (models .TokenTypeNumber ) || p .isType (models .TokenTypeIdentifier ) {
83- dataTypeStr += p .currentToken .Token .Value
84- p .advance ()
85- }
86-
87- // Check for second parameter (e.g., DECIMAL(10,2))
88- if p .isType (models .TokenTypeComma ) {
89- dataTypeStr += ","
90- p .advance ()
91- if p .isType (models .TokenTypeNumber ) || p .isType (models .TokenTypeIdentifier ) {
92- dataTypeStr += p .currentToken .Token .Value
93- p .advance ()
94- }
95- }
96-
97- if ! p .isType (models .TokenTypeRParen ) {
98- return nil , p .expectedError (") after type parameters" )
83+ args , err := p .parseTypeArgsString ()
84+ if err != nil {
85+ return nil , err
9986 }
100- dataTypeStr += ")"
101- p .advance () // Consume )
87+ dataTypeStr += args
10288 }
10389
10490 colDef := & ast.ColumnDef {
@@ -480,3 +466,73 @@ func (p *Parser) parseConstraintColumnList() ([]string, error) {
480466
481467 return columns , nil
482468}
469+
470+ // parseTypeArgsString consumes a balanced parenthesised type-argument list
471+ // and returns it as a string (including the outer parens). Supports nested
472+ // types like Array(Nullable(String)), Map(String, Array(UInt32)),
473+ // Tuple(a UInt8, b String), DateTime64(3, 'UTC'), and engine arguments like
474+ // ReplicatedMergeTree('/path', '{replica}'). The current token must be '('.
475+ func (p * Parser ) parseTypeArgsString () (string , error ) {
476+ if ! p .isType (models .TokenTypeLParen ) {
477+ return "" , p .expectedError ("(" )
478+ }
479+
480+ var buf strings.Builder
481+ depth := 0
482+ prevWasIdent := false // for inserting spaces between adjacent tokens (e.g. "a UInt8")
483+
484+ for {
485+ tok := p .currentToken .Token
486+ switch tok .Type {
487+ case models .TokenTypeEOF :
488+ return "" , p .expectedError (") to close type arguments" )
489+ case models .TokenTypeLParen :
490+ buf .WriteByte ('(' )
491+ depth ++
492+ prevWasIdent = false
493+ p .advance ()
494+ continue
495+ case models .TokenTypeRParen :
496+ buf .WriteByte (')' )
497+ depth --
498+ p .advance ()
499+ if depth == 0 {
500+ return buf .String (), nil
501+ }
502+ prevWasIdent = false
503+ continue
504+ case models .TokenTypeComma :
505+ buf .WriteString (", " )
506+ prevWasIdent = false
507+ p .advance ()
508+ continue
509+ }
510+
511+ // Render leaf token. Quote string literals; everything else is rendered
512+ // by its raw value (numbers, identifiers, keywords like Nullable / Array).
513+ val := tok .Value
514+ if val == "" {
515+ return "" , p .expectedError ("type argument" )
516+ }
517+
518+ // Insert a space when two adjacent leaf tokens both look like identifiers
519+ // or numbers — this preserves "name Type" pairs in named tuple elements.
520+ if prevWasIdent {
521+ buf .WriteByte (' ' )
522+ }
523+
524+ switch tok .Type {
525+ case models .TokenTypeString , models .TokenTypeSingleQuotedString ,
526+ models .TokenTypeDoubleQuotedString :
527+ buf .WriteByte ('\'' )
528+ buf .WriteString (val )
529+ buf .WriteByte ('\'' )
530+ prevWasIdent = false
531+ default :
532+ buf .WriteString (val )
533+ prevWasIdent = true
534+ }
535+
536+ p .advance ()
537+ }
538+ }
0 commit comments