pipework 0.290.0 → 0.291.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 (51) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/REFERENCE.md +50 -0
  3. package/dist/data/db/function-definition.d.ts +46 -0
  4. package/dist/data/db/function-definition.d.ts.map +1 -0
  5. package/dist/data/db/function-definition.js +84 -0
  6. package/dist/data/db/function-definition.js.map +1 -0
  7. package/dist/data/db/function-source.d.ts +33 -2
  8. package/dist/data/db/function-source.d.ts.map +1 -1
  9. package/dist/data/db/function-source.js +47 -19
  10. package/dist/data/db/function-source.js.map +1 -1
  11. package/dist/data/db/namespace.d.ts +1 -1
  12. package/dist/data/db/namespace.d.ts.map +1 -1
  13. package/dist/data/db/namespace.js +1 -1
  14. package/dist/data/db/namespace.js.map +1 -1
  15. package/dist/data/migrate/diff.d.ts +7 -0
  16. package/dist/data/migrate/diff.d.ts.map +1 -1
  17. package/dist/data/migrate/diff.js +37 -0
  18. package/dist/data/migrate/diff.js.map +1 -1
  19. package/dist/data/migrate/history.d.ts +2 -0
  20. package/dist/data/migrate/history.d.ts.map +1 -1
  21. package/dist/data/migrate/history.js +8 -0
  22. package/dist/data/migrate/history.js.map +1 -1
  23. package/dist/data/migrate/snapshot.d.ts +3 -0
  24. package/dist/data/migrate/snapshot.d.ts.map +1 -1
  25. package/dist/data/migrate/snapshot.js +13 -0
  26. package/dist/data/migrate/snapshot.js.map +1 -1
  27. package/dist/data/migrate/sql-emit.d.ts.map +1 -1
  28. package/dist/data/migrate/sql-emit.js +30 -2
  29. package/dist/data/migrate/sql-emit.js.map +1 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/test/function-reads.d.ts +97 -0
  34. package/dist/test/function-reads.d.ts.map +1 -0
  35. package/dist/test/function-reads.js +202 -0
  36. package/dist/test/function-reads.js.map +1 -0
  37. package/dist/test/index.d.ts +1 -0
  38. package/dist/test/index.d.ts.map +1 -1
  39. package/dist/test/index.js +1 -0
  40. package/dist/test/index.js.map +1 -1
  41. package/package.json +2 -2
  42. package/src/data/db/function-definition.ts +172 -0
  43. package/src/data/db/function-source.ts +86 -14
  44. package/src/data/db/namespace.ts +1 -1
  45. package/src/data/migrate/diff.ts +45 -0
  46. package/src/data/migrate/history.ts +9 -0
  47. package/src/data/migrate/snapshot.ts +17 -0
  48. package/src/data/migrate/sql-emit.ts +33 -3
  49. package/src/index.ts +2 -1
  50. package/src/test/function-reads.ts +290 -0
  51. package/src/test/index.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.291.0
4
+
5
+ - **Added: SQL functions as pipework-defined reads** (#979, #983 step 2). A `pipe.fn` given a `body` defines a `LANGUAGE sql` function that pipework creates and migrates: typed `args` (a list is one array argument, used as `= ANY($n)`), `columns` plus a required `rows` estimate for a set, or `returns` for a scalar, and `volatility` (`stable` by default, or `immutable`). The call is typed as before. Migrations emit `CREATE OR REPLACE FUNCTION` for a body, volatility or ROWS change, `DROP` then `CREATE` for a return-shape change, and `DROP FUNCTION` (no CASCADE) on removal; drops run first and creates last. `pipe.fn(...)` refuses, with a `[pipework] pipe.fn("name"):` error naming the fix, anything that stops Postgres inlining the function or makes its cost grow with history: a SET clause (`set` / `config` options or `set_config()` in the body; put settings such as jit on the role), another language, VOLATILE, more than one statement or any DML, `FOR UPDATE` / `FOR SHARE`, a `MATERIALIZED` CTE, a list passed as `IN ($2, $3)` or `ARRAY[$2, $3]`, a missing ROWS estimate, or a `$n` beyond the arguments. New test helpers `checkFunctionRead` / `expectFunctionRead` (`pipework/test`) judge a function, including one created by raw DDL: they fail it when it is not inlined, when the rows it touches grow with history (small vs 10x history), or when it touches more with missing statistics than after ANALYZE, and report "not checked" when the call touched no rows or the role is not a superuser (missing statistics cannot then be measured). Calls go through the labelled statement path, so `pipe.labelled` timing and the `query:large` budgets apply.
6
+
3
7
  ## 0.290.0
4
8
 
5
9
  - **Added: `filter.row(...)` and `filter.rowEquals(lhs, rhs)`, row comparisons for composite keys** (#1010). `eq` / `ne` / `gt` / `gte` / `lt` / `lte` now accept a row on both sides: `gte(row(t.a, t.b, t.c), row(k.a, k.b, k.c))` renders `("t"."a", "t"."b", "t"."c") >= ("k"."a", "k"."b", "k"."c")`, so a composite-key join against a `pipe.rows` key list needs no `pipe.sql`. Each element is a column, a SQL expression (a `pipe.rows` column is one) or a JS value. A value is bound and cast to the SQL type of the column facing it on the other row (`($1::text, $2::text)`); facing an expression it is bound uncast. `rowEquals` emits the range pair `lhs >= rhs and lhs <= rhs`: Postgres splits `(a, b, c) = (x, y, z)` into three equalities and probes the shortest index holding a and b, while a row comparison is an index condition only on an index holding all its columns in order, so the pair takes the (a, b, c) index and reads only matching rows (measured under FORCE ROW LEVEL SECURITY: 50 rows read for 50 returned, against 800 read with three `eq`s). It equals equality for never-NULL keys; with a NULL element it can yield NULL where equality yields false, which differs only under `not(...)`. A row of fewer than two elements, rows of unequal length, or a row compared with a non-row throw `[pipework] row:`.
package/REFERENCE.md CHANGED
@@ -995,6 +995,56 @@ A result column of one function (`unnestUuids(f.x)`) is a valid argument to anot
995
995
 
996
996
  The default alias is the unqualified function name. Fields work with `eq`, `castUuid`, `orderBy`, joins and `select({ x: f.fromPath })`.
997
997
 
998
+ #### A function pipework defines: a read
999
+
1000
+ Give `pipe.fn` a `body` and pipework creates the function: the migration generator emits `CREATE OR REPLACE FUNCTION` for it, the call is typed from the definition, and it is a read under the rule that reads are key lookups. Export it from a schema file the way a table is exported; the generator finds it there. A `pipe.fn` without a `body` only refers to a function that exists already, as above.
1001
+
1002
+ ```ts
1003
+ export const headImports = pipe.fn('canvas.head_imports', {
1004
+ args: [pipe.field.uuid(), pipe.field.text()], // the body refers to them as $1, $2
1005
+ columns: { fromPath: pipe.field.text(), line: pipe.field.integer() }, // RETURNS TABLE(from_path text, line integer)
1006
+ body: `select i.from_path, i.line from canvas.imports i where i.repo_id = $1 and i.sha = $2`,
1007
+ volatility: 'stable', // 'stable' (the default) or 'immutable'
1008
+ rows: 50, // typical result size of one call; required for a set
1009
+ })
1010
+ const f = headImports(repoId, sha) // the typed source described above
1011
+ await pipe.labelled('head-imports', async () => await db.select().from(f).where(eq(f.line, 3)))
1012
+ ```
1013
+
1014
+ For a scalar function declare `returns: pipe.field.integer()` instead of `columns` and `rows`; its call is a typed SQL expression (`select canvas.f($1::uuid)`). A list is one array argument (`pipe.field.uuid().array()`, `column = ANY($2)`). The call is an ordinary query on the connection, so `pipe.labelled`, the timing alerts and the `query:large` budgets apply to it.
1015
+
1016
+ Migrations. The snapshot records each function by `schema.name(argument types)`. A new function, or a changed body, volatility or ROWS, is `CREATE OR REPLACE FUNCTION`. A changed result shape (Postgres cannot replace a return type) is `DROP FUNCTION` then `CREATE`. A changed argument list is a different function: the old signature is dropped and the new one created. A function removed from the schema files is dropped, without CASCADE, so a dependent object fails the migration loudly. Function drops come first in a migration, before any table or column is dropped, and function creates come last, after every table, column, index and foreign key, because a `LANGUAGE sql` body is checked when the function is created. The schema it lives in must exist, as for a table. The statement is dollar-quoted (`$pw_body$`) and always `LANGUAGE sql`, never `STRICT`, `SECURITY DEFINER` or `SET`.
1017
+
1018
+ Static rules, checked when `pipe.fn` is called, each with a `[pipework] pipe.fn("name"): ... Fix: ...` error. A read is inlined into the query that calls it only when the function allows it, and a function that is not inlined is a black box: the planner cannot push the caller's keys into it, so the rows it touches grow with history instead of with its keys. Each rule is a reason Postgres does not inline, or a reason keys do not reach the table:
1019
+
1020
+ - No SET clause (`set`, `config`, or `set_config(` in the body): a function with `proconfig` set is never inlined. A session setting such as `jit` belongs on the role, `ALTER ROLE app SET jit = off`.
1021
+ - `language` is `sql`: Postgres inlines only LANGUAGE sql; a plpgsql function runs per call. What cannot be one SELECT is a derivation, not a read.
1022
+ - `volatility` is `stable` or `immutable`: a VOLATILE function is not inlined.
1023
+ - The body is one SELECT (`WITH ... SELECT` allowed), with no second statement, no INSERT/UPDATE/DELETE/MERGE (also inside a CTE), no `FOR UPDATE` / `FOR SHARE`: Postgres inlines only a single plain SELECT.
1024
+ - No `AS MATERIALIZED` CTE: it is computed in full before the caller's keys are applied, an optimisation fence, so what it scans grows with history.
1025
+ - A list is one array parameter: `IN ($2, $3)` or `ARRAY[$2, $3]` over separate parameters gives each list length its own signature and plan.
1026
+ - A set-returning function declares `rows`: without ROWS Postgres plans every join around a guess of 1000 rows.
1027
+ - `$n` stays within the declared arguments. `SECURITY DEFINER`, `STRICT`, `PARALLEL` and `COST` are not options: each stops inlining.
1028
+
1029
+ #### Checking a read: `checkFunctionRead` and `expectFunctionRead` (`pipework/test`)
1030
+
1031
+ The rules above are static; whether a read is a key lookup is shown by its plan. `checkFunctionRead(db, { fn, args, tables, seed, history?, tolerance? })` returns a report (`status`: `passed`, `failed` or `not-checked`; `problems`; the four measurements) and `expectFunctionRead` throws a `[pipework]` error unless it passed. `fn` is what `pipe.fn` returned (a referenced function is checked too), `args` representative arguments, `tables` the tables it reads, and `seed(db, history)` leaves exactly `history` rows of history for the same keys the call looks up (it may truncate and refill); it is called with 200 and then 2000 by default (`history: [n, 10n]`).
1032
+
1033
+ ```ts
1034
+ await expectFunctionRead(db, {
1035
+ fn: headImports, args: [repoId, 'v0'], tables: ['canvas.imports'],
1036
+ seed: async (db, history) => { /* truncate canvas.imports; insert `history` shas for repoId, 3 rows each */ },
1037
+ })
1038
+ ```
1039
+
1040
+ It counts rows touched from `EXPLAIN (ANALYZE, VERBOSE, FORMAT JSON)` of the typed call with parallelism off: for each node that scans a relation, rows returned plus rows removed by filter or index recheck, times loops, summed. It measures four times: fresh statistics at the small history (after ANALYZE), the large history seeded without analyzing (stale statistics), the large history after ANALYZE (fresh), and the large history after the tables' `pg_statistic` rows are deleted (missing; this needs a superuser, as the test cluster's role is, and autovacuum is switched off on the tables for the run and restored). A role that is not a superuser cannot delete statistics: the missing state is then not measured, the report lists it in `notChecked` and its `problems`, `status` is `not-checked` (or `failed` if something else failed) and `expectFunctionRead` throws naming it, so the state is never passed quietly. A role that cannot switch autovacuum off (not the tables' owner) makes the check throw, naming the fix: run it as the owner or a superuser. It fails when:
1041
+
1042
+ - the function was not inlined: the plan has a Function Scan on it or its call text, or its catalog row has a SET clause, a language other than sql, VOLATILE, SECURITY DEFINER or STRICT (so a function created by raw DDL is judged too, and the message says which);
1043
+ - rows touched at the large history exceed those at the small one by more than the tolerance (default 10% plus 5 rows): the read scans history; the message names the largest scan, and a CTE Scan (a MATERIALIZED CTE);
1044
+ - rows touched with stale or missing statistics exceed those after ANALYZE by more than the tolerance: the plan depends on the statistics being current (a 50-190x blow-up with stale statistics is the failure this catches).
1045
+
1046
+ A read that touches no rows on the fixture at the large history is `not-checked`, never a pass: seed rows for the keys in `args`. Scalar functions are checked the same way; one that reads a table is a call in the plan, so it is reported as not inlined.
1047
+
998
1048
  ### `pipe.field`
999
1049
 
1000
1050
  Field builders for domain definitions — uuid(), text(), integer(), etc.
@@ -0,0 +1,46 @@
1
+ /** Marks a callable returned by `pipe.fn` that carries a definition (a `body`), so the migration generator can find it among a schema file's exports. */
2
+ export declare const FUNCTION_DEFINITION: unique symbol;
3
+ export type FunctionVolatility = 'stable' | 'immutable';
4
+ /** One column of a set-returning function's result, as the snapshot and the DDL carry it. */
5
+ export interface FunctionColumnDefinition {
6
+ readonly name: string;
7
+ readonly type: string;
8
+ }
9
+ /** A function pipework creates and migrates: what the snapshot records and the DDL is emitted from. */
10
+ export interface FunctionDefinition {
11
+ readonly schema: string;
12
+ readonly name: string;
13
+ /** SQL types of the arguments, in call order. */
14
+ readonly args: readonly string[];
15
+ /** `table`: RETURNS TABLE(columns) with a ROWS estimate. `scalar`: RETURNS type. */
16
+ readonly returns: {
17
+ readonly kind: 'table';
18
+ readonly columns: readonly FunctionColumnDefinition[];
19
+ readonly rows: number;
20
+ } | {
21
+ readonly kind: 'scalar';
22
+ readonly type: string;
23
+ };
24
+ readonly body: string;
25
+ readonly volatility: FunctionVolatility;
26
+ }
27
+ /** What `pipe.fn` was given, for the rules that look at it before it becomes a definition. */
28
+ export interface FunctionDefinitionInput {
29
+ readonly name: string;
30
+ readonly argCount: number;
31
+ readonly body: string;
32
+ readonly spec: Readonly<Record<string, unknown>>;
33
+ readonly setReturning: boolean;
34
+ }
35
+ /** The identity of a function in a snapshot: `schema.name(type, type)`; a changed argument list is a different function. */
36
+ export declare function functionKey(def: Pick<FunctionDefinition, 'schema' | 'name' | 'args'>): string;
37
+ /**
38
+ * The static rules of a pipework-defined read. Each exists because Postgres inlines a SQL function into the
39
+ * calling query only when the function allows it, and a function that is not inlined is a black box the planner
40
+ * cannot push the caller's keys into; its rows touched then grow with history instead of with its keys.
41
+ * Throws a `[pipework]` error naming the rule and the fix.
42
+ */
43
+ export declare function assertReadFunction(input: FunctionDefinitionInput): void;
44
+ /** The dollar-quote tag for a body: `$pw_body$`, lengthened until the body does not contain it. */
45
+ export declare function bodyQuote(body: string): string;
46
+ //# sourceMappingURL=function-definition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-definition.d.ts","sourceRoot":"","sources":["../../../src/data/db/function-definition.ts"],"names":[],"mappings":"AAEA,yJAAyJ;AACzJ,eAAO,MAAM,mBAAmB,eAAuC,CAAA;AAEvE,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,WAAW,CAAA;AAEvD,6FAA6F;AAC7F,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,uGAAuG;AACvG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EACZ;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,wBAAwB,EAAE,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GACxG;QAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAA;CACxC;AAED,8FAA8F;AAC9F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAChD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;CAC/B;AAQD,4HAA4H;AAC5H,wBAAgB,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAE7F;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CA8GvE;AAED,mGAAmG;AACnG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9C"}
@@ -0,0 +1,84 @@
1
+ import { blankLiterals, stripComments } from '../migrate/statements.js';
2
+ /** Marks a callable returned by `pipe.fn` that carries a definition (a `body`), so the migration generator can find it among a schema file's exports. */
3
+ export const FUNCTION_DEFINITION = Symbol.for('pipework.fn.definition');
4
+ const SPEC_KEYS = new Set(['args', 'columns', 'returns', 'body', 'volatility', 'rows', 'language']);
5
+ function fail(name, message, fix) {
6
+ throw new Error(`[pipework] pipe.fn("${name}"): ${message}\n\n Fix: ${fix}\n`);
7
+ }
8
+ /** The identity of a function in a snapshot: `schema.name(type, type)`; a changed argument list is a different function. */
9
+ export function functionKey(def) {
10
+ return `${def.schema}.${def.name}(${def.args.join(', ')})`;
11
+ }
12
+ /**
13
+ * The static rules of a pipework-defined read. Each exists because Postgres inlines a SQL function into the
14
+ * calling query only when the function allows it, and a function that is not inlined is a black box the planner
15
+ * cannot push the caller's keys into; its rows touched then grow with history instead of with its keys.
16
+ * Throws a `[pipework]` error naming the rule and the fix.
17
+ */
18
+ export function assertReadFunction(input) {
19
+ const { name, spec, body } = input;
20
+ for (const key of ['set', 'config', 'settings']) {
21
+ if (key in spec) {
22
+ fail(name, `"${key}" is a SET clause, which a read function cannot have. Postgres never inlines a function with a SET clause (proconfig is set), so the planner cannot push the caller's keys into it and every call plans and scans on its own.`, 'remove it. A session setting such as jit belongs on the role (`ALTER ROLE app SET jit = off`), not on a function.');
23
+ }
24
+ }
25
+ const unknown = Object.keys(spec).filter(k => !SPEC_KEYS.has(k) && k !== 'set' && k !== 'config' && k !== 'settings');
26
+ if (unknown.length > 0) {
27
+ fail(name, `unknown option${unknown.length > 1 ? 's' : ''} ${unknown.map(k => `"${k}"`).join(', ')}.`, `a read function takes ${[...SPEC_KEYS].join(', ')}. SECURITY DEFINER, STRICT, PARALLEL and COST are not offered: each stops Postgres inlining the function.`);
28
+ }
29
+ const language = spec['language'];
30
+ if (language !== undefined && language !== 'sql') {
31
+ fail(name, `language "${String(language)}" is not allowed for a read. Postgres inlines only LANGUAGE sql functions; a plpgsql function is a black box that runs per call and cannot be pushed into.`, 'write the read as one LANGUAGE sql SELECT. A computation that cannot be one SELECT is a derivation (maintained on write), not a read.');
32
+ }
33
+ const volatility = spec['volatility'];
34
+ if (volatility !== undefined && volatility !== 'stable' && volatility !== 'immutable') {
35
+ fail(name, `volatility "${String(volatility)}" is not allowed for a read. Postgres does not inline a VOLATILE function into the calling query.`, "use 'stable' (reads tables) or 'immutable' (reads nothing that changes).");
36
+ }
37
+ if (input.setReturning) {
38
+ const rows = spec['rows'];
39
+ if (typeof rows !== 'number' || !Number.isInteger(rows) || rows <= 0) {
40
+ fail(name, 'a set-returning function needs a ROWS estimate (a positive integer). Without it Postgres assumes 1000 rows for the call, and plans every join and filter around that guess.', 'give `rows` the typical result size for one call, e.g. `rows: 50`.');
41
+ }
42
+ }
43
+ else if (spec['rows'] !== undefined) {
44
+ fail(name, '`rows` applies to a set-returning function, and this one returns a scalar.', 'remove `rows`, or declare `columns` to return a set.');
45
+ }
46
+ const code = blankLiterals(stripComments(body));
47
+ const trimmed = code.trim().replace(/;\s*$/, '');
48
+ if (/;/.test(trimmed)) {
49
+ fail(name, 'the body has more than one statement. Postgres inlines a function only when its body is a single SELECT.', 'make the body one SELECT (use a CTE or a join for the second step).');
50
+ }
51
+ if (!/^(select|with|\(\s*select)\b/i.test(trimmed)) {
52
+ fail(name, 'the body is not a SELECT. A read function is one SELECT statement; Postgres inlines only a single SELECT.', 'start the body with SELECT or WITH ... SELECT.');
53
+ }
54
+ if (/\b(insert\s+into|update\s+[\w".]+\s+set|delete\s+from|merge\s+into|truncate\s+(?:table\s+)?["\w])/i.test(trimmed)) {
55
+ fail(name, 'the body writes (INSERT, UPDATE, DELETE, MERGE or TRUNCATE, possibly inside a CTE). A read function is one SELECT; a body with a data-modifying statement is not inlined.', 'move the write to pipe.insert / pipe.update / pipe.delete.');
56
+ }
57
+ if (/\bfor\s+(no\s+key\s+update|update|key\s+share|share)\b/i.test(trimmed)) {
58
+ fail(name, 'the body takes row locks (FOR UPDATE / FOR SHARE). Postgres does not inline a function that locks rows.', 'remove the locking clause; a read does not lock.');
59
+ }
60
+ if (/\bset_config\s*\(/i.test(trimmed)) {
61
+ fail(name, 'the body calls set_config(), which is a SET clause in disguise: it makes the function VOLATILE and Postgres does not inline it.', 'remove it. A session setting belongs on the role (`ALTER ROLE app SET ... `).');
62
+ }
63
+ if (/\bas\s+materialized\b/i.test(trimmed)) {
64
+ fail(name, 'the body has a MATERIALIZED CTE. Postgres computes it once, in full, before the caller\'s keys are applied: it is an optimisation fence, and the rows it scans grow with history, not with the keys.', 'drop the MATERIALIZED keyword (a CTE used once is inlined by default), or write the CTE as a subquery or a join.');
65
+ }
66
+ if (/\bin\s*\(\s*\$\d+\s*(?:,\s*\$\d+\s*)+\)/i.test(trimmed) || /\barray\s*\[\s*\$\d+\s*(?:,\s*\$\d+\s*)+\]/i.test(trimmed)) {
67
+ fail(name, 'a list is passed as separate parameters ($1, $2, ...). A list argument is one array parameter: separate parameters give every list length its own signature and its own plan.', 'declare one array argument (`pipe.field.uuid().array()`) and write `column = ANY($n)`.');
68
+ }
69
+ for (const m of trimmed.matchAll(/\$(\d+)/g)) {
70
+ const n = Number(m[1]);
71
+ if (n < 1 || n > input.argCount) {
72
+ fail(name, `the body refers to $${n}, but the function declares ${input.argCount} argument${input.argCount === 1 ? '' : 's'}.`, 'refer to arguments as $1 ... $n, in the order of `args`.');
73
+ }
74
+ }
75
+ }
76
+ /** The dollar-quote tag for a body: `$pw_body$`, lengthened until the body does not contain it. */
77
+ export function bodyQuote(body) {
78
+ let tag = '$pw_body$';
79
+ let i = 0;
80
+ while (body.includes(tag))
81
+ tag = `$pw_body${++i}$`;
82
+ return tag;
83
+ }
84
+ //# sourceMappingURL=function-definition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-definition.js","sourceRoot":"","sources":["../../../src/data/db/function-definition.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import { Subquery } from '../query/subquery.js';
3
3
  import type { Column } from './types.js';
4
4
  import type { InferFieldOutput } from '../domain/brand.js';
5
5
  import type { FieldDescriptor } from '../domain/types.js';
6
+ import { type FunctionDefinition, type FunctionVolatility } from './function-definition.js';
6
7
  type AnyField = FieldDescriptor<any, any>;
7
8
  /** Declares a SQL function's argument and result-column shapes. See {@link fn}. */
8
9
  export interface FunctionSpec<TArgs extends readonly AnyField[], TColumns extends Record<string, AnyField>> {
@@ -10,6 +11,35 @@ export interface FunctionSpec<TArgs extends readonly AnyField[], TColumns extend
10
11
  readonly args: readonly [...TArgs];
11
12
  /** One `pipe.field.*` descriptor per result column; keys are camelCase, columns are their snake_case. */
12
13
  readonly columns: TColumns;
14
+ /** The body of a function pipework creates: one `LANGUAGE sql` SELECT that refers to the arguments as `$1`, `$2`, ... Without it the function is only referenced (it exists already). */
15
+ readonly body?: string;
16
+ /** `'stable'` (the default) or `'immutable'`; a read is never volatile. Only with `body`. */
17
+ readonly volatility?: FunctionVolatility;
18
+ /** The typical number of rows one call returns, a planner estimate (ROWS). Required with `body`. */
19
+ readonly rows?: number;
20
+ /** Only `'sql'`: Postgres inlines no other language. */
21
+ readonly language?: 'sql';
22
+ }
23
+ /** Declares a SQL function that returns one value. See {@link fn}. */
24
+ export interface ScalarFunctionSpec<TArgs extends readonly AnyField[], TReturn extends AnyField> {
25
+ readonly args: readonly [...TArgs];
26
+ readonly returns: TReturn;
27
+ readonly body: string;
28
+ readonly volatility?: FunctionVolatility;
29
+ readonly language?: 'sql';
30
+ }
31
+ /** A function callable with typed arguments; `definition` is set when pipework creates it (the spec had a `body`). */
32
+ export type DefinedCall<TCall> = TCall & {
33
+ readonly definition: FunctionDefinition | undefined;
34
+ /** Where the function lives and its argument types; what a plan or catalog check looks it up by. */
35
+ readonly ref: FunctionRef;
36
+ };
37
+ /** A function's schema, name and SQL argument types. */
38
+ export interface FunctionRef {
39
+ readonly schema: string;
40
+ readonly name: string;
41
+ readonly args: readonly string[];
42
+ readonly setReturning: boolean;
13
43
  }
14
44
  /** A column whose data type is `T` (and that cannot be null unless `T` can). */
15
45
  type ColumnOf<T> = Column & {
@@ -33,7 +63,7 @@ export type FunctionSource<TAlias extends string, TColumns extends Record<string
33
63
  as<TNew extends string>(alias: TNew): FunctionSource<TNew, TColumns>;
34
64
  };
35
65
  /**
36
- * Declares a set-returning SQL function and returns a callable that yields a source for `.from()` and joins.
66
+ * Declares a SQL function and returns a callable. A set-returning one (`columns`) yields a source for `.from()` and joins.
37
67
  * Each argument is a value, a column or a SQL expression of the declared type. A value is bound as a parameter,
38
68
  * converted the way a column of that field is on insert (jsonb as JSON text, dates, bigints, arrays), and cast to its
39
69
  * declared SQL type: `ext.f($1::uuid, $2::text)`. A null binds SQL NULL. An array argument (`field.uuid().array()`) is
@@ -41,6 +71,7 @@ export type FunctionSource<TAlias extends string, TColumns extends Record<string
41
71
  * `unnest(t.arr::uuid[])`, so an array column can be unnested in a lateral join (`.crossJoinLateral(unnestUuids(t.arr))`).
42
72
  * Result columns are typed from `columns`; the default alias is the function's unqualified name.
43
73
  */
44
- export declare function fn<const TArgs extends readonly AnyField[], TColumns extends Record<string, AnyField>>(name: string, spec: FunctionSpec<TArgs, TColumns>): (...values: ArgValues<TArgs>) => FunctionSource<string, TColumns>;
74
+ export declare function fn<const TArgs extends readonly AnyField[], TReturn extends AnyField>(name: string, spec: ScalarFunctionSpec<TArgs, TReturn>): DefinedCall<(...values: ArgValues<TArgs>) => SQL<InferFieldOutput<TReturn>>>;
75
+ export declare function fn<const TArgs extends readonly AnyField[], TColumns extends Record<string, AnyField>>(name: string, spec: FunctionSpec<TArgs, TColumns>): DefinedCall<(...values: ArgValues<TArgs>) => FunctionSource<string, TColumns>>;
45
76
  export {};
46
77
  //# sourceMappingURL=function-source.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"function-source.d.ts","sourceRoot":"","sources":["../../../src/data/db/function-source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAI/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAKzD,KAAK,QAAQ,GAAG,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;AAIzC,mFAAmF;AACnF,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,SAAS,QAAQ,EAAE,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;IACxG,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,CAAA;IAClC,yGAAyG;IACzG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAA;CAC3B;AAED,gFAAgF;AAChF,KAAK,QAAQ,CAAC,CAAC,IAAI,MAAM,GAAG;IAC1B,QAAQ,CAAC,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,SAAS,CAAC,GAAG,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;CACnG,CAAA;AAED,6FAA6F;AAC7F,KAAK,QAAQ,CAAC,CAAC,SAAS,QAAQ,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAA;AAElH,KAAK,SAAS,CAAC,KAAK,SAAS,SAAS,QAAQ,EAAE,IAAI;KAAG,CAAC,IAAI,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;CAAE,CAAA;AAE9F,8FAA8F;AAC9F,MAAM,MAAM,oBAAoB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI;KAC3E,CAAC,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;CAC1D,CAAA;AAED,8FAA8F;AAC9F,MAAM,MAAM,cAAc,CAAC,MAAM,SAAS,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IACvF,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC,GAChD,oBAAoB,CAAC,QAAQ,CAAC,GAC9B;IACA,uFAAuF;IACvF,EAAE,CAAC,IAAI,SAAS,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;CACrE,CAAA;AAUH;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,KAAK,CAAC,KAAK,SAAS,SAAS,QAAQ,EAAE,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EACnG,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,GAClC,CAAC,GAAG,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAsDnE"}
1
+ {"version":3,"file":"function-source.d.ts","sourceRoot":"","sources":["../../../src/data/db/function-source.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { getTableColumns } from '../query/utils.js';
5
5
  import { bindIfParam } from '../query/index.js';
6
6
  import { projectTable } from '../domain/build-table.js';
7
7
  import { camelToSnake } from '../domain/naming.js';
8
+ import { assertReadFunction, FUNCTION_DEFINITION } from './function-definition.js';
8
9
  const NAME_PART = /^[a-z_][a-z0-9_]*$/;
9
10
  function fail(message, fix) {
10
11
  throw new Error(`[pipework] ${message}\n\n Fix: ${fix}\n`);
@@ -12,41 +13,63 @@ function fail(message, fix) {
12
13
  function quote(name) {
13
14
  return `"${name.replace(/"/g, '""')}"`;
14
15
  }
15
- /**
16
- * Declares a set-returning SQL function and returns a callable that yields a source for `.from()` and joins.
17
- * Each argument is a value, a column or a SQL expression of the declared type. A value is bound as a parameter,
18
- * converted the way a column of that field is on insert (jsonb as JSON text, dates, bigints, arrays), and cast to its
19
- * declared SQL type: `ext.f($1::uuid, $2::text)`. A null binds SQL NULL. An array argument (`field.uuid().array()`) is
20
- * one parameter: `ext.f($1::uuid[])`. A column or SQL expression is embedded as written, still cast:
21
- * `unnest(t.arr::uuid[])`, so an array column can be unnested in a lateral join (`.crossJoinLateral(unnestUuids(t.arr))`).
22
- * Result columns are typed from `columns`; the default alias is the function's unqualified name.
23
- */
24
16
  export function fn(name, spec) {
25
17
  const parts = name.split('.');
26
18
  if (parts.length > 2 || parts.some(p => !NAME_PART.test(p))) {
27
19
  fail(`pipe.fn: "${name}" is not a function name.`, 'use "fn" or "schema.fn", each part lowercase letters, digits and underscores, not starting with a digit.');
28
20
  }
29
- const keys = Object.keys(spec.columns);
30
- if (keys.length === 0)
21
+ const scalar = 'returns' in spec ? spec.returns : undefined;
22
+ const columns = 'columns' in spec ? spec.columns : {};
23
+ const keys = Object.keys(columns);
24
+ if (scalar === undefined && keys.length === 0)
31
25
  fail(`pipe.fn("${name}"): columns is empty.`, 'declare at least one result column.');
26
+ if (scalar !== undefined && 'columns' in spec)
27
+ fail(`pipe.fn("${name}"): both returns and columns are declared.`, 'declare `returns` for a scalar function or `columns` for a set-returning one.');
32
28
  for (const key of keys) {
33
29
  if (key === 'as' || key === '_') {
34
30
  fail(`pipe.fn("${name}"): a result column cannot be named "${key}".`, 'rename the key; the column name comes from camelToSnake(key).');
35
31
  }
36
32
  }
33
+ const sqlTypeOf = (fields) => {
34
+ const cols = getTableColumns(projectTable('pw_fn_args', fields, {}));
35
+ return Object.fromEntries(Object.entries(cols).map(([k, c]) => {
36
+ const t = c.getSQLType();
37
+ return [k, t === 'serial' ? 'integer' : t === 'bigserial' ? 'bigint' : t];
38
+ }));
39
+ };
37
40
  const argFields = {};
38
41
  spec.args.forEach((a, i) => { argFields[`a${i}`] = a; });
39
42
  const argCols = getTableColumns(projectTable('pw_fn_args', argFields, {}));
40
- const argTypes = spec.args.map((_, i) => {
41
- const t = argCols[`a${i}`].getSQLType();
42
- return t === 'serial' ? 'integer' : t === 'bigserial' ? 'bigint' : t;
43
- });
43
+ const argTypes = spec.args.map((_, i) => sqlTypeOf({ a: spec.args[i] })['a']);
44
+ let definition;
45
+ if (spec.body !== undefined) {
46
+ if (typeof spec.body !== 'string')
47
+ fail(`pipe.fn("${name}"): body must be a string.`, 'pass the SELECT as a string, with arguments as $1, $2, ...');
48
+ assertReadFunction({ name, argCount: spec.args.length, body: spec.body, spec: spec, setReturning: scalar === undefined });
49
+ const returnTypes = scalar === undefined ? sqlTypeOf(columns) : sqlTypeOf({ r: scalar });
50
+ definition = {
51
+ schema: parts.length === 2 ? parts[0] : 'public',
52
+ name: parts[parts.length - 1],
53
+ args: argTypes,
54
+ returns: scalar === undefined
55
+ ? { kind: 'table', columns: keys.map(k => ({ name: camelToSnake(k), type: returnTypes[k] })), rows: spec.rows }
56
+ : { kind: 'scalar', type: returnTypes['r'] },
57
+ body: spec.body,
58
+ volatility: spec.volatility ?? 'stable',
59
+ };
60
+ }
61
+ else {
62
+ for (const key of ['volatility', 'rows', 'language']) {
63
+ if (key in spec)
64
+ fail(`pipe.fn("${name}"): "${key}" is given without a body.`, 'add `body` to have pipework create the function, or remove the option (a referenced function keeps what it was created with).');
65
+ }
66
+ }
44
67
  const fnSql = sql.raw(parts.map(quote).join('.'));
45
68
  const defaultAlias = parts[parts.length - 1];
69
+ const argList = (values) => sql.join(values.map((v, i) => sql `${bindIfParam(v, argCols[`a${i}`])}::${sql.raw(argTypes[i])}`), sql `, `);
46
70
  const build = (values, alias) => {
47
71
  const columnList = sql.join(keys.map(k => sql.raw(quote(camelToSnake(k)))), sql `, `);
48
- const argList = sql.join(values.map((v, i) => sql `${bindIfParam(v, argCols[`a${i}`])}::${sql.raw(argTypes[i])}`), sql `, `);
49
- const inner = sql `select ${columnList} from ${fnSql}(${argList})`;
72
+ const inner = sql `select ${columnList} from ${fnSql}(${argList(values)})`;
50
73
  const fields = {};
51
74
  for (const k of keys) {
52
75
  fields[k] = sql.raw(`${quote(alias)}.${quote(camelToSnake(k))}`);
@@ -61,11 +84,16 @@ export function fn(name, spec) {
61
84
  },
62
85
  });
63
86
  };
64
- return (...values) => {
87
+ const call = (...values) => {
65
88
  if (values.length !== spec.args.length) {
66
89
  fail(`pipe.fn("${name}"): called with ${values.length} argument(s), declared ${spec.args.length}.`, 'pass one value per declared argument.');
67
90
  }
68
- return build(values, defaultAlias);
91
+ return scalar !== undefined ? sql `${fnSql}(${argList(values)})` : build(values, defaultAlias);
69
92
  };
93
+ return Object.defineProperties(call, {
94
+ definition: { value: definition, enumerable: true },
95
+ ref: { value: { schema: parts.length === 2 ? parts[0] : 'public', name: defaultAlias, args: argTypes, setReturning: scalar === undefined }, enumerable: true },
96
+ [FUNCTION_DEFINITION]: { value: definition },
97
+ });
70
98
  }
71
99
  //# sourceMappingURL=function-source.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"function-source.js","sourceRoot":"","sources":["../../../src/data/db/function-source.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"function-source.js","sourceRoot":"","sources":["../../../src/data/db/function-source.ts"],"names":[],"mappings":"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"}
@@ -183,7 +183,7 @@ export declare const pipe: typeof getDb & {
183
183
  values: typeof values;
184
184
  /** A typed rowset from parallel array parameters, one parameter per column whatever the row count, as a source for `.from()` and joins — `pipe.rows({ g: 'text', id: 'int' }, { g: [...], id: [...] })` renders `unnest($1::text[], $2::int[]) AS rows(g, id)`. For composite-key matches that would otherwise be a union or a JS-side tuple match. */
185
185
  rows: typeof rows;
186
- /** A set-returning SQL function as a typed source for `.from()` and joins — `pipe.fn('schema.f', { args: [field.uuid()], columns: { path: field.text() } })`. Arguments are bound and cast to their declared types. */
186
+ /** A set-returning SQL function as a typed source for `.from()` and joins — `pipe.fn('schema.f', { args: [field.uuid()], columns: { path: field.text() } })`. Arguments are bound and cast to their declared types. With a `body` (one LANGUAGE sql SELECT, `volatility`, `rows`) pipework creates the function and migrates it, under rules that keep it inlinable; check it with `expectFunctionRead` from `pipework/test`. */
187
187
  fn: typeof fn;
188
188
  /** Field builders for domain definitions — uuid(), text(), integer(), etc. */
189
189
  field: {
@@ -1 +1 @@
1
- {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/data/db/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAE5D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAGnM,OAAO,EAAS,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGzE,YAAY,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE3E,6EAA6E;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6EAA6E;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,mJAAmJ;AACnJ,eAAO,MAAM,IAAI;IACf,iQAAiQ;;IAGjQ,6QAA6Q;;IAG7Q,6EAA6E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAG7E,2oBAA2oB;;IAG3oB,+RAA+R;;;;;;;;;;;;;IAkB/R,oDAAoD;;IAEpD,mDAAmD;;IAGnD,6EAA6E;;IAG7E,gFAAgF;;IAGhF,qHAAqH;;IAGrH,kHAAkH;;IAGlH,oMAAoM;;IAGpM,2GAA2G;;IAG3G,0GAA0G;;IAG1G,uVAAuV;;IAGvV,uNAAuN;;IAGvN,8EAA8E;;;;;;;;;;;;;;;;;;;;;;;IAE9E,qMAAqM;;IAErM,6DAA6D;;IAE7D,+LAA+L;;IAG/L,2GAA2G;;IAE3G,kNAAkN;;IAElN,gHAAgH;;IAEhH,kQAAkQ;;CAElQ,CAAA"}
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/data/db/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAE5D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAGnM,OAAO,EAAS,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGzE,YAAY,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE3E,6EAA6E;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6EAA6E;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,mJAAmJ;AACnJ,eAAO,MAAM,IAAI;IACf,iQAAiQ;;IAGjQ,6QAA6Q;;IAG7Q,6EAA6E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAG7E,2oBAA2oB;;IAG3oB,+RAA+R;;;;;;;;;;;;;IAkB/R,oDAAoD;;IAEpD,mDAAmD;;IAGnD,6EAA6E;;IAG7E,gFAAgF;;IAGhF,qHAAqH;;IAGrH,kHAAkH;;IAGlH,oMAAoM;;IAGpM,2GAA2G;;IAG3G,0GAA0G;;IAG1G,uVAAuV;;IAGvV,iaAAia;;IAGja,8EAA8E;;;;;;;;;;;;;;;;;;;;;;;IAE9E,qMAAqM;;IAErM,6DAA6D;;IAE7D,+LAA+L;;IAG/L,2GAA2G;;IAE3G,kNAAkN;;IAElN,gHAAgH;;IAEhH,kQAAkQ;;CAElQ,CAAA"}
@@ -64,7 +64,7 @@ export const pipe = Object.assign(getDb, {
64
64
  values,
65
65
  /** A typed rowset from parallel array parameters, one parameter per column whatever the row count, as a source for `.from()` and joins — `pipe.rows({ g: 'text', id: 'int' }, { g: [...], id: [...] })` renders `unnest($1::text[], $2::int[]) AS rows(g, id)`. For composite-key matches that would otherwise be a union or a JS-side tuple match. */
66
66
  rows,
67
- /** A set-returning SQL function as a typed source for `.from()` and joins — `pipe.fn('schema.f', { args: [field.uuid()], columns: { path: field.text() } })`. Arguments are bound and cast to their declared types. */
67
+ /** A set-returning SQL function as a typed source for `.from()` and joins — `pipe.fn('schema.f', { args: [field.uuid()], columns: { path: field.text() } })`. Arguments are bound and cast to their declared types. With a `body` (one LANGUAGE sql SELECT, `volatility`, `rows`) pipework creates the function and migrates it, under rules that keep it inlinable; check it with `expectFunctionRead` from `pipework/test`. */
68
68
  fn,
69
69
  /** Field builders for domain definitions — uuid(), text(), integer(), etc. */
70
70
  field,
@@ -1 +1 @@
1
- {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/data/db/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAEnM,qBAAqB;AACrB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAKzE,6EAA6E;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6EAA6E;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,mJAAmJ;AACnJ,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;IACvC,iQAAiQ;IACjQ,MAAM,EAAE,UAAU;IAElB,6QAA6Q;IAC7Q,IAAI,EAAE,QAAQ;IAEd,6EAA6E;IAC7E,MAAM;IAEN,2oBAA2oB;IAC3oB,QAAQ;IAER,+RAA+R;IAC/R,GAAG;IAEH,kGAAkG;IAClG,eAAe;IACf,YAAY;IACZ,kBAAkB;IAClB,eAAe;IACf,YAAY;IACZ,WAAW;IACX,qBAAqB;IACrB,OAAO;IAEP,qDAAqD;IACrD,UAAU;IACV,SAAS;IACT,SAAS;IAET,oDAAoD;IACpD,OAAO,EAAE,UAAU;IACnB,mDAAmD;IACnD,UAAU;IAEV,6EAA6E;IAC7E,QAAQ;IAER,gFAAgF;IAChF,YAAY;IAEZ,qHAAqH;IACrH,eAAe;IAEf,kHAAkH;IAClH,YAAY;IAEZ,oMAAoM;IACpM,YAAY;IAEZ,2GAA2G;IAC3G,OAAO;IAEP,0GAA0G;IAC1G,MAAM;IAEN,uVAAuV;IACvV,IAAI;IAEJ,uNAAuN;IACvN,EAAE;IAEF,8EAA8E;IAC9E,KAAK;IACL,qMAAqM;IACrM,MAAM;IACN,6DAA6D;IAC7D,WAAW;IACX,+LAA+L;IAC/L,QAAQ;IAER,2GAA2G;IAC3G,MAAM;IACN,kNAAkN;IAClN,MAAM;IACN,gHAAgH;IAChH,MAAM,EAAE,MAAM;IACd,kQAAkQ;IAClQ,MAAM;CACP,CAAC,CAAA"}
1
+ {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/data/db/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,qBAAqB,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAEnM,qBAAqB;AACrB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAKzE,6EAA6E;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6EAA6E;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,mJAAmJ;AACnJ,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;IACvC,iQAAiQ;IACjQ,MAAM,EAAE,UAAU;IAElB,6QAA6Q;IAC7Q,IAAI,EAAE,QAAQ;IAEd,6EAA6E;IAC7E,MAAM;IAEN,2oBAA2oB;IAC3oB,QAAQ;IAER,+RAA+R;IAC/R,GAAG;IAEH,kGAAkG;IAClG,eAAe;IACf,YAAY;IACZ,kBAAkB;IAClB,eAAe;IACf,YAAY;IACZ,WAAW;IACX,qBAAqB;IACrB,OAAO;IAEP,qDAAqD;IACrD,UAAU;IACV,SAAS;IACT,SAAS;IAET,oDAAoD;IACpD,OAAO,EAAE,UAAU;IACnB,mDAAmD;IACnD,UAAU;IAEV,6EAA6E;IAC7E,QAAQ;IAER,gFAAgF;IAChF,YAAY;IAEZ,qHAAqH;IACrH,eAAe;IAEf,kHAAkH;IAClH,YAAY;IAEZ,oMAAoM;IACpM,YAAY;IAEZ,2GAA2G;IAC3G,OAAO;IAEP,0GAA0G;IAC1G,MAAM;IAEN,uVAAuV;IACvV,IAAI;IAEJ,iaAAia;IACja,EAAE;IAEF,8EAA8E;IAC9E,KAAK;IACL,qMAAqM;IACrM,MAAM;IACN,6DAA6D;IAC7D,WAAW;IACX,+LAA+L;IAC/L,QAAQ;IAER,2GAA2G;IAC3G,MAAM;IACN,kNAAkN;IAClN,MAAM;IACN,gHAAgH;IAChH,MAAM,EAAE,MAAM;IACd,kQAAkQ;IAClQ,MAAM;CACP,CAAC,CAAA"}
@@ -1,5 +1,12 @@
1
+ import type { FunctionDefinition } from '../db/function-definition.js';
1
2
  import type { PwSnapshot, PwTableSnapshot, PwColumnSnapshot, PwIndexSnapshot, PwForeignKeySnapshot, PwCheckSnapshot, PwUniqueSnapshot, PwCompositePKSnapshot } from './snapshot.js';
2
3
  export type DiffOp = {
4
+ type: 'create_function';
5
+ fn: FunctionDefinition;
6
+ } | {
7
+ type: 'drop_function';
8
+ fn: FunctionDefinition;
9
+ } | {
3
10
  type: 'create_table';
4
11
  table: PwTableSnapshot;
5
12
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/diff.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/diff.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,14 @@
1
1
  /** The wire name of the table an op acts on, across all `DiffOp` shapes. */
2
2
  export function diffOpTableName(op) {
3
+ if (op.type === 'create_function' || op.type === 'drop_function')
4
+ return '';
3
5
  return op.type === 'create_table' || op.type === 'drop_table' ? op.table.name : op.tableName;
4
6
  }
5
7
  export function diffSnapshots(prev, cur) {
6
8
  const ops = [];
9
+ const functionOps = diffFunctions(prev.functions ?? {}, cur.functions ?? {});
10
+ // Function drops come first: a function that reads a table must be gone before the table or a column it reads is.
11
+ ops.push(...functionOps.filter(op => op.type === 'drop_function'));
7
12
  const prevKeys = new Set(Object.keys(prev.tables));
8
13
  const curKeys = new Set(Object.keys(cur.tables));
9
14
  // Phase 0: A table's partitioning is fixed at creation — Postgres has no
@@ -49,6 +54,38 @@ export function diffSnapshots(prev, cur) {
49
54
  const curTable = cur.tables[key];
50
55
  diffAddConstraints(prevTable, curTable, ops);
51
56
  }
57
+ // Function creates come last: a LANGUAGE sql body is checked when the function is created, so every table,
58
+ // column and index it names must exist by then.
59
+ ops.push(...functionOps.filter(op => op.type === 'create_function'));
60
+ return ops;
61
+ }
62
+ /** What a function returns, without its ROWS estimate (which CREATE OR REPLACE can change). */
63
+ function shape(fn) {
64
+ return fn.returns.kind === 'table' ? { columns: fn.returns.columns } : fn.returns;
65
+ }
66
+ /**
67
+ * A function is identified by `schema.name(argtypes)`. A new key is created; a key with a changed body, volatility
68
+ * or ROWS is replaced in place; a changed result shape cannot be replaced (Postgres refuses to change a return
69
+ * type), so it is dropped and created; a key that is gone is dropped. A changed argument list is a different key,
70
+ * so the old signature is dropped and the new one created.
71
+ */
72
+ function diffFunctions(prev, cur) {
73
+ const ops = [];
74
+ for (const [key, old] of Object.entries(prev)) {
75
+ const now = cur[key];
76
+ if (now === undefined || JSON.stringify(shape(now)) !== JSON.stringify(shape(old))) {
77
+ ops.push({ type: 'drop_function', fn: old });
78
+ }
79
+ }
80
+ for (const [key, now] of Object.entries(cur)) {
81
+ const old = prev[key];
82
+ if (old === undefined) {
83
+ ops.push({ type: 'create_function', fn: now });
84
+ }
85
+ else if (JSON.stringify(old) !== JSON.stringify(now)) {
86
+ ops.push({ type: 'create_function', fn: now });
87
+ }
88
+ }
52
89
  return ops;
53
90
  }
54
91
  function diffDropConstraints(prev, cur, ops) {