@intentius/chant-lexicon-sql 0.115.0 → 0.117.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/codegen/registry.d.ts.map +1 -1
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +5 -5
- package/dist/lint/rules/sqlpg001.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 +35 -0
- package/dist/okf/index.md +7 -0
- package/dist/okf/types/PostgresDefaultPrivileges.md +9 -0
- package/dist/okf/types/PostgresFunction.md +9 -0
- package/dist/okf/types/PostgresGrant.md +9 -0
- package/dist/okf/types/PostgresPolicy.md +9 -0
- package/dist/okf/types/PostgresProcedure.md +9 -0
- package/dist/okf/types/PostgresRole.md +9 -0
- package/dist/okf/types/PostgresTrigger.md +9 -0
- package/dist/op/activities/postgres-migration.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +10 -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 +6 -1
- package/dist/postgres/apply/apply.d.ts.map +1 -1
- package/dist/postgres/apply/statements.d.ts +12 -2
- package/dist/postgres/apply/statements.d.ts.map +1 -1
- package/dist/postgres/entities.d.ts +212 -3
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/postgres/entity-types.d.ts +7 -0
- package/dist/postgres/entity-types.d.ts.map +1 -1
- package/dist/postgres/import/generator.d.ts +4 -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 +25 -4
- package/dist/postgres/live/catalog.d.ts.map +1 -1
- package/dist/postgres/live/describe-resources.d.ts +15 -1
- 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 +155 -5
- 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 +11 -2
- package/dist/postgres/plan/diff.d.ts.map +1 -1
- package/dist/postgres/plan/normalize.d.ts +28 -2
- package/dist/postgres/plan/normalize.d.ts.map +1 -1
- package/dist/postgres/plan/rules.d.ts +14 -0
- package/dist/postgres/plan/rules.d.ts.map +1 -1
- package/dist/postgres/plan/schema.d.ts +12 -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/sqlpg001.ts +10 -4
- package/dist/validate.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/codegen/postgres-classifier-page.ts +1 -1
- package/src/codegen/registry.ts +7 -0
- package/src/config.ts +10 -0
- package/src/generated/lexicon-sql.json +35 -0
- package/src/index.ts +1 -1
- package/src/lint/rules/postgres-templates.ts +2 -2
- package/src/lint/rules/sqlpg001.ts +10 -4
- package/src/lsp/postgres-template.ts +8 -0
- package/src/mcp/postgres.ts +1 -1
- package/src/migration-statements.ts +7 -0
- package/src/op/activities/postgres-migration.ts +27 -2
- package/src/op/activity-contracts.ts +1 -0
- package/src/plugin.ts +6 -0
- 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 +135 -6
- package/src/postgres/apply/statements.ts +104 -16
- package/src/postgres/entities.ts +613 -14
- package/src/postgres/entity-types.ts +7 -0
- package/src/postgres/import/generator.ts +18 -5
- package/src/postgres/import/live-export.ts +16 -2
- package/src/postgres/live/bind.ts +3 -0
- package/src/postgres/live/catalog.ts +155 -5
- package/src/postgres/live/describe-resources.ts +43 -9
- package/src/postgres/live/routines.e2e.test.ts +173 -0
- package/src/postgres/migrate/batches.e2e.test.ts +2 -1
- package/src/postgres/migrate/migrate.e2e.test.ts +2 -1
- 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 +554 -9
- package/src/postgres/plan/commands.ts +61 -8
- package/src/postgres/plan/deep.ts +79 -18
- package/src/postgres/plan/diff.ts +114 -9
- package/src/postgres/plan/normalize.ts +272 -5
- package/src/postgres/plan/rules.ts +80 -0
- package/src/postgres/plan/schema.ts +27 -6
- package/src/postgres/plan/server-normalize.ts +53 -1
- package/src/postgres/routines.test.ts +150 -0
- package/src/postgres.ts +22 -0
- package/src/skill-defs.ts +1 -1
- package/src/skills/chant-sql-postgres-migration.md +3 -1
- package/src/skills/chant-sql-postgres.md +25 -2
- package/src/validate.ts +7 -0
package/src/postgres/entities.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Postgres entities: `schema`, `table`, `index`, `view`, `sequence`,
|
|
3
|
-
* `type`, `domain
|
|
4
|
-
*
|
|
3
|
+
* `type`, `domain`, `extension`, `func`, `procedure`, `trigger`, `policy`,
|
|
4
|
+
* `role` and `grant` tagged templates that parse their DDL at fold time into
|
|
5
|
+
* one Declarable each (chant #3278, #3279, #3680, #3681).
|
|
5
6
|
*
|
|
6
7
|
* ```ts
|
|
7
8
|
* import { schema, table, index } from "@intentius/chant-lexicon-sql/postgres";
|
|
@@ -40,6 +41,12 @@
|
|
|
40
41
|
* - `literal(value)` is a quoted string literal (`'` doubled; a backslash is an
|
|
41
42
|
* ordinary character, since standard_conforming_strings is on).
|
|
42
43
|
*
|
|
44
|
+
* A function's or procedure's body is a string constant (`AS $$ ... $$`), kept
|
|
45
|
+
* verbatim. An interpolation inside it renders as text (an object as its
|
|
46
|
+
* schema-qualified name, a column reference as the column's name) and is
|
|
47
|
+
* recorded as a reference, so the routine is created after what its body
|
|
48
|
+
* names: `` func`... AS $$ SELECT count(*) FROM ${users} $$` ``.
|
|
49
|
+
*
|
|
43
50
|
* Postgres has no inline comment clause, so a template may follow its CREATE
|
|
44
51
|
* with `COMMENT ON` statements for the same object, its columns and its
|
|
45
52
|
* constraints. They fold into the entity's `comment`, each column's and each
|
|
@@ -49,7 +56,7 @@
|
|
|
49
56
|
import type { AttrRef } from "@intentius/chant/attrref";
|
|
50
57
|
import { setInterpolationFields } from "@intentius/chant/provenance";
|
|
51
58
|
import { isTrivia, POSTGRES_LEXICAL, SqlSyntaxError, untokenize, type Token } from "./tokens";
|
|
52
|
-
import { identValue, parseStatements, type ColumnNode, type CommentNode, type ConstraintNode, type NameNode, type Span, type StatementNode } from "./parser";
|
|
59
|
+
import { identValue, parseStatements, type ColumnNode, type CommentNode, type ConstraintNode, type GrantNode, type NameNode, type RowSecurityNode, type Span, type StatementNode } from "./parser";
|
|
53
60
|
import { keywordCategory, quoteIdent } from "./keywords";
|
|
54
61
|
import { feed, lineage, spanText as text, splice as spliceWith, templateSyntaxError, type TemplateCtx, type TemplateDialect } from "../core/interpolation";
|
|
55
62
|
import { SqlObject, isColumnRefOf, isSqlObjectOf, makeSqlEntity } from "../core/entity";
|
|
@@ -176,6 +183,10 @@ export interface TableProps extends CommonProps {
|
|
|
176
183
|
with?: string;
|
|
177
184
|
onCommit?: string;
|
|
178
185
|
tablespace?: string;
|
|
186
|
+
/** `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` after the CREATE (true), or `DISABLE` (false). */
|
|
187
|
+
rowSecurity?: boolean;
|
|
188
|
+
/** `ALTER TABLE ... FORCE ROW LEVEL SECURITY` after the CREATE (true), or `NO FORCE` (false): the owner is subject to the policies too. */
|
|
189
|
+
forceRowSecurity?: boolean;
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
export interface IndexProps extends CommonProps {
|
|
@@ -258,6 +269,143 @@ export interface ExtensionProps extends Omit<CommonProps, "schema"> {
|
|
|
258
269
|
ifNotExists?: boolean;
|
|
259
270
|
}
|
|
260
271
|
|
|
272
|
+
/** One parameter of a function or procedure, as written. */
|
|
273
|
+
export interface RoutineArgDef {
|
|
274
|
+
/** `in` when not written. */
|
|
275
|
+
mode: "in" | "out" | "inout" | "variadic";
|
|
276
|
+
name?: string;
|
|
277
|
+
type: string;
|
|
278
|
+
default?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface RoutineProps extends CommonProps {
|
|
282
|
+
orReplace?: boolean;
|
|
283
|
+
args: RoutineArgDef[];
|
|
284
|
+
/** `RETURNS type`, `SETOF` included (`setof app.users`). */
|
|
285
|
+
returns?: string;
|
|
286
|
+
/** `RETURNS TABLE (...)`'s columns. */
|
|
287
|
+
returnsTable?: Array<{ name: string; type: string }>;
|
|
288
|
+
language?: string;
|
|
289
|
+
volatility?: "immutable" | "stable" | "volatile";
|
|
290
|
+
/** `STRICT` / `RETURNS NULL ON NULL INPUT` (true) or `CALLED ON NULL INPUT` (false). */
|
|
291
|
+
strict?: boolean;
|
|
292
|
+
/** `SECURITY DEFINER` (true) or `SECURITY INVOKER` (false). */
|
|
293
|
+
securityDefiner?: boolean;
|
|
294
|
+
leakproof?: boolean;
|
|
295
|
+
parallel?: "unsafe" | "restricted" | "safe";
|
|
296
|
+
cost?: string;
|
|
297
|
+
rows?: string;
|
|
298
|
+
support?: string;
|
|
299
|
+
window?: boolean;
|
|
300
|
+
/** `TRANSFORM FOR TYPE ...`, as written. */
|
|
301
|
+
transform?: string;
|
|
302
|
+
/** `SET name = value` clauses: each parameter, folded, to its value as written. */
|
|
303
|
+
set?: Record<string, string>;
|
|
304
|
+
/** The definition: the string constant's value, verbatim. */
|
|
305
|
+
body: string;
|
|
306
|
+
/** A C function's link symbol (`AS 'obj_file', 'link_symbol'`). */
|
|
307
|
+
link?: string;
|
|
308
|
+
/** The objects the body interpolates. */
|
|
309
|
+
reads: PostgresObject[];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** `func` declares a function; `procedure` a procedure, which has no result and none of a function's planner attributes. */
|
|
313
|
+
export type FunctionProps = RoutineProps;
|
|
314
|
+
export type ProcedureProps = Omit<RoutineProps, "returns" | "returnsTable" | "volatility" | "strict" | "leakproof" | "parallel" | "cost" | "rows" | "support" | "window">;
|
|
315
|
+
|
|
316
|
+
export interface TriggerProps extends CommonProps {
|
|
317
|
+
orReplace?: boolean;
|
|
318
|
+
/** `CREATE CONSTRAINT TRIGGER`. */
|
|
319
|
+
constraint?: boolean;
|
|
320
|
+
timing: "before" | "after" | "instead of";
|
|
321
|
+
/** The events, each `insert`, `update`, `delete` or `truncate`; an `update` may name its columns. */
|
|
322
|
+
events: Array<{ event: "insert" | "update" | "delete" | "truncate"; columns?: string[] }>;
|
|
323
|
+
/** The table or view: the entity when interpolated, else its name. */
|
|
324
|
+
table: PostgresObject | string;
|
|
325
|
+
/** The table's SQL name. */
|
|
326
|
+
tableName: string;
|
|
327
|
+
/** A constraint trigger's `FROM` table's SQL name. */
|
|
328
|
+
from?: string;
|
|
329
|
+
deferrable?: boolean;
|
|
330
|
+
initiallyDeferred?: boolean;
|
|
331
|
+
/** `REFERENCING OLD TABLE AS ...` / `NEW TABLE AS ...`. */
|
|
332
|
+
referencing?: { old?: string; new?: string };
|
|
333
|
+
/** `FOR EACH ROW` or `FOR EACH STATEMENT` (the default). */
|
|
334
|
+
forEach: "row" | "statement";
|
|
335
|
+
/** The `WHEN` condition, parentheses excluded. */
|
|
336
|
+
when?: string;
|
|
337
|
+
/** The function it executes: the entity when interpolated, else its name. */
|
|
338
|
+
function: PostgresObject | string;
|
|
339
|
+
/** The function's SQL name. */
|
|
340
|
+
functionName: string;
|
|
341
|
+
/** The arguments, as written. */
|
|
342
|
+
args: string[];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface PolicyProps extends CommonProps {
|
|
346
|
+
/** The table: the entity when interpolated, else its name. */
|
|
347
|
+
table: PostgresObject | string;
|
|
348
|
+
tableName: string;
|
|
349
|
+
/** `AS PERMISSIVE` (the default) or `AS RESTRICTIVE`. */
|
|
350
|
+
permissive: boolean;
|
|
351
|
+
command: "all" | "select" | "insert" | "update" | "delete";
|
|
352
|
+
/** The roles it applies to; `public` when it names none. */
|
|
353
|
+
roles: string[];
|
|
354
|
+
/** `USING (...)`, parentheses excluded. */
|
|
355
|
+
using?: string;
|
|
356
|
+
/** `WITH CHECK (...)`, parentheses excluded. */
|
|
357
|
+
check?: string;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface RoleProps extends Omit<CommonProps, "schema"> {
|
|
361
|
+
/** The options written, each by its key word, lower case (`login`, `noinherit`). */
|
|
362
|
+
options: string[];
|
|
363
|
+
/** `CONNECTION LIMIT n`. */
|
|
364
|
+
connectionLimit?: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** A privilege as written: `select`, `update` on some columns, `all`. */
|
|
368
|
+
export interface PrivilegeDef {
|
|
369
|
+
privilege: string;
|
|
370
|
+
columns?: string[];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export interface GrantProps {
|
|
374
|
+
/** A grant's or revoke's name, which no catalog holds: what it grants on what to whom. */
|
|
375
|
+
name: string;
|
|
376
|
+
action: "grant" | "revoke";
|
|
377
|
+
/** `REVOKE GRANT OPTION FOR`: only the right to grant on is revoked. */
|
|
378
|
+
grantOptionFor?: boolean;
|
|
379
|
+
privileges: PrivilegeDef[];
|
|
380
|
+
/** The kind of object. */
|
|
381
|
+
on: "table" | "sequence" | "schema" | "function" | "procedure" | "routine";
|
|
382
|
+
/** The objects: each the entity when interpolated, else its name. */
|
|
383
|
+
objects: Array<PostgresObject | string>;
|
|
384
|
+
/** The objects' SQL names; a routine's with its parameter types when they are written or interpolated. */
|
|
385
|
+
objectNames: string[];
|
|
386
|
+
/** Role names; `public` for PUBLIC. */
|
|
387
|
+
grantees: string[];
|
|
388
|
+
withGrantOption?: boolean;
|
|
389
|
+
ddl: string;
|
|
390
|
+
source: { strings: string[] };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export interface DefaultPrivilegesProps {
|
|
394
|
+
name: string;
|
|
395
|
+
action: "grant" | "revoke";
|
|
396
|
+
grantOptionFor?: boolean;
|
|
397
|
+
/** `FOR ROLE`: the roles whose new objects it applies to; the role that applies it when not written. */
|
|
398
|
+
forRoles?: string[];
|
|
399
|
+
/** `IN SCHEMA`: the schemas; every schema when not written. */
|
|
400
|
+
inSchemas?: string[];
|
|
401
|
+
privileges: string[];
|
|
402
|
+
on: "tables" | "sequences" | "functions" | "types" | "schemas";
|
|
403
|
+
grantees: string[];
|
|
404
|
+
withGrantOption?: boolean;
|
|
405
|
+
ddl: string;
|
|
406
|
+
source: { strings: string[] };
|
|
407
|
+
}
|
|
408
|
+
|
|
261
409
|
/**
|
|
262
410
|
* A string as a quoted Postgres string literal: `'` doubled, a backslash left
|
|
263
411
|
* as written (standard_conforming_strings is on). An interpolated plain string
|
|
@@ -324,6 +472,36 @@ export interface PostgresExtension extends PostgresObject {
|
|
|
324
472
|
readonly props: ExtensionProps;
|
|
325
473
|
}
|
|
326
474
|
|
|
475
|
+
export interface PostgresFunction extends PostgresObject {
|
|
476
|
+
readonly entityType: "Postgres::Function";
|
|
477
|
+
readonly props: FunctionProps;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export interface PostgresProcedure extends PostgresObject {
|
|
481
|
+
readonly entityType: "Postgres::Procedure";
|
|
482
|
+
readonly props: ProcedureProps;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export interface PostgresTrigger extends PostgresObject {
|
|
486
|
+
readonly entityType: "Postgres::Trigger";
|
|
487
|
+
readonly props: TriggerProps;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export interface PostgresPolicy extends PostgresObject {
|
|
491
|
+
readonly entityType: "Postgres::Policy";
|
|
492
|
+
readonly props: PolicyProps;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export interface PostgresRole extends PostgresObject {
|
|
496
|
+
readonly entityType: "Postgres::Role";
|
|
497
|
+
readonly props: RoleProps;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export interface PostgresGrant extends PostgresObject {
|
|
501
|
+
readonly entityType: "Postgres::Grant" | "Postgres::DefaultPrivileges";
|
|
502
|
+
readonly props: GrantProps | DefaultPrivilegesProps;
|
|
503
|
+
}
|
|
504
|
+
|
|
327
505
|
export function isPostgresObject(value: unknown): value is PostgresObject {
|
|
328
506
|
return isSqlObjectOf(value, "Postgres::");
|
|
329
507
|
}
|
|
@@ -426,7 +604,7 @@ export const POSTGRES_TEMPLATES: TemplateDialect = {
|
|
|
426
604
|
|
|
427
605
|
type Ctx = TemplateCtx;
|
|
428
606
|
|
|
429
|
-
export type PostgresTag = "schema" | "table" | "index" | "view" | "sequence" | "type" | "domain" | "extension";
|
|
607
|
+
export type PostgresTag = "schema" | "table" | "index" | "view" | "sequence" | "type" | "domain" | "extension" | "func" | "procedure" | "trigger" | "policy" | "role" | "grant";
|
|
430
608
|
|
|
431
609
|
const STATEMENT_OF: Record<PostgresTag, StatementNode["statement"]> = {
|
|
432
610
|
schema: "schema",
|
|
@@ -437,6 +615,12 @@ const STATEMENT_OF: Record<PostgresTag, StatementNode["statement"]> = {
|
|
|
437
615
|
type: "enum",
|
|
438
616
|
domain: "domain",
|
|
439
617
|
extension: "extension",
|
|
618
|
+
func: "function",
|
|
619
|
+
procedure: "procedure",
|
|
620
|
+
trigger: "trigger",
|
|
621
|
+
policy: "policy",
|
|
622
|
+
role: "role",
|
|
623
|
+
grant: "grant",
|
|
440
624
|
};
|
|
441
625
|
|
|
442
626
|
const TAG_OF: Record<StatementNode["statement"], PostgresTag | undefined> = {
|
|
@@ -448,6 +632,13 @@ const TAG_OF: Record<StatementNode["statement"], PostgresTag | undefined> = {
|
|
|
448
632
|
enum: "type",
|
|
449
633
|
domain: "domain",
|
|
450
634
|
extension: "extension",
|
|
635
|
+
function: "func",
|
|
636
|
+
procedure: "procedure",
|
|
637
|
+
trigger: "trigger",
|
|
638
|
+
policy: "policy",
|
|
639
|
+
role: "role",
|
|
640
|
+
grant: "grant",
|
|
641
|
+
rowSecurity: undefined,
|
|
451
642
|
comment: undefined,
|
|
452
643
|
};
|
|
453
644
|
|
|
@@ -461,6 +652,13 @@ export const STATEMENT_NAMES: Record<StatementNode["statement"], string> = {
|
|
|
461
652
|
enum: "CREATE TYPE",
|
|
462
653
|
domain: "CREATE DOMAIN",
|
|
463
654
|
extension: "CREATE EXTENSION",
|
|
655
|
+
function: "CREATE FUNCTION",
|
|
656
|
+
procedure: "CREATE PROCEDURE",
|
|
657
|
+
trigger: "CREATE TRIGGER",
|
|
658
|
+
policy: "CREATE POLICY",
|
|
659
|
+
role: "CREATE ROLE",
|
|
660
|
+
grant: "GRANT",
|
|
661
|
+
rowSecurity: "ALTER TABLE",
|
|
464
662
|
comment: "COMMENT ON",
|
|
465
663
|
};
|
|
466
664
|
|
|
@@ -678,7 +876,10 @@ function applyComments(
|
|
|
678
876
|
if (value !== undefined) constraint!.comment = value;
|
|
679
877
|
} else {
|
|
680
878
|
if (!objectTypes.includes(c.objectType)) refuse(c, `does not comment on the ${tag} this template declares`);
|
|
681
|
-
if (
|
|
879
|
+
if (c.objectType === "TRIGGER" || c.objectType === "POLICY") {
|
|
880
|
+
// `COMMENT ON TRIGGER name ON table`: the name is the trigger's, the table its own.
|
|
881
|
+
if (qualified(ctx, c.target).name !== self.name || !c.on || !isSelf({ ...qualified(ctx, c.on), name: self.name })) refuse(c, `names another ${c.objectType.toLowerCase()} than ${self.name}`);
|
|
882
|
+
} else if (!isSelf(qualified(ctx, c.target))) refuse(c, `names another object than ${sqlNameOf(self)}`);
|
|
682
883
|
comment = value;
|
|
683
884
|
}
|
|
684
885
|
}
|
|
@@ -694,8 +895,188 @@ function lastDot(ctx: Ctx, span: Span): number {
|
|
|
694
895
|
return span.from;
|
|
695
896
|
}
|
|
696
897
|
|
|
697
|
-
|
|
698
|
-
|
|
898
|
+
/**
|
|
899
|
+
* The first statement after the CREATE that its template may not hold: a
|
|
900
|
+
* template is one object, then COMMENT ON statements for it; a table's may
|
|
901
|
+
* also turn row-level security on or off (`ALTER TABLE ... ENABLE ROW LEVEL
|
|
902
|
+
* SECURITY`), as `pg_dump` prints it. A grant is one statement.
|
|
903
|
+
*/
|
|
904
|
+
export function followOnStray(tag: PostgresTag, rest: readonly StatementNode[]): StatementNode | undefined {
|
|
905
|
+
if (tag === "grant") return rest[0];
|
|
906
|
+
return rest.find((n) => n.statement !== "comment" && !(tag === "table" && n.statement === "rowSecurity"));
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* The interpolations that sit inside a string constant (a routine's
|
|
911
|
+
* `AS $$ ... $$` body), rendered into the text around them: a string
|
|
912
|
+
* constant is one token, so the template is joined there before it is
|
|
913
|
+
* tokenized. Returns the joined parts and values, the original index of each
|
|
914
|
+
* value kept, and the objects the inlined interpolations name.
|
|
915
|
+
*/
|
|
916
|
+
function inlineQuoted(parts: readonly string[], values: readonly unknown[]): { parts: string[]; values: unknown[]; original: number[]; inlined: number[]; refs: unknown[] } {
|
|
917
|
+
// The lexical state at the end of each part: inside a '...' string, a $tag$ string, or neither.
|
|
918
|
+
type State = { kind: "none" } | { kind: "single"; escapes: boolean } | { kind: "dollar"; tag: string } | { kind: "dquote" } | { kind: "line" } | { kind: "block"; depth: number };
|
|
919
|
+
let state: State = { kind: "none" };
|
|
920
|
+
const insideAt: Array<"single" | "dollar" | undefined> = [];
|
|
921
|
+
parts.forEach((src, part) => {
|
|
922
|
+
for (let i = 0; i < src.length; i++) {
|
|
923
|
+
const c = src[i]!;
|
|
924
|
+
const rest = src.slice(i);
|
|
925
|
+
switch (state.kind) {
|
|
926
|
+
case "none": {
|
|
927
|
+
if (c === "'") state = { kind: "single", escapes: /[Ee]$/.test(src.slice(0, i)) && !/[A-Za-z0-9_$][Ee]$/.test(src.slice(0, i)) };
|
|
928
|
+
else if (c === '"') state = { kind: "dquote" };
|
|
929
|
+
else if (rest.startsWith("--")) state = { kind: "line" };
|
|
930
|
+
else if (rest.startsWith("/*")) {
|
|
931
|
+
state = { kind: "block", depth: 1 };
|
|
932
|
+
i++;
|
|
933
|
+
} else if (c === "$" && !/[A-Za-z0-9_$]$/.test(src.slice(0, i))) {
|
|
934
|
+
const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(rest);
|
|
935
|
+
if (m) {
|
|
936
|
+
state = { kind: "dollar", tag: m[0] };
|
|
937
|
+
i += m[0].length - 1;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case "single":
|
|
943
|
+
if (state.escapes && c === "\\") i++;
|
|
944
|
+
else if (c === "'") {
|
|
945
|
+
if (src[i + 1] === "'") i++;
|
|
946
|
+
else state = { kind: "none" };
|
|
947
|
+
}
|
|
948
|
+
break;
|
|
949
|
+
case "dquote":
|
|
950
|
+
if (c === '"') {
|
|
951
|
+
if (src[i + 1] === '"') i++;
|
|
952
|
+
else state = { kind: "none" };
|
|
953
|
+
}
|
|
954
|
+
break;
|
|
955
|
+
case "dollar":
|
|
956
|
+
if (rest.startsWith(state.tag)) {
|
|
957
|
+
i += state.tag.length - 1;
|
|
958
|
+
state = { kind: "none" };
|
|
959
|
+
}
|
|
960
|
+
break;
|
|
961
|
+
case "line":
|
|
962
|
+
if (c === "\n") state = { kind: "none" };
|
|
963
|
+
break;
|
|
964
|
+
case "block":
|
|
965
|
+
if (rest.startsWith("/*")) {
|
|
966
|
+
state.depth++;
|
|
967
|
+
i++;
|
|
968
|
+
} else if (rest.startsWith("*/")) {
|
|
969
|
+
i++;
|
|
970
|
+
state = state.depth === 1 ? { kind: "none" } : { kind: "block", depth: state.depth - 1 };
|
|
971
|
+
}
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (part < parts.length - 1) insideAt.push(state.kind === "single" ? "single" : state.kind === "dollar" ? "dollar" : undefined);
|
|
976
|
+
});
|
|
977
|
+
if (!insideAt.some(Boolean)) return { parts: [...parts], values: [...values], original: values.map((_, i) => i), inlined: [], refs: [] };
|
|
978
|
+
const out: string[] = [parts[0]!];
|
|
979
|
+
const outValues: unknown[] = [];
|
|
980
|
+
const original: number[] = [];
|
|
981
|
+
const inlined: number[] = [];
|
|
982
|
+
const refs: unknown[] = [];
|
|
983
|
+
values.forEach((v, i) => {
|
|
984
|
+
if (!insideAt[i]) {
|
|
985
|
+
outValues.push(v);
|
|
986
|
+
original.push(i);
|
|
987
|
+
out.push(parts[i + 1]!);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
let text: string;
|
|
991
|
+
if (isPostgresObject(v) || isColumnRef(v)) {
|
|
992
|
+
text = renderReference(v);
|
|
993
|
+
refs.push(v);
|
|
994
|
+
} else if (v instanceof SqlLiteral) text = v.sql;
|
|
995
|
+
else if (v === null) text = "NULL";
|
|
996
|
+
else if (typeof v === "string" || typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") text = String(v);
|
|
997
|
+
else throw new SqlTemplateError("func", `the interpolation inside the body is ${describe(v)}; a body takes an object, a column reference, a string or a number`, i, parts[i]!.length);
|
|
998
|
+
// Inside a '...' string a quote is doubled.
|
|
999
|
+
out[out.length - 1] += (insideAt[i] === "single" ? text.replace(/'/g, "''") : text) + parts[i + 1]!;
|
|
1000
|
+
inlined.push(i);
|
|
1001
|
+
});
|
|
1002
|
+
return { parts: out, values: outValues, original, inlined, refs };
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/** A role named where one is granted to: an interpolated role entity gives its name. */
|
|
1006
|
+
function roleName(ctx: Ctx, r: { name: string; span: Span }): string {
|
|
1007
|
+
if (r.name) return r.name;
|
|
1008
|
+
const t = ctx.tokens[r.span.from];
|
|
1009
|
+
const v = t?.kind === "ref" ? ctx.values[t.part] : undefined;
|
|
1010
|
+
if (isPostgresObject(v)) return (v.props as { name: string }).name;
|
|
1011
|
+
return identValue(req(text(ctx, r.span)));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/** A GRANT's, REVOKE's or ALTER DEFAULT PRIVILEGES's props. */
|
|
1015
|
+
function grantProps(ctx: Ctx, node: GrantNode, ddl: string, source: { strings: string[] }): GrantProps | DefaultPrivilegesProps {
|
|
1016
|
+
const action = node.revoke ? "revoke" : "grant";
|
|
1017
|
+
const grantees = node.grantees.map((g) => roleName(ctx, g));
|
|
1018
|
+
if (node.defaults) {
|
|
1019
|
+
if (node.privileges.some((p) => p.columns.length > 0)) throw new SqlTemplateError("grant", "default privileges are on whole objects; a column list is for a grant on one table", 0, 0);
|
|
1020
|
+
const on = (node.on === "ROUTINES" ? "functions" : node.on.toLowerCase()) as DefaultPrivilegesProps["on"];
|
|
1021
|
+
const forRoles = node.forRoles?.map((r) => roleName(ctx, r));
|
|
1022
|
+
const inSchemas = node.inSchemas?.map((s) => columnName(ctx, s));
|
|
1023
|
+
if (on === "schemas" && inSchemas) throw new SqlTemplateError("grant", "default privileges on schemas cannot be limited to a schema (IN SCHEMA)", 0, 0);
|
|
1024
|
+
const privileges = node.privileges.map((p) => p.privilege.toLowerCase());
|
|
1025
|
+
return stripUnset({
|
|
1026
|
+
name: `default privileges ${action === "grant" ? "grant" : "revoke"} ${privileges.join(", ")} on ${on}${inSchemas ? ` in ${inSchemas.join(", ")}` : ""}${forRoles ? ` for ${forRoles.join(", ")}` : ""} ${action === "grant" ? "to" : "from"} ${grantees.join(", ")}`,
|
|
1027
|
+
action,
|
|
1028
|
+
grantOptionFor: node.grantOptionFor || undefined,
|
|
1029
|
+
forRoles,
|
|
1030
|
+
inSchemas,
|
|
1031
|
+
privileges,
|
|
1032
|
+
on,
|
|
1033
|
+
grantees,
|
|
1034
|
+
withGrantOption: node.withGrantOption || undefined,
|
|
1035
|
+
ddl,
|
|
1036
|
+
source,
|
|
1037
|
+
}) as DefaultPrivilegesProps;
|
|
1038
|
+
}
|
|
1039
|
+
const on = (node.on === "TABLE" ? "table" : node.on.toLowerCase()) as GrantProps["on"];
|
|
1040
|
+
const routine = on === "function" || on === "procedure" || on === "routine";
|
|
1041
|
+
const objects: Array<PostgresObject | string> = [];
|
|
1042
|
+
const objectNames: string[] = [];
|
|
1043
|
+
for (const o of node.objects) {
|
|
1044
|
+
const t = target(ctx, o.name);
|
|
1045
|
+
objects.push(t);
|
|
1046
|
+
let sqlName = typeof t === "string" ? t : on === "schema" ? (t.props as { name: string }).name : t.sqlName;
|
|
1047
|
+
if (routine) {
|
|
1048
|
+
if (o.args) sqlName += `(${req(text(ctx, o.args, "objects")) ?? ""})`;
|
|
1049
|
+
else if (typeof t !== "string" && (t.entityType === POSTGRES_ENTITY_TYPES.function || t.entityType === POSTGRES_ENTITY_TYPES.procedure)) {
|
|
1050
|
+
// An interpolated routine is named by its own parameter types.
|
|
1051
|
+
sqlName += `(${(t.props as RoutineProps).args.filter((a) => a.mode !== "out").map((a) => a.type).join(", ")})`;
|
|
1052
|
+
} else throw new SqlTemplateError("grant", `${sqlName}: name the parameter types (${sqlName}(...)), or interpolate the routine, since overloads share a name`, 0, 0);
|
|
1053
|
+
}
|
|
1054
|
+
objectNames.push(sqlName);
|
|
1055
|
+
}
|
|
1056
|
+
const privileges = node.privileges.map((p) => stripUnset({ privilege: p.privilege.toLowerCase(), columns: p.columns.length > 0 ? p.columns.map((c) => columnName(ctx, c)) : undefined }) as PrivilegeDef);
|
|
1057
|
+
if (on !== "table" && privileges.some((p) => p.columns)) throw new SqlTemplateError("grant", "a column list is for privileges on a table", 0, 0);
|
|
1058
|
+
const verb = action === "grant" ? "to" : "from";
|
|
1059
|
+
return stripUnset({
|
|
1060
|
+
name: `${action} ${privileges.map((p) => `${p.privilege}${p.columns ? `(${p.columns.join(", ")})` : ""}`).join(", ")} on ${on} ${objectNames.join(", ")} ${verb} ${grantees.join(", ")}`,
|
|
1061
|
+
action,
|
|
1062
|
+
grantOptionFor: node.grantOptionFor || undefined,
|
|
1063
|
+
privileges,
|
|
1064
|
+
on,
|
|
1065
|
+
objects,
|
|
1066
|
+
objectNames,
|
|
1067
|
+
grantees,
|
|
1068
|
+
withGrantOption: node.withGrantOption || undefined,
|
|
1069
|
+
ddl,
|
|
1070
|
+
source,
|
|
1071
|
+
}) as GrantProps;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string[], rawValues: readonly unknown[]): PostgresObject {
|
|
1075
|
+
const rawParts = templateParts(strings);
|
|
1076
|
+
const routine = tag === "func" || tag === "procedure";
|
|
1077
|
+
const joined = routine ? inlineQuoted(rawParts, rawValues) : undefined;
|
|
1078
|
+
const parts = joined ? joined.parts : rawParts;
|
|
1079
|
+
const values = joined ? joined.values : rawValues;
|
|
699
1080
|
const tokens = spliceWith(POSTGRES_TEMPLATES, tag, parts, values);
|
|
700
1081
|
let nodes: StatementNode[];
|
|
701
1082
|
try {
|
|
@@ -708,11 +1089,28 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
|
|
|
708
1089
|
if (node.statement !== STATEMENT_OF[tag]) {
|
|
709
1090
|
const holds = STATEMENT_NAMES[node.statement];
|
|
710
1091
|
const use = TAG_OF[node.statement];
|
|
711
|
-
throw new SqlTemplateError(
|
|
1092
|
+
throw new SqlTemplateError(
|
|
1093
|
+
tag,
|
|
1094
|
+
use
|
|
1095
|
+
? `holds a ${holds}; use the ${use} tag`
|
|
1096
|
+
: node.statement === "rowSecurity"
|
|
1097
|
+
? "holds an ALTER TABLE ... ROW LEVEL SECURITY before any CREATE; it goes after the table's CREATE TABLE, in the table's template"
|
|
1098
|
+
: `holds a ${holds} before any CREATE; a COMMENT ON goes after the CREATE it comments on`,
|
|
1099
|
+
0,
|
|
1100
|
+
0,
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
const stray = followOnStray(tag, rest);
|
|
1104
|
+
if (stray) {
|
|
1105
|
+
throw new SqlTemplateError(
|
|
1106
|
+
tag,
|
|
1107
|
+
`holds a second statement (${STATEMENT_NAMES[stray.statement]}); a template declares one object, followed only by COMMENT ON statements for it${tag === "table" ? " and ALTER TABLE ... ROW LEVEL SECURITY" : ""}`,
|
|
1108
|
+
0,
|
|
1109
|
+
0,
|
|
1110
|
+
);
|
|
712
1111
|
}
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
const comments = rest as CommentNode[];
|
|
1112
|
+
const comments = rest.filter((n): n is CommentNode => n.statement === "comment");
|
|
1113
|
+
const rowSecurity = rest.filter((n): n is RowSecurityNode => n.statement === "rowSecurity");
|
|
716
1114
|
|
|
717
1115
|
const positions = regclassPositions(tokens, values);
|
|
718
1116
|
const render = (v: unknown, i?: number): string => {
|
|
@@ -723,11 +1121,18 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
|
|
|
723
1121
|
const ctx: Ctx = { d: { ...POSTGRES_TEMPLATES, renderReference: render }, tokens, values, fed: values.map(() => new Set<string>()) };
|
|
724
1122
|
for (const t of tokens) if (t.splice !== undefined) ctx.fed[t.splice]!.add("ddl");
|
|
725
1123
|
const ddl = untokenize(tokens, (i) => render(values[i], i)).trim().replace(/;\s*$/, "");
|
|
726
|
-
const source = { strings:
|
|
727
|
-
const dependsOn = [...new Set(values.filter((v) => isPostgresObject(v) || isColumnRef(v)))];
|
|
1124
|
+
const source = { strings: rawParts };
|
|
1125
|
+
const dependsOn = [...new Set([...values, ...(joined?.refs ?? [])].filter((v) => isPostgresObject(v) || isColumnRef(v)))];
|
|
728
1126
|
const make = <T extends PostgresObject>(type: PostgresEntityType, q: { schema?: string; name: string }, props: object, columnNames?: readonly string[]): T => {
|
|
729
1127
|
const entity = makeSqlEntity(PostgresObject.prototype as PostgresObject, type, sqlNameOf(q), props, columnNames, dependsOn) as T;
|
|
730
|
-
|
|
1128
|
+
const fed = ctx.fed.map((paths) => [...paths].sort());
|
|
1129
|
+
if (!joined) setInterpolationFields(entity, fed);
|
|
1130
|
+
else {
|
|
1131
|
+
// Back to the template's own interpolations: one inlined into the body feeds the body.
|
|
1132
|
+
const byOriginal: string[][] = rawValues.map(() => ["body", "ddl"]);
|
|
1133
|
+
joined.original.forEach((orig, i) => (byOriginal[orig] = fed[i]!));
|
|
1134
|
+
setInterpolationFields(entity, byOriginal);
|
|
1135
|
+
}
|
|
731
1136
|
return entity;
|
|
732
1137
|
};
|
|
733
1138
|
|
|
@@ -864,6 +1269,16 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
|
|
|
864
1269
|
const named = new Map<string, Commentable>();
|
|
865
1270
|
for (const c of [primaryKey, ...uniques, ...checks, ...foreignKeys, ...exclusions]) if (c?.name) named.set(c.name, c);
|
|
866
1271
|
const comment = applyComments(tag, ctx, comments, q, ["TABLE"], { columns: new Map(columns.map((c) => [c.name, c])), constraints: named });
|
|
1272
|
+
let rls: boolean | undefined;
|
|
1273
|
+
let force: boolean | undefined;
|
|
1274
|
+
for (const r of rowSecurity) {
|
|
1275
|
+
const t = qualified(ctx, r.table);
|
|
1276
|
+
if (t.name !== q.name || (t.schema !== undefined && q.schema !== undefined && t.schema !== q.schema)) {
|
|
1277
|
+
throw new SqlTemplateError(tag, `ALTER TABLE ${sqlNameOf(t)} ... ROW LEVEL SECURITY names another table than ${sqlNameOf(q)}`, 0, 0);
|
|
1278
|
+
}
|
|
1279
|
+
if (r.action === "ENABLE" || r.action === "DISABLE") rls = r.action === "ENABLE";
|
|
1280
|
+
else force = r.action === "FORCE";
|
|
1281
|
+
}
|
|
867
1282
|
const props: TableProps = strip(
|
|
868
1283
|
{
|
|
869
1284
|
...q,
|
|
@@ -885,12 +1300,16 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
|
|
|
885
1300
|
with: text(ctx, node.with, "with"),
|
|
886
1301
|
onCommit: text(ctx, node.onCommit),
|
|
887
1302
|
tablespace: text(ctx, node.tablespace, "tablespace"),
|
|
1303
|
+
rowSecurity: rls,
|
|
1304
|
+
forceRowSecurity: force,
|
|
888
1305
|
comment,
|
|
889
1306
|
ddl,
|
|
890
1307
|
source,
|
|
891
1308
|
},
|
|
892
1309
|
["columns", "uniques", "checks", "foreignKeys", "exclusions", "like", "inherits"],
|
|
893
1310
|
);
|
|
1311
|
+
if (rls === false) props.rowSecurity = false;
|
|
1312
|
+
if (force === false) props.forceRowSecurity = false;
|
|
894
1313
|
return make(POSTGRES_ENTITY_TYPES.table, q, props, columns.map((c) => c.name));
|
|
895
1314
|
}
|
|
896
1315
|
case "index": {
|
|
@@ -970,6 +1389,142 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
|
|
|
970
1389
|
feed(ctx, node.query, "lineage");
|
|
971
1390
|
return make(node.materialized ? POSTGRES_ENTITY_TYPES.materializedView : POSTGRES_ENTITY_TYPES.view, q, props, outputs);
|
|
972
1391
|
}
|
|
1392
|
+
case "function":
|
|
1393
|
+
case "procedure": {
|
|
1394
|
+
const q = qualified(ctx, node.name);
|
|
1395
|
+
const comment = applyComments(tag, ctx, comments, q, node.statement === "function" ? ["FUNCTION", "ROUTINE"] : ["PROCEDURE", "ROUTINE"]);
|
|
1396
|
+
const flag = (o: string) => node.options.find((x) => x.option === o)?.flag;
|
|
1397
|
+
const value = (o: string, path: string) => {
|
|
1398
|
+
const x = node.options.find((y) => y.option === o);
|
|
1399
|
+
return x?.value ? text(ctx, x.value, path) : undefined;
|
|
1400
|
+
};
|
|
1401
|
+
const set: Record<string, string> = {};
|
|
1402
|
+
for (const s of node.set) set[s.name] = req(text(ctx, s.value, "set"));
|
|
1403
|
+
const [def, link] = node.body.map((b) => stringValue(text(ctx, b, "body")));
|
|
1404
|
+
const lang = text(ctx, node.language, "language");
|
|
1405
|
+
const props: RoutineProps = strip(
|
|
1406
|
+
{
|
|
1407
|
+
...q,
|
|
1408
|
+
orReplace: node.orReplace || undefined,
|
|
1409
|
+
args: node.args.map((a) =>
|
|
1410
|
+
stripUnset({
|
|
1411
|
+
mode: (a.mode?.toLowerCase() ?? "in") as RoutineArgDef["mode"],
|
|
1412
|
+
name: a.name,
|
|
1413
|
+
type: req(text(ctx, a.type, "args")),
|
|
1414
|
+
default: text(ctx, a.default, "args"),
|
|
1415
|
+
}),
|
|
1416
|
+
),
|
|
1417
|
+
returns: node.returns ? `${node.returns.setof ? "SETOF " : ""}${req(text(ctx, node.returns.type, "returns"))}` : undefined,
|
|
1418
|
+
returnsTable: node.returnsTable?.map((c) => ({ name: c.name, type: req(text(ctx, c.type, "returns")) })),
|
|
1419
|
+
language: lang === undefined ? undefined : lang.startsWith("'") ? stringValue(lang) : identValue(lang),
|
|
1420
|
+
volatility: value("VOLATILITY", "volatility")?.toLowerCase() as RoutineProps["volatility"],
|
|
1421
|
+
strict: flag("STRICT"),
|
|
1422
|
+
securityDefiner: flag("SECURITY"),
|
|
1423
|
+
leakproof: flag("LEAKPROOF"),
|
|
1424
|
+
parallel: value("PARALLEL", "parallel")?.toLowerCase() as RoutineProps["parallel"],
|
|
1425
|
+
cost: value("COST", "cost"),
|
|
1426
|
+
rows: value("ROWS", "rows"),
|
|
1427
|
+
support: value("SUPPORT", "support"),
|
|
1428
|
+
window: flag("WINDOW"),
|
|
1429
|
+
transform: value("TRANSFORM", "transform"),
|
|
1430
|
+
set: Object.keys(set).length > 0 ? set : undefined,
|
|
1431
|
+
body: req(def),
|
|
1432
|
+
link,
|
|
1433
|
+
reads: (joined?.refs ?? []).filter((v): v is PostgresObject => isPostgresObject(v)),
|
|
1434
|
+
comment,
|
|
1435
|
+
ddl,
|
|
1436
|
+
source,
|
|
1437
|
+
},
|
|
1438
|
+
["args", "reads"],
|
|
1439
|
+
);
|
|
1440
|
+
for (const k of ["strict", "securityDefiner", "leakproof"] as const) if (flag(k === "securityDefiner" ? "SECURITY" : k.toUpperCase()) === false) props[k] = false;
|
|
1441
|
+
return make(node.statement === "function" ? POSTGRES_ENTITY_TYPES.function : POSTGRES_ENTITY_TYPES.procedure, q, props);
|
|
1442
|
+
}
|
|
1443
|
+
case "trigger": {
|
|
1444
|
+
const tq = qualified(ctx, node.table);
|
|
1445
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
1446
|
+
feed(ctx, node.name.span, "name");
|
|
1447
|
+
// A trigger is its table's: it is named in the table's schema.
|
|
1448
|
+
const q = { schema: tq.schema, name };
|
|
1449
|
+
const comment = applyComments(tag, ctx, comments, q, ["TRIGGER"]);
|
|
1450
|
+
const referencing: { old?: string; new?: string } = {};
|
|
1451
|
+
for (const r of node.referencing) referencing[r.which === "OLD" ? "old" : "new"] = r.name;
|
|
1452
|
+
const props: TriggerProps = strip(
|
|
1453
|
+
{
|
|
1454
|
+
...q,
|
|
1455
|
+
orReplace: node.orReplace || undefined,
|
|
1456
|
+
constraint: node.constraint || undefined,
|
|
1457
|
+
timing: node.timing.toLowerCase() as TriggerProps["timing"],
|
|
1458
|
+
events: node.events.map((e) => {
|
|
1459
|
+
for (const c of e.columns) feed(ctx, c.span, "events");
|
|
1460
|
+
const columns = e.columns.map((c) => columnName(ctx, c));
|
|
1461
|
+
return { event: e.event.toLowerCase() as TriggerProps["events"][number]["event"], ...(columns.length > 0 ? { columns } : {}) };
|
|
1462
|
+
}),
|
|
1463
|
+
table: target(ctx, node.table),
|
|
1464
|
+
tableName: sqlNameOf(tq),
|
|
1465
|
+
from: node.from ? targetName(ctx, node.from) : undefined,
|
|
1466
|
+
deferrable: node.deferrable,
|
|
1467
|
+
initiallyDeferred: node.initiallyDeferred,
|
|
1468
|
+
referencing: Object.keys(referencing).length > 0 ? referencing : undefined,
|
|
1469
|
+
forEach: (node.forEach ?? "STATEMENT").toLowerCase() as TriggerProps["forEach"],
|
|
1470
|
+
when: text(ctx, node.when, "when"),
|
|
1471
|
+
function: target(ctx, node.function),
|
|
1472
|
+
functionName: targetName(ctx, node.function),
|
|
1473
|
+
args: node.args.map((a) => req(text(ctx, a, "args"))),
|
|
1474
|
+
comment,
|
|
1475
|
+
ddl,
|
|
1476
|
+
source,
|
|
1477
|
+
},
|
|
1478
|
+
["events", "args"],
|
|
1479
|
+
);
|
|
1480
|
+
return make(POSTGRES_ENTITY_TYPES.trigger, q, props);
|
|
1481
|
+
}
|
|
1482
|
+
case "policy": {
|
|
1483
|
+
const tq = qualified(ctx, node.table);
|
|
1484
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
1485
|
+
feed(ctx, node.name.span, "name");
|
|
1486
|
+
// A policy is its table's, named in the table's schema.
|
|
1487
|
+
const q = { schema: tq.schema, name };
|
|
1488
|
+
const comment = applyComments(tag, ctx, comments, q, ["POLICY"]);
|
|
1489
|
+
const props: PolicyProps = stripUnset({
|
|
1490
|
+
...q,
|
|
1491
|
+
table: target(ctx, node.table),
|
|
1492
|
+
tableName: sqlNameOf(tq),
|
|
1493
|
+
permissive: node.permissive !== false,
|
|
1494
|
+
command: (node.command ?? "ALL").toLowerCase() as PolicyProps["command"],
|
|
1495
|
+
roles: node.roles.length > 0 ? node.roles.map((r) => roleName(ctx, r)) : ["public"],
|
|
1496
|
+
using: text(ctx, node.using, "using"),
|
|
1497
|
+
check: text(ctx, node.check, "check"),
|
|
1498
|
+
comment,
|
|
1499
|
+
ddl,
|
|
1500
|
+
source,
|
|
1501
|
+
});
|
|
1502
|
+
// AS RESTRICTIVE is a false that means something.
|
|
1503
|
+
props.permissive = node.permissive !== false;
|
|
1504
|
+
return make(POSTGRES_ENTITY_TYPES.policy, q, props);
|
|
1505
|
+
}
|
|
1506
|
+
case "role": {
|
|
1507
|
+
const name = node.name.pieces[0] || identValue(req(text(ctx, node.name.span)));
|
|
1508
|
+
feed(ctx, node.name.span, "name");
|
|
1509
|
+
const comment = applyComments(tag, ctx, comments, { name }, ["ROLE"]);
|
|
1510
|
+
const value = (o: string) => {
|
|
1511
|
+
const x = node.options.find((y) => y.option === o);
|
|
1512
|
+
return x?.value ? text(ctx, x.value, "options") : undefined;
|
|
1513
|
+
};
|
|
1514
|
+
const props: RoleProps = stripUnset({
|
|
1515
|
+
name,
|
|
1516
|
+
options: node.options.filter((o) => o.value === undefined).map((o) => o.option.toLowerCase()),
|
|
1517
|
+
connectionLimit: value("CONNECTION LIMIT"),
|
|
1518
|
+
comment,
|
|
1519
|
+
ddl,
|
|
1520
|
+
source,
|
|
1521
|
+
});
|
|
1522
|
+
return make(POSTGRES_ENTITY_TYPES.role, { name }, props);
|
|
1523
|
+
}
|
|
1524
|
+
case "grant":
|
|
1525
|
+
return make(node.defaults ? POSTGRES_ENTITY_TYPES.defaultPrivileges : POSTGRES_ENTITY_TYPES.grant, { name: "" }, grantProps(ctx, node, ddl, source));
|
|
1526
|
+
case "rowSecurity":
|
|
1527
|
+
throw new SqlTemplateError(tag, "an ALTER TABLE ... ROW LEVEL SECURITY goes after the table's CREATE TABLE, in the table's template", 0, 0);
|
|
973
1528
|
case "comment":
|
|
974
1529
|
throw new SqlTemplateError(tag, "a COMMENT ON goes after the CREATE it comments on", 0, 0);
|
|
975
1530
|
}
|
|
@@ -1014,3 +1569,47 @@ export function domain(strings: TemplateStringsArray, ...values: unknown[]): Pos
|
|
|
1014
1569
|
export function extension(strings: TemplateStringsArray, ...values: unknown[]): PostgresExtension {
|
|
1015
1570
|
return build("extension", strings, values) as PostgresExtension;
|
|
1016
1571
|
}
|
|
1572
|
+
|
|
1573
|
+
/**
|
|
1574
|
+
* `` func`CREATE [OR REPLACE] FUNCTION name(args) RETURNS ... LANGUAGE ... AS $$ ... $$` ``:
|
|
1575
|
+
* one function, its body a string kept verbatim. (`function` is a JavaScript
|
|
1576
|
+
* key word, hence `func`.)
|
|
1577
|
+
*/
|
|
1578
|
+
export function func(strings: TemplateStringsArray, ...values: unknown[]): PostgresFunction {
|
|
1579
|
+
return build("func", strings, values) as PostgresFunction;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/** `` procedure`CREATE [OR REPLACE] PROCEDURE name(args) LANGUAGE ... AS $$ ... $$` ``: one procedure. */
|
|
1583
|
+
export function procedure(strings: TemplateStringsArray, ...values: unknown[]): PostgresProcedure {
|
|
1584
|
+
return build("procedure", strings, values) as PostgresProcedure;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/** `` trigger`CREATE [OR REPLACE] [CONSTRAINT] TRIGGER name ... ON ${table} ... EXECUTE FUNCTION ${fn}()` ``: one trigger on its table. */
|
|
1588
|
+
export function trigger(strings: TemplateStringsArray, ...values: unknown[]): PostgresTrigger {
|
|
1589
|
+
return build("trigger", strings, values) as PostgresTrigger;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/** `` policy`CREATE POLICY name ON ${table} ... USING (...)` ``: one row-level security policy on its table. */
|
|
1593
|
+
export function policy(strings: TemplateStringsArray, ...values: unknown[]): PostgresPolicy {
|
|
1594
|
+
return build("policy", strings, values) as PostgresPolicy;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* `` role`CREATE ROLE name [WITH] options` ``: a role the schema needs, created
|
|
1599
|
+
* when it is missing. A password and memberships are the environment's and are
|
|
1600
|
+
* refused; a role the environment provisions is named as text where it is
|
|
1601
|
+
* granted to, never declared.
|
|
1602
|
+
*/
|
|
1603
|
+
export function role(strings: TemplateStringsArray, ...values: unknown[]): PostgresRole {
|
|
1604
|
+
return build("role", strings, values) as PostgresRole;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* `` grant`GRANT ... ON ... TO ...` ``, `REVOKE ... FROM ...`, or
|
|
1609
|
+
* `ALTER DEFAULT PRIVILEGES ... GRANT|REVOKE ...`: privileges on schemas,
|
|
1610
|
+
* tables, columns, sequences and routines, applied where the profile manages
|
|
1611
|
+
* access (`sql.profiles.<env>.access`).
|
|
1612
|
+
*/
|
|
1613
|
+
export function grant(strings: TemplateStringsArray, ...values: unknown[]): PostgresGrant {
|
|
1614
|
+
return build("grant", strings, values) as PostgresGrant;
|
|
1615
|
+
}
|