@mason-data/cli 0.0.0-stage → 0.7.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 (109) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +268 -2
  3. package/dist/src/api/error.js +18 -0
  4. package/dist/src/api/postgres/catalog-worker.js +92 -0
  5. package/dist/src/api/postgres/catalog.js +477 -0
  6. package/dist/src/api/postgres/encode.js +541 -0
  7. package/dist/src/api/postgres/explain.js +143 -0
  8. package/dist/src/api/postgres/listener.js +60 -0
  9. package/dist/src/api/postgres/parser-worker.js +58 -0
  10. package/dist/src/api/postgres/parser.js +121 -0
  11. package/dist/src/api/postgres/plain.js +33 -0
  12. package/dist/src/api/postgres/protocol.js +404 -0
  13. package/dist/src/api/postgres/schema.js +54 -0
  14. package/dist/src/api/postgres/server.js +1771 -0
  15. package/dist/src/api/postgres/translate.js +248 -0
  16. package/dist/src/api/request-log.js +50 -0
  17. package/dist/src/cdc/pg-wire.js +588 -0
  18. package/dist/src/cdc/pgoutput.js +107 -0
  19. package/dist/src/cdc/poll.js +67 -0
  20. package/dist/src/cdc/source.js +65 -0
  21. package/dist/src/cli/background.js +198 -0
  22. package/dist/src/cli/command.js +173 -0
  23. package/dist/src/cli/commands.js +1277 -0
  24. package/dist/src/cli/control.js +36 -0
  25. package/dist/src/cli/demo.js +149 -0
  26. package/dist/src/cli/dev.js +669 -0
  27. package/dist/src/cli/doctor.js +260 -0
  28. package/dist/src/cli/guides.js +158 -0
  29. package/dist/src/cli/init.js +366 -0
  30. package/dist/src/cli/machine.js +162 -0
  31. package/dist/src/cli/main.js +83 -0
  32. package/dist/src/cli/mcp.js +726 -0
  33. package/dist/src/cli/pg-tls.js +100 -0
  34. package/dist/src/cli/pg-url.js +52 -0
  35. package/dist/src/cli/pin.js +36 -0
  36. package/dist/src/cli/ports.js +118 -0
  37. package/dist/src/cli/preflight.js +347 -0
  38. package/dist/src/cli/project.js +129 -0
  39. package/dist/src/cli/readiness.js +76 -0
  40. package/dist/src/cli/registry.js +220 -0
  41. package/dist/src/cli/replication-checks.js +120 -0
  42. package/dist/src/cli/replication-mode.js +148 -0
  43. package/dist/src/cli/run-server.js +311 -0
  44. package/dist/src/cli/runs.js +120 -0
  45. package/dist/src/cli/security.js +86 -0
  46. package/dist/src/cli/self-signed-cert.js +64 -0
  47. package/dist/src/cli/serve.js +301 -0
  48. package/dist/src/cli/source-edit.js +116 -0
  49. package/dist/src/cli/sources.js +564 -0
  50. package/dist/src/cli/state.js +58 -0
  51. package/dist/src/cli/style.js +187 -0
  52. package/dist/src/cli/supervise.js +248 -0
  53. package/dist/src/cli/sync.js +94 -0
  54. package/dist/src/cli/ui.js +107 -0
  55. package/dist/src/cli/user.js +229 -0
  56. package/dist/src/cli/version.js +23 -0
  57. package/dist/src/demo/seed.js +237 -0
  58. package/dist/src/demo/source.js +655 -0
  59. package/dist/src/duck/analyze.js +113 -0
  60. package/dist/src/duck/cancel.js +49 -0
  61. package/dist/src/duck/distinct-key.js +228 -0
  62. package/dist/src/duck/executor-worker.js +268 -0
  63. package/dist/src/duck/executor.js +567 -0
  64. package/dist/src/duck/explain.js +238 -0
  65. package/dist/src/duck/functions.js +819 -0
  66. package/dist/src/duck/gaps.js +94 -0
  67. package/dist/src/duck/jsonb-order.js +175 -0
  68. package/dist/src/duck/jsonb.js +188 -0
  69. package/dist/src/duck/kept-route.js +299 -0
  70. package/dist/src/duck/kept.js +964 -0
  71. package/dist/src/duck/late.js +221 -0
  72. package/dist/src/duck/libc-order.js +190 -0
  73. package/dist/src/duck/pg-eval-worker.js +34 -0
  74. package/dist/src/duck/pg-eval.js +59 -0
  75. package/dist/src/duck/pgtree.js +378 -0
  76. package/dist/src/duck/plan.js +259 -0
  77. package/dist/src/duck/publication.js +207 -0
  78. package/dist/src/duck/requests.js +209 -0
  79. package/dist/src/duck/resolved.js +248 -0
  80. package/dist/src/duck/rewrite.js +5101 -0
  81. package/dist/src/duck/store.js +907 -0
  82. package/dist/src/duck/stream.js +518 -0
  83. package/dist/src/duck/sync.js +2929 -0
  84. package/dist/src/duck/to-char.js +482 -0
  85. package/dist/src/duck/token.js +31 -0
  86. package/dist/src/duck/types.js +427 -0
  87. package/dist/src/duck/users.js +231 -0
  88. package/dist/src/duck/verified.js +1001 -0
  89. package/dist/src/duck/views.js +150 -0
  90. package/dist/src/lib/database-url.js +74 -0
  91. package/dist/src/lib/did-you-mean.js +43 -0
  92. package/dist/src/lib/dotenv.js +72 -0
  93. package/dist/src/lib/exit-when-written.js +20 -0
  94. package/dist/src/lib/float-equal.js +69 -0
  95. package/dist/src/lib/http-json.js +22 -0
  96. package/dist/src/lib/log.js +161 -0
  97. package/dist/src/lib/pg-numeric.js +489 -0
  98. package/dist/src/lib/port.js +22 -0
  99. package/dist/src/lib/project-file.js +49 -0
  100. package/dist/src/lib/project-files.js +43 -0
  101. package/dist/src/lib/project-version.js +76 -0
  102. package/dist/src/lib/runnable.js +14 -0
  103. package/dist/src/lib/shell-word.js +4 -0
  104. package/dist/src/lib/timezone.js +237 -0
  105. package/dist/src/lib/zone-names.js +114 -0
  106. package/dist/src/web/app.css +76 -0
  107. package/dist/src/web/app.js +491 -0
  108. package/dist/src/web/index.html +44 -0
  109. package/package.json +22 -4
@@ -0,0 +1,248 @@
1
+ /**
2
+ * WHAT POSTGRES RESOLVED A STATEMENT TO, as keys the DuckDB endpoint admits only once the oracle has held them to
3
+ * Postgres (docs/SQL-SUPPORT.md "What is verified"; src/duck/verified.ts). The analysed tree (src/duck/pgtree.ts) names
4
+ * every function, operator and aggregate by the OID Postgres chose for its argument types — `round(numeric, integer)`,
5
+ * not "round" — every cast by its two types, every value by its type, and every construct by its node and the field that
6
+ * says which kind of it it is (a LEFT from an INNER join, a correlated EXISTS from a scalar subquery). A statement whose
7
+ * keys are all verified is made only of pieces the oracle answered exactly as Postgres does; anything else is refused by
8
+ * name rather than answered by a rewrite nobody checked (the class a review found: a construct the
9
+ * rewrite did not know, run by DuckDB as DuckDB means it).
10
+ *
11
+ * A key is `kind:value`: `node:OPEXPR`, `function:1711`, `operator:96`, `aggregate:2108`, `window:3100`, `type:1700`,
12
+ * `cast:25>1700` (a cast's input and result types), `field:JOINEXPR.jointype=1`. Built-in OIDs are fixed by Postgres's
13
+ * catalog (pg_proc.dat and its siblings), the same in the catalog Postgres as in the source; a function a customer
14
+ * defined is never in the catalog Postgres, so it never reaches here.
15
+ */
16
+ import { exprType } from "./pgtree.js";
17
+ const isNode = (v) => v !== null && v !== undefined && typeof v === "object" && !Array.isArray(v);
18
+ /** an OID a customer's database made (an extension's type or operator, a table's row type) differs between databases:
19
+ * marked `u<oid>` here, and named by the catalog before it is a key (`nameUserKeys`: `ext:=(citext,citext)`) */
20
+ const oidKey = (oid) => Number(oid) >= 16384 ? `u${oid}` : String(oid);
21
+ const USER = /^u(\d+)$/;
22
+ /**
23
+ * The keys holding a user OID, named by what the catalog says it is — an operator as `ext:<regoperator>`, a function or
24
+ * aggregate as `ext:<regprocedure>`, a type as `ext:<typname>` (a table's row type as `rowtype`, whatever the table) —
25
+ * so a key is the same in every catalog that has the extension. Unknown to the catalog, it stays `u<oid>`.
26
+ */
27
+ export async function nameUserKeys(rows, keys) {
28
+ const list = [...keys];
29
+ if (!list.some((k) => USER.test(k.slice(k.indexOf(":") + 1)) || /[>:]u\d+$/.test(k)))
30
+ return new Set(list);
31
+ const oids = { proc: new Set(), op: new Set(), type: new Set() };
32
+ for (const k of list) {
33
+ const kind = k.slice(0, k.indexOf(":")), value = k.slice(k.indexOf(":") + 1);
34
+ const parts = kind === "cast" ? value.split(">") : [value];
35
+ for (const p of parts) {
36
+ const m = USER.exec(p);
37
+ if (m === null)
38
+ continue;
39
+ (kind === "operator" ? oids.op : kind === "type" || kind === "cast" ? oids.type : oids.proc).add(m[1]);
40
+ }
41
+ }
42
+ const read = async (set, sql) => set.size === 0 ? new Map() : new Map((await rows(sql([...set].join(", ")))).map((r) => [r[0], r[1]]));
43
+ const proc = await read(oids.proc, (l) => `SELECT oid::text, 'ext:' || oid::regprocedure::text FROM pg_proc WHERE oid IN (${l})`);
44
+ const op = await read(oids.op, (l) => `SELECT oid::text, 'ext:' || oid::regoperator::text FROM pg_operator WHERE oid IN (${l})`);
45
+ const type = await read(oids.type, (l) => `SELECT oid::text, CASE WHEN typtype = 'c' THEN 'rowtype' ELSE 'ext:' || typname END FROM pg_type WHERE oid IN (${l})`);
46
+ const name = (kind, p) => { const m = USER.exec(p); return m === null ? p : (kind === "operator" ? op : kind === "type" || kind === "cast" ? type : proc).get(m[1]) ?? p; };
47
+ return new Set(list.map((k) => {
48
+ const kind = k.slice(0, k.indexOf(":")), value = k.slice(k.indexOf(":") + 1);
49
+ return kind === "cast" ? `cast:${value.split(">").map((p) => name(kind, p)).join(">")}` : `${kind}:${name(kind, value)}`;
50
+ }));
51
+ }
52
+ const nonEmpty = (v) => Array.isArray(v) ? v.length > 0 : v !== null && v !== undefined && v !== "<>";
53
+ /** the fields whose value says which KIND of a node it is — each kind answered differently, so each verified apart */
54
+ const KIND_FIELDS = {
55
+ BOOLEXPR: ["boolop"], NULLTEST: ["nulltesttype"], BOOLEANTEST: ["booltesttype"], SUBLINK: ["subLinkType"],
56
+ SQLVALUEFUNCTION: ["op"], MINMAXEXPR: ["op"], SCALARARRAYOPEXPR: ["useOr"], ROWCOMPAREEXPR: ["cmptype", "rctype"],
57
+ JOINEXPR: ["jointype"], RANGETBLENTRY: ["rtekind", "lateral"], SETOPERATIONSTMT: ["op", "all"], AGGREF: ["aggkind", "aggstar"],
58
+ WINDOWCLAUSE: ["frameOptions"], QUERY: ["limitOption", "hasDistinctOn", "hasRecursive", "hasTargetSRFs", "hasForUpdate"],
59
+ SORTGROUPCLAUSE: ["nulls_first"], GROUPINGSET: ["kind"], XMLEXPR: ["op"], FUNCEXPR: ["funcretset", "funcvariadic"],
60
+ WINDOWFUNC: ["winstar", "winagg"], CTE: ["ctematerialized"], COMMONTABLEEXPR: ["ctematerialized"],
61
+ };
62
+ /** the flags whose false is a kind of its own */
63
+ const BOTH_WAYS = new Set(["SCALARARRAYOPEXPR.useOr", "SETOPERATIONSTMT.all", "SORTGROUPCLAUSE.nulls_first"]);
64
+ /** the casts: a node whose input is its `arg` and whose result is its own type */
65
+ const CASTS = new Set(["RELABELTYPE", "COERCEVIAIO", "ARRAYCOERCEEXPR", "COERCETODOMAIN"]);
66
+ /** fields that hold an OID of what Postgres resolved the node to */
67
+ const RESOLVED = {
68
+ FUNCEXPR: ["funcid", "function"], OPEXPR: ["opno", "operator"], DISTINCTEXPR: ["opno", "operator"], NULLIFEXPR: ["opno", "operator"],
69
+ SCALARARRAYOPEXPR: ["opno", "operator"], AGGREF: ["aggfnoid", "aggregate"], WINDOWFUNC: ["winfnoid", "window"],
70
+ };
71
+ /** Every key of an analysed tree (a QUERY and everything under it). */
72
+ export function resolvedKeys(tree) {
73
+ const keys = new Set();
74
+ const visit = (v) => {
75
+ if (Array.isArray(v)) {
76
+ for (const x of v)
77
+ visit(x);
78
+ return;
79
+ }
80
+ if (!isNode(v))
81
+ return;
82
+ const name = v._node;
83
+ keys.add(`node:${name}`);
84
+ const t = exprType(v);
85
+ if (t !== null && t.type !== 0)
86
+ keys.add(`type:${oidKey(t.type)}`);
87
+ const resolved = RESOLVED[name];
88
+ if (resolved) {
89
+ const oid = v[resolved[0]];
90
+ if (typeof oid === "string")
91
+ keys.add(`${resolved[1]}:${oidKey(oid)}`);
92
+ }
93
+ if (CASTS.has(name) && isNode(v.arg)) {
94
+ const from = exprType(v.arg);
95
+ if (from !== null && t !== null)
96
+ keys.add(`cast:${oidKey(from.type)}>${oidKey(t.type)}`);
97
+ }
98
+ // a flag that is false is the plain node, which `node:` already names (but `<> ALL` is not `IN`, nor UNION UNION ALL)
99
+ for (const f of KIND_FIELDS[name] ?? []) {
100
+ const x = v[f];
101
+ if (typeof x === "string" && (x !== "false" || BOTH_WAYS.has(`${name}.${f}`)))
102
+ keys.add(`field:${name}.${f}=${x}`);
103
+ }
104
+ // an aggregate's DISTINCT, ORDER BY and FILTER, and a window's PARTITION BY and ORDER BY, each change its answer
105
+ if (name === "AGGREF")
106
+ for (const f of ["aggdistinct", "aggorder", "aggfilter", "aggdirectargs"])
107
+ if (nonEmpty(v[f]))
108
+ keys.add(`field:AGGREF.${f}`);
109
+ if (name === "WINDOWFUNC" && nonEmpty(v.aggfilter))
110
+ keys.add("field:WINDOWFUNC.aggfilter");
111
+ if (name === "QUERY")
112
+ for (const f of ["distinctClause", "groupingSets", "havingQual", "limitCount", "limitOffset", "cteList", "setOperations", "windowClause"])
113
+ if (nonEmpty(v[f]))
114
+ keys.add(`field:QUERY.${f}`);
115
+ // a join's alias list names every column of both sides, used or not: a column nobody reads decides nothing
116
+ for (const [k, x] of Object.entries(v))
117
+ if (k !== "_node" && k !== "joinaliasvars")
118
+ visit(x);
119
+ };
120
+ visit(tree);
121
+ return keys;
122
+ }
123
+ // --------------------------------------------------------------------------------------------- what a key names
124
+ /** each kind field's values in SQL's words (the enums of Postgres 18's primnodes.h / parsenodes.h, as outfuncs.c writes them) */
125
+ const VALUES = {
126
+ "BOOLEXPR.boolop": { and: "AND", or: "OR", not: "NOT" },
127
+ "NULLTEST.nulltesttype": { 0: "IS NULL", 1: "IS NOT NULL" },
128
+ "BOOLEANTEST.booltesttype": { 0: "IS TRUE", 1: "IS NOT TRUE", 2: "IS FALSE", 3: "IS NOT FALSE", 4: "IS UNKNOWN", 5: "IS NOT UNKNOWN" },
129
+ "SUBLINK.subLinkType": { 0: "EXISTS (subquery)", 1: "op ALL (subquery)", 2: "IN / op ANY (subquery)", 3: "a row compared with a subquery", 4: "a scalar subquery", 5: "a multi-column assignment", 6: "ARRAY(subquery)", 7: "a WITH subquery" },
130
+ "SQLVALUEFUNCTION.op": { 0: "CURRENT_DATE", 1: "CURRENT_TIME", 2: "CURRENT_TIME(n)", 3: "CURRENT_TIMESTAMP", 4: "CURRENT_TIMESTAMP(n)", 5: "LOCALTIME", 6: "LOCALTIME(n)", 7: "LOCALTIMESTAMP", 8: "LOCALTIMESTAMP(n)", 9: "CURRENT_ROLE", 10: "CURRENT_USER", 11: "USER", 12: "SESSION_USER", 13: "CURRENT_CATALOG", 14: "CURRENT_SCHEMA" },
131
+ "MINMAXEXPR.op": { 0: "GREATEST", 1: "LEAST" },
132
+ "SCALARARRAYOPEXPR.useOr": { true: "op ANY (array) / IN (list)", false: "op ALL (array)" },
133
+ "JOINEXPR.jointype": { 0: "INNER JOIN", 1: "LEFT JOIN", 2: "FULL JOIN", 3: "RIGHT JOIN", 4: "a semi-join", 5: "an anti-join" },
134
+ "RANGETBLENTRY.rtekind": { 0: "a table", 1: "a subquery in FROM", 2: "a join", 3: "a function in FROM", 4: "a table function", 5: "VALUES", 6: "a WITH query", 7: "a named tuplestore", 8: "an empty FROM", 9: "GROUP BY" },
135
+ "RANGETBLENTRY.lateral": { true: "LATERAL" },
136
+ "SETOPERATIONSTMT.op": { 1: "UNION", 2: "INTERSECT", 3: "EXCEPT" },
137
+ "SETOPERATIONSTMT.all": { true: "UNION / INTERSECT / EXCEPT ALL", false: "UNION / INTERSECT / EXCEPT without ALL" },
138
+ "AGGREF.aggkind": { n: "an aggregate", o: "an ordered-set aggregate (WITHIN GROUP)", h: "a hypothetical-set aggregate" },
139
+ "AGGREF.aggstar": { true: "an aggregate of * (count(*))" },
140
+ "QUERY.limitOption": { 0: "LIMIT / FETCH FIRST … ONLY", 1: "FETCH FIRST … WITH TIES" },
141
+ "QUERY.hasDistinctOn": { true: "DISTINCT ON" }, "QUERY.hasRecursive": { true: "WITH RECURSIVE" },
142
+ "QUERY.hasTargetSRFs": { true: "a set-returning function in the SELECT list" }, "QUERY.hasForUpdate": { true: "FOR UPDATE / FOR SHARE" },
143
+ "SORTGROUPCLAUSE.nulls_first": { true: "NULLs first (NULLS FIRST, or DESC)", false: "NULLs last (NULLS LAST, or ASC)" },
144
+ "GROUPINGSET.kind": { 0: "an empty grouping set ()", 1: "a grouping set's column", 2: "ROLLUP", 3: "CUBE", 4: "GROUPING SETS" },
145
+ "FUNCEXPR.funcretset": { true: "a set-returning function" }, "FUNCEXPR.funcvariadic": { true: "a VARIADIC argument" },
146
+ "WINDOWFUNC.winstar": { true: "a window function of *" }, "WINDOWFUNC.winagg": { true: "an aggregate OVER a window" },
147
+ "CTE.ctematerialized": { 0: "WITH", 1: "WITH … AS MATERIALIZED", 2: "WITH … AS NOT MATERIALIZED" },
148
+ "COMMONTABLEEXPR.ctematerialized": { 0: "WITH", 1: "WITH … AS MATERIALIZED", 2: "WITH … AS NOT MATERIALIZED" },
149
+ };
150
+ const FLAGS = {
151
+ "AGGREF.aggdistinct": "an aggregate of DISTINCT values", "AGGREF.aggorder": "an aggregate's own ORDER BY", "AGGREF.aggfilter": "an aggregate's FILTER (WHERE …)",
152
+ "AGGREF.aggdirectargs": "an ordered-set aggregate's direct argument", "WINDOWFUNC.aggfilter": "a window aggregate's FILTER (WHERE …)",
153
+ "QUERY.distinctClause": "SELECT DISTINCT", "QUERY.groupingSets": "GROUPING SETS / ROLLUP / CUBE", "QUERY.havingQual": "HAVING",
154
+ "QUERY.limitCount": "LIMIT", "QUERY.limitOffset": "OFFSET", "QUERY.cteList": "WITH", "QUERY.setOperations": "UNION / INTERSECT / EXCEPT", "QUERY.windowClause": "a window (OVER)",
155
+ };
156
+ const NODES = {
157
+ AGGREF: "an aggregate", WINDOWFUNC: "a window function", SUBLINK: "a subquery", CASEEXPR: "CASE", CASEWHEN: "CASE's WHEN", CASETESTEXPR: "CASE x WHEN …",
158
+ COALESCEEXPR: "COALESCE", NULLIFEXPR: "NULLIF", DISTINCTEXPR: "IS DISTINCT FROM", MINMAXEXPR: "GREATEST / LEAST", ARRAYEXPR: "ARRAY[…]",
159
+ ROWEXPR: "ROW(…)", ROWCOMPAREEXPR: "a row comparison", SUBSCRIPTINGREF: "a subscript (a[i], a[i:j])", FIELDSELECT: "a field of a row value",
160
+ COLLATEEXPR: "COLLATE", GROUPINGFUNC: "GROUPING()", XMLEXPR: "an XML function", SQLVALUEFUNCTION: "CURRENT_DATE and its kin",
161
+ NULLTEST: "IS [NOT] NULL", BOOLEANTEST: "IS [NOT] TRUE / FALSE / UNKNOWN", BOOLEXPR: "AND / OR / NOT", SCALARARRAYOPEXPR: "IN (list) / op ANY|ALL (array)",
162
+ OPEXPR: "an operator", FUNCEXPR: "a function call", CONST: "a literal", PARAM: "a parameter ($n)", VAR: "a column", JOINEXPR: "a JOIN",
163
+ SETOPERATIONSTMT: "UNION / INTERSECT / EXCEPT", COMMONTABLEEXPR: "a WITH query", WINDOWCLAUSE: "a window (OVER)", GROUPINGSET: "a grouping set",
164
+ RELABELTYPE: "a binary-compatible cast", COERCEVIAIO: "a cast through text", ARRAYCOERCEEXPR: "a cast of an array's elements", COERCETODOMAIN: "a cast to a domain",
165
+ JSONEXPR: "a SQL/JSON function", JSONCONSTRUCTOREXPR: "a SQL/JSON constructor", JSONISPREDICATE: "IS JSON", MERGESUPPORTFUNC: "MERGE's support function",
166
+ ALIAS: "an alias", FROMEXPR: "FROM and WHERE", NAMEDARGEXPR: "a named argument (name => value)", QUERY: "a query", RANGETBLENTRY: "an item of FROM",
167
+ RANGETBLFUNCTION: "a function in FROM", RANGETBLREF: "an item of FROM", RTEPERMISSIONINFO: "a table read", SORTGROUPCLAUSE: "ORDER BY / GROUP BY / DISTINCT",
168
+ TARGETENTRY: "an item of the SELECT list", WINDOWFUNCRUNCONDITION: "a window function's run condition",
169
+ NEXTVALUEEXPR: "NEXT VALUE FOR", CURRENTOFEXPR: "CURRENT OF", INFERENCEELEM: "ON CONFLICT", TABLEFUNC: "a table function (XMLTABLE / JSON_TABLE)",
170
+ };
171
+ /** a window frame's options (FRAMEOPTION_* in parsenodes.h) as the clause that sets them */
172
+ export function frameText(options) {
173
+ if ((options & 1) === 0)
174
+ return "the default window frame";
175
+ const mode = options & 4 ? "ROWS" : options & 8 ? "GROUPS" : "RANGE";
176
+ const bound = (unboundedPreceding, unboundedFollowing, current, offsetPreceding, offsetFollowing) => options & unboundedPreceding ? "UNBOUNDED PRECEDING" : options & unboundedFollowing ? "UNBOUNDED FOLLOWING" : options & current ? "CURRENT ROW"
177
+ : options & offsetPreceding ? "n PRECEDING" : options & offsetFollowing ? "n FOLLOWING" : "?";
178
+ const start = bound(32, 128, 512, 2048, 8192);
179
+ const text = options & 16 ? `${mode} BETWEEN ${start} AND ${bound(64, 256, 1024, 4096, 16384)}` : `${mode} ${start}`;
180
+ const exclude = options & 32768 ? " EXCLUDE CURRENT ROW" : options & 65536 ? " EXCLUDE GROUP" : options & 131072 ? " EXCLUDE TIES" : "";
181
+ return text + exclude;
182
+ }
183
+ /**
184
+ * What each key names, in SQL's words: a function's, operator's or type's from the catalog through `rows` (the catalog
185
+ * Postgres's, or any Postgres 18's: built-in OIDs are the same in every one), a construct's from the tables above.
186
+ */
187
+ export async function describeKeys(rows, keys) {
188
+ const out = new Map();
189
+ const procs = new Set(), ops = new Set(), types = new Set();
190
+ const list = [...keys];
191
+ for (const k of list) {
192
+ const [kind, value] = [k.slice(0, k.indexOf(":")), k.slice(k.indexOf(":") + 1)];
193
+ if (kind === "function" || kind === "aggregate" || kind === "window")
194
+ procs.add(value);
195
+ else if (kind === "operator")
196
+ ops.add(value);
197
+ else if (kind === "type")
198
+ types.add(value);
199
+ else if (kind === "cast")
200
+ for (const t of value.split(">"))
201
+ types.add(t);
202
+ }
203
+ const read = async (oids, sql) => {
204
+ const valid = [...oids].filter((o) => /^\d+$/.test(o));
205
+ return valid.length === 0 ? new Map() : new Map((await rows(sql(valid.join(", ")))).map((r) => [r[0], r[1]]));
206
+ };
207
+ const proc = await read(procs, (l) => `SELECT oid::text, oid::regprocedure::text FROM pg_proc WHERE oid IN (${l})`);
208
+ const op = await read(ops, (l) => `SELECT oid::text, oid::regoperator::text FROM pg_operator WHERE oid IN (${l})`);
209
+ const type = await read(types, (l) => `SELECT oid::text, format_type(oid, NULL) FROM pg_type WHERE oid IN (${l})`);
210
+ const typeName = (t) => t === "rowtype" ? "a table's row type" : t.startsWith("ext:") ? `${t.slice(4)} (an extension's)` : type.get(t) ?? `type OID ${t}`;
211
+ const extName = (v) => v.startsWith("ext:") ? `${v.slice(4)} (an extension's)` : `OID ${v}`;
212
+ for (const k of list) {
213
+ const kind = k.slice(0, k.indexOf(":")), value = k.slice(k.indexOf(":") + 1);
214
+ switch (kind) {
215
+ case "node":
216
+ out.set(k, NODES[value] ?? `Postgres's ${value} node`);
217
+ break;
218
+ case "function":
219
+ out.set(k, `the function ${proc.get(value) ?? extName(value)}`);
220
+ break;
221
+ case "aggregate":
222
+ out.set(k, `the aggregate ${proc.get(value) ?? extName(value)}`);
223
+ break;
224
+ case "window":
225
+ out.set(k, `the window function ${proc.get(value) ?? extName(value)}`);
226
+ break;
227
+ case "operator":
228
+ out.set(k, `the operator ${op.get(value) ?? extName(value)}`);
229
+ break;
230
+ case "type":
231
+ out.set(k, `a value of type ${typeName(value)}`);
232
+ break;
233
+ case "cast": {
234
+ const [from, to] = value.split(">");
235
+ out.set(k, `a cast from ${typeName(from)} to ${typeName(to)}`);
236
+ break;
237
+ }
238
+ case "field": {
239
+ const eq = value.indexOf("=");
240
+ const field = eq < 0 ? value : value.slice(0, eq), v = eq < 0 ? "" : value.slice(eq + 1);
241
+ out.set(k, eq < 0 ? FLAGS[field] ?? field : field === "WINDOWCLAUSE.frameOptions" ? `a window frame: ${frameText(Number(v))}` : VALUES[field]?.[v] ?? `${field} = ${v}`);
242
+ break;
243
+ }
244
+ default: out.set(k, k);
245
+ }
246
+ }
247
+ return out;
248
+ }