@intentius/chant-lexicon-sql 0.124.18 → 0.124.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clickhouse/access.d.ts +36 -7
- package/dist/clickhouse/access.d.ts.map +1 -1
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/entities.d.ts +60 -0
- package/dist/clickhouse/entities.d.ts.map +1 -1
- package/dist/clickhouse/import/generator.d.ts +1 -7
- package/dist/clickhouse/import/generator.d.ts.map +1 -1
- package/dist/clickhouse/import/live-export.d.ts +8 -0
- package/dist/clickhouse/import/live-export.d.ts.map +1 -1
- package/dist/clickhouse/live/catalog.d.ts +6 -5
- package/dist/clickhouse/live/catalog.d.ts.map +1 -1
- package/dist/clickhouse/live/describe-resources.d.ts.map +1 -1
- package/dist/clickhouse/live/undeclarable.d.ts +32 -0
- package/dist/clickhouse/live/undeclarable.d.ts.map +1 -0
- package/dist/clickhouse/parser.d.ts +16 -0
- package/dist/clickhouse/parser.d.ts.map +1 -1
- package/dist/clickhouse/plan/deep.d.ts.map +1 -1
- package/dist/clickhouse/plan/diff.d.ts.map +1 -1
- package/dist/clickhouse/plan/normalize.d.ts +6 -3
- package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
- package/dist/clickhouse/plan/rules.d.ts +3 -0
- package/dist/clickhouse/plan/rules.d.ts.map +1 -1
- package/dist/clickhouse/testing/fake-server.d.ts +6 -0
- package/dist/clickhouse/testing/fake-server.d.ts.map +1 -1
- package/dist/clickhouse/topology.d.ts.map +1 -1
- package/dist/clickhouse.d.ts +1 -1
- package/dist/clickhouse.d.ts.map +1 -1
- package/dist/codegen/coverage-page.d.ts +7 -0
- package/dist/codegen/coverage-page.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/core/undeclarable.d.ts +38 -0
- package/dist/core/undeclarable.d.ts.map +1 -0
- package/dist/coverage/clickhouse-commands.json +24 -0
- package/dist/coverage/clickhouse.json +25 -0
- package/dist/coverage/postgres-commands.json +245 -0
- package/dist/coverage/postgres.json +51 -0
- package/dist/coverage.d.ts +68 -0
- package/dist/coverage.d.ts.map +1 -1
- package/dist/files/clickhouse.d.ts +2 -1
- package/dist/files/clickhouse.d.ts.map +1 -1
- package/dist/files/common.d.ts +12 -0
- package/dist/files/common.d.ts.map +1 -1
- package/dist/files/postgres.d.ts +1 -1
- package/dist/files/postgres.d.ts.map +1 -1
- package/dist/import-parser.d.ts +7 -2
- package/dist/import-parser.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +10 -10
- package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
- package/dist/lint/post-synth/sql101.d.ts.map +1 -1
- package/dist/lint/rules/sqlch001.d.ts +1 -1
- package/dist/lint/rules/sqlch001.d.ts.map +1 -1
- package/dist/lint/rules/sqlpg001.d.ts.map +1 -1
- package/dist/lint/rules/templates.d.ts +1 -1
- package/dist/lint/rules/templates.d.ts.map +1 -1
- package/dist/lsp/postgres-template.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/meta.json +40 -0
- package/dist/okf/index.md +9 -1
- package/dist/okf/rules/SQL101.md +1 -0
- package/dist/okf/rules/SQLCH001.md +2 -2
- package/dist/okf/rules/SQLPG113.md +2 -0
- package/dist/okf/types/PostgresAggregate.md +13 -0
- package/dist/okf/types/PostgresCast.md +9 -0
- package/dist/okf/types/PostgresCollation.md +9 -0
- package/dist/okf/types/PostgresCompositeType.md +13 -0
- package/dist/okf/types/PostgresEventTrigger.md +9 -0
- package/dist/okf/types/PostgresRangeType.md +13 -0
- package/dist/okf/types/Quota.md +9 -0
- package/dist/okf/types/SettingsProfile.md +9 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/postgres/apply/apply.d.ts.map +1 -1
- package/dist/postgres/apply/statements.d.ts +4 -3
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/entities.d.ts +129 -6
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entity-types.d.ts +6 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/import/generator.d.ts +3 -2
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/import/live-export.d.ts +8 -0
- package/dist/postgres/import/live-export.d.ts.map +1 -1
- package/dist/postgres/live/catalog-kinds.d.ts +28 -0
- package/dist/postgres/live/catalog-kinds.d.ts.map +1 -0
- package/dist/postgres/live/catalog.d.ts +23 -8
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/live/undeclarable.d.ts +42 -0
- package/dist/postgres/live/undeclarable.d.ts.map +1 -0
- package/dist/postgres/parser.d.ts +88 -5
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.d.ts +17 -2
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/rules.d.ts +3 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/schema.d.ts +3 -2
- package/dist/postgres/plan/schema.d.ts.map +1 -1
- package/dist/postgres/testing/fake-client.d.ts +6 -0
- package/dist/postgres/testing/fake-client.d.ts.map +1 -1
- package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
- package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/rules/postgres-names.ts +15 -2
- package/dist/rules/postgres-templates.ts +2 -2
- package/dist/rules/sql101.ts +1 -0
- package/dist/rules/sqlch001.ts +10 -3
- package/dist/rules/sqlpg001.ts +6 -21
- package/dist/rules/sqlpg113.ts +1 -1
- package/dist/rules/templates.ts +2 -2
- package/dist/validate.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/clickhouse/access.ts +197 -22
- package/src/clickhouse/apply/statements.ts +17 -5
- package/src/clickhouse/entities.ts +78 -2
- package/src/clickhouse/import/generator.ts +16 -3
- package/src/clickhouse/import/live-export.ts +17 -2
- package/src/clickhouse/live/catalog.ts +57 -10
- package/src/clickhouse/live/describe-resources.ts +3 -1
- package/src/clickhouse/live/kinds.e2e.test.ts +179 -0
- package/src/clickhouse/live/undeclarable.e2e.test.ts +65 -0
- package/src/clickhouse/live/undeclarable.ts +66 -0
- package/src/clickhouse/parser.test.ts +45 -2
- package/src/clickhouse/parser.ts +111 -3
- package/src/clickhouse/plan/deep.ts +25 -6
- package/src/clickhouse/plan/diff.ts +41 -1
- package/src/clickhouse/plan/normalize.ts +12 -4
- package/src/clickhouse/plan/rules.ts +22 -1
- package/src/clickhouse/quota-profile.test.ts +186 -0
- package/src/clickhouse/table-function.test.ts +54 -0
- package/src/clickhouse/testdata/server-ddl-baseline.json +17 -14
- package/src/clickhouse/testing/fake-server.ts +5 -0
- package/src/clickhouse/topology.ts +4 -2
- package/src/clickhouse.ts +6 -0
- package/src/codegen/classifier-page.test.ts +6 -0
- package/src/codegen/coverage-page.ts +77 -0
- package/src/codegen/docs.ts +5 -0
- package/src/codegen/postgres-classifier-page.ts +1 -1
- package/src/codegen/registry.ts +8 -0
- package/src/core/undeclarable.test.ts +141 -0
- package/src/core/undeclarable.ts +66 -0
- package/src/coverage/clickhouse-commands.json +24 -0
- package/src/coverage/clickhouse.json +25 -0
- package/src/coverage/postgres-commands.json +245 -0
- package/src/coverage/postgres.json +51 -0
- package/src/coverage.test.ts +78 -0
- package/src/coverage.ts +142 -0
- package/src/files/clickhouse.ts +20 -7
- package/src/files/common.ts +18 -0
- package/src/files/postgres.ts +25 -6
- package/src/generated/lexicon-sql.json +40 -0
- package/src/import-parser.ts +12 -4
- package/src/index.ts +2 -2
- package/src/lint/post-synth/postgres-names.ts +15 -2
- package/src/lint/post-synth/sql101.ts +1 -0
- package/src/lint/post-synth/sqlpg113.ts +1 -1
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lint/rules/sqlch001.ts +10 -3
- package/src/lint/rules/sqlpg001.ts +6 -21
- package/src/lint/rules/templates.ts +2 -2
- package/src/lsp/postgres-template.ts +14 -0
- package/src/mcp/index.ts +5 -5
- package/src/mcp/postgres.ts +1 -1
- package/src/plugin.ts +13 -2
- package/src/postgres/apply/apply.ts +8 -2
- package/src/postgres/apply/statements.ts +29 -6
- package/src/postgres/entities.test.ts +2 -2
- package/src/postgres/entities.ts +316 -28
- package/src/postgres/entity-types.ts +6 -0
- package/src/postgres/import/generator.ts +31 -4
- package/src/postgres/import/live-export.ts +25 -2
- package/src/postgres/kinds.test.ts +140 -0
- package/src/postgres/live/catalog-kinds.ts +312 -0
- package/src/postgres/live/catalog.ts +33 -11
- package/src/postgres/live/describe-resources.ts +14 -5
- package/src/postgres/live/kinds.e2e.test.ts +182 -0
- package/src/postgres/live/roundtrip.e2e.test.ts +9 -3
- package/src/postgres/live/undeclarable.e2e.test.ts +83 -0
- package/src/postgres/live/undeclarable.ts +141 -0
- package/src/postgres/parser.test.ts +2 -1
- package/src/postgres/parser.ts +307 -17
- package/src/postgres/plan/deep.ts +33 -0
- package/src/postgres/plan/diff.ts +45 -0
- package/src/postgres/plan/normalize.ts +153 -2
- package/src/postgres/plan/rules.ts +21 -0
- package/src/postgres/plan/schema.ts +6 -4
- package/src/postgres/testing/fake-client.ts +5 -0
- package/src/postgres/testing/roundtrip-schema.ts +19 -1
- package/src/postgres.ts +19 -0
- package/src/skill-defs.ts +1 -1
- package/src/skills/chant-sql-postgres.md +7 -3
- package/src/skills/chant-sql.md +3 -1
- package/src/unknown-clauses.test.ts +2 -1
- package/src/validate.ts +8 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "doc/src/sgml/ref/create_*.sgml <refname>, at each pinned major's tag",
|
|
3
|
+
"majors": {
|
|
4
|
+
"14": {
|
|
5
|
+
"version": "14.24",
|
|
6
|
+
"tag": "REL_14_24",
|
|
7
|
+
"commands": [
|
|
8
|
+
"CREATE ACCESS METHOD",
|
|
9
|
+
"CREATE AGGREGATE",
|
|
10
|
+
"CREATE CAST",
|
|
11
|
+
"CREATE COLLATION",
|
|
12
|
+
"CREATE CONVERSION",
|
|
13
|
+
"CREATE DATABASE",
|
|
14
|
+
"CREATE DOMAIN",
|
|
15
|
+
"CREATE EVENT TRIGGER",
|
|
16
|
+
"CREATE EXTENSION",
|
|
17
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
18
|
+
"CREATE FOREIGN TABLE",
|
|
19
|
+
"CREATE FUNCTION",
|
|
20
|
+
"CREATE GROUP",
|
|
21
|
+
"CREATE INDEX",
|
|
22
|
+
"CREATE LANGUAGE",
|
|
23
|
+
"CREATE MATERIALIZED VIEW",
|
|
24
|
+
"CREATE OPERATOR",
|
|
25
|
+
"CREATE OPERATOR CLASS",
|
|
26
|
+
"CREATE OPERATOR FAMILY",
|
|
27
|
+
"CREATE POLICY",
|
|
28
|
+
"CREATE PROCEDURE",
|
|
29
|
+
"CREATE PUBLICATION",
|
|
30
|
+
"CREATE ROLE",
|
|
31
|
+
"CREATE RULE",
|
|
32
|
+
"CREATE SCHEMA",
|
|
33
|
+
"CREATE SEQUENCE",
|
|
34
|
+
"CREATE SERVER",
|
|
35
|
+
"CREATE STATISTICS",
|
|
36
|
+
"CREATE SUBSCRIPTION",
|
|
37
|
+
"CREATE TABLE",
|
|
38
|
+
"CREATE TABLE AS",
|
|
39
|
+
"CREATE TABLESPACE",
|
|
40
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
41
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
42
|
+
"CREATE TEXT SEARCH PARSER",
|
|
43
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
44
|
+
"CREATE TRANSFORM",
|
|
45
|
+
"CREATE TRIGGER",
|
|
46
|
+
"CREATE TYPE",
|
|
47
|
+
"CREATE USER",
|
|
48
|
+
"CREATE USER MAPPING",
|
|
49
|
+
"CREATE VIEW"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"15": {
|
|
53
|
+
"version": "15.19",
|
|
54
|
+
"tag": "REL_15_19",
|
|
55
|
+
"commands": [
|
|
56
|
+
"CREATE ACCESS METHOD",
|
|
57
|
+
"CREATE AGGREGATE",
|
|
58
|
+
"CREATE CAST",
|
|
59
|
+
"CREATE COLLATION",
|
|
60
|
+
"CREATE CONVERSION",
|
|
61
|
+
"CREATE DATABASE",
|
|
62
|
+
"CREATE DOMAIN",
|
|
63
|
+
"CREATE EVENT TRIGGER",
|
|
64
|
+
"CREATE EXTENSION",
|
|
65
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
66
|
+
"CREATE FOREIGN TABLE",
|
|
67
|
+
"CREATE FUNCTION",
|
|
68
|
+
"CREATE GROUP",
|
|
69
|
+
"CREATE INDEX",
|
|
70
|
+
"CREATE LANGUAGE",
|
|
71
|
+
"CREATE MATERIALIZED VIEW",
|
|
72
|
+
"CREATE OPERATOR",
|
|
73
|
+
"CREATE OPERATOR CLASS",
|
|
74
|
+
"CREATE OPERATOR FAMILY",
|
|
75
|
+
"CREATE POLICY",
|
|
76
|
+
"CREATE PROCEDURE",
|
|
77
|
+
"CREATE PUBLICATION",
|
|
78
|
+
"CREATE ROLE",
|
|
79
|
+
"CREATE RULE",
|
|
80
|
+
"CREATE SCHEMA",
|
|
81
|
+
"CREATE SEQUENCE",
|
|
82
|
+
"CREATE SERVER",
|
|
83
|
+
"CREATE STATISTICS",
|
|
84
|
+
"CREATE SUBSCRIPTION",
|
|
85
|
+
"CREATE TABLE",
|
|
86
|
+
"CREATE TABLE AS",
|
|
87
|
+
"CREATE TABLESPACE",
|
|
88
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
89
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
90
|
+
"CREATE TEXT SEARCH PARSER",
|
|
91
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
92
|
+
"CREATE TRANSFORM",
|
|
93
|
+
"CREATE TRIGGER",
|
|
94
|
+
"CREATE TYPE",
|
|
95
|
+
"CREATE USER",
|
|
96
|
+
"CREATE USER MAPPING",
|
|
97
|
+
"CREATE VIEW"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
"16": {
|
|
101
|
+
"version": "16.15",
|
|
102
|
+
"tag": "REL_16_15",
|
|
103
|
+
"commands": [
|
|
104
|
+
"CREATE ACCESS METHOD",
|
|
105
|
+
"CREATE AGGREGATE",
|
|
106
|
+
"CREATE CAST",
|
|
107
|
+
"CREATE COLLATION",
|
|
108
|
+
"CREATE CONVERSION",
|
|
109
|
+
"CREATE DATABASE",
|
|
110
|
+
"CREATE DOMAIN",
|
|
111
|
+
"CREATE EVENT TRIGGER",
|
|
112
|
+
"CREATE EXTENSION",
|
|
113
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
114
|
+
"CREATE FOREIGN TABLE",
|
|
115
|
+
"CREATE FUNCTION",
|
|
116
|
+
"CREATE GROUP",
|
|
117
|
+
"CREATE INDEX",
|
|
118
|
+
"CREATE LANGUAGE",
|
|
119
|
+
"CREATE MATERIALIZED VIEW",
|
|
120
|
+
"CREATE OPERATOR",
|
|
121
|
+
"CREATE OPERATOR CLASS",
|
|
122
|
+
"CREATE OPERATOR FAMILY",
|
|
123
|
+
"CREATE POLICY",
|
|
124
|
+
"CREATE PROCEDURE",
|
|
125
|
+
"CREATE PUBLICATION",
|
|
126
|
+
"CREATE ROLE",
|
|
127
|
+
"CREATE RULE",
|
|
128
|
+
"CREATE SCHEMA",
|
|
129
|
+
"CREATE SEQUENCE",
|
|
130
|
+
"CREATE SERVER",
|
|
131
|
+
"CREATE STATISTICS",
|
|
132
|
+
"CREATE SUBSCRIPTION",
|
|
133
|
+
"CREATE TABLE",
|
|
134
|
+
"CREATE TABLE AS",
|
|
135
|
+
"CREATE TABLESPACE",
|
|
136
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
137
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
138
|
+
"CREATE TEXT SEARCH PARSER",
|
|
139
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
140
|
+
"CREATE TRANSFORM",
|
|
141
|
+
"CREATE TRIGGER",
|
|
142
|
+
"CREATE TYPE",
|
|
143
|
+
"CREATE USER",
|
|
144
|
+
"CREATE USER MAPPING",
|
|
145
|
+
"CREATE VIEW"
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"17": {
|
|
149
|
+
"version": "17.11",
|
|
150
|
+
"tag": "REL_17_11",
|
|
151
|
+
"commands": [
|
|
152
|
+
"CREATE ACCESS METHOD",
|
|
153
|
+
"CREATE AGGREGATE",
|
|
154
|
+
"CREATE CAST",
|
|
155
|
+
"CREATE COLLATION",
|
|
156
|
+
"CREATE CONVERSION",
|
|
157
|
+
"CREATE DATABASE",
|
|
158
|
+
"CREATE DOMAIN",
|
|
159
|
+
"CREATE EVENT TRIGGER",
|
|
160
|
+
"CREATE EXTENSION",
|
|
161
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
162
|
+
"CREATE FOREIGN TABLE",
|
|
163
|
+
"CREATE FUNCTION",
|
|
164
|
+
"CREATE GROUP",
|
|
165
|
+
"CREATE INDEX",
|
|
166
|
+
"CREATE LANGUAGE",
|
|
167
|
+
"CREATE MATERIALIZED VIEW",
|
|
168
|
+
"CREATE OPERATOR",
|
|
169
|
+
"CREATE OPERATOR CLASS",
|
|
170
|
+
"CREATE OPERATOR FAMILY",
|
|
171
|
+
"CREATE POLICY",
|
|
172
|
+
"CREATE PROCEDURE",
|
|
173
|
+
"CREATE PUBLICATION",
|
|
174
|
+
"CREATE ROLE",
|
|
175
|
+
"CREATE RULE",
|
|
176
|
+
"CREATE SCHEMA",
|
|
177
|
+
"CREATE SEQUENCE",
|
|
178
|
+
"CREATE SERVER",
|
|
179
|
+
"CREATE STATISTICS",
|
|
180
|
+
"CREATE SUBSCRIPTION",
|
|
181
|
+
"CREATE TABLE",
|
|
182
|
+
"CREATE TABLE AS",
|
|
183
|
+
"CREATE TABLESPACE",
|
|
184
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
185
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
186
|
+
"CREATE TEXT SEARCH PARSER",
|
|
187
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
188
|
+
"CREATE TRANSFORM",
|
|
189
|
+
"CREATE TRIGGER",
|
|
190
|
+
"CREATE TYPE",
|
|
191
|
+
"CREATE USER",
|
|
192
|
+
"CREATE USER MAPPING",
|
|
193
|
+
"CREATE VIEW"
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
"18": {
|
|
197
|
+
"version": "18.6",
|
|
198
|
+
"tag": "REL_18_6",
|
|
199
|
+
"commands": [
|
|
200
|
+
"CREATE ACCESS METHOD",
|
|
201
|
+
"CREATE AGGREGATE",
|
|
202
|
+
"CREATE CAST",
|
|
203
|
+
"CREATE COLLATION",
|
|
204
|
+
"CREATE CONVERSION",
|
|
205
|
+
"CREATE DATABASE",
|
|
206
|
+
"CREATE DOMAIN",
|
|
207
|
+
"CREATE EVENT TRIGGER",
|
|
208
|
+
"CREATE EXTENSION",
|
|
209
|
+
"CREATE FOREIGN DATA WRAPPER",
|
|
210
|
+
"CREATE FOREIGN TABLE",
|
|
211
|
+
"CREATE FUNCTION",
|
|
212
|
+
"CREATE GROUP",
|
|
213
|
+
"CREATE INDEX",
|
|
214
|
+
"CREATE LANGUAGE",
|
|
215
|
+
"CREATE MATERIALIZED VIEW",
|
|
216
|
+
"CREATE OPERATOR",
|
|
217
|
+
"CREATE OPERATOR CLASS",
|
|
218
|
+
"CREATE OPERATOR FAMILY",
|
|
219
|
+
"CREATE POLICY",
|
|
220
|
+
"CREATE PROCEDURE",
|
|
221
|
+
"CREATE PUBLICATION",
|
|
222
|
+
"CREATE ROLE",
|
|
223
|
+
"CREATE RULE",
|
|
224
|
+
"CREATE SCHEMA",
|
|
225
|
+
"CREATE SEQUENCE",
|
|
226
|
+
"CREATE SERVER",
|
|
227
|
+
"CREATE STATISTICS",
|
|
228
|
+
"CREATE SUBSCRIPTION",
|
|
229
|
+
"CREATE TABLE",
|
|
230
|
+
"CREATE TABLE AS",
|
|
231
|
+
"CREATE TABLESPACE",
|
|
232
|
+
"CREATE TEXT SEARCH CONFIGURATION",
|
|
233
|
+
"CREATE TEXT SEARCH DICTIONARY",
|
|
234
|
+
"CREATE TEXT SEARCH PARSER",
|
|
235
|
+
"CREATE TEXT SEARCH TEMPLATE",
|
|
236
|
+
"CREATE TRANSFORM",
|
|
237
|
+
"CREATE TRIGGER",
|
|
238
|
+
"CREATE TYPE",
|
|
239
|
+
"CREATE USER",
|
|
240
|
+
"CREATE USER MAPPING",
|
|
241
|
+
"CREATE VIEW"
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dialect": "postgres",
|
|
3
|
+
"kinds": [
|
|
4
|
+
{ "command": "CREATE ACCESS METHOD", "kind": "access method", "status": "refused", "reason": "no tag declares an access method; an extension usually installs one, and the extension is declared instead" },
|
|
5
|
+
{ "command": "CREATE AGGREGATE", "kind": "aggregate", "status": "declared", "entity": "Postgres::Aggregate" },
|
|
6
|
+
{ "command": "CREATE CAST", "kind": "cast", "status": "declared", "entity": "Postgres::Cast", "note": "the database's, named by its two types; read where a type or its function is in scope" },
|
|
7
|
+
{ "command": "CREATE COLLATION", "kind": "collation", "status": "declared", "entity": "Postgres::Collation" },
|
|
8
|
+
{ "command": "CREATE CONVERSION", "kind": "conversion", "status": "refused", "reason": "no tag declares an encoding conversion" },
|
|
9
|
+
{ "command": "CREATE DATABASE", "kind": "database", "status": "out-of-scope", "reason": "a project's schema lives in one database, which the environment provides (sql.profiles.<env>)" },
|
|
10
|
+
{ "command": "CREATE DOMAIN", "kind": "domain", "status": "declared", "entity": "Postgres::Domain" },
|
|
11
|
+
{ "command": "CREATE EVENT TRIGGER", "kind": "event trigger", "status": "declared", "entity": "Postgres::EventTrigger", "note": "the database's, not a schema's; creating one takes a superuser" },
|
|
12
|
+
{ "command": "CREATE EXTENSION", "kind": "extension", "status": "declared", "entity": "Postgres::Extension" },
|
|
13
|
+
{ "command": "CREATE FOREIGN DATA WRAPPER", "kind": "foreign data wrapper", "status": "refused", "reason": "no tag declares a foreign data wrapper yet; one an extension installs comes with the extension" },
|
|
14
|
+
{ "command": "CREATE FOREIGN TABLE", "kind": "foreign table", "status": "refused", "reason": "no tag declares a foreign table yet" },
|
|
15
|
+
{ "command": "CREATE FUNCTION", "kind": "function", "status": "declared", "entity": "Postgres::Function" },
|
|
16
|
+
{ "command": "CREATE FUNCTION", "form": "RETURN or BEGIN ATOMIC body", "match": "\\b(?:RETURN|BEGIN\\s+ATOMIC)\\b", "kind": "routine with a SQL-standard body", "status": "refused", "reason": "the server stores a SQL-standard body parsed and prints it back rewritten, so a declaration cannot be compared with it; declare the routine with a string body (AS $$ ... $$)" },
|
|
17
|
+
{ "command": "CREATE GROUP", "kind": "group", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE" },
|
|
18
|
+
{ "command": "CREATE INDEX", "kind": "index", "status": "declared", "entity": "Postgres::Index" },
|
|
19
|
+
{ "command": "CREATE LANGUAGE", "kind": "procedural language", "status": "refused", "reason": "no tag declares a procedural language; an extension installs one, and the extension is declared instead" },
|
|
20
|
+
{ "command": "CREATE MATERIALIZED VIEW", "kind": "materialized view", "status": "declared", "entity": "Postgres::MaterializedView" },
|
|
21
|
+
{ "command": "CREATE OPERATOR", "kind": "operator", "status": "refused", "reason": "no tag declares an operator yet" },
|
|
22
|
+
{ "command": "CREATE OPERATOR CLASS", "kind": "operator class", "status": "refused", "reason": "no tag declares an operator class yet" },
|
|
23
|
+
{ "command": "CREATE OPERATOR FAMILY", "kind": "operator family", "status": "refused", "reason": "no tag declares an operator family yet" },
|
|
24
|
+
{ "command": "CREATE POLICY", "kind": "policy", "status": "declared", "entity": "Postgres::Policy", "note": "read where the profile manages access (sql.profiles.<env>.access)" },
|
|
25
|
+
{ "command": "CREATE PROCEDURE", "kind": "procedure", "status": "declared", "entity": "Postgres::Procedure" },
|
|
26
|
+
{ "command": "CREATE PUBLICATION", "kind": "publication", "status": "refused", "reason": "no tag declares a publication yet" },
|
|
27
|
+
{ "command": "CREATE ROLE", "kind": "role", "status": "declared", "entity": "Postgres::Role", "note": "import reads only the roles a build declares, since a role is the cluster's" },
|
|
28
|
+
{ "command": "CREATE RULE", "kind": "rule", "status": "refused", "reason": "no tag declares a rewrite rule; a view's own rule is the view's" },
|
|
29
|
+
{ "command": "CREATE SCHEMA", "kind": "schema", "status": "declared", "entity": "Postgres::Schema" },
|
|
30
|
+
{ "command": "CREATE SEQUENCE", "kind": "sequence", "status": "declared", "entity": "Postgres::Sequence" },
|
|
31
|
+
{ "command": "CREATE SERVER", "kind": "foreign server", "status": "refused", "reason": "no tag declares a foreign server yet" },
|
|
32
|
+
{ "command": "CREATE STATISTICS", "kind": "extended statistics", "status": "refused", "reason": "no tag declares extended statistics yet" },
|
|
33
|
+
{ "command": "CREATE SUBSCRIPTION", "kind": "subscription", "status": "refused", "reason": "no tag declares a subscription yet" },
|
|
34
|
+
{ "command": "CREATE TABLE", "kind": "table", "status": "declared", "entity": "Postgres::Table" },
|
|
35
|
+
{ "command": "CREATE TABLE AS", "kind": "table from a query", "status": "declared", "entity": "Postgres::Table", "note": "the server keeps no query, so the table it made reads as a CREATE TABLE" },
|
|
36
|
+
{ "command": "CREATE TABLESPACE", "kind": "tablespace", "status": "out-of-scope", "reason": "a tablespace is the cluster's, a directory on the server; a table's TABLESPACE clause is kept" },
|
|
37
|
+
{ "command": "CREATE TEXT SEARCH CONFIGURATION", "kind": "text search configuration", "status": "refused", "reason": "no tag declares a text search configuration yet" },
|
|
38
|
+
{ "command": "CREATE TEXT SEARCH DICTIONARY", "kind": "text search dictionary", "status": "refused", "reason": "no tag declares a text search dictionary yet" },
|
|
39
|
+
{ "command": "CREATE TEXT SEARCH PARSER", "kind": "text search parser", "status": "refused", "reason": "no tag declares a text search parser; one is C code an extension installs" },
|
|
40
|
+
{ "command": "CREATE TEXT SEARCH TEMPLATE", "kind": "text search template", "status": "refused", "reason": "no tag declares a text search template; one is C code an extension installs" },
|
|
41
|
+
{ "command": "CREATE TRANSFORM", "kind": "transform", "status": "refused", "reason": "no tag declares a transform; an extension installs one" },
|
|
42
|
+
{ "command": "CREATE TRIGGER", "kind": "trigger", "status": "declared", "entity": "Postgres::Trigger" },
|
|
43
|
+
{ "command": "CREATE TYPE", "form": "AS ENUM", "match": "\\bAS\\s+ENUM\\b", "kind": "enum type", "status": "declared", "entity": "Postgres::Enum" },
|
|
44
|
+
{ "command": "CREATE TYPE", "form": "AS (...)", "match": "\\bAS\\s*\\(", "kind": "composite type", "status": "declared", "entity": "Postgres::CompositeType" },
|
|
45
|
+
{ "command": "CREATE TYPE", "form": "AS RANGE", "match": "\\bAS\\s+RANGE\\b", "kind": "range type", "status": "declared", "entity": "Postgres::RangeType", "note": "its multirange type comes with it (MULTIRANGE_TYPE_NAME)" },
|
|
46
|
+
{ "command": "CREATE TYPE", "form": "INPUT, OUTPUT, or a shell type", "kind": "base type", "status": "refused", "reason": "no tag declares a base type; its input and output functions are C code an extension installs" },
|
|
47
|
+
{ "command": "CREATE USER", "kind": "user", "status": "declared", "entity": "Postgres::Role", "note": "an alias of CREATE ROLE ... LOGIN" },
|
|
48
|
+
{ "command": "CREATE USER MAPPING", "kind": "user mapping", "status": "refused", "reason": "no tag declares a user mapping yet" },
|
|
49
|
+
{ "command": "CREATE VIEW", "kind": "view", "status": "declared", "entity": "Postgres::View" }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { KIND_COVERAGE, PINNED_COMMANDS, kindCoverageProblems, pinnedCommandsCurrent, statementKind } from "./coverage";
|
|
3
|
+
import { POSTGRES_PROBES } from "./postgres/live/undeclarable";
|
|
4
|
+
import { CLICKHOUSE_PROBES } from "./clickhouse/live/undeclarable";
|
|
5
|
+
import { SQL_DIALECTS } from "./dialects";
|
|
6
|
+
|
|
7
|
+
describe("the object coverage table (#3911)", () => {
|
|
8
|
+
test.each(SQL_DIALECTS)("%s: every CREATE command a pinned server has is marked, and nothing else", (dialect) => {
|
|
9
|
+
expect(kindCoverageProblems(dialect)).toEqual([]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test.each(SQL_DIALECTS)("%s: the command lists are the pinned servers' (run scripts/sql-create-commands.ts after a pin move)", (dialect) => {
|
|
13
|
+
expect(pinnedCommandsCurrent(dialect)).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("a pinned server with a command no row covers fails the check", () => {
|
|
17
|
+
const pins = [{ version: "19.0", commands: [...PINNED_COMMANDS.postgres[0]!.commands, "CREATE PROPERTY GRAPH"] }];
|
|
18
|
+
expect(kindCoverageProblems("postgres", { pins })).toContain("postgres 19.0 has CREATE PROPERTY GRAPH, and no row in src/coverage/postgres.json covers it");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("a row must say why it is not declared, and name its entity when it is", () => {
|
|
22
|
+
const rows = [
|
|
23
|
+
{ command: "CREATE CAST", kind: "cast", status: "refused" as const },
|
|
24
|
+
{ command: "CREATE TABLE", kind: "table", status: "declared" as const },
|
|
25
|
+
];
|
|
26
|
+
expect(kindCoverageProblems("postgres", { pins: [{ version: "x", commands: ["CREATE CAST", "CREATE TABLE"] }], rows })).toEqual([
|
|
27
|
+
"cast is refused, and gives no reason",
|
|
28
|
+
"table is declared, and names no entity type",
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("every refused kind has the live query that finds it, and every query a refused-or-declared row", () => {
|
|
33
|
+
const probes = { postgres: POSTGRES_PROBES, clickhouse: CLICKHOUSE_PROBES };
|
|
34
|
+
for (const dialect of SQL_DIALECTS) {
|
|
35
|
+
const refused = KIND_COVERAGE[dialect].filter((r) => r.status === "refused").map((r) => r.kind);
|
|
36
|
+
for (const kind of refused) expect(Object.keys(probes[dialect]), `${dialect} ${kind}`).toContain(kind);
|
|
37
|
+
const kinds = KIND_COVERAGE[dialect].map((r) => r.kind);
|
|
38
|
+
for (const kind of Object.keys(probes[dialect])) expect(kinds, `${dialect} probe ${kind}`).toContain(kind);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("a statement's kind", () => {
|
|
44
|
+
test.each([
|
|
45
|
+
["CREATE TYPE app.pair AS (a int, b text)", "composite type"],
|
|
46
|
+
["CREATE TYPE app.r AS RANGE (subtype = float8)", "range type"],
|
|
47
|
+
["CREATE TYPE app.mood AS ENUM ('ok')", "enum type"],
|
|
48
|
+
["CREATE TYPE app.shell", "base type"],
|
|
49
|
+
["CREATE AGGREGATE app.total(int) (sfunc = int4pl, stype = int)", "aggregate"],
|
|
50
|
+
["CREATE CAST (app.pair AS text) WITH FUNCTION app.f(app.pair)", "cast"],
|
|
51
|
+
["CREATE OR REPLACE TRUSTED PROCEDURAL LANGUAGE plx", "procedural language"],
|
|
52
|
+
["CREATE DEFAULT CONVERSION c FOR 'LATIN1' TO 'UTF8' FROM f", "conversion"],
|
|
53
|
+
["CREATE OPERATOR CLASS c FOR TYPE int USING btree AS OPERATOR 1 <", "operator class"],
|
|
54
|
+
["CREATE TEXT SEARCH CONFIGURATION app.eng (COPY = english)", "text search configuration"],
|
|
55
|
+
["CREATE USER MAPPING FOR app SERVER s", "user mapping"],
|
|
56
|
+
["CREATE UNIQUE INDEX i ON app.t (a)", "index"],
|
|
57
|
+
// A body's RETURN is inside its string, not a SQL-standard body.
|
|
58
|
+
["CREATE FUNCTION app.f() RETURNS int LANGUAGE plpgsql AS $$ BEGIN RETURN 1; END $$", "function"],
|
|
59
|
+
["CREATE FUNCTION app.f(a int) RETURNS int LANGUAGE sql RETURN a + 1", "routine with a SQL-standard body"],
|
|
60
|
+
])("postgres: %s is a %s", (sql, kind) => {
|
|
61
|
+
expect(statementKind("postgres", sql)?.kind).toBe(kind);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test.each([
|
|
65
|
+
["CREATE TEMPORARY TABLE t (a Int8)", "temporary table"],
|
|
66
|
+
["CREATE TABLE t (a Int8) ENGINE = Memory", "table"],
|
|
67
|
+
["CREATE WINDOW VIEW w AS SELECT 1", "window view"],
|
|
68
|
+
["CREATE SETTINGS PROFILE p SETTINGS max_threads = 1", "settings profile"],
|
|
69
|
+
["CREATE OR REPLACE FUNCTION f AS (x) -> x + 1", "SQL function"],
|
|
70
|
+
["CREATE QUOTA q FOR INTERVAL 1 hour MAX queries = 10", "quota"],
|
|
71
|
+
])("clickhouse: %s is a %s", (sql, kind) => {
|
|
72
|
+
expect(statementKind("clickhouse", sql)?.kind).toBe(kind);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("a statement that is not a CREATE has no kind", () => {
|
|
76
|
+
expect(statementKind("postgres", "ALTER TABLE t ADD COLUMN a int")).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
package/src/coverage.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Coverage, two ways.
|
|
3
|
+
*
|
|
4
|
+
* Which kinds of object each dialect declares (#3911): every `CREATE` command
|
|
5
|
+
* a pinned server has (`./coverage/<dialect>-commands.json`, written by
|
|
6
|
+
* `scripts/sql-create-commands.ts` from the server's own sources) has a row
|
|
7
|
+
* in `./coverage/<dialect>.json` marking it declared, refused with a reason,
|
|
8
|
+
* or out of scope. A command with several forms that differ in what chant can
|
|
9
|
+
* hold (`CREATE TYPE ... AS ENUM` and `CREATE TYPE ... AS RANGE`) has a row
|
|
10
|
+
* per form. Import refuses an object of a refused kind by that row's `kind`
|
|
11
|
+
* (`./core/undeclarable.ts`), and the docs page is rendered from the rows
|
|
12
|
+
* (`./codegen/coverage-page.ts`). To declare a kind, flip its row to
|
|
13
|
+
* `declared` with the `entity` that holds it, and drop the reason.
|
|
14
|
+
*
|
|
2
15
|
* How much of the pinned ClickHouse catalog the dialect types beyond its name.
|
|
3
16
|
*
|
|
4
17
|
* Every engine, type family, codec, skip index type and setting at the pin is
|
|
@@ -9,6 +22,135 @@
|
|
|
9
22
|
*/
|
|
10
23
|
|
|
11
24
|
import { CODECS, SKIP_INDEX_TYPES, TABLE_ENGINES, CLICKHOUSE_VERSION } from "./generated/clickhouse";
|
|
25
|
+
import { POSTGRES_PINS } from "./spec/postgres-pin";
|
|
26
|
+
import type { SqlDialect } from "./dialects";
|
|
27
|
+
import postgresKinds from "./coverage/postgres.json" with { type: "json" };
|
|
28
|
+
import clickhouseKinds from "./coverage/clickhouse.json" with { type: "json" };
|
|
29
|
+
import postgresCommands from "./coverage/postgres-commands.json" with { type: "json" };
|
|
30
|
+
import clickhouseCommands from "./coverage/clickhouse-commands.json" with { type: "json" };
|
|
31
|
+
|
|
32
|
+
// ── Object kinds ───────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/** `declared`: a tag holds it. `refused`: import names it and stops. `out-of-scope`: the environment's, never imported. */
|
|
35
|
+
export type KindStatus = "declared" | "refused" | "out-of-scope";
|
|
36
|
+
|
|
37
|
+
/** One row of a dialect's coverage table: a `CREATE` command, or one form of it. */
|
|
38
|
+
export interface KindRow {
|
|
39
|
+
/** The command as the server's reference names it: `CREATE TYPE`. */
|
|
40
|
+
command: string;
|
|
41
|
+
/** The form of the command the row is about, when the command has forms chant treats apart: `AS RANGE`. */
|
|
42
|
+
form?: string;
|
|
43
|
+
/**
|
|
44
|
+
* How a statement of the command is told to be this form: a regular
|
|
45
|
+
* expression, case-insensitive, over the statement with its comments and
|
|
46
|
+
* string literals taken out. The command's row without one is the rest.
|
|
47
|
+
*/
|
|
48
|
+
match?: string;
|
|
49
|
+
/** The object kind, unique in the dialect, as messages name it: `range type`. */
|
|
50
|
+
kind: string;
|
|
51
|
+
status: KindStatus;
|
|
52
|
+
/** The entity type that declares it, when declared. */
|
|
53
|
+
entity?: string;
|
|
54
|
+
/** Why it is refused or out of scope. */
|
|
55
|
+
reason?: string;
|
|
56
|
+
/** Anything else a reader should know. */
|
|
57
|
+
note?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Each dialect's coverage table. */
|
|
61
|
+
export const KIND_COVERAGE: Record<SqlDialect, readonly KindRow[]> = {
|
|
62
|
+
postgres: postgresKinds.kinds as KindRow[],
|
|
63
|
+
clickhouse: clickhouseKinds.kinds as KindRow[],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** The `CREATE` commands each pinned server has: per Postgres major, and the pinned ClickHouse release. */
|
|
67
|
+
export const PINNED_COMMANDS: Record<SqlDialect, ReadonlyArray<{ version: string; commands: readonly string[] }>> = {
|
|
68
|
+
postgres: Object.values(postgresCommands.majors),
|
|
69
|
+
clickhouse: [{ version: clickhouseCommands.version, commands: clickhouseCommands.commands }],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** A dialect's row for `kind`; throws for a kind the table does not have, so a probe cannot name one that is not there. */
|
|
73
|
+
export function kindRow(dialect: SqlDialect, kind: string): KindRow {
|
|
74
|
+
const row = KIND_COVERAGE[dialect].find((r) => r.kind === kind);
|
|
75
|
+
if (!row) throw new Error(`no ${dialect} coverage row for the kind "${kind}" (src/coverage/${dialect}.json)`);
|
|
76
|
+
return row;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* What is wrong with a dialect's coverage table, against its pinned servers'
|
|
81
|
+
* commands: a command a pinned server has and no row covers, a row for a
|
|
82
|
+
* command no pinned server has, a kind named twice, a declared row with no
|
|
83
|
+
* entity, a refused or out-of-scope row with no reason. Empty when complete.
|
|
84
|
+
*/
|
|
85
|
+
export function kindCoverageProblems(dialect: SqlDialect, opts: { pins?: ReadonlyArray<{ version: string; commands: readonly string[] }>; rows?: readonly KindRow[] } = {}): string[] {
|
|
86
|
+
const rows = opts.rows ?? KIND_COVERAGE[dialect];
|
|
87
|
+
const pins = opts.pins ?? PINNED_COMMANDS[dialect];
|
|
88
|
+
const problems: string[] = [];
|
|
89
|
+
const covered = new Set(rows.map((r) => r.command));
|
|
90
|
+
const listed = new Set(pins.flatMap((p) => p.commands));
|
|
91
|
+
for (const p of pins) {
|
|
92
|
+
for (const c of p.commands) if (!covered.has(c)) problems.push(`${dialect} ${p.version} has ${c}, and no row in src/coverage/${dialect}.json covers it`);
|
|
93
|
+
}
|
|
94
|
+
for (const c of covered) if (!listed.has(c)) problems.push(`src/coverage/${dialect}.json has a row for ${c}, which no pinned ${dialect} server has`);
|
|
95
|
+
const seen = new Set<string>();
|
|
96
|
+
for (const r of rows) {
|
|
97
|
+
if (seen.has(r.kind)) problems.push(`the kind "${r.kind}" has two rows`);
|
|
98
|
+
seen.add(r.kind);
|
|
99
|
+
if (!["declared", "refused", "out-of-scope"].includes(r.status)) problems.push(`${r.kind}: status "${r.status}" is not declared, refused or out-of-scope`);
|
|
100
|
+
if (r.status === "declared" && !r.entity) problems.push(`${r.kind} is declared, and names no entity type`);
|
|
101
|
+
if (r.status !== "declared" && !r.reason) problems.push(`${r.kind} is ${r.status}, and gives no reason`);
|
|
102
|
+
}
|
|
103
|
+
return problems;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A statement's words, upper case, with comments and string literals (quoted and dollar-quoted) taken out. */
|
|
107
|
+
function bareStatement(sql: string): string {
|
|
108
|
+
return sql
|
|
109
|
+
.replace(/--[^\n]*/g, " ")
|
|
110
|
+
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
111
|
+
.replace(/\$([A-Za-z_][A-Za-z0-9_]*)?\$[\s\S]*?\$\1\$/g, " '' ")
|
|
112
|
+
.replace(/'(?:[^'\\]|''|\\.)*'/g, " '' ")
|
|
113
|
+
.replace(/\s+/g, " ")
|
|
114
|
+
.trim()
|
|
115
|
+
.toUpperCase();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Words a `CREATE` can carry before the command's own: `CREATE OR REPLACE TRUSTED PROCEDURAL LANGUAGE`, `CREATE DEFAULT CONVERSION`. */
|
|
119
|
+
const MODIFIERS = new Set(["OR", "REPLACE", "TRUSTED", "PROCEDURAL", "DEFAULT", "UNIQUE", "UNLOGGED", "RECURSIVE", "CONSTRAINT", "GLOBAL", "LOCAL", "TEMP", "TEMPORARY"]);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The coverage row a `CREATE` statement is of, by its command (the longest
|
|
123
|
+
* that its words start with) and, among the command's forms, the first whose
|
|
124
|
+
* `match` it matches; undefined for a statement no row covers.
|
|
125
|
+
*/
|
|
126
|
+
export function statementKind(dialect: SqlDialect, sql: string): KindRow | undefined {
|
|
127
|
+
const bare = bareStatement(sql);
|
|
128
|
+
const words = bare.split(/[\s(]+/);
|
|
129
|
+
if (words[0] !== "CREATE") return undefined;
|
|
130
|
+
const rest = words.slice(1).filter((w, i, all) => !MODIFIERS.has(w) || all.slice(0, i).some((x) => !MODIFIERS.has(x)));
|
|
131
|
+
const rows = KIND_COVERAGE[dialect];
|
|
132
|
+
let command: string | undefined;
|
|
133
|
+
for (const r of rows) {
|
|
134
|
+
const cmd = r.command.split(" ").slice(1);
|
|
135
|
+
if (cmd.every((w, i) => rest[i] === w) && (!command || cmd.length > command.split(" ").length - 1)) command = r.command;
|
|
136
|
+
}
|
|
137
|
+
if (!command) return undefined;
|
|
138
|
+
const forms = rows.filter((r) => r.command === command);
|
|
139
|
+
return forms.find((r) => r.match !== undefined && new RegExp(r.match, "i").test(bare)) ?? forms.find((r) => r.match === undefined);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Whether the commands file was written for the pinned servers: a list for each pinned version, and for no other. */
|
|
143
|
+
export function pinnedCommandsCurrent(dialect: SqlDialect): string[] {
|
|
144
|
+
const have = PINNED_COMMANDS[dialect].map((p) => p.version);
|
|
145
|
+
const want = dialect === "postgres" ? POSTGRES_PINS.map((p) => p.version) : [CLICKHOUSE_VERSION];
|
|
146
|
+
const run = `run npx tsx scripts/sql-create-commands.ts ${dialect}`;
|
|
147
|
+
return [
|
|
148
|
+
...want.filter((v) => !have.includes(v)).map((v) => `${dialect} ${v} is pinned, and src/coverage/${dialect}-commands.json has no command list for it: ${run}`),
|
|
149
|
+
...have.filter((v) => !want.includes(v)).map((v) => `src/coverage/${dialect}-commands.json lists ${dialect} ${v}, which is no longer pinned: ${run}`),
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── ClickHouse argument typing ─────────────────────────────────────────
|
|
12
154
|
|
|
13
155
|
export interface CoverageSection {
|
|
14
156
|
section: string;
|
package/src/files/clickhouse.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* A file of ClickHouse DDL as the objects it declares: one per `CREATE
|
|
3
3
|
* DATABASE`, `TABLE`, `VIEW`, `MATERIALIZED VIEW`, `DICTIONARY` or `FUNCTION`, as `chant import
|
|
4
4
|
* <file>.sql` reads them, and one per `CREATE USER`, `CREATE ROLE`, `CREATE
|
|
5
|
-
* ROW POLICY`
|
|
5
|
+
* ROW POLICY`, `CREATE QUOTA`, `CREATE SETTINGS PROFILE` (#3911) and `GRANT`
|
|
6
|
+
* (#3711), each read by its tag, so what the tag
|
|
6
7
|
* refuses (`IDENTIFIED BY`, a `REVOKE`) is refused with the tag's message.
|
|
7
8
|
* Any other statement, and any statement the parser refuses, is a problem
|
|
8
9
|
* naming it.
|
|
@@ -15,16 +16,22 @@
|
|
|
15
16
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
16
17
|
import { isTrivia, tokenizeText } from "../clickhouse/tokens";
|
|
17
18
|
import { parseCreate } from "../clickhouse/parser";
|
|
18
|
-
import { CLICKHOUSE_ENTITY_TYPES, database, dictionary, func, grant, policy, role, table, user, view } from "../clickhouse/entities";
|
|
19
|
+
import { CLICKHOUSE_ENTITY_TYPES, database, dictionary, func, grant, policy, quota, role, settingsProfile, table, user, view } from "../clickhouse/entities";
|
|
19
20
|
import { isAccessStatement, parseAccess, type AccessNode } from "../clickhouse/access";
|
|
20
21
|
import type { Token } from "../clickhouse/tokens";
|
|
21
22
|
import { CLICKHOUSE_TAG_OF } from "../clickhouse/import/generator";
|
|
22
23
|
import { describeStatement, type ImportedObject } from "../clickhouse/import/ir";
|
|
23
|
-
import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, type FileLine, type ReadOptions } from "./common";
|
|
24
|
+
import { fileUnparsed, firstLine, lineAt, quoteBare, splitTokens, tokensText, undeclarableStatement, type FileLine, type ReadOptions } from "./common";
|
|
24
25
|
import { templateSafe, templateStrings } from "./postgres";
|
|
25
26
|
|
|
26
27
|
type Tag = (strings: TemplateStringsArray, ...values: unknown[]) => Declarable;
|
|
27
|
-
const TAGS: Record<string, Tag> = { database, table, view, dictionary, func, user, role, policy, grant };
|
|
28
|
+
const TAGS: Record<string, Tag> = { database, table, view, dictionary, func, user, role, policy, quota, settingsProfile, grant };
|
|
29
|
+
|
|
30
|
+
/** The tag an access statement's words name: `CREATE USER`, `CREATE SETTINGS PROFILE`, `GRANT`. */
|
|
31
|
+
function accessTag(words: string[]): Tag {
|
|
32
|
+
if (words[0] !== "CREATE") return grant;
|
|
33
|
+
return words[1] === "USER" ? user : words[1] === "ROLE" ? role : words[1] === "QUOTA" ? quota : words[1] === "SETTINGS" || words[1] === "PROFILE" ? settingsProfile : policy;
|
|
34
|
+
}
|
|
28
35
|
|
|
29
36
|
/** Tokens with `[db.]name`'s bare name qualified with `schema`. A `*` (all tables) is left alone. */
|
|
30
37
|
function qualifyBare(tokens: Token[], span: { from: number; to: number } | undefined, schema: string): string | undefined {
|
|
@@ -44,8 +51,7 @@ function readAccess(sql: string, options: ReadOptions): ImportedObject | string
|
|
|
44
51
|
node = parseAccess(tokens);
|
|
45
52
|
} catch (e) {
|
|
46
53
|
// What the tag refuses (a password) is refused in the tag's words.
|
|
47
|
-
const
|
|
48
|
-
const tag = words[0] === "CREATE" ? (words[1] === "USER" ? user : words[1] === "ROLE" ? role : policy) : grant;
|
|
54
|
+
const tag = accessTag(sql.replace(/--[^\n]*\n/g, " ").trim().split(/\s+/, 2).map((w) => w.toUpperCase()));
|
|
49
55
|
try {
|
|
50
56
|
tag(templateStrings([templateSafe(sql)]));
|
|
51
57
|
} catch (refused) {
|
|
@@ -61,7 +67,7 @@ function readAccess(sql: string, options: ReadOptions): ImportedObject | string
|
|
|
61
67
|
node = parseAccess(tokens);
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
|
-
const tag =
|
|
70
|
+
const tag = { user, role, rowPolicy: policy, quota, settingsProfile, grant }[node.statement];
|
|
65
71
|
let props: { name: string; database?: string; table?: string };
|
|
66
72
|
try {
|
|
67
73
|
props = (tag(templateStrings([templateSafe(sql)])) as unknown as { props: typeof props }).props;
|
|
@@ -70,6 +76,8 @@ function readAccess(sql: string, options: ReadOptions): ImportedObject | string
|
|
|
70
76
|
}
|
|
71
77
|
if (node.statement === "user") return { type: CLICKHOUSE_ENTITY_TYPES.user, name: props.name, ddl: sql };
|
|
72
78
|
if (node.statement === "role") return { type: CLICKHOUSE_ENTITY_TYPES.role, name: props.name, ddl: sql };
|
|
79
|
+
if (node.statement === "quota") return { type: CLICKHOUSE_ENTITY_TYPES.quota, name: props.name, ddl: sql };
|
|
80
|
+
if (node.statement === "settingsProfile") return { type: CLICKHOUSE_ENTITY_TYPES.settingsProfile, name: props.name, ddl: sql };
|
|
73
81
|
if (node.statement === "rowPolicy") return { type: CLICKHOUSE_ENTITY_TYPES.rowPolicy, ...(props.database ? { database: props.database } : {}), name: props.name, ddl: sql };
|
|
74
82
|
// A grant has no name of its own: what it grants, on what, to whom.
|
|
75
83
|
return { type: CLICKHOUSE_ENTITY_TYPES.grant, name: `grant ${tokensText(tokens.slice(tokens.findIndex((t) => !isTrivia(t)) + 1)).replace(/\s+/g, " ").trim().toLowerCase()}`, ddl: sql };
|
|
@@ -87,6 +95,11 @@ export function readClickHouse(ddl: string, options: ReadOptions, problems: stri
|
|
|
87
95
|
for (const stmt of splitTokens(tokenizeText(ddl, 0), isTrivia)) {
|
|
88
96
|
let sql = tokensText(stmt);
|
|
89
97
|
const line = lineAt(ddl, stmt[0]!.start);
|
|
98
|
+
const undeclarable = undeclarableStatement("clickhouse", sql);
|
|
99
|
+
if (undeclarable) {
|
|
100
|
+
(options.undeclarable ?? problems).push(undeclarable);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
90
103
|
if (isAccessStatement(sql)) {
|
|
91
104
|
try {
|
|
92
105
|
const read = readAccess(sql, options);
|
package/src/files/common.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Token } from "../core/tokens";
|
|
4
4
|
import type { UnknownClause } from "../core/cursor";
|
|
5
|
+
import type { SqlDialect } from "../dialects";
|
|
6
|
+
import { statementKind } from "../coverage";
|
|
5
7
|
|
|
6
8
|
export interface ReadOptions {
|
|
7
9
|
/** Names the DDL in errors: a file's path, a command. */
|
|
@@ -11,6 +13,22 @@ export interface ReadOptions {
|
|
|
11
13
|
* unqualified, as an ORM does when the schema comes from its connection.
|
|
12
14
|
*/
|
|
13
15
|
schema?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Where a statement of a kind chant cannot declare goes (#3911), as a line
|
|
18
|
+
* naming it, instead of among the problems: `chant import
|
|
19
|
+
* --parser-option skipUndeclarable` lists them as warnings.
|
|
20
|
+
*/
|
|
21
|
+
undeclarable?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A `CREATE` of a kind the coverage table does not mark declared, as the line
|
|
26
|
+
* that names it with why, or undefined for any other statement.
|
|
27
|
+
*/
|
|
28
|
+
export function undeclarableStatement(dialect: SqlDialect, sql: string): string | undefined {
|
|
29
|
+
const row = statementKind(dialect, sql);
|
|
30
|
+
if (!row || row.status === "declared") return undefined;
|
|
31
|
+
return `${row.kind}: chant cannot declare it (${row.reason ?? row.status}): ${firstLine(sql)}`;
|
|
14
32
|
}
|
|
15
33
|
|
|
16
34
|
/** A file of DDL with statements that declare nothing chant can hold: each problem names its statement. */
|