Skip to content

Commit 88f462c

Browse files
authored
Fix should allow column list before ENGINE and fix DEPENDS ON multi-table in CREATE MATERIALIZED VIEW (#270)
1. Column list before ENGINE (new): ClickHouse SHOW CREATE TABLE for refreshable materialized views with ENGINE (e.g. Memory) outputs the column list between REFRESH and ENGINE: CREATE MATERIALIZED VIEW db1.mv_name REFRESH EVERY 1 SECOND (col1 String, col2 Int8) ENGINE = Memory AS SELECT ... The parser previously expected TO or ENGINE immediately after REFRESH clauses. Added support for (columns) before ENGINE by parsing a TableSchemaClause when a left paren is encountered. 2. DEPENDS ON multi-table (bug fix): The comma in 'DEPENDS ON db1.mv_a, db1.mv_b' was not consumed before parsing the next table identifier. Added missing consumeToken() call in the comma loop, matching the pattern used in parser_query.go. Changes: - parser/parser_view.go: handle (columns) before ENGINE; consume comma in DEPENDS ON loop - parser/ast.go: add TableSchema field to CreateMaterializedView - parser/format.go: emit TableSchema before ENGINE in FormatSQL - parser/walk.go: traverse TableSchema in Walk Tests: - create_materialized_view_rmv_engine_with_columns.sql (column list + ENGINE) - create_materialized_view_rmv_depends_on_multi.sql (multi-table DEPENDS ON) - All 21 MV syntax variants tested, all existing tests pass
1 parent 33c6bf2 commit 88f462c

21 files changed

Lines changed: 1191 additions & 0 deletions

parser/ast.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,7 @@ type CreateMaterializedView struct {
13561356
Settings *SettingsClause
13571357
HasAppend bool
13581358
Engine *EngineExpr
1359+
TableSchema *TableSchemaClause // column list for ENGINE-based MVs (no TO clause)
13591360
HasEmpty bool
13601361
Destination *DestinationClause
13611362
SubQuery *SubQuery
@@ -1410,6 +1411,11 @@ func (c *CreateMaterializedView) Accept(visitor ASTVisitor) error {
14101411
return err
14111412
}
14121413
}
1414+
if c.TableSchema != nil {
1415+
if err := c.TableSchema.Accept(visitor); err != nil {
1416+
return err
1417+
}
1418+
}
14131419
if c.Engine != nil {
14141420
if err := c.Engine.Accept(visitor); err != nil {
14151421
return err

parser/format.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,10 @@ func (c *CreateMaterializedView) FormatSQL(formatter *Formatter) {
907907
formatter.Break()
908908
formatter.WriteString("APPEND")
909909
}
910+
if c.TableSchema != nil {
911+
formatter.Break()
912+
formatter.WriteExpr(c.TableSchema)
913+
}
910914
if c.Engine != nil {
911915
formatter.Break()
912916
formatter.WriteExpr(c.Engine)

parser/parser_view.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func (p *Parser) parseCreateMaterializedView(pos Pos) (*CreateMaterializedView,
6666
}
6767
dependsOnTables = append(dependsOnTables, table)
6868
for p.matchTokenKind(TokenKindComma) {
69+
_ = p.lexer.consumeToken()
6970
table, err := p.parseTableIdentifier(p.Pos())
7071
if err != nil {
7172
return nil, err
@@ -96,6 +97,22 @@ func (p *Parser) parseCreateMaterializedView(pos Pos) (*CreateMaterializedView,
9697
}
9798
createMaterializedView.Destination.TableSchema = tableSchema
9899
}
100+
case p.matchTokenKind(TokenKindLParen):
101+
// Column list before ENGINE (e.g. SHOW CREATE TABLE output for RMVs with ENGINE = Memory)
102+
tableSchema, err := p.parseTableSchemaClause(p.Pos())
103+
if err != nil {
104+
return nil, err
105+
}
106+
createMaterializedView.TableSchema = tableSchema
107+
if !p.matchKeyword(KeywordEngine) {
108+
return nil, fmt.Errorf("unexpected token: %q, expected ENGINE after column list", p.lastTokenKind())
109+
}
110+
engineExpr, err := p.parseEngineExpr(p.Pos())
111+
if err != nil {
112+
return nil, err
113+
}
114+
createMaterializedView.Engine = engineExpr
115+
createMaterializedView.StatementEnd = engineExpr.End()
99116
case p.matchKeyword(KeywordEngine):
100117
engineExpr, err := p.parseEngineExpr(p.Pos())
101118
if err != nil {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CREATE MATERIALIZED VIEW db1.attrs_rollup_v0
2+
REFRESH EVERY 5 MINUTE
3+
DEPENDS ON db1.metadata_mv_v0, db1.metadata_mv_v1
4+
ENGINE = Memory
5+
AS SELECT DISTINCT
6+
service_name,
7+
attr_key,
8+
'profiles' AS dataset,
9+
'string' AS attr_type
10+
FROM db1.metadata_v0
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
CREATE MATERIALIZED VIEW db1.config_memory_v0
2+
REFRESH EVERY 1 SECOND
3+
(
4+
`schedule_id` String,
5+
`sample_rate` Int8,
6+
`start_at` DateTime64(9),
7+
`end_at` DateTime64(9),
8+
`created_at` DateTime64(9),
9+
`created_by` String,
10+
`properties` Map(String, String),
11+
`cluster_percentage` Float64,
12+
`schedule_filters` String
13+
)
14+
ENGINE = Memory
15+
SETTINGS min_rows_to_keep = 250000, max_rows_to_keep = 500000
16+
DEFINER = default SQL SECURITY DEFINER
17+
COMMENT 'test comment'
18+
AS SELECT
19+
schedule_id,
20+
sample_rate,
21+
start_at,
22+
end_at,
23+
created_at,
24+
created_by,
25+
properties,
26+
cluster_percentage,
27+
schedule_filters
28+
FROM
29+
(
30+
SELECT
31+
*,
32+
row_number() OVER (PARTITION BY schedule_filters ORDER BY created_at DESC) AS rn
33+
FROM db1.config_v0
34+
WHERE schedule_filters != '{}'
35+
)
36+
WHERE rn = 1
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
-- Origin SQL:
2+
CREATE MATERIALIZED VIEW db1.attrs_rollup_v0
3+
REFRESH EVERY 5 MINUTE
4+
DEPENDS ON db1.metadata_mv_v0, db1.metadata_mv_v1
5+
ENGINE = Memory
6+
AS SELECT DISTINCT
7+
service_name,
8+
attr_key,
9+
'profiles' AS dataset,
10+
'string' AS attr_type
11+
FROM db1.metadata_v0
12+
13+
14+
-- Beautify SQL:
15+
CREATE MATERIALIZED VIEW db1.attrs_rollup_v0
16+
REFRESH EVERY 5 MINUTE
17+
DEPENDS ON db1.metadata_mv_v0, db1.metadata_mv_v1
18+
ENGINE = Memory
19+
AS
20+
SELECT DISTINCT
21+
service_name,
22+
attr_key,
23+
'profiles' AS dataset,
24+
'string' AS attr_type
25+
FROM
26+
db1.metadata_v0;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
-- Origin SQL:
2+
CREATE MATERIALIZED VIEW db1.config_memory_v0
3+
REFRESH EVERY 1 SECOND
4+
(
5+
`schedule_id` String,
6+
`sample_rate` Int8,
7+
`start_at` DateTime64(9),
8+
`end_at` DateTime64(9),
9+
`created_at` DateTime64(9),
10+
`created_by` String,
11+
`properties` Map(String, String),
12+
`cluster_percentage` Float64,
13+
`schedule_filters` String
14+
)
15+
ENGINE = Memory
16+
SETTINGS min_rows_to_keep = 250000, max_rows_to_keep = 500000
17+
DEFINER = default SQL SECURITY DEFINER
18+
COMMENT 'test comment'
19+
AS SELECT
20+
schedule_id,
21+
sample_rate,
22+
start_at,
23+
end_at,
24+
created_at,
25+
created_by,
26+
properties,
27+
cluster_percentage,
28+
schedule_filters
29+
FROM
30+
(
31+
SELECT
32+
*,
33+
row_number() OVER (PARTITION BY schedule_filters ORDER BY created_at DESC) AS rn
34+
FROM db1.config_v0
35+
WHERE schedule_filters != '{}'
36+
)
37+
WHERE rn = 1
38+
39+
40+
-- Beautify SQL:
41+
CREATE MATERIALIZED VIEW db1.config_memory_v0
42+
REFRESH EVERY 1 SECOND
43+
(
44+
`schedule_id` String,
45+
`sample_rate` Int8,
46+
`start_at` DateTime64(9),
47+
`end_at` DateTime64(9),
48+
`created_at` DateTime64(9),
49+
`created_by` String,
50+
`properties` Map(String, String),
51+
`cluster_percentage` Float64,
52+
`schedule_filters` String
53+
)
54+
ENGINE = Memory
55+
SETTINGS
56+
min_rows_to_keep=250000,
57+
max_rows_to_keep=500000
58+
DEFINER = default
59+
SQL SECURITY DEFINER
60+
AS
61+
SELECT
62+
schedule_id,
63+
sample_rate,
64+
start_at,
65+
end_at,
66+
created_at,
67+
created_by,
68+
properties,
69+
cluster_percentage,
70+
schedule_filters
71+
FROM
72+
(SELECT
73+
*,
74+
row_number() OVER (PARTITION BY schedule_filters ORDER BY
75+
created_at DESC) AS rn
76+
FROM
77+
db1.config_v0
78+
WHERE
79+
schedule_filters != '{}')
80+
WHERE
81+
rn = 1
82+
COMMENT 'test comment';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Origin SQL:
2+
CREATE MATERIALIZED VIEW db1.attrs_rollup_v0
3+
REFRESH EVERY 5 MINUTE
4+
DEPENDS ON db1.metadata_mv_v0, db1.metadata_mv_v1
5+
ENGINE = Memory
6+
AS SELECT DISTINCT
7+
service_name,
8+
attr_key,
9+
'profiles' AS dataset,
10+
'string' AS attr_type
11+
FROM db1.metadata_v0
12+
13+
14+
-- Format SQL:
15+
CREATE MATERIALIZED VIEW db1.attrs_rollup_v0 REFRESH EVERY 5 MINUTE DEPENDS ON db1.metadata_mv_v0, db1.metadata_mv_v1 ENGINE = Memory AS SELECT DISTINCT service_name, attr_key, 'profiles' AS dataset, 'string' AS attr_type FROM db1.metadata_v0;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
-- Origin SQL:
2+
CREATE MATERIALIZED VIEW db1.config_memory_v0
3+
REFRESH EVERY 1 SECOND
4+
(
5+
`schedule_id` String,
6+
`sample_rate` Int8,
7+
`start_at` DateTime64(9),
8+
`end_at` DateTime64(9),
9+
`created_at` DateTime64(9),
10+
`created_by` String,
11+
`properties` Map(String, String),
12+
`cluster_percentage` Float64,
13+
`schedule_filters` String
14+
)
15+
ENGINE = Memory
16+
SETTINGS min_rows_to_keep = 250000, max_rows_to_keep = 500000
17+
DEFINER = default SQL SECURITY DEFINER
18+
COMMENT 'test comment'
19+
AS SELECT
20+
schedule_id,
21+
sample_rate,
22+
start_at,
23+
end_at,
24+
created_at,
25+
created_by,
26+
properties,
27+
cluster_percentage,
28+
schedule_filters
29+
FROM
30+
(
31+
SELECT
32+
*,
33+
row_number() OVER (PARTITION BY schedule_filters ORDER BY created_at DESC) AS rn
34+
FROM db1.config_v0
35+
WHERE schedule_filters != '{}'
36+
)
37+
WHERE rn = 1
38+
39+
40+
-- Format SQL:
41+
CREATE MATERIALIZED VIEW db1.config_memory_v0 REFRESH EVERY 1 SECOND (`schedule_id` String, `sample_rate` Int8, `start_at` DateTime64(9), `end_at` DateTime64(9), `created_at` DateTime64(9), `created_by` String, `properties` Map(String, String), `cluster_percentage` Float64, `schedule_filters` String) ENGINE = Memory SETTINGS min_rows_to_keep=250000, max_rows_to_keep=500000 DEFINER = default SQL SECURITY DEFINER AS SELECT schedule_id, sample_rate, start_at, end_at, created_at, created_by, properties, cluster_percentage, schedule_filters FROM (SELECT *, row_number() OVER (PARTITION BY schedule_filters ORDER BY created_at DESC) AS rn FROM db1.config_v0 WHERE schedule_filters != '{}') WHERE rn = 1 COMMENT 'test comment';

parser/testdata/ddl/output/bug_001.sql.golden.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"Settings": null,
3232
"HasAppend": false,
3333
"Engine": null,
34+
"TableSchema": null,
3435
"HasEmpty": false,
3536
"Destination": {
3637
"ToPos": 89,

0 commit comments

Comments
 (0)