diff --git a/ast/ast.go b/ast/ast.go index 0e66dd343d..aac9a60a02 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -848,6 +848,47 @@ func (s *ShowCreateQuotaQuery) Pos() token.Position { return s.Position } func (s *ShowCreateQuotaQuery) End() token.Position { return s.Position } func (s *ShowCreateQuotaQuery) statementNode() {} +// CreateSettingsProfileQuery represents a CREATE SETTINGS PROFILE statement. +type CreateSettingsProfileQuery struct { + Position token.Position `json:"-"` + Names []string `json:"names,omitempty"` +} + +func (c *CreateSettingsProfileQuery) Pos() token.Position { return c.Position } +func (c *CreateSettingsProfileQuery) End() token.Position { return c.Position } +func (c *CreateSettingsProfileQuery) statementNode() {} + +// AlterSettingsProfileQuery represents an ALTER SETTINGS PROFILE statement. +type AlterSettingsProfileQuery struct { + Position token.Position `json:"-"` + Names []string `json:"names,omitempty"` +} + +func (a *AlterSettingsProfileQuery) Pos() token.Position { return a.Position } +func (a *AlterSettingsProfileQuery) End() token.Position { return a.Position } +func (a *AlterSettingsProfileQuery) statementNode() {} + +// DropSettingsProfileQuery represents a DROP SETTINGS PROFILE statement. +type DropSettingsProfileQuery struct { + Position token.Position `json:"-"` + Names []string `json:"names,omitempty"` + IfExists bool `json:"if_exists,omitempty"` +} + +func (d *DropSettingsProfileQuery) Pos() token.Position { return d.Position } +func (d *DropSettingsProfileQuery) End() token.Position { return d.Position } +func (d *DropSettingsProfileQuery) statementNode() {} + +// ShowCreateSettingsProfileQuery represents a SHOW CREATE SETTINGS PROFILE statement. +type ShowCreateSettingsProfileQuery struct { + Position token.Position `json:"-"` + Names []string `json:"names,omitempty"` +} + +func (s *ShowCreateSettingsProfileQuery) Pos() token.Position { return s.Position } +func (s *ShowCreateSettingsProfileQuery) End() token.Position { return s.Position } +func (s *ShowCreateSettingsProfileQuery) statementNode() {} + // CreateIndexQuery represents a CREATE INDEX statement. type CreateIndexQuery struct { Position token.Position `json:"-"` diff --git a/internal/explain/explain.go b/internal/explain/explain.go index 64c0defc2c..9c49c241ac 100644 --- a/internal/explain/explain.go +++ b/internal/explain/explain.go @@ -127,6 +127,20 @@ func Node(sb *strings.Builder, node interface{}, depth int) { fmt.Fprintf(sb, "%sShowPrivilegesQuery\n", indent) case *ast.ShowCreateQuotaQuery: fmt.Fprintf(sb, "%sSHOW CREATE QUOTA query\n", indent) + case *ast.CreateSettingsProfileQuery: + fmt.Fprintf(sb, "%sCreateSettingsProfileQuery\n", indent) + case *ast.AlterSettingsProfileQuery: + // ALTER SETTINGS PROFILE uses CreateSettingsProfileQuery in ClickHouse's explain + fmt.Fprintf(sb, "%sCreateSettingsProfileQuery\n", indent) + case *ast.DropSettingsProfileQuery: + fmt.Fprintf(sb, "%sDROP SETTINGS PROFILE query\n", indent) + case *ast.ShowCreateSettingsProfileQuery: + // Use PROFILES (plural) when multiple profiles are specified + if len(n.Names) > 1 { + fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILES query\n", indent) + } else { + fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILE query\n", indent) + } case *ast.ShowGrantsQuery: fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent) case *ast.GrantQuery: diff --git a/parser/parser.go b/parser/parser.go index bd3eaaf76c..406732afdf 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -123,12 +123,28 @@ func (p *Parser) parseStatement() ast.Statement { case token.CREATE: return p.parseCreate() case token.DROP: + // Check for DROP SETTINGS PROFILE + if p.peekIs(token.SETTINGS) { + return p.parseDropSettingsProfile() + } + // Check for DROP PROFILE + if p.peek.Token == token.IDENT && strings.ToUpper(p.peek.Value) == "PROFILE" { + return p.parseDropSettingsProfile() + } return p.parseDrop() case token.ALTER: // Check for ALTER USER if p.peekIs(token.USER) { return p.parseAlterUser() } + // Check for ALTER SETTINGS PROFILE + if p.peekIs(token.SETTINGS) { + return p.parseAlterSettingsProfile() + } + // Check for ALTER PROFILE + if p.peek.Token == token.IDENT && strings.ToUpper(p.peek.Value) == "PROFILE" { + return p.parseAlterSettingsProfile() + } return p.parseAlter() case token.TRUNCATE: return p.parseTruncate() @@ -1314,6 +1330,9 @@ func (p *Parser) parseCreate() ast.Statement { create.CreateUser = true p.nextToken() p.parseCreateUser(create) + case token.SETTINGS: + // CREATE SETTINGS PROFILE + return p.parseCreateSettingsProfile(pos) case token.IDENT: // Handle CREATE DICTIONARY, CREATE RESOURCE, CREATE WORKLOAD, CREATE NAMED COLLECTION, etc. identUpper := strings.ToUpper(p.current.Value) @@ -1330,7 +1349,10 @@ func (p *Parser) parseCreate() ast.Statement { p.nextToken() } p.parseCreateGeneric(create) - case "RESOURCE", "WORKLOAD", "POLICY", "ROLE", "QUOTA", "PROFILE": + case "PROFILE": + // CREATE PROFILE (without SETTINGS keyword) + return p.parseCreateSettingsProfile(pos) + case "RESOURCE", "WORKLOAD", "POLICY", "ROLE", "QUOTA": // Skip these statements - just consume tokens until semicolon p.parseCreateGeneric(create) default: @@ -1866,6 +1888,182 @@ func (p *Parser) parseCreateGeneric(create *ast.CreateQuery) { } } +func (p *Parser) parseCreateSettingsProfile(pos token.Position) *ast.CreateSettingsProfileQuery { + query := &ast.CreateSettingsProfileQuery{ + Position: pos, + } + + // Skip SETTINGS if present (CREATE SETTINGS PROFILE vs CREATE PROFILE) + if p.currentIs(token.SETTINGS) { + p.nextToken() + } + + // Skip PROFILE + if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" { + p.nextToken() + } + + // Handle IF NOT EXISTS + if p.currentIs(token.IF) { + p.nextToken() + if p.currentIs(token.NOT) { + p.nextToken() + } + if p.currentIs(token.EXISTS) { + p.nextToken() + } + } + + // Parse profile names (can be multiple: s1, s2, s3) + for { + name := p.parseIdentifierName() + if name != "" { + query.Names = append(query.Names, name) + } + if p.currentIs(token.COMMA) { + p.nextToken() + continue + } + break + } + + // Skip the rest of the statement (SETTINGS, TO, etc.) + for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) { + p.nextToken() + } + + return query +} + +func (p *Parser) parseDropSettingsProfile() *ast.DropSettingsProfileQuery { + query := &ast.DropSettingsProfileQuery{ + Position: p.current.Pos, + } + + p.nextToken() // skip DROP + + // Skip SETTINGS if present (DROP SETTINGS PROFILE vs DROP PROFILE) + if p.currentIs(token.SETTINGS) { + p.nextToken() + } + + // Skip PROFILE + if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" { + p.nextToken() + } + + // Handle IF EXISTS + if p.currentIs(token.IF) { + p.nextToken() + if p.currentIs(token.EXISTS) { + query.IfExists = true + p.nextToken() + } + } + + // Parse profile names (can be multiple: s1, s2, s3) + for { + name := p.parseIdentifierName() + if name != "" { + query.Names = append(query.Names, name) + } + if p.currentIs(token.COMMA) { + p.nextToken() + continue + } + break + } + + // Skip the rest of the statement + for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) { + p.nextToken() + } + + return query +} + +func (p *Parser) parseAlterSettingsProfile() *ast.AlterSettingsProfileQuery { + query := &ast.AlterSettingsProfileQuery{ + Position: p.current.Pos, + } + + p.nextToken() // skip ALTER + + // Skip SETTINGS if present (ALTER SETTINGS PROFILE vs ALTER PROFILE) + if p.currentIs(token.SETTINGS) { + p.nextToken() + } + + // Skip PROFILE + if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" { + p.nextToken() + } + + // Handle IF EXISTS + if p.currentIs(token.IF) { + p.nextToken() + if p.currentIs(token.EXISTS) { + p.nextToken() + } + } + + // Parse profile names (can be multiple: s1, s2, s3) + for { + name := p.parseIdentifierName() + if name != "" { + query.Names = append(query.Names, name) + } + if p.currentIs(token.COMMA) { + p.nextToken() + continue + } + break + } + + // Skip the rest of the statement (SETTINGS, RENAME TO, etc.) + for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) { + p.nextToken() + } + + return query +} + +func (p *Parser) parseShowCreateSettingsProfile(pos token.Position) *ast.ShowCreateSettingsProfileQuery { + query := &ast.ShowCreateSettingsProfileQuery{ + Position: pos, + } + + // Skip SETTINGS if present (SHOW CREATE SETTINGS PROFILE vs SHOW CREATE PROFILE) + if p.currentIs(token.SETTINGS) { + p.nextToken() + } + + // Skip PROFILE + if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" { + p.nextToken() + } + + // Parse profile names (can be multiple: s1, s2, s3) + for { + name := p.parseIdentifierName() + if name != "" { + query.Names = append(query.Names, name) + } + if p.currentIs(token.COMMA) { + p.nextToken() + continue + } + break + } + + // Skip the rest of the statement + for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) { + p.nextToken() + } + + return query +} + func (p *Parser) parseCreateDictionary(create *ast.CreateQuery) { // Handle IF NOT EXISTS if p.currentIs(token.IF) { @@ -3358,6 +3556,9 @@ func (p *Parser) parseShow() ast.Statement { p.nextToken() } return &ast.ShowCreateQuotaQuery{Position: pos, Name: name} + } else if p.currentIs(token.SETTINGS) || (p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE") { + // SHOW CREATE SETTINGS PROFILE or SHOW CREATE PROFILE + return p.parseShowCreateSettingsProfile(pos) } else if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "DICTIONARY" { show.ShowType = ast.ShowCreateDictionary p.nextToken() diff --git a/parser/testdata/01294_create_settings_profile/metadata.json b/parser/testdata/01294_create_settings_profile/metadata.json index 580f3de408..cf94ac4194 100644 --- a/parser/testdata/01294_create_settings_profile/metadata.json +++ b/parser/testdata/01294_create_settings_profile/metadata.json @@ -1,98 +1,7 @@ { "explain_todo": { - "stmt1": true, - "stmt10": true, - "stmt100": true, - "stmt101": true, - "stmt102": true, - "stmt103": true, - "stmt107": true, "stmt108": true, - "stmt11": true, - "stmt12": true, - "stmt14": true, - "stmt15": true, - "stmt16": true, - "stmt17": true, - "stmt19": true, - "stmt2": true, - "stmt20": true, - "stmt21": true, - "stmt22": true, - "stmt23": true, - "stmt24": true, - "stmt25": true, - "stmt26": true, - "stmt27": true, - "stmt28": true, - "stmt29": true, - "stmt30": true, - "stmt31": true, - "stmt32": true, - "stmt33": true, - "stmt34": true, - "stmt35": true, - "stmt36": true, - "stmt37": true, - "stmt38": true, - "stmt39": true, "stmt4": true, - "stmt40": true, - "stmt41": true, - "stmt42": true, - "stmt43": true, - "stmt44": true, - "stmt45": true, - "stmt47": true, - "stmt49": true, - "stmt50": true, - "stmt51": true, - "stmt52": true, - "stmt53": true, - "stmt54": true, - "stmt55": true, - "stmt56": true, - "stmt57": true, - "stmt58": true, - "stmt59": true, - "stmt6": true, - "stmt60": true, - "stmt61": true, - "stmt62": true, - "stmt63": true, - "stmt64": true, - "stmt65": true, - "stmt66": true, - "stmt67": true, - "stmt69": true, - "stmt7": true, - "stmt70": true, - "stmt71": true, - "stmt72": true, - "stmt73": true, - "stmt75": true, - "stmt76": true, - "stmt77": true, - "stmt78": true, - "stmt79": true, - "stmt80": true, - "stmt81": true, - "stmt82": true, - "stmt84": true, - "stmt85": true, - "stmt86": true, - "stmt87": true, - "stmt88": true, - "stmt89": true, - "stmt9": true, - "stmt90": true, - "stmt91": true, - "stmt92": true, - "stmt93": true, - "stmt94": true, - "stmt95": true, - "stmt96": true, - "stmt98": true, - "stmt99": true + "stmt47": true } } diff --git a/parser/testdata/01418_custom_settings/metadata.json b/parser/testdata/01418_custom_settings/metadata.json index a8bdead886..0967ef424b 100644 --- a/parser/testdata/01418_custom_settings/metadata.json +++ b/parser/testdata/01418_custom_settings/metadata.json @@ -1,11 +1 @@ -{ - "explain_todo": { - "stmt1": true, - "stmt41": true, - "stmt42": true, - "stmt43": true, - "stmt50": true, - "stmt51": true, - "stmt52": true - } -} +{} diff --git a/parser/testdata/01605_drop_settings_profile_while_assigned/metadata.json b/parser/testdata/01605_drop_settings_profile_while_assigned/metadata.json index f5dd12602b..0967ef424b 100644 --- a/parser/testdata/01605_drop_settings_profile_while_assigned/metadata.json +++ b/parser/testdata/01605_drop_settings_profile_while_assigned/metadata.json @@ -1,6 +1 @@ -{ - "explain_todo": { - "stmt2": true, - "stmt5": true - } -} +{} diff --git a/parser/testdata/01702_system_query_log/metadata.json b/parser/testdata/01702_system_query_log/metadata.json index f121c8af1a..5fbf30078f 100644 --- a/parser/testdata/01702_system_query_log/metadata.json +++ b/parser/testdata/01702_system_query_log/metadata.json @@ -8,7 +8,6 @@ "stmt17": true, "stmt18": true, "stmt19": true, - "stmt20": true, "stmt24": true, "stmt26": true, "stmt27": true, @@ -54,8 +53,6 @@ "stmt83": true, "stmt84": true, "stmt85": true, - "stmt86": true, - "stmt87": true, - "stmt9": true + "stmt86": true } } diff --git a/parser/testdata/01825_new_type_json_in_array/metadata.json b/parser/testdata/01825_new_type_json_in_array/metadata.json index 9ec65fdf84..0967ef424b 100644 --- a/parser/testdata/01825_new_type_json_in_array/metadata.json +++ b/parser/testdata/01825_new_type_json_in_array/metadata.json @@ -1,7 +1 @@ -{ - "explain_todo": { - "stmt14": true, - "stmt15": true, - "stmt16": true - } -} +{} diff --git a/parser/testdata/02294_fp_seconds_profile/metadata.json b/parser/testdata/02294_fp_seconds_profile/metadata.json index 1aae0cd1d0..0967ef424b 100644 --- a/parser/testdata/02294_fp_seconds_profile/metadata.json +++ b/parser/testdata/02294_fp_seconds_profile/metadata.json @@ -1,10 +1 @@ -{ - "explain_todo": { - "stmt1": true, - "stmt2": true, - "stmt3": true, - "stmt4": true, - "stmt5": true, - "stmt6": true - } -} +{} diff --git a/parser/testdata/03208_array_of_json_read_subcolumns_2_memory/metadata.json b/parser/testdata/03208_array_of_json_read_subcolumns_2_memory/metadata.json index aac43023ea..90fa5ee0a1 100644 --- a/parser/testdata/03208_array_of_json_read_subcolumns_2_memory/metadata.json +++ b/parser/testdata/03208_array_of_json_read_subcolumns_2_memory/metadata.json @@ -1,32 +1,15 @@ { "explain_todo": { - "stmt12": true, - "stmt13": true, - "stmt14": true, - "stmt15": true, - "stmt16": true, - "stmt17": true, "stmt18": true, - "stmt19": true, "stmt20": true, - "stmt21": true, - "stmt22": true, "stmt23": true, "stmt24": true, - "stmt25": true, - "stmt26": true, "stmt27": true, "stmt28": true, - "stmt29": true, "stmt30": true, - "stmt31": true, "stmt32": true, - "stmt33": true, - "stmt34": true, "stmt35": true, "stmt36": true, - "stmt37": true, - "stmt38": true, "stmt39": true, "stmt40": true, "stmt5": true diff --git a/parser/testdata/03222_json_squashing/metadata.json b/parser/testdata/03222_json_squashing/metadata.json index bb771e4356..0967ef424b 100644 --- a/parser/testdata/03222_json_squashing/metadata.json +++ b/parser/testdata/03222_json_squashing/metadata.json @@ -1,13 +1 @@ -{ - "explain_todo": { - "stmt49": true, - "stmt51": true, - "stmt53": true, - "stmt57": true, - "stmt59": true, - "stmt61": true, - "stmt65": true, - "stmt67": true, - "stmt69": true - } -} +{} diff --git a/parser/testdata/03223_nested_json_in_shared_data_merges/metadata.json b/parser/testdata/03223_nested_json_in_shared_data_merges/metadata.json index 728d75a643..0967ef424b 100644 --- a/parser/testdata/03223_nested_json_in_shared_data_merges/metadata.json +++ b/parser/testdata/03223_nested_json_in_shared_data_merges/metadata.json @@ -1,10 +1 @@ -{ - "explain_todo": { - "stmt10": true, - "stmt12": true, - "stmt16": true, - "stmt18": true, - "stmt20": true, - "stmt8": true - } -} +{} diff --git a/parser/testdata/03224_nested_json_merges_new_type_in_shared_data/metadata.json b/parser/testdata/03224_nested_json_merges_new_type_in_shared_data/metadata.json index 6f422a855e..0967ef424b 100644 --- a/parser/testdata/03224_nested_json_merges_new_type_in_shared_data/metadata.json +++ b/parser/testdata/03224_nested_json_merges_new_type_in_shared_data/metadata.json @@ -1,11 +1 @@ -{ - "explain_todo": { - "stmt11": true, - "stmt15": true, - "stmt16": true, - "stmt17": true, - "stmt21": true, - "stmt22": true, - "stmt23": true - } -} +{} diff --git a/parser/testdata/03272_json_to_json_cast_6/metadata.json b/parser/testdata/03272_json_to_json_cast_6/metadata.json index ef58f80315..0967ef424b 100644 --- a/parser/testdata/03272_json_to_json_cast_6/metadata.json +++ b/parser/testdata/03272_json_to_json_cast_6/metadata.json @@ -1,5 +1 @@ -{ - "explain_todo": { - "stmt2": true - } -} +{} diff --git a/parser/testdata/03274_json_to_json_alter_nested_json/metadata.json b/parser/testdata/03274_json_to_json_alter_nested_json/metadata.json index e050a434c5..0967ef424b 100644 --- a/parser/testdata/03274_json_to_json_alter_nested_json/metadata.json +++ b/parser/testdata/03274_json_to_json_alter_nested_json/metadata.json @@ -1,20 +1 @@ -{ - "explain_todo": { - "stmt10": true, - "stmt12": true, - "stmt14": true, - "stmt21": true, - "stmt23": true, - "stmt25": true, - "stmt27": true, - "stmt33": true, - "stmt35": true, - "stmt37": true, - "stmt39": true, - "stmt45": true, - "stmt47": true, - "stmt49": true, - "stmt51": true, - "stmt8": true - } -} +{} diff --git a/parser/testdata/03381_remote_constants/metadata.json b/parser/testdata/03381_remote_constants/metadata.json index f67cd83996..32d6abfac9 100644 --- a/parser/testdata/03381_remote_constants/metadata.json +++ b/parser/testdata/03381_remote_constants/metadata.json @@ -3,10 +3,6 @@ "stmt14": true, "stmt15": true, "stmt16": true, - "stmt17": true, - "stmt20": true, - "stmt21": true, - "stmt22": true, - "stmt23": true + "stmt17": true } } diff --git a/parser/testdata/03455_direct_io_read_array_values/metadata.json b/parser/testdata/03455_direct_io_read_array_values/metadata.json index e69c938af1..25122ac4f4 100644 --- a/parser/testdata/03455_direct_io_read_array_values/metadata.json +++ b/parser/testdata/03455_direct_io_read_array_values/metadata.json @@ -1,6 +1,5 @@ { "explain_todo": { - "stmt11": true, "stmt5": true, "stmt9": true } diff --git a/parser/testdata/03554_json_shared_data_advanced_serialization_compact_part_big/metadata.json b/parser/testdata/03554_json_shared_data_advanced_serialization_compact_part_big/metadata.json index b4b184d331..0967ef424b 100644 --- a/parser/testdata/03554_json_shared_data_advanced_serialization_compact_part_big/metadata.json +++ b/parser/testdata/03554_json_shared_data_advanced_serialization_compact_part_big/metadata.json @@ -1,29 +1 @@ -{ - "explain_todo": { - "stmt17": true, - "stmt19": true, - "stmt21": true, - "stmt23": true, - "stmt25": true, - "stmt27": true, - "stmt29": true, - "stmt31": true, - "stmt33": true, - "stmt39": true, - "stmt41": true, - "stmt43": true, - "stmt45": true, - "stmt55": true, - "stmt57": true, - "stmt59": true, - "stmt61": true, - "stmt63": true, - "stmt65": true, - "stmt67": true, - "stmt69": true, - "stmt71": true, - "stmt73": true, - "stmt75": true, - "stmt77": true - } -} +{} diff --git a/parser/testdata/03554_json_shared_data_advanced_serialization_wide_part_big/metadata.json b/parser/testdata/03554_json_shared_data_advanced_serialization_wide_part_big/metadata.json index 0fd9ec12ad..0967ef424b 100644 --- a/parser/testdata/03554_json_shared_data_advanced_serialization_wide_part_big/metadata.json +++ b/parser/testdata/03554_json_shared_data_advanced_serialization_wide_part_big/metadata.json @@ -1,29 +1 @@ -{ - "explain_todo": { - "stmt11": true, - "stmt13": true, - "stmt15": true, - "stmt17": true, - "stmt19": true, - "stmt21": true, - "stmt23": true, - "stmt25": true, - "stmt27": true, - "stmt33": true, - "stmt35": true, - "stmt37": true, - "stmt39": true, - "stmt49": true, - "stmt51": true, - "stmt53": true, - "stmt55": true, - "stmt57": true, - "stmt59": true, - "stmt61": true, - "stmt63": true, - "stmt65": true, - "stmt67": true, - "stmt69": true, - "stmt71": true - } -} +{}