Skip to content

Commit 1af4b3d

Browse files
authored
Handle unary negation in tuple literal detection for IN lists (#113)
1 parent b355de2 commit 1af4b3d

File tree

153 files changed

+1076
-959
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

153 files changed

+1076
-959
lines changed

ast/ast.go

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ type Expression interface {
3131

3232
// SelectWithUnionQuery represents a SELECT query possibly with UNION.
3333
type SelectWithUnionQuery struct {
34-
Position token.Position `json:"-"`
35-
Selects []Statement `json:"selects"`
36-
UnionAll bool `json:"union_all,omitempty"`
37-
UnionModes []string `json:"union_modes,omitempty"` // "ALL", "DISTINCT", or "" for each union
34+
Position token.Position `json:"-"`
35+
Selects []Statement `json:"selects"`
36+
UnionAll bool `json:"union_all,omitempty"`
37+
UnionModes []string `json:"union_modes,omitempty"` // "ALL", "DISTINCT", or "" for each union
38+
Settings []*SettingExpr `json:"settings,omitempty"` // Union-level SETTINGS
39+
SettingsAfterFormat bool `json:"settings_after_format,omitempty"`
40+
SettingsBeforeFormat bool `json:"settings_before_format,omitempty"`
3841
}
3942

4043
func (s *SelectWithUnionQuery) Pos() token.Position { return s.Position }
@@ -80,8 +83,9 @@ type SelectQuery struct {
8083
LimitByLimit Expression `json:"limit_by_limit,omitempty"` // LIMIT value before BY (e.g., LIMIT 1 BY x LIMIT 3)
8184
LimitByHasLimit bool `json:"limit_by_has_limit,omitempty"` // true if LIMIT BY was followed by another LIMIT
8285
Offset Expression `json:"offset,omitempty"`
83-
Settings []*SettingExpr `json:"settings,omitempty"`
84-
SettingsAfterFormat bool `json:"settings_after_format,omitempty"` // true if SETTINGS came after FORMAT
86+
Settings []*SettingExpr `json:"settings,omitempty"`
87+
SettingsAfterFormat bool `json:"settings_after_format,omitempty"` // true if SETTINGS came after FORMAT (at union level)
88+
SettingsBeforeFormat bool `json:"settings_before_format,omitempty"` // true if SETTINGS came before FORMAT (at union level)
8589
IntoOutfile *IntoOutfileClause `json:"into_outfile,omitempty"`
8690
Format *Identifier `json:"format,omitempty"`
8791
}
@@ -285,6 +289,7 @@ type CreateQuery struct {
285289
Settings []*SettingExpr `json:"settings,omitempty"`
286290
AsSelect Statement `json:"as_select,omitempty"`
287291
AsTableFunction Expression `json:"as_table_function,omitempty"` // AS table_function(...) in CREATE TABLE
292+
CloneAs string `json:"clone_as,omitempty"` // CLONE AS source_table in CREATE TABLE
288293
Comment string `json:"comment,omitempty"`
289294
OnCluster string `json:"on_cluster,omitempty"`
290295
CreateDatabase bool `json:"create_database,omitempty"`
@@ -319,6 +324,7 @@ type ColumnDeclaration struct {
319324
TTL Expression `json:"ttl,omitempty"`
320325
PrimaryKey bool `json:"primary_key,omitempty"` // PRIMARY KEY constraint
321326
Comment string `json:"comment,omitempty"`
327+
Settings []*SettingExpr `json:"settings,omitempty"` // Column-level SETTINGS
322328
}
323329

324330
func (c *ColumnDeclaration) Pos() token.Position { return c.Position }
@@ -572,11 +578,13 @@ type AlterCommand struct {
572578
Index string `json:"index,omitempty"`
573579
IndexExpr Expression `json:"index_expr,omitempty"`
574580
IndexType string `json:"index_type,omitempty"`
581+
IndexDef *IndexDefinition `json:"index_def,omitempty"` // For ADD INDEX with full definition
575582
Granularity int `json:"granularity,omitempty"`
576583
Constraint *Constraint `json:"constraint,omitempty"`
577584
ConstraintName string `json:"constraint_name,omitempty"`
578585
Partition Expression `json:"partition,omitempty"`
579586
PartitionIsID bool `json:"partition_is_id,omitempty"` // True when using PARTITION ID 'value' syntax
587+
IsPart bool `json:"-"` // True for PART (not PARTITION) - output directly without Partition wrapper
580588
FromTable string `json:"from_table,omitempty"`
581589
ToDatabase string `json:"to_database,omitempty"` // For MOVE PARTITION TO TABLE
582590
ToTable string `json:"to_table,omitempty"` // For MOVE PARTITION TO TABLE
@@ -591,6 +599,8 @@ type AlterCommand struct {
591599
StatisticsTypes []*FunctionCall `json:"statistics_types,omitempty"` // For ADD/MODIFY STATISTICS TYPE
592600
Comment string `json:"comment,omitempty"` // For COMMENT COLUMN
593601
OrderByExpr []Expression `json:"order_by_expr,omitempty"` // For MODIFY ORDER BY
602+
SampleByExpr Expression `json:"sample_by_expr,omitempty"` // For MODIFY SAMPLE BY
603+
ResetSettings []string `json:"reset_settings,omitempty"` // For MODIFY COLUMN ... RESET SETTING
594604
}
595605

596606
// Projection represents a projection definition.
@@ -633,6 +643,7 @@ const (
633643
AlterModifyColumn AlterCommandType = "MODIFY_COLUMN"
634644
AlterRenameColumn AlterCommandType = "RENAME_COLUMN"
635645
AlterClearColumn AlterCommandType = "CLEAR_COLUMN"
646+
AlterMaterializeColumn AlterCommandType = "MATERIALIZE_COLUMN"
636647
AlterCommentColumn AlterCommandType = "COMMENT_COLUMN"
637648
AlterAddIndex AlterCommandType = "ADD_INDEX"
638649
AlterDropIndex AlterCommandType = "DROP_INDEX"
@@ -665,6 +676,8 @@ const (
665676
AlterMaterializeStatistics AlterCommandType = "MATERIALIZE_STATISTICS"
666677
AlterModifyComment AlterCommandType = "MODIFY_COMMENT"
667678
AlterModifyOrderBy AlterCommandType = "MODIFY_ORDER_BY"
679+
AlterModifySampleBy AlterCommandType = "MODIFY_SAMPLE_BY"
680+
AlterRemoveSampleBy AlterCommandType = "REMOVE_SAMPLE_BY"
668681
)
669682

670683
// TruncateQuery represents a TRUNCATE statement.
@@ -674,6 +687,7 @@ type TruncateQuery struct {
674687
Database string `json:"database,omitempty"`
675688
Table string `json:"table"`
676689
OnCluster string `json:"on_cluster,omitempty"`
690+
Settings []*SettingExpr `json:"settings,omitempty"`
677691
}
678692

679693
func (t *TruncateQuery) Pos() token.Position { return t.Position }
@@ -704,9 +718,10 @@ func (u *UseQuery) statementNode() {}
704718

705719
// DetachQuery represents a DETACH statement.
706720
type DetachQuery struct {
707-
Position token.Position `json:"-"`
708-
Database string `json:"database,omitempty"`
709-
Table string `json:"table,omitempty"`
721+
Position token.Position `json:"-"`
722+
Database string `json:"database,omitempty"`
723+
Table string `json:"table,omitempty"`
724+
Dictionary string `json:"dictionary,omitempty"`
710725
}
711726

712727
func (d *DetachQuery) Pos() token.Position { return d.Position }
@@ -718,6 +733,7 @@ type AttachQuery struct {
718733
Position token.Position `json:"-"`
719734
Database string `json:"database,omitempty"`
720735
Table string `json:"table,omitempty"`
736+
Dictionary string `json:"dictionary,omitempty"`
721737
Columns []*ColumnDeclaration `json:"columns,omitempty"`
722738
ColumnsPrimaryKey []Expression `json:"columns_primary_key,omitempty"` // PRIMARY KEY in column list
723739
Engine *EngineClause `json:"engine,omitempty"`
@@ -832,6 +848,7 @@ type OptimizeQuery struct {
832848
Cleanup bool `json:"cleanup,omitempty"`
833849
Dedupe bool `json:"dedupe,omitempty"`
834850
OnCluster string `json:"on_cluster,omitempty"`
851+
Settings []*SettingExpr `json:"settings,omitempty"`
835852
}
836853

837854
func (o *OptimizeQuery) Pos() token.Position { return o.Position }
@@ -891,6 +908,7 @@ type RenameQuery struct {
891908
From string `json:"from,omitempty"` // Deprecated: for backward compat
892909
To string `json:"to,omitempty"` // Deprecated: for backward compat
893910
OnCluster string `json:"on_cluster,omitempty"`
911+
Settings []*SettingExpr `json:"settings,omitempty"`
894912
}
895913

896914
func (r *RenameQuery) Pos() token.Position { return r.Position }
@@ -927,6 +945,7 @@ type ExistsQuery struct {
927945
ExistsType ExistsType `json:"exists_type,omitempty"`
928946
Database string `json:"database,omitempty"`
929947
Table string `json:"table"`
948+
Settings []*SettingExpr `json:"settings,omitempty"`
930949
}
931950

932951
func (e *ExistsQuery) Pos() token.Position { return e.Position }
@@ -1153,9 +1172,10 @@ func (c *CreateIndexQuery) statementNode() {}
11531172

11541173
// Identifier represents an identifier.
11551174
type Identifier struct {
1156-
Position token.Position `json:"-"`
1157-
Parts []string `json:"parts"` // e.g., ["db", "table", "column"] for db.table.column
1158-
Alias string `json:"alias,omitempty"`
1175+
Position token.Position `json:"-"`
1176+
Parts []string `json:"parts"` // e.g., ["db", "table", "column"] for db.table.column
1177+
Alias string `json:"alias,omitempty"`
1178+
Parenthesized bool `json:"-"` // true if wrapped in parentheses, affects dot access parsing
11591179
}
11601180

11611181
func (i *Identifier) Pos() token.Position { return i.Position }

internal/explain/dictionary.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,15 @@ func explainDictionaryDefinition(sb *strings.Builder, n *ast.DictionaryDefinitio
9999
// explainDictionarySource outputs a dictionary SOURCE clause.
100100
func explainDictionarySource(sb *strings.Builder, n *ast.DictionarySource, indent string, depth int) {
101101
// FunctionWithKeyValueArguments has extra space before name
102-
children := 0
102+
// Always has 1 child for ExpressionList (even when empty)
103+
fmt.Fprintf(sb, "%sFunctionWithKeyValueArguments %s (children %d)\n", indent, strings.ToLower(n.Type), 1)
103104
if len(n.Args) > 0 {
104-
children = 1
105-
}
106-
if children > 0 {
107-
fmt.Fprintf(sb, "%sFunctionWithKeyValueArguments %s (children %d)\n", indent, strings.ToLower(n.Type), children)
108105
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.Args))
109106
for _, arg := range n.Args {
110107
explainKeyValuePair(sb, arg, indent+" ", depth+2)
111108
}
112109
} else {
113-
fmt.Fprintf(sb, "%sFunctionWithKeyValueArguments %s\n", indent, strings.ToLower(n.Type))
110+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
114111
}
115112
}
116113

internal/explain/explain.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ func Column(sb *strings.Builder, col *ast.ColumnDeclaration, depth int) {
331331
if col.Codec != nil {
332332
children++
333333
}
334+
if len(col.Settings) > 0 {
335+
children++
336+
}
334337
if children > 0 {
335338
fmt.Fprintf(sb, "%sColumnDeclaration %s (children %d)\n", indent, col.Name, children)
336339
} else {
@@ -354,6 +357,9 @@ func Column(sb *strings.Builder, col *ast.ColumnDeclaration, depth int) {
354357
if col.Codec != nil {
355358
explainCodecExpr(sb, col.Codec, indent+" ", depth+1)
356359
}
360+
if len(col.Settings) > 0 {
361+
fmt.Fprintf(sb, "%s Set\n", indent)
362+
}
357363
}
358364

359365
// explainCodecExpr handles CODEC expressions in column declarations

internal/explain/expressions.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -670,13 +670,8 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
670670
// CAST expressions always show the alias from the AliasedExpr wrapper
671671
explainCastExprWithAlias(sb, e, n.Alias, indent, depth)
672672
case *ast.ArrayAccess:
673-
// Array access - show alias only when array is not a literal
674-
// ClickHouse hides alias when array access is on a literal
675-
if _, isLit := e.Array.(*ast.Literal); isLit {
676-
explainArrayAccess(sb, e, indent, depth)
677-
} else {
678-
explainArrayAccessWithAlias(sb, e, n.Alias, indent, depth)
679-
}
673+
// Array access with alias
674+
explainArrayAccessWithAlias(sb, e, n.Alias, indent, depth)
680675
case *ast.TupleAccess:
681676
// Tuple access with alias
682677
explainTupleAccessWithAlias(sb, e, n.Alias, indent, depth)

internal/explain/format.go

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -341,25 +341,23 @@ func formatUnaryExprForType(expr *ast.UnaryExpr) string {
341341
// NormalizeFunctionName normalizes function names to match ClickHouse's EXPLAIN AST output
342342
func NormalizeFunctionName(name string) string {
343343
// ClickHouse normalizes certain function names in EXPLAIN AST
344+
// Note: lcase, ucase, mid are preserved as-is by ClickHouse EXPLAIN AST
344345
normalized := map[string]string{
345-
"trim": "trimBoth",
346-
"ltrim": "trimLeft",
347-
"rtrim": "trimRight",
348-
"lcase": "lower",
349-
"ucase": "upper",
350-
"mid": "substring",
351-
"ceiling": "ceil",
352-
"ln": "log",
353-
"log10": "log10",
354-
"log2": "log2",
355-
"rand": "rand",
356-
"ifnull": "ifNull",
357-
"nullif": "nullIf",
358-
"coalesce": "coalesce",
359-
"greatest": "greatest",
360-
"least": "least",
361-
"concat_ws": "concat",
362-
"position": "position",
346+
"trim": "trimBoth",
347+
"ltrim": "trimLeft",
348+
"rtrim": "trimRight",
349+
"ceiling": "ceil",
350+
"ln": "log",
351+
"log10": "log10",
352+
"log2": "log2",
353+
"rand": "rand",
354+
"ifnull": "ifNull",
355+
"nullif": "nullIf",
356+
"coalesce": "coalesce",
357+
"greatest": "greatest",
358+
"least": "least",
359+
"concat_ws": "concat",
360+
"position": "position",
363361
"date_diff": "dateDiff",
364362
"datediff": "dateDiff",
365363
// SQL standard ANY/ALL subquery operators - simple cases

0 commit comments

Comments
 (0)