@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.
Files changed (116) hide show
  1. package/dist/codegen/registry.d.ts.map +1 -1
  2. package/dist/config.d.ts +20 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/integrity.json +5 -5
  7. package/dist/lint/rules/sqlpg001.d.ts.map +1 -1
  8. package/dist/lsp/postgres-template.d.ts.map +1 -1
  9. package/dist/manifest.json +1 -1
  10. package/dist/meta.json +35 -0
  11. package/dist/okf/index.md +7 -0
  12. package/dist/okf/types/PostgresDefaultPrivileges.md +9 -0
  13. package/dist/okf/types/PostgresFunction.md +9 -0
  14. package/dist/okf/types/PostgresGrant.md +9 -0
  15. package/dist/okf/types/PostgresPolicy.md +9 -0
  16. package/dist/okf/types/PostgresProcedure.md +9 -0
  17. package/dist/okf/types/PostgresRole.md +9 -0
  18. package/dist/okf/types/PostgresTrigger.md +9 -0
  19. package/dist/op/activities/postgres-migration.d.ts.map +1 -1
  20. package/dist/op/activity-contracts.d.ts +10 -0
  21. package/dist/op/activity-contracts.d.ts.map +1 -1
  22. package/dist/plugin.d.ts.map +1 -1
  23. package/dist/postgres/access/acl.d.ts +119 -0
  24. package/dist/postgres/access/acl.d.ts.map +1 -0
  25. package/dist/postgres/access/import.d.ts +13 -0
  26. package/dist/postgres/access/import.d.ts.map +1 -0
  27. package/dist/postgres/access/live.d.ts +24 -0
  28. package/dist/postgres/access/live.d.ts.map +1 -0
  29. package/dist/postgres/apply/apply.d.ts +6 -1
  30. package/dist/postgres/apply/apply.d.ts.map +1 -1
  31. package/dist/postgres/apply/statements.d.ts +12 -2
  32. package/dist/postgres/apply/statements.d.ts.map +1 -1
  33. package/dist/postgres/entities.d.ts +212 -3
  34. package/dist/postgres/entities.d.ts.map +1 -1
  35. package/dist/postgres/entity-types.d.ts +7 -0
  36. package/dist/postgres/entity-types.d.ts.map +1 -1
  37. package/dist/postgres/import/generator.d.ts +4 -1
  38. package/dist/postgres/import/generator.d.ts.map +1 -1
  39. package/dist/postgres/import/live-export.d.ts +6 -0
  40. package/dist/postgres/import/live-export.d.ts.map +1 -1
  41. package/dist/postgres/live/bind.d.ts +2 -0
  42. package/dist/postgres/live/bind.d.ts.map +1 -1
  43. package/dist/postgres/live/catalog.d.ts +25 -4
  44. package/dist/postgres/live/catalog.d.ts.map +1 -1
  45. package/dist/postgres/live/describe-resources.d.ts +15 -1
  46. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  47. package/dist/postgres/migrate/op.d.ts +29 -0
  48. package/dist/postgres/migrate/op.d.ts.map +1 -1
  49. package/dist/postgres/parser.d.ts +155 -5
  50. package/dist/postgres/parser.d.ts.map +1 -1
  51. package/dist/postgres/plan/commands.d.ts +12 -0
  52. package/dist/postgres/plan/commands.d.ts.map +1 -1
  53. package/dist/postgres/plan/deep.d.ts.map +1 -1
  54. package/dist/postgres/plan/diff.d.ts +11 -2
  55. package/dist/postgres/plan/diff.d.ts.map +1 -1
  56. package/dist/postgres/plan/normalize.d.ts +28 -2
  57. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  58. package/dist/postgres/plan/rules.d.ts +14 -0
  59. package/dist/postgres/plan/rules.d.ts.map +1 -1
  60. package/dist/postgres/plan/schema.d.ts +12 -1
  61. package/dist/postgres/plan/schema.d.ts.map +1 -1
  62. package/dist/postgres/plan/server-normalize.d.ts +4 -1
  63. package/dist/postgres/plan/server-normalize.d.ts.map +1 -1
  64. package/dist/postgres.d.ts +1 -1
  65. package/dist/postgres.d.ts.map +1 -1
  66. package/dist/rules/postgres-templates.ts +2 -2
  67. package/dist/rules/sqlpg001.ts +10 -4
  68. package/dist/validate.d.ts.map +1 -1
  69. package/package.json +2 -2
  70. package/src/codegen/postgres-classifier-page.ts +1 -1
  71. package/src/codegen/registry.ts +7 -0
  72. package/src/config.ts +10 -0
  73. package/src/generated/lexicon-sql.json +35 -0
  74. package/src/index.ts +1 -1
  75. package/src/lint/rules/postgres-templates.ts +2 -2
  76. package/src/lint/rules/sqlpg001.ts +10 -4
  77. package/src/lsp/postgres-template.ts +8 -0
  78. package/src/mcp/postgres.ts +1 -1
  79. package/src/migration-statements.ts +7 -0
  80. package/src/op/activities/postgres-migration.ts +27 -2
  81. package/src/op/activity-contracts.ts +1 -0
  82. package/src/plugin.ts +6 -0
  83. package/src/postgres/access/access.e2e.test.ts +191 -0
  84. package/src/postgres/access/access.test.ts +147 -0
  85. package/src/postgres/access/acl.ts +400 -0
  86. package/src/postgres/access/import.ts +64 -0
  87. package/src/postgres/access/live.ts +123 -0
  88. package/src/postgres/apply/apply.ts +135 -6
  89. package/src/postgres/apply/statements.ts +104 -16
  90. package/src/postgres/entities.ts +613 -14
  91. package/src/postgres/entity-types.ts +7 -0
  92. package/src/postgres/import/generator.ts +18 -5
  93. package/src/postgres/import/live-export.ts +16 -2
  94. package/src/postgres/live/bind.ts +3 -0
  95. package/src/postgres/live/catalog.ts +155 -5
  96. package/src/postgres/live/describe-resources.ts +43 -9
  97. package/src/postgres/live/routines.e2e.test.ts +173 -0
  98. package/src/postgres/migrate/batches.e2e.test.ts +2 -1
  99. package/src/postgres/migrate/migrate.e2e.test.ts +2 -1
  100. package/src/postgres/migrate/migrate.test.ts +24 -1
  101. package/src/postgres/migrate/op.ts +80 -35
  102. package/src/postgres/migrate/outer.e2e.test.ts +163 -0
  103. package/src/postgres/parser.ts +554 -9
  104. package/src/postgres/plan/commands.ts +61 -8
  105. package/src/postgres/plan/deep.ts +79 -18
  106. package/src/postgres/plan/diff.ts +114 -9
  107. package/src/postgres/plan/normalize.ts +272 -5
  108. package/src/postgres/plan/rules.ts +80 -0
  109. package/src/postgres/plan/schema.ts +27 -6
  110. package/src/postgres/plan/server-normalize.ts +53 -1
  111. package/src/postgres/routines.test.ts +150 -0
  112. package/src/postgres.ts +22 -0
  113. package/src/skill-defs.ts +1 -1
  114. package/src/skills/chant-sql-postgres-migration.md +3 -1
  115. package/src/skills/chant-sql-postgres.md +25 -2
  116. package/src/validate.ts +7 -0
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The Postgres entities: `schema`, `table`, `index`, `view`, `sequence`,
3
- * `type`, `domain` and `extension` tagged templates that parse their DDL at
4
- * fold time into one Declarable each (chant #3278, #3279).
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 (!isSelf(qualified(ctx, c.target))) refuse(c, `names another object than ${sqlNameOf(self)}`);
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
- function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string[], values: readonly unknown[]): PostgresObject {
698
- const parts = templateParts(strings);
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(tag, use ? `holds a ${holds}; use the ${use} tag` : `holds a ${holds} before any CREATE; a COMMENT ON goes after the CREATE it comments on`, 0, 0);
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 stray = rest.find((n) => n.statement !== "comment");
714
- if (stray) throw new SqlTemplateError(tag, `holds a second statement (${STATEMENT_NAMES[stray.statement]}); a template declares one object, followed only by COMMENT ON statements for it`, 0, 0);
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: parts };
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
- setInterpolationFields(entity, ctx.fed.map((paths) => [...paths].sort()));
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
+ }