@intentius/chant-lexicon-sql 0.116.0 → 0.118.0
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 +132 -0
- package/dist/clickhouse/access.d.ts.map +1 -0
- package/dist/clickhouse/apply/apply.d.ts.map +1 -1
- package/dist/clickhouse/apply/statements.d.ts +2 -0
- package/dist/clickhouse/apply/statements.d.ts.map +1 -1
- package/dist/clickhouse/entities.d.ts +171 -2
- package/dist/clickhouse/entities.d.ts.map +1 -1
- package/dist/clickhouse/http.d.ts +3 -0
- package/dist/clickhouse/http.d.ts.map +1 -1
- package/dist/clickhouse/import/generator.d.ts +28 -0
- package/dist/clickhouse/import/generator.d.ts.map +1 -1
- package/dist/clickhouse/import/ir.d.ts.map +1 -1
- package/dist/clickhouse/import/parser.d.ts +3 -3
- package/dist/clickhouse/import/parser.d.ts.map +1 -1
- package/dist/clickhouse/live/bind.d.ts.map +1 -1
- package/dist/clickhouse/live/catalog.d.ts +41 -6
- package/dist/clickhouse/live/catalog.d.ts.map +1 -1
- package/dist/clickhouse/live/describe-resources.d.ts +3 -2
- package/dist/clickhouse/live/describe-resources.d.ts.map +1 -1
- package/dist/clickhouse/parser.d.ts +54 -3
- package/dist/clickhouse/parser.d.ts.map +1 -1
- package/dist/clickhouse/plan/commands.d.ts +3 -1
- package/dist/clickhouse/plan/commands.d.ts.map +1 -1
- package/dist/clickhouse/plan/deep.d.ts.map +1 -1
- package/dist/clickhouse/plan/diff.d.ts +12 -0
- package/dist/clickhouse/plan/diff.d.ts.map +1 -1
- package/dist/clickhouse/plan/normalize.d.ts +61 -1
- package/dist/clickhouse/plan/normalize.d.ts.map +1 -1
- package/dist/clickhouse/plan/report.d.ts.map +1 -1
- package/dist/clickhouse/plan/rules.d.ts +7 -0
- package/dist/clickhouse/plan/rules.d.ts.map +1 -1
- package/dist/clickhouse/plan/schema.d.ts +8 -4
- package/dist/clickhouse/plan/schema.d.ts.map +1 -1
- package/dist/clickhouse/rebuild/backfill.d.ts +3 -1
- package/dist/clickhouse/rebuild/backfill.d.ts.map +1 -1
- package/dist/clickhouse/rebuild/observe.d.ts +7 -3
- package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
- package/dist/clickhouse/rebuild/op.d.ts +8 -0
- package/dist/clickhouse/rebuild/op.d.ts.map +1 -1
- package/dist/clickhouse/rebuild/steps.d.ts +48 -0
- package/dist/clickhouse/rebuild/steps.d.ts.map +1 -1
- package/dist/clickhouse/testing/fake-server.d.ts.map +1 -1
- package/dist/clickhouse/testing/writable-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/classifier-page.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/config.d.ts +70 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/core/references.d.ts +4 -2
- package/dist/core/references.d.ts.map +1 -1
- package/dist/core/statements.d.ts +11 -0
- package/dist/core/statements.d.ts.map +1 -1
- package/dist/files/build-root.d.ts +26 -0
- package/dist/files/build-root.d.ts.map +1 -0
- package/dist/files/clickhouse.d.ts +19 -0
- package/dist/files/clickhouse.d.ts.map +1 -0
- package/dist/files/common.d.ts +25 -0
- package/dist/files/common.d.ts.map +1 -0
- package/dist/files/postgres.d.ts +36 -0
- package/dist/files/postgres.d.ts.map +1 -0
- package/dist/import-parser.d.ts +25 -0
- package/dist/import-parser.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +8 -8
- package/dist/lint/rules/sqlch001.d.ts +1 -1
- package/dist/lint/rules/sqlch001.d.ts.map +1 -1
- package/dist/lint/rules/sqlch002.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 +50 -0
- package/dist/okf/index.md +11 -1
- package/dist/okf/rules/SQLCH001.md +2 -2
- package/dist/okf/types/Dictionary.md +9 -0
- package/dist/okf/types/Function.md +9 -0
- package/dist/okf/types/Grant.md +9 -0
- package/dist/okf/types/PostgresDefaultPrivileges.md +9 -0
- package/dist/okf/types/PostgresGrant.md +9 -0
- package/dist/okf/types/PostgresPolicy.md +9 -0
- package/dist/okf/types/PostgresRole.md +9 -0
- package/dist/okf/types/Role.md +9 -0
- package/dist/okf/types/RowPolicy.md +9 -0
- package/dist/okf/types/User.md +9 -0
- package/dist/op/activities/clickhouse-rebuild.d.ts.map +1 -1
- package/dist/op/activities/postgres-migration.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +19 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/postgres/access/acl.d.ts +119 -0
- package/dist/postgres/access/acl.d.ts.map +1 -0
- package/dist/postgres/access/import.d.ts +13 -0
- package/dist/postgres/access/import.d.ts.map +1 -0
- package/dist/postgres/access/live.d.ts +24 -0
- package/dist/postgres/access/live.d.ts.map +1 -0
- package/dist/postgres/apply/apply.d.ts.map +1 -1
- package/dist/postgres/apply/statements.d.ts +71 -0
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/entities.d.ts +106 -4
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entity-types.d.ts +4 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/import/generator.d.ts +37 -1
- package/dist/postgres/import/generator.d.ts.map +1 -1
- package/dist/postgres/import/live-export.d.ts +6 -0
- package/dist/postgres/import/live-export.d.ts.map +1 -1
- package/dist/postgres/live/bind.d.ts +2 -0
- package/dist/postgres/live/bind.d.ts.map +1 -1
- package/dist/postgres/live/catalog.d.ts +11 -2
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/client.d.ts +3 -0
- package/dist/postgres/live/client.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.d.ts +9 -0
- package/dist/postgres/live/describe-resources.d.ts.map +1 -1
- package/dist/postgres/migrate/op.d.ts +29 -0
- package/dist/postgres/migrate/op.d.ts.map +1 -1
- package/dist/postgres/parser.d.ts +76 -3
- package/dist/postgres/parser.d.ts.map +1 -1
- package/dist/postgres/plan/commands.d.ts +12 -0
- package/dist/postgres/plan/commands.d.ts.map +1 -1
- package/dist/postgres/plan/deep.d.ts.map +1 -1
- package/dist/postgres/plan/diff.d.ts +10 -1
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.d.ts +8 -1
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/rules.d.ts +8 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/schema.d.ts +2 -1
- package/dist/postgres/plan/schema.d.ts.map +1 -1
- package/dist/postgres/plan/server-normalize.d.ts +4 -1
- package/dist/postgres/plan/server-normalize.d.ts.map +1 -1
- package/dist/postgres.d.ts +1 -1
- package/dist/postgres.d.ts.map +1 -1
- package/dist/rules/postgres-templates.ts +2 -2
- package/dist/rules/sqlch001.ts +26 -4
- package/dist/rules/sqlch002.ts +3 -2
- package/dist/rules/sqlpg001.ts +7 -4
- package/dist/rules/templates.ts +2 -2
- package/dist/serializer.d.ts +2 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/sql-files.d.ts +79 -0
- package/dist/sql-files.d.ts.map +1 -0
- package/dist/token-source.d.ts +88 -0
- package/dist/token-source.d.ts.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/clickhouse/access.test.ts +203 -0
- package/src/clickhouse/access.ts +595 -0
- package/src/clickhouse/apply/apply.ts +29 -20
- package/src/clickhouse/apply/statements.ts +105 -8
- package/src/clickhouse/dictionary.test.ts +177 -0
- package/src/clickhouse/entities.ts +356 -6
- package/src/clickhouse/function.test.ts +91 -0
- package/src/clickhouse/http.ts +11 -2
- package/src/clickhouse/import/generator.ts +81 -46
- package/src/clickhouse/import/import.test.ts +9 -3
- package/src/clickhouse/import/ir.ts +6 -2
- package/src/clickhouse/import/parser.ts +10 -18
- package/src/clickhouse/live/access.e2e.test.ts +218 -0
- package/src/clickhouse/live/bind.ts +17 -6
- package/src/clickhouse/live/catalog.ts +106 -10
- package/src/clickhouse/live/describe-resources.ts +23 -10
- package/src/clickhouse/live/dictionaries.e2e.test.ts +186 -0
- package/src/clickhouse/live/functions.e2e.test.ts +155 -0
- package/src/clickhouse/live/live.test.ts +2 -2
- package/src/clickhouse/parser.ts +136 -4
- package/src/clickhouse/plan/commands.ts +18 -7
- package/src/clickhouse/plan/deep.ts +71 -11
- package/src/clickhouse/plan/diff.ts +65 -2
- package/src/clickhouse/plan/normalize.ts +200 -6
- package/src/clickhouse/plan/report.ts +14 -3
- package/src/clickhouse/plan/rules.ts +44 -1
- package/src/clickhouse/plan/schema.ts +40 -17
- package/src/clickhouse/plan/server-format.ts +1 -1
- package/src/clickhouse/rebuild/backfill.ts +5 -25
- package/src/clickhouse/rebuild/observe.ts +7 -3
- package/src/clickhouse/rebuild/op.ts +9 -0
- package/src/clickhouse/rebuild/rebuild.e2e.test.ts +62 -0
- package/src/clickhouse/rebuild/rebuild.test.ts +54 -0
- package/src/clickhouse/rebuild/steps.ts +136 -1
- package/src/clickhouse/testing/fake-server.ts +2 -0
- package/src/clickhouse/testing/writable-server.ts +2 -0
- package/src/clickhouse/topology.ts +17 -2
- package/src/clickhouse.ts +19 -0
- package/src/codegen/classifier-page.ts +2 -0
- package/src/codegen/registry.ts +10 -0
- package/src/config.ts +22 -3
- package/src/core/references.ts +5 -3
- package/src/core/statements.ts +11 -1
- package/src/files/build-root.ts +62 -0
- package/src/files/clickhouse.ts +52 -0
- package/src/files/common.ts +54 -0
- package/src/files/postgres.ts +371 -0
- package/src/generated/lexicon-sql.json +50 -0
- package/src/import-parser.ts +61 -0
- package/src/index.ts +14 -1
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lint/rules/sqlch001.ts +26 -4
- package/src/lint/rules/sqlch002.ts +3 -2
- package/src/lint/rules/sqlpg001.ts +7 -4
- package/src/lint/rules/templates.ts +2 -2
- package/src/lsp/postgres-template.ts +2 -0
- package/src/lsp/template.ts +3 -3
- package/src/mcp/index.ts +6 -6
- package/src/mcp/postgres.ts +1 -1
- package/src/migration-statements.test.ts +25 -0
- package/src/migration-statements.ts +8 -0
- package/src/op/activities/clickhouse-rebuild.ts +1 -0
- package/src/op/activities/postgres-migration.ts +27 -2
- package/src/op/activity-contracts.ts +2 -0
- package/src/plugin.ts +29 -5
- package/src/postgres/access/access.e2e.test.ts +191 -0
- package/src/postgres/access/access.test.ts +147 -0
- package/src/postgres/access/acl.ts +400 -0
- package/src/postgres/access/import.ts +64 -0
- package/src/postgres/access/live.ts +123 -0
- package/src/postgres/apply/apply.ts +117 -4
- package/src/postgres/apply/constraint-sequences.e2e.test.ts +179 -0
- package/src/postgres/apply/safe-sequences.e2e.test.ts +176 -0
- package/src/postgres/apply/statements.test.ts +138 -6
- package/src/postgres/apply/statements.ts +210 -18
- package/src/postgres/entities.ts +287 -11
- package/src/postgres/entity-types.ts +4 -0
- package/src/postgres/import/generator.ts +129 -46
- package/src/postgres/import/live-export.ts +12 -2
- package/src/postgres/live/bind.ts +20 -6
- package/src/postgres/live/catalog.ts +79 -3
- package/src/postgres/live/client.ts +10 -2
- package/src/postgres/live/describe-resources.ts +27 -3
- package/src/postgres/migrate/migrate.test.ts +24 -1
- package/src/postgres/migrate/op.ts +80 -35
- package/src/postgres/migrate/outer.e2e.test.ts +163 -0
- package/src/postgres/parser.ts +247 -8
- package/src/postgres/plan/commands.ts +60 -7
- package/src/postgres/plan/deep.ts +24 -3
- package/src/postgres/plan/diff.ts +71 -6
- package/src/postgres/plan/normalize.ts +115 -2
- package/src/postgres/plan/plan.test.ts +2 -2
- package/src/postgres/plan/rules.ts +49 -5
- package/src/postgres/plan/schema.ts +4 -2
- package/src/postgres/plan/server-normalize.ts +29 -1
- package/src/postgres.ts +11 -0
- package/src/serializer.ts +14 -9
- package/src/skill-defs.ts +1 -1
- package/src/skills/chant-sql-postgres-migration.md +3 -1
- package/src/skills/chant-sql-postgres-plan.md +3 -3
- package/src/skills/chant-sql-postgres.md +9 -2
- package/src/skills/chant-sql-rebuild.md +3 -2
- package/src/skills/chant-sql.md +7 -1
- package/src/sql-files.test.ts +188 -0
- package/src/sql-files.ts +218 -0
- package/src/token-source.e2e.test.ts +142 -0
- package/src/token-source.test.ts +119 -0
- package/src/token-source.ts +189 -0
- package/src/validate.ts +10 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The ClickHouse entities: `database`, `table
|
|
3
|
-
* parse their DDL at fold time into one
|
|
2
|
+
* The ClickHouse entities: `database`, `table`, `view`, `dictionary` and
|
|
3
|
+
* `func` (#3682) tagged templates that parse their DDL at fold time into one
|
|
4
|
+
* Declarable each (chant #3196, #3197).
|
|
4
5
|
*
|
|
5
6
|
* ```ts
|
|
6
7
|
* import { table, view } from "@intentius/chant-lexicon-sql/clickhouse";
|
|
@@ -40,6 +41,7 @@ import type { AttrRef } from "@intentius/chant/attrref";
|
|
|
40
41
|
import { setInterpolationFields } from "@intentius/chant/provenance";
|
|
41
42
|
import { CLICKHOUSE_LEXICAL, isTrivia, SqlSyntaxError, untokenize } from "./tokens";
|
|
42
43
|
import { parseCreate, unquote, type ColumnNode, type CreateNode, type Span, type StorageNode } from "./parser";
|
|
44
|
+
import { parseAccess, type AccessNode } from "./access";
|
|
43
45
|
import { feed, lineage, spanText as text, splice as spliceWith, templateSyntaxError, type TemplateCtx, type TemplateDialect } from "../core/interpolation";
|
|
44
46
|
import { SQL_LEXICON, SqlObject, isColumnRefOf, isSqlObjectOf, makeSqlEntity } from "../core/entity";
|
|
45
47
|
import type { LineageEdge } from "../core/references";
|
|
@@ -54,6 +56,12 @@ export const CLICKHOUSE_ENTITY_TYPES = {
|
|
|
54
56
|
table: "ClickHouse::Table",
|
|
55
57
|
view: "ClickHouse::View",
|
|
56
58
|
materializedView: "ClickHouse::MaterializedView",
|
|
59
|
+
dictionary: "ClickHouse::Dictionary",
|
|
60
|
+
function: "ClickHouse::Function",
|
|
61
|
+
user: "ClickHouse::User",
|
|
62
|
+
role: "ClickHouse::Role",
|
|
63
|
+
rowPolicy: "ClickHouse::RowPolicy",
|
|
64
|
+
grant: "ClickHouse::Grant",
|
|
57
65
|
} as const;
|
|
58
66
|
|
|
59
67
|
export type ClickHouseEntityType = (typeof CLICKHOUSE_ENTITY_TYPES)[keyof typeof CLICKHOUSE_ENTITY_TYPES];
|
|
@@ -136,6 +144,36 @@ export interface ViewProps extends CommonProps, StorageProps {
|
|
|
136
144
|
ifNotExists?: boolean;
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
/** One attribute of a dictionary, as declared. */
|
|
148
|
+
export interface DictionaryAttributeDef {
|
|
149
|
+
name: string;
|
|
150
|
+
type: string;
|
|
151
|
+
/** The value for a key the source does not have: `DEFAULT expr`. */
|
|
152
|
+
default?: string;
|
|
153
|
+
/** `EXPRESSION expr`: what the source computes the attribute from. */
|
|
154
|
+
expression?: string;
|
|
155
|
+
/** `HIERARCHICAL`, `BIDIRECTIONAL`, `INJECTIVE`, `IS_OBJECT_ID`, as written. */
|
|
156
|
+
flags?: string[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface DictionaryProps extends CommonProps {
|
|
160
|
+
/** The attributes, key columns included. */
|
|
161
|
+
columns: DictionaryAttributeDef[];
|
|
162
|
+
/** The key columns: `PRIMARY KEY id`. */
|
|
163
|
+
primaryKey: string;
|
|
164
|
+
/** What is inside `SOURCE(...)`: `CLICKHOUSE(TABLE 'rates' DB 'shop')`. */
|
|
165
|
+
dataSource: string;
|
|
166
|
+
/** What is inside `LAYOUT(...)`: `HASHED()`. */
|
|
167
|
+
layout: string;
|
|
168
|
+
/** What is inside `LIFETIME(...)`: `300`, `MIN 0 MAX 300`. */
|
|
169
|
+
lifetime?: string;
|
|
170
|
+
/** What is inside `RANGE(...)`, for a range layout. */
|
|
171
|
+
range?: string;
|
|
172
|
+
settings?: Record<string, string>;
|
|
173
|
+
orReplace?: boolean;
|
|
174
|
+
ifNotExists?: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
139
177
|
/**
|
|
140
178
|
* A string as a quoted, escaped ClickHouse string literal. An interpolated
|
|
141
179
|
* plain string is SQL text; wrap it in `literal()` when it is a value.
|
|
@@ -176,6 +214,109 @@ export interface ClickHouseView extends ClickHouseRelation {
|
|
|
176
214
|
readonly props: ViewProps;
|
|
177
215
|
}
|
|
178
216
|
|
|
217
|
+
/** A SQL user-defined function, `CREATE FUNCTION name AS (x) -> expr`. It belongs to no database. */
|
|
218
|
+
export interface FunctionProps {
|
|
219
|
+
name: string;
|
|
220
|
+
onCluster?: string;
|
|
221
|
+
/** The parameter names, in order. */
|
|
222
|
+
params: string[];
|
|
223
|
+
/** The expression after `->`, as written. */
|
|
224
|
+
body: string;
|
|
225
|
+
orReplace?: boolean;
|
|
226
|
+
ifNotExists?: boolean;
|
|
227
|
+
ddl: string;
|
|
228
|
+
source: { strings: string[] };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface ClickHouseFunction extends ClickHouseObject {
|
|
232
|
+
readonly entityType: "ClickHouse::Function";
|
|
233
|
+
readonly props: FunctionProps;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** A user (#3682). Its password is the environment's and is never declared (`./access.ts`). */
|
|
237
|
+
export interface UserProps {
|
|
238
|
+
name: string;
|
|
239
|
+
onCluster?: string;
|
|
240
|
+
/** `IDENTIFIED ...` after the keyword, for a method that holds no secret; `NOT IDENTIFIED` as itself. */
|
|
241
|
+
identified?: string;
|
|
242
|
+
host?: string;
|
|
243
|
+
validUntil?: string;
|
|
244
|
+
defaultRole?: string;
|
|
245
|
+
defaultDatabase?: string;
|
|
246
|
+
grantees?: string;
|
|
247
|
+
settings?: string;
|
|
248
|
+
orReplace?: boolean;
|
|
249
|
+
ifNotExists?: boolean;
|
|
250
|
+
ddl: string;
|
|
251
|
+
source: { strings: string[] };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface RoleProps {
|
|
255
|
+
name: string;
|
|
256
|
+
onCluster?: string;
|
|
257
|
+
settings?: string;
|
|
258
|
+
orReplace?: boolean;
|
|
259
|
+
ifNotExists?: boolean;
|
|
260
|
+
ddl: string;
|
|
261
|
+
source: { strings: string[] };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface RowPolicyProps {
|
|
265
|
+
name: string;
|
|
266
|
+
onCluster?: string;
|
|
267
|
+
/** The table's database, when the DDL qualifies it. */
|
|
268
|
+
database?: string;
|
|
269
|
+
table: string;
|
|
270
|
+
/** `PERMISSIVE` or `RESTRICTIVE`. */
|
|
271
|
+
as?: string;
|
|
272
|
+
for?: string;
|
|
273
|
+
using: string;
|
|
274
|
+
to?: string;
|
|
275
|
+
orReplace?: boolean;
|
|
276
|
+
ifNotExists?: boolean;
|
|
277
|
+
ddl: string;
|
|
278
|
+
source: { strings: string[] };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface GrantProps {
|
|
282
|
+
/** The grant's name in chant: its grantees, for a report. */
|
|
283
|
+
name: string;
|
|
284
|
+
onCluster?: string;
|
|
285
|
+
/** The privileges granted (`SELECT(a)`, `SHOW TABLES`), or the roles. */
|
|
286
|
+
privileges?: string[];
|
|
287
|
+
roles?: string[];
|
|
288
|
+
/** `db.table`, `db.*`, `*.*`. */
|
|
289
|
+
on?: string;
|
|
290
|
+
to: string[];
|
|
291
|
+
withGrantOption?: boolean;
|
|
292
|
+
withAdminOption?: boolean;
|
|
293
|
+
ddl: string;
|
|
294
|
+
source: { strings: string[] };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export interface ClickHouseUser extends ClickHouseObject {
|
|
298
|
+
readonly entityType: "ClickHouse::User";
|
|
299
|
+
readonly props: UserProps;
|
|
300
|
+
}
|
|
301
|
+
export interface ClickHouseRole extends ClickHouseObject {
|
|
302
|
+
readonly entityType: "ClickHouse::Role";
|
|
303
|
+
readonly props: RoleProps;
|
|
304
|
+
}
|
|
305
|
+
export interface ClickHouseRowPolicy extends ClickHouseObject {
|
|
306
|
+
readonly entityType: "ClickHouse::RowPolicy";
|
|
307
|
+
readonly props: RowPolicyProps;
|
|
308
|
+
}
|
|
309
|
+
export interface ClickHouseGrant extends ClickHouseObject {
|
|
310
|
+
readonly entityType: "ClickHouse::Grant";
|
|
311
|
+
readonly props: GrantProps;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** A dictionary: its attributes are columns `dictGet` reads, so SQL can reference them. */
|
|
315
|
+
export interface ClickHouseDictionary extends ClickHouseRelation {
|
|
316
|
+
readonly entityType: "ClickHouse::Dictionary";
|
|
317
|
+
readonly props: DictionaryProps;
|
|
318
|
+
}
|
|
319
|
+
|
|
179
320
|
function makeEntity(
|
|
180
321
|
entityType: ClickHouseEntityType,
|
|
181
322
|
sqlName: string,
|
|
@@ -303,7 +444,7 @@ function qualified(ctx: Ctx, span: Span): { database?: string; name: string } {
|
|
|
303
444
|
return pieces.length >= 2 ? { database: pieces[pieces.length - 2], name: pieces[pieces.length - 1]! } : { name: pieces[0] ?? "" };
|
|
304
445
|
}
|
|
305
446
|
|
|
306
|
-
type Expect = "database" | "table" | "view";
|
|
447
|
+
type Expect = "database" | "table" | "view" | "dictionary" | "func";
|
|
307
448
|
|
|
308
449
|
function build(tag: Expect, strings: TemplateStringsArray | readonly string[], values: readonly unknown[]): ClickHouseObject {
|
|
309
450
|
const parts = templateParts(strings);
|
|
@@ -317,9 +458,18 @@ function build(tag: Expect, strings: TemplateStringsArray | readonly string[], v
|
|
|
317
458
|
}
|
|
318
459
|
const ctx: Ctx = { d: CLICKHOUSE_TEMPLATES, tokens, values, fed: values.map(() => new Set<string>()) };
|
|
319
460
|
for (const t of tokens) if (t.splice !== undefined) ctx.fed[t.splice]!.add("ddl");
|
|
320
|
-
if (node.statement !== tag) {
|
|
321
|
-
const holds =
|
|
322
|
-
|
|
461
|
+
if (node.statement !== (tag === "func" ? "function" : tag)) {
|
|
462
|
+
const holds =
|
|
463
|
+
node.statement === "database"
|
|
464
|
+
? "CREATE DATABASE"
|
|
465
|
+
: node.statement === "table"
|
|
466
|
+
? "CREATE TABLE"
|
|
467
|
+
: node.statement === "dictionary"
|
|
468
|
+
? "CREATE DICTIONARY"
|
|
469
|
+
: node.statement === "function"
|
|
470
|
+
? "CREATE FUNCTION"
|
|
471
|
+
: "CREATE VIEW";
|
|
472
|
+
throw new SqlTemplateError(tag, `holds a ${holds}; use the ${node.statement === "function" ? "func" : node.statement} tag`, 0, 0);
|
|
323
473
|
}
|
|
324
474
|
const { database, name } = qualified(ctx, node.name);
|
|
325
475
|
const sqlName = database ? `${quoteIdentifier(database)}.${quoteIdentifier(name)}` : quoteIdentifier(name);
|
|
@@ -375,6 +525,50 @@ function build(tag: Expect, strings: TemplateStringsArray | readonly string[], v
|
|
|
375
525
|
return done(makeEntity(CLICKHOUSE_ENTITY_TYPES.table, sqlName, props, columns.map((c) => c.name), dependsOn));
|
|
376
526
|
}
|
|
377
527
|
|
|
528
|
+
if (node.statement === "function") {
|
|
529
|
+
for (const p of node.params) feed(ctx, p.span, "params");
|
|
530
|
+
const props: FunctionProps = strip({
|
|
531
|
+
name,
|
|
532
|
+
onCluster: common.onCluster,
|
|
533
|
+
orReplace: node.orReplace,
|
|
534
|
+
ifNotExists: node.ifNotExists,
|
|
535
|
+
params: node.params.map((p) => p.name || req(text(ctx, p.span))),
|
|
536
|
+
body: req(text(ctx, node.body, "body")),
|
|
537
|
+
ddl,
|
|
538
|
+
source,
|
|
539
|
+
});
|
|
540
|
+
return done(makeEntity(CLICKHOUSE_ENTITY_TYPES.function, quoteIdentifier(name), props, undefined, dependsOn));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (node.statement === "dictionary") {
|
|
544
|
+
const columns: DictionaryAttributeDef[] = node.attributes.map((a) => {
|
|
545
|
+
for (const span of [a.nameSpan, a.type, a.default, a.expression]) feed(ctx, span, "columns");
|
|
546
|
+
return strip({
|
|
547
|
+
name: a.name || req(text(ctx, a.nameSpan)),
|
|
548
|
+
type: req(text(ctx, a.type)),
|
|
549
|
+
default: text(ctx, a.default),
|
|
550
|
+
expression: text(ctx, a.expression),
|
|
551
|
+
flags: a.flags.length > 0 ? a.flags : undefined,
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
const settings = text(ctx, node.settings, "settings");
|
|
555
|
+
const props: DictionaryProps = strip({
|
|
556
|
+
...common,
|
|
557
|
+
orReplace: node.orReplace,
|
|
558
|
+
ifNotExists: node.ifNotExists,
|
|
559
|
+
columns,
|
|
560
|
+
primaryKey: req(text(ctx, node.primaryKey, "primaryKey")),
|
|
561
|
+
dataSource: req(text(ctx, node.source, "dataSource")),
|
|
562
|
+
layout: req(text(ctx, node.layout, "layout")),
|
|
563
|
+
lifetime: text(ctx, node.lifetime, "lifetime"),
|
|
564
|
+
range: text(ctx, node.range, "range"),
|
|
565
|
+
settings: settings === undefined ? undefined : dictionarySettings(settings),
|
|
566
|
+
ddl,
|
|
567
|
+
source,
|
|
568
|
+
});
|
|
569
|
+
return done(makeEntity(CLICKHOUSE_ENTITY_TYPES.dictionary, sqlName, props, columns.map((c) => c.name), dependsOn));
|
|
570
|
+
}
|
|
571
|
+
|
|
378
572
|
const lin = lineage<ClickHouseObject>(ctx, node.select);
|
|
379
573
|
let to: ClickHouseObject | string | undefined;
|
|
380
574
|
if (node.to) {
|
|
@@ -406,6 +600,26 @@ function build(tag: Expect, strings: TemplateStringsArray | readonly string[], v
|
|
|
406
600
|
return done(makeEntity(type, sqlName, props, outputs, dependsOn));
|
|
407
601
|
}
|
|
408
602
|
|
|
603
|
+
/** `a = 1, b = 'x'` as a record of each setting's value as written. */
|
|
604
|
+
function dictionarySettings(text: string): Record<string, string> {
|
|
605
|
+
const out: Record<string, string> = {};
|
|
606
|
+
let depth = 0;
|
|
607
|
+
let cur = "";
|
|
608
|
+
const flush = () => {
|
|
609
|
+
const at = cur.indexOf("=");
|
|
610
|
+
if (at > 0) out[cur.slice(0, at).trim()] = cur.slice(at + 1).trim();
|
|
611
|
+
cur = "";
|
|
612
|
+
};
|
|
613
|
+
for (const ch of text) {
|
|
614
|
+
if (ch === "(") depth++;
|
|
615
|
+
else if (ch === ")") depth--;
|
|
616
|
+
if (ch === "," && depth === 0) flush();
|
|
617
|
+
else cur += ch;
|
|
618
|
+
}
|
|
619
|
+
flush();
|
|
620
|
+
return out;
|
|
621
|
+
}
|
|
622
|
+
|
|
409
623
|
/** `` database`CREATE DATABASE ...` ``: one ClickHouse database, parsed at build time. */
|
|
410
624
|
export function database(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseDatabase {
|
|
411
625
|
return build("database", strings, values) as ClickHouseDatabase;
|
|
@@ -423,3 +637,139 @@ export function table(strings: TemplateStringsArray, ...values: unknown[]): Clic
|
|
|
423
637
|
export function view(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseView {
|
|
424
638
|
return build("view", strings, values) as ClickHouseView;
|
|
425
639
|
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* `` dictionary`CREATE DICTIONARY ...` ``: one dictionary, its attributes,
|
|
643
|
+
* key, source, layout and lifetime, parsed at build time (#3682). Its
|
|
644
|
+
* attributes are columns: `${rates.columns.rate}`.
|
|
645
|
+
*/
|
|
646
|
+
export function dictionary(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseDictionary {
|
|
647
|
+
return build("dictionary", strings, values) as ClickHouseDictionary;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* `` func`CREATE FUNCTION ...` ``: one SQL user-defined function, a lambda
|
|
652
|
+
* with a name (#3682). It belongs to no database, and has no comment, so it
|
|
653
|
+
* carries no ownership marker: chant replaces it when its declaration
|
|
654
|
+
* changes and never drops it.
|
|
655
|
+
*/
|
|
656
|
+
export function func(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseFunction {
|
|
657
|
+
return build("func", strings, values) as ClickHouseFunction;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
type AccessTag = "user" | "role" | "policy" | "grant";
|
|
661
|
+
const ACCESS_STATEMENT: Record<AccessTag, AccessNode["statement"]> = { user: "user", role: "role", policy: "rowPolicy", grant: "grant" };
|
|
662
|
+
const ACCESS_HOLDS: Record<AccessNode["statement"], [string, AccessTag]> = {
|
|
663
|
+
user: ["CREATE USER", "user"],
|
|
664
|
+
role: ["CREATE ROLE", "role"],
|
|
665
|
+
rowPolicy: ["CREATE ROW POLICY", "policy"],
|
|
666
|
+
grant: ["GRANT", "grant"],
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
function buildAccess(tag: AccessTag, strings: TemplateStringsArray | readonly string[], values: readonly unknown[]): ClickHouseObject {
|
|
670
|
+
const parts = templateParts(strings);
|
|
671
|
+
const tokens = splice(tag, parts, values);
|
|
672
|
+
let node: AccessNode;
|
|
673
|
+
try {
|
|
674
|
+
node = parseAccess(tokens);
|
|
675
|
+
} catch (err) {
|
|
676
|
+
if (!(err instanceof SqlSyntaxError)) throw err;
|
|
677
|
+
throw templateSyntaxError(tag, parts, err);
|
|
678
|
+
}
|
|
679
|
+
if (node.statement !== ACCESS_STATEMENT[tag]) {
|
|
680
|
+
const [holds, use] = ACCESS_HOLDS[node.statement];
|
|
681
|
+
throw new SqlTemplateError(tag, `holds a ${holds}; use the ${use} tag`, 0, 0);
|
|
682
|
+
}
|
|
683
|
+
if (node.revoke) throw new SqlTemplateError(tag, "holds a REVOKE; declare the grants a grantee keeps, and a plan revokes the rest", 0, 0);
|
|
684
|
+
const ctx: Ctx = { d: CLICKHOUSE_TEMPLATES, tokens, values, fed: values.map(() => new Set<string>()) };
|
|
685
|
+
for (const t of tokens) if (t.splice !== undefined) ctx.fed[t.splice]!.add("ddl");
|
|
686
|
+
const ddl = req(untokenize(tokens, (i) => renderReference(values[i])).trim().replace(/;\s*$/, ""));
|
|
687
|
+
const source = { strings: parts };
|
|
688
|
+
const dependsOn = [...new Set(values.filter((v) => isClickHouseObject(v) || isColumnRef(v)))];
|
|
689
|
+
const done = <T extends ClickHouseObject>(entity: T): T => {
|
|
690
|
+
setInterpolationFields(entity, ctx.fed.map((paths) => [...paths].sort()));
|
|
691
|
+
return entity;
|
|
692
|
+
};
|
|
693
|
+
const rendered = (span: Span | undefined): string | undefined =>
|
|
694
|
+
span ? untokenize(tokens.slice(span.from, span.to), (i) => renderReference(values[i])).trim() || undefined : undefined;
|
|
695
|
+
const name = node.name ? unquote(rendered(node.name) ?? "") : "";
|
|
696
|
+
const c = node.clauses;
|
|
697
|
+
const common = { name, onCluster: text(ctx, node.onCluster, "onCluster"), orReplace: node.orReplace || undefined, ifNotExists: node.ifNotExists || undefined };
|
|
698
|
+
if (node.statement === "user") {
|
|
699
|
+
const props: UserProps = strip({
|
|
700
|
+
...common,
|
|
701
|
+
identified: c.notIdentified ? "NOT IDENTIFIED" : rendered(c.identified),
|
|
702
|
+
host: rendered(c.host),
|
|
703
|
+
validUntil: rendered(c.validUntil),
|
|
704
|
+
defaultRole: rendered(c.defaultRole),
|
|
705
|
+
defaultDatabase: rendered(c.defaultDatabase),
|
|
706
|
+
grantees: rendered(c.grantees),
|
|
707
|
+
settings: rendered(c.settings),
|
|
708
|
+
ddl,
|
|
709
|
+
source,
|
|
710
|
+
});
|
|
711
|
+
return done(makeEntity(CLICKHOUSE_ENTITY_TYPES.user, quoteIdentifier(name), props, undefined, dependsOn));
|
|
712
|
+
}
|
|
713
|
+
if (node.statement === "role") {
|
|
714
|
+
const props: RoleProps = strip({ ...common, settings: rendered(c.settings), ddl, source });
|
|
715
|
+
return done(makeEntity(CLICKHOUSE_ENTITY_TYPES.role, quoteIdentifier(name), props, undefined, dependsOn));
|
|
716
|
+
}
|
|
717
|
+
if (node.statement === "rowPolicy") {
|
|
718
|
+
const { database, name: tableName } = qualified(ctx, node.table!);
|
|
719
|
+
const props: RowPolicyProps = strip({
|
|
720
|
+
...common,
|
|
721
|
+
database,
|
|
722
|
+
table: tableName,
|
|
723
|
+
as: rendered(c.as)?.toUpperCase(),
|
|
724
|
+
for: rendered(c.for),
|
|
725
|
+
using: req(rendered(c.using)),
|
|
726
|
+
to: rendered(c.to),
|
|
727
|
+
ddl,
|
|
728
|
+
source,
|
|
729
|
+
});
|
|
730
|
+
return done(makeEntity(CLICKHOUSE_ENTITY_TYPES.rowPolicy, quoteIdentifier(name), props, undefined, dependsOn));
|
|
731
|
+
}
|
|
732
|
+
const items = node.items.map((i) => rendered(i) ?? "");
|
|
733
|
+
const to = node.grantees.map((g) => unquote(rendered(g) ?? ""));
|
|
734
|
+
const props: GrantProps = strip({
|
|
735
|
+
name: to.join(", "),
|
|
736
|
+
onCluster: common.onCluster,
|
|
737
|
+
privileges: node.target ? items : undefined,
|
|
738
|
+
roles: node.target ? undefined : items.map(unquote),
|
|
739
|
+
on: rendered(node.target),
|
|
740
|
+
to,
|
|
741
|
+
withGrantOption: node.withOption === "GRANT" || undefined,
|
|
742
|
+
withAdminOption: node.withOption === "ADMIN" || undefined,
|
|
743
|
+
ddl,
|
|
744
|
+
source,
|
|
745
|
+
});
|
|
746
|
+
return done(makeEntity(CLICKHOUSE_ENTITY_TYPES.grant, "", props, undefined, dependsOn));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* `` user`CREATE USER ...` ``: one user (#3682). A password is the
|
|
751
|
+
* environment's: `IDENTIFIED BY` is refused, and a user declared without
|
|
752
|
+
* `IDENTIFIED` is compared without it and is not created by chant.
|
|
753
|
+
*/
|
|
754
|
+
export function user(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseUser {
|
|
755
|
+
return buildAccess("user", strings, values) as ClickHouseUser;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/** `` role`CREATE ROLE ...` ``: one role (#3682). Interpolate it where a grant or a user names it. */
|
|
759
|
+
export function role(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseRole {
|
|
760
|
+
return buildAccess("role", strings, values) as ClickHouseRole;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/** `` policy`CREATE ROW POLICY ...` ``: one row policy on one table (#3682). */
|
|
764
|
+
export function policy(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseRowPolicy {
|
|
765
|
+
return buildAccess("policy", strings, values) as ClickHouseRowPolicy;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* `` grant`GRANT ...` ``: privileges or roles granted to users or roles
|
|
770
|
+
* (#3682). Every grant declaration naming a grantee adds up to its complete
|
|
771
|
+
* list: a plan revokes what it holds besides.
|
|
772
|
+
*/
|
|
773
|
+
export function grant(strings: TemplateStringsArray, ...values: unknown[]): ClickHouseGrant {
|
|
774
|
+
return buildAccess("grant", strings, values) as ClickHouseGrant;
|
|
775
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL user-defined functions (#3682): the `func` tag, a function keyed
|
|
3
|
+
* apart from any database, a changed lambda replacing it (SQLCH260), no
|
|
4
|
+
* marker and no restamp, and `ON CLUSTER` per topology.
|
|
5
|
+
* `live/functions.e2e.test.ts` runs them against a server.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "vitest";
|
|
9
|
+
import { func, table, CLICKHOUSE_ENTITY_TYPES } from "./entities";
|
|
10
|
+
import { canonicalObject, objectKey, scopeOf } from "./plan/normalize";
|
|
11
|
+
import { diffSchemas } from "./plan/diff";
|
|
12
|
+
import { CLASSIFIER_RULES } from "./plan/rules";
|
|
13
|
+
import { createStatement, dropStatement, planStatements, type DeclaredObject } from "./apply/statements";
|
|
14
|
+
import { renderStatement } from "./topology";
|
|
15
|
+
|
|
16
|
+
const DECLARED = "CREATE FUNCTION linear AS (x, k, b) -> k*x + b";
|
|
17
|
+
// What clickhouse-server 26.8.15.10 prints for DECLARED (`system.functions.create_query`).
|
|
18
|
+
const SHOWN = "CREATE FUNCTION linear AS (x, k, b) -> ((k * x) + b)";
|
|
19
|
+
|
|
20
|
+
const declared = (ddl: string): DeclaredObject => {
|
|
21
|
+
const canonical = canonicalObject(ddl);
|
|
22
|
+
return { exportName: "linear", type: CLICKHOUSE_ENTITY_TYPES.function, key: objectKey(canonical), ddl, canonical, dependsOn: [] };
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("the func tag", () => {
|
|
26
|
+
test("parses the name, the parameters and the expression; a function has no database", () => {
|
|
27
|
+
const f = func([DECLARED] as unknown as TemplateStringsArray);
|
|
28
|
+
expect(f.entityType).toBe("ClickHouse::Function");
|
|
29
|
+
expect(f.sqlName).toBe("linear");
|
|
30
|
+
expect(f.props).toMatchObject({ name: "linear", params: ["x", "k", "b"], body: "k*x + b" });
|
|
31
|
+
expect(func(["CREATE FUNCTION twice AS x -> x * 2"] as unknown as TemplateStringsArray).props.params).toEqual(["x"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("a qualified name, a missing arrow and another statement are refused", () => {
|
|
35
|
+
expect(() => func(["CREATE FUNCTION shop.linear AS (x) -> x"] as unknown as TemplateStringsArray)).toThrow(/no database/);
|
|
36
|
+
expect(() => func(["CREATE FUNCTION linear AS (x) x"] as unknown as TemplateStringsArray)).toThrow(/expected ->/);
|
|
37
|
+
expect(() => table([DECLARED] as unknown as TemplateStringsArray)).toThrow(/use the func tag/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("keyed and scoped apart from a database of the same name", () => {
|
|
41
|
+
const f = canonicalObject("CREATE FUNCTION shop AS (x) -> x");
|
|
42
|
+
const db = canonicalObject("CREATE DATABASE shop");
|
|
43
|
+
expect([objectKey(f), objectKey(db)]).toEqual(["function shop", "shop"]);
|
|
44
|
+
expect([scopeOf(f), scopeOf(db)]).toEqual(["function shop", "shop"]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("a function compared and changed", () => {
|
|
49
|
+
test("a changed expression is SQLCH260; the server's formatting is left to its formatter", () => {
|
|
50
|
+
const changes = diffSchemas([{ key: "f", canonical: canonicalObject(SHOWN) }], [{ key: "f", canonical: canonicalObject(DECLARED) }]).changes;
|
|
51
|
+
// The rules leave the parentheses the server adds; a plan against a server asks its formatter (`dropFormattingOnly`).
|
|
52
|
+
expect(changes.map((c) => [c.field, c.rule])).toEqual([["lambda", "SQLCH260"]]);
|
|
53
|
+
expect(CLASSIFIER_RULES.SQLCH260.class).toBe("metadata");
|
|
54
|
+
expect(diffSchemas([{ key: "f", canonical: canonicalObject(DECLARED) }], [{ key: "f", canonical: canonicalObject("CREATE FUNCTION linear AS (x, k, b) -> k * x + b") }]).changes).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("created as declared, with no marker; replaced with CREATE OR REPLACE; never restamped", () => {
|
|
58
|
+
const marker = { stack: "shop", env: "prod" };
|
|
59
|
+
expect(createStatement(declared(DECLARED), marker)).toBe(DECLARED);
|
|
60
|
+
const after = declared("CREATE FUNCTION linear AS (x, k, b) -> k*x + b + 1");
|
|
61
|
+
const current = new Map([[after.key, canonicalObject(DECLARED)]]);
|
|
62
|
+
const changes = diffSchemas([{ key: after.key, canonical: canonicalObject(DECLARED) }], [{ key: after.key, canonical: after.canonical }]).changes;
|
|
63
|
+
const plan = planStatements({ declared: [after], changes, current, marker, carriesMarker: () => false });
|
|
64
|
+
const entry = plan.objects[0]!;
|
|
65
|
+
expect(entry.verdict === "alter" ? entry.steps.map((s) => s.sql) : []).toEqual(["CREATE OR REPLACE FUNCTION linear AS (x, k, b) -> k*x + b + 1"]);
|
|
66
|
+
// As declared, and with no marker to carry: nothing is sent.
|
|
67
|
+
const same = planStatements({ declared: [declared(DECLARED)], changes: [], current: new Map([[after.key, canonicalObject(DECLARED)]]), marker, carriesMarker: () => false });
|
|
68
|
+
expect(same.objects[0]!.verdict === "alter" ? same.objects[0]!.steps : ["?"]).toEqual([]);
|
|
69
|
+
expect(dropStatement(CLICKHOUSE_ENTITY_TYPES.function, undefined, "linear")).toBe("DROP FUNCTION `linear`");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("ON CLUSTER on a cluster, and in the replicated topology its cluster, since a function is in no database", () => {
|
|
73
|
+
expect(renderStatement(DECLARED, { kind: "cluster", cluster: "main" })).toBe("CREATE FUNCTION linear ON CLUSTER `main` AS (x, k, b) -> k*x + b");
|
|
74
|
+
expect(renderStatement(DECLARED, { kind: "replicated", cluster: "main" })).toBe("CREATE FUNCTION linear ON CLUSTER `main` AS (x, k, b) -> k*x + b");
|
|
75
|
+
expect(renderStatement(DECLARED, { kind: "replicated" })).toBe(DECLARED);
|
|
76
|
+
expect(renderStatement(DECLARED, { kind: "single" })).toBe(DECLARED);
|
|
77
|
+
expect(renderStatement("DROP FUNCTION `linear`", { kind: "cluster", cluster: "main" })).toBe("DROP FUNCTION `linear` ON CLUSTER `main`");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("importing a function", () => {
|
|
82
|
+
test("the server's statement becomes a func declaration", async () => {
|
|
83
|
+
const { ClickHouseSqlParser } = await import("./import/parser");
|
|
84
|
+
const { ClickHouseGenerator } = await import("./import/generator");
|
|
85
|
+
const ir = new ClickHouseSqlParser().parse(`${SHOWN};`);
|
|
86
|
+
expect(ir.resources.map((r) => [r.logicalId, r.type])).toEqual([["linear", "ClickHouse::Function"]]);
|
|
87
|
+
const [file] = new ClickHouseGenerator().generate(ir);
|
|
88
|
+
expect(file!.content).toContain('import { func } from "@intentius/chant-lexicon-sql/clickhouse";');
|
|
89
|
+
expect(file!.content).toContain("export const linear = func`");
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/clickhouse/http.ts
CHANGED
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
* line, which is all generation (and, later, observation and import) reads.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { CredentialSource } from "../token-source";
|
|
11
|
+
|
|
10
12
|
/** Where a server answers, and as whom. */
|
|
11
13
|
export interface ClickHouseEndpoint {
|
|
12
14
|
/** Base URL of the HTTP interface, e.g. `http://127.0.0.1:8123`. */
|
|
13
15
|
url: string;
|
|
14
16
|
user?: string;
|
|
15
17
|
password?: string;
|
|
18
|
+
/** Mints the password for each request, in place of `password` (a profile's token source, #3685). */
|
|
19
|
+
token?: CredentialSource;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/** A query the server refused, with the server's own message. */
|
|
@@ -49,7 +53,8 @@ export async function clickhouseQuery<Row = Record<string, unknown>>(
|
|
|
49
53
|
): Promise<Row[]> {
|
|
50
54
|
const headers: Record<string, string> = { "content-type": "text/plain; charset=utf-8" };
|
|
51
55
|
if (endpoint.user !== undefined) headers["X-ClickHouse-User"] = endpoint.user;
|
|
52
|
-
|
|
56
|
+
const password = endpoint.token ? await endpoint.token.get() : endpoint.password;
|
|
57
|
+
if (password !== undefined) headers["X-ClickHouse-Key"] = password;
|
|
53
58
|
const params = new URLSearchParams({ default_format: "JSONEachRow", ...(opts.queryId !== undefined ? { query_id: opts.queryId } : {}), ...opts.settings });
|
|
54
59
|
const res = await fetch(`${endpoint.url.replace(/\/$/, "")}/?${params.toString()}`, {
|
|
55
60
|
method: "POST",
|
|
@@ -58,7 +63,11 @@ export async function clickhouseQuery<Row = Record<string, unknown>>(
|
|
|
58
63
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
59
64
|
});
|
|
60
65
|
const text = await res.text();
|
|
61
|
-
if (!res.ok)
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
// A refused token is not reused: the next request mints another.
|
|
68
|
+
if (endpoint.token && (res.status === 401 || res.status === 403 || /AUTHENTICATION_FAILED|Code: 516/.test(text))) endpoint.token.invalidate();
|
|
69
|
+
throw new ClickHouseQueryError(res.status, text, sql);
|
|
70
|
+
}
|
|
62
71
|
return text
|
|
63
72
|
.split("\n")
|
|
64
73
|
.filter((line) => line.length > 0)
|