@@ -2509,6 +2509,12 @@ func (p *Parser) parseColumnDeclaration() *ast.ColumnDeclaration {
25092509 col .Type = p .parseDataType ()
25102510 }
25112511
2512+ // Parse STATISTICS clause (e.g., STATISTICS(tdigest, uniq))
2513+ if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "STATISTICS" {
2514+ p .nextToken ()
2515+ col .Statistics = p .parseStatisticsExpr ()
2516+ }
2517+
25122518 // Handle COLLATE clause (MySQL compatibility, e.g., varchar(255) COLLATE binary)
25132519 if p .currentIs (token .COLLATE ) {
25142520 p .nextToken ()
@@ -2759,6 +2765,100 @@ func (p *Parser) parseCodecExpr() *ast.CodecExpr {
27592765 return codec
27602766}
27612767
2768+ func (p * Parser ) parseStatisticsExpr () []* ast.FunctionCall {
2769+ var stats []* ast.FunctionCall
2770+
2771+ if ! p .expect (token .LPAREN ) {
2772+ return nil
2773+ }
2774+
2775+ for ! p .currentIs (token .RPAREN ) && ! p .currentIs (token .EOF ) {
2776+ if p .currentIs (token .IDENT ) {
2777+ name := p .current .Value
2778+ pos := p .current .Pos
2779+ p .nextToken ()
2780+
2781+ fn := & ast.FunctionCall {
2782+ Position : pos ,
2783+ Name : name ,
2784+ }
2785+
2786+ // Statistics types can have optional parameters: e.g., tdigest(100)
2787+ if p .currentIs (token .LPAREN ) {
2788+ p .nextToken ()
2789+ if ! p .currentIs (token .RPAREN ) {
2790+ fn .Arguments = p .parseExpressionList ()
2791+ }
2792+ p .expect (token .RPAREN )
2793+ }
2794+
2795+ stats = append (stats , fn )
2796+ }
2797+
2798+ if p .currentIs (token .COMMA ) {
2799+ p .nextToken ()
2800+ } else {
2801+ break
2802+ }
2803+ }
2804+
2805+ p .expect (token .RPAREN )
2806+ return stats
2807+ }
2808+
2809+ // parseStatisticsColumnList parses comma-separated column names for ALTER STATISTICS commands
2810+ func (p * Parser ) parseStatisticsColumnList () []string {
2811+ var columns []string
2812+
2813+ for p .currentIs (token .IDENT ) || p .current .Token .IsKeyword () {
2814+ columns = append (columns , p .current .Value )
2815+ p .nextToken ()
2816+
2817+ if p .currentIs (token .COMMA ) {
2818+ p .nextToken ()
2819+ } else {
2820+ break
2821+ }
2822+ }
2823+
2824+ return columns
2825+ }
2826+
2827+ // parseStatisticsTypeList parses comma-separated statistics type names for ALTER STATISTICS TYPE clause
2828+ func (p * Parser ) parseStatisticsTypeList () []* ast.FunctionCall {
2829+ var types []* ast.FunctionCall
2830+
2831+ for p .currentIs (token .IDENT ) {
2832+ name := p .current .Value
2833+ pos := p .current .Pos
2834+ p .nextToken ()
2835+
2836+ fn := & ast.FunctionCall {
2837+ Position : pos ,
2838+ Name : name ,
2839+ }
2840+
2841+ // Statistics types can have optional parameters
2842+ if p .currentIs (token .LPAREN ) {
2843+ p .nextToken ()
2844+ if ! p .currentIs (token .RPAREN ) {
2845+ fn .Arguments = p .parseExpressionList ()
2846+ }
2847+ p .expect (token .RPAREN )
2848+ }
2849+
2850+ types = append (types , fn )
2851+
2852+ if p .currentIs (token .COMMA ) {
2853+ p .nextToken ()
2854+ } else {
2855+ break
2856+ }
2857+ }
2858+
2859+ return types
2860+ }
2861+
27622862func (p * Parser ) parseEngineClause () * ast.EngineClause {
27632863 engine := & ast.EngineClause {
27642864 Position : p .current .Pos ,
@@ -3188,6 +3288,27 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
31883288 cmd .Type = ast .AlterAddProjection
31893289 p .nextToken ()
31903290 cmd .Projection = p .parseProjection ()
3291+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "STATISTICS" {
3292+ cmd .Type = ast .AlterAddStatistics
3293+ p .nextToken ()
3294+ // Handle IF NOT EXISTS
3295+ if p .currentIs (token .IF ) {
3296+ p .nextToken ()
3297+ if p .currentIs (token .NOT ) {
3298+ p .nextToken ()
3299+ if p .currentIs (token .EXISTS ) {
3300+ cmd .IfNotExists = true
3301+ p .nextToken ()
3302+ }
3303+ }
3304+ }
3305+ // Parse column list (comma-separated identifiers)
3306+ cmd .StatisticsColumns = p .parseStatisticsColumnList ()
3307+ // Parse TYPE clause
3308+ if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "TYPE" {
3309+ p .nextToken ()
3310+ cmd .StatisticsTypes = p .parseStatisticsTypeList ()
3311+ }
31913312 }
31923313 case token .DROP :
31933314 p .nextToken ()
@@ -3233,6 +3354,15 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
32333354 cmd .ProjectionName = p .current .Value
32343355 p .nextToken ()
32353356 }
3357+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "STATISTICS" {
3358+ cmd .Type = ast .AlterDropStatistics
3359+ p .nextToken ()
3360+ if p .currentIs (token .IF ) {
3361+ p .nextToken ()
3362+ p .expect (token .EXISTS )
3363+ cmd .IfExists = true
3364+ }
3365+ cmd .StatisticsColumns = p .parseStatisticsColumnList ()
32363366 }
32373367 case token .IDENT :
32383368 // Handle CLEAR, MATERIALIZE
@@ -3260,6 +3390,15 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
32603390 cmd .ProjectionName = p .current .Value
32613391 p .nextToken ()
32623392 }
3393+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "STATISTICS" {
3394+ cmd .Type = ast .AlterClearStatistics
3395+ p .nextToken ()
3396+ if p .currentIs (token .IF ) {
3397+ p .nextToken ()
3398+ p .expect (token .EXISTS )
3399+ cmd .IfExists = true
3400+ }
3401+ cmd .StatisticsColumns = p .parseStatisticsColumnList ()
32633402 }
32643403 } else if upper == "MATERIALIZE" {
32653404 p .nextToken ()
@@ -3277,6 +3416,15 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
32773416 cmd .ProjectionName = p .current .Value
32783417 p .nextToken ()
32793418 }
3419+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "STATISTICS" {
3420+ cmd .Type = ast .AlterMaterializeStatistics
3421+ p .nextToken ()
3422+ if p .currentIs (token .IF ) {
3423+ p .nextToken ()
3424+ p .expect (token .EXISTS )
3425+ cmd .IfExists = true
3426+ }
3427+ cmd .StatisticsColumns = p .parseStatisticsColumnList ()
32803428 }
32813429 } else {
32823430 return nil
@@ -3299,6 +3447,16 @@ func (p *Parser) parseAlterCommand() *ast.AlterCommand {
32993447 cmd .Type = ast .AlterModifySetting
33003448 p .nextToken ()
33013449 cmd .Settings = p .parseSettingsList ()
3450+ } else if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "STATISTICS" {
3451+ cmd .Type = ast .AlterModifyStatistics
3452+ p .nextToken ()
3453+ // Parse column list (comma-separated identifiers)
3454+ cmd .StatisticsColumns = p .parseStatisticsColumnList ()
3455+ // Parse TYPE clause
3456+ if p .currentIs (token .IDENT ) && strings .ToUpper (p .current .Value ) == "TYPE" {
3457+ p .nextToken ()
3458+ cmd .StatisticsTypes = p .parseStatisticsTypeList ()
3459+ }
33023460 }
33033461 case token .RENAME :
33043462 p .nextToken ()
0 commit comments