@onreza/sqlx-js 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +496 -0
- package/ROADMAP.md +28 -0
- package/dist/bin/sqlx-js.d.ts +2 -0
- package/dist/bin/sqlx-js.js +180 -0
- package/dist/src/bun-runtime.d.ts +7 -0
- package/dist/src/bun-runtime.js +45 -0
- package/dist/src/bun.d.ts +22 -0
- package/dist/src/bun.js +9 -0
- package/dist/src/cache.d.ts +36 -0
- package/dist/src/cache.js +104 -0
- package/dist/src/codegen.d.ts +2 -0
- package/dist/src/codegen.js +74 -0
- package/dist/src/commands/migrate.d.ts +63 -0
- package/dist/src/commands/migrate.js +283 -0
- package/dist/src/commands/prepare.d.ts +23 -0
- package/dist/src/commands/prepare.js +314 -0
- package/dist/src/commands/schema.d.ts +14 -0
- package/dist/src/commands/schema.js +72 -0
- package/dist/src/commands/watch.d.ts +21 -0
- package/dist/src/commands/watch.js +94 -0
- package/dist/src/config.d.ts +6 -0
- package/dist/src/config.js +23 -0
- package/dist/src/index.d.ts +23 -0
- package/dist/src/index.js +10 -0
- package/dist/src/pg/analyze.d.ts +13 -0
- package/dist/src/pg/analyze.js +479 -0
- package/dist/src/pg/extensions.d.ts +2 -0
- package/dist/src/pg/extensions.js +15 -0
- package/dist/src/pg/narrow.d.ts +3 -0
- package/dist/src/pg/narrow.js +146 -0
- package/dist/src/pg/oids.d.ts +10 -0
- package/dist/src/pg/oids.js +137 -0
- package/dist/src/pg/param-map.d.ts +12 -0
- package/dist/src/pg/param-map.js +180 -0
- package/dist/src/pg/schema.d.ts +61 -0
- package/dist/src/pg/schema.js +225 -0
- package/dist/src/pg/wire.d.ts +134 -0
- package/dist/src/pg/wire.js +705 -0
- package/dist/src/postgres-runtime.d.ts +11 -0
- package/dist/src/postgres-runtime.js +150 -0
- package/dist/src/runtime.d.ts +69 -0
- package/dist/src/runtime.js +446 -0
- package/dist/src/scan/scanner.d.ts +12 -0
- package/dist/src/scan/scanner.js +283 -0
- package/dist/src/schema-snapshot.d.ts +104 -0
- package/dist/src/schema-snapshot.js +473 -0
- package/dist/src/typed.d.ts +25 -0
- package/dist/src/typed.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { parse } from "libpg-query";
|
|
2
|
+
import { narrowFromWhere, isNarrowed } from "./narrow.js";
|
|
3
|
+
export async function analyzeQuery(sql, rowDesc, schema) {
|
|
4
|
+
const ast = await parse(sql);
|
|
5
|
+
const stmt = ast?.stmts?.[0]?.stmt;
|
|
6
|
+
if (!stmt)
|
|
7
|
+
return conservative(rowDesc, "libpg-query returned no statements");
|
|
8
|
+
if (stmt.SelectStmt) {
|
|
9
|
+
return await analyzeSelect(stmt.SelectStmt, rowDesc, schema);
|
|
10
|
+
}
|
|
11
|
+
if (stmt.InsertStmt) {
|
|
12
|
+
return await analyzeDml(stmt.InsertStmt, rowDesc, schema, "insert");
|
|
13
|
+
}
|
|
14
|
+
if (stmt.UpdateStmt) {
|
|
15
|
+
return await analyzeDml(stmt.UpdateStmt, rowDesc, schema, "update");
|
|
16
|
+
}
|
|
17
|
+
if (stmt.DeleteStmt) {
|
|
18
|
+
return await analyzeDml(stmt.DeleteStmt, rowDesc, schema, "delete");
|
|
19
|
+
}
|
|
20
|
+
const kind = Object.keys(stmt)[0] ?? "unknown";
|
|
21
|
+
return conservative(rowDesc, `unsupported statement type: ${kind}`);
|
|
22
|
+
}
|
|
23
|
+
function conservative(rowDesc, reason) {
|
|
24
|
+
return {
|
|
25
|
+
perColumnNullable: rowDesc.map(() => true),
|
|
26
|
+
referencedTables: [],
|
|
27
|
+
...(reason && rowDesc.length > 0 ? { degraded: { reason } } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function analyzeSelect(select, rowDesc, schema) {
|
|
31
|
+
if (!select.targetList || !select.fromClause) {
|
|
32
|
+
if (select.targetList && !select.fromClause) {
|
|
33
|
+
const scope = await buildScope(select, schema);
|
|
34
|
+
return runTargets(select.targetList, rowDesc, scope);
|
|
35
|
+
}
|
|
36
|
+
return conservative(rowDesc, "SELECT without targetList");
|
|
37
|
+
}
|
|
38
|
+
const scope = await buildScope(select, schema);
|
|
39
|
+
return runTargets(select.targetList, rowDesc, scope);
|
|
40
|
+
}
|
|
41
|
+
async function analyzeDml(stmt, rowDesc, schema, kind) {
|
|
42
|
+
const returningList = stmt.returningList ?? [];
|
|
43
|
+
if (returningList.length === 0 && rowDesc.length === 0) {
|
|
44
|
+
return { perColumnNullable: [], referencedTables: tablesFromRelation(stmt.relation) };
|
|
45
|
+
}
|
|
46
|
+
const fakeSelect = dmlAsSelect(stmt, kind, returningList);
|
|
47
|
+
const scope = await buildScope(fakeSelect, schema);
|
|
48
|
+
return runTargets(returningList, rowDesc, scope);
|
|
49
|
+
}
|
|
50
|
+
function dmlAsSelect(stmt, kind, targetList) {
|
|
51
|
+
const fromClause = stmt.relation ? [{ RangeVar: stmt.relation }] : [];
|
|
52
|
+
if (kind === "update" && Array.isArray(stmt.fromClause)) {
|
|
53
|
+
fromClause.push(...stmt.fromClause);
|
|
54
|
+
}
|
|
55
|
+
if (kind === "delete" && Array.isArray(stmt.usingClause)) {
|
|
56
|
+
fromClause.push(...stmt.usingClause);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
targetList,
|
|
60
|
+
fromClause,
|
|
61
|
+
whereClause: kind === "update" || kind === "delete" ? stmt.whereClause : undefined,
|
|
62
|
+
withClause: stmt.withClause,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function tablesFromRelation(relation) {
|
|
66
|
+
if (!relation || typeof relation.relname !== "string")
|
|
67
|
+
return [];
|
|
68
|
+
const out = { name: relation.relname };
|
|
69
|
+
if (relation.schemaname)
|
|
70
|
+
out.schema = relation.schemaname;
|
|
71
|
+
return [out];
|
|
72
|
+
}
|
|
73
|
+
async function buildScope(select, schema) {
|
|
74
|
+
const scope = {
|
|
75
|
+
aliases: new Map(),
|
|
76
|
+
aliasOidByName: new Map(),
|
|
77
|
+
tableRefsByOid: new Map(),
|
|
78
|
+
hasStar: false,
|
|
79
|
+
schema,
|
|
80
|
+
forcedNonNull: narrowFromWhere(select.whereClause),
|
|
81
|
+
cteColumnInfo: new Map(),
|
|
82
|
+
};
|
|
83
|
+
if (select.withClause?.ctes) {
|
|
84
|
+
for (const cteWrap of select.withClause.ctes) {
|
|
85
|
+
const cte = cteWrap?.CommonTableExpr;
|
|
86
|
+
if (!cte)
|
|
87
|
+
continue;
|
|
88
|
+
const name = cte.ctename;
|
|
89
|
+
if (!name)
|
|
90
|
+
continue;
|
|
91
|
+
const colsInfo = await analyzeCteColumns(cte, schema);
|
|
92
|
+
scope.cteColumnInfo.set(name, colsInfo);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const entry of select.fromClause ?? []) {
|
|
96
|
+
walkFrom(entry, false, scope);
|
|
97
|
+
}
|
|
98
|
+
for (const t of select.targetList ?? []) {
|
|
99
|
+
if (containsStar(t?.ResTarget?.val))
|
|
100
|
+
scope.hasStar = true;
|
|
101
|
+
}
|
|
102
|
+
const referencedTables = [];
|
|
103
|
+
for (const a of scope.aliases.values()) {
|
|
104
|
+
if (a.kind === "table")
|
|
105
|
+
referencedTables.push({ schema: a.schema, name: a.relname });
|
|
106
|
+
}
|
|
107
|
+
await schema.loadTableNames(referencedTables);
|
|
108
|
+
const allOids = [];
|
|
109
|
+
for (const [aliasName, a] of scope.aliases) {
|
|
110
|
+
if (a.kind !== "table")
|
|
111
|
+
continue;
|
|
112
|
+
const oid = schema.resolveTable(a.schema, a.relname);
|
|
113
|
+
if (oid === undefined)
|
|
114
|
+
continue;
|
|
115
|
+
scope.aliasOidByName.set(aliasName, oid);
|
|
116
|
+
allOids.push(oid);
|
|
117
|
+
const arr = scope.tableRefsByOid.get(oid) ?? [];
|
|
118
|
+
arr.push(a);
|
|
119
|
+
scope.tableRefsByOid.set(oid, arr);
|
|
120
|
+
}
|
|
121
|
+
await schema.loadColumnsForTables(allOids);
|
|
122
|
+
return scope;
|
|
123
|
+
}
|
|
124
|
+
async function analyzeCteColumns(cte, schema) {
|
|
125
|
+
const result = new Map();
|
|
126
|
+
const explicitColNames = Array.isArray(cte.aliascolnames)
|
|
127
|
+
? cte.aliascolnames.map((n) => n?.String?.sval).filter((s) => typeof s === "string")
|
|
128
|
+
: undefined;
|
|
129
|
+
const inner = cte.ctequery?.SelectStmt
|
|
130
|
+
?? cte.ctequery?.InsertStmt
|
|
131
|
+
?? cte.ctequery?.UpdateStmt
|
|
132
|
+
?? cte.ctequery?.DeleteStmt;
|
|
133
|
+
if (!inner)
|
|
134
|
+
return result;
|
|
135
|
+
let targetList;
|
|
136
|
+
let dmlKind;
|
|
137
|
+
if (cte.ctequery?.SelectStmt) {
|
|
138
|
+
targetList = inner.targetList;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
targetList = inner.returningList ?? [];
|
|
142
|
+
if (cte.ctequery?.InsertStmt)
|
|
143
|
+
dmlKind = "insert";
|
|
144
|
+
else if (cte.ctequery?.UpdateStmt)
|
|
145
|
+
dmlKind = "update";
|
|
146
|
+
else if (cte.ctequery?.DeleteStmt)
|
|
147
|
+
dmlKind = "delete";
|
|
148
|
+
}
|
|
149
|
+
if (!Array.isArray(targetList) || targetList.length === 0)
|
|
150
|
+
return result;
|
|
151
|
+
const isSelect = !!cte.ctequery?.SelectStmt;
|
|
152
|
+
const scope = isSelect
|
|
153
|
+
? await buildScope(inner, schema)
|
|
154
|
+
: await buildScope(dmlAsSelect(inner, dmlKind, targetList), schema);
|
|
155
|
+
for (let i = 0; i < targetList.length; i++) {
|
|
156
|
+
const t = targetList[i];
|
|
157
|
+
const explicit = explicitColNames?.[i];
|
|
158
|
+
const colName = explicit ?? t?.ResTarget?.name ?? colNameOfColumnRef(t?.ResTarget?.val) ?? `?column?${i}`;
|
|
159
|
+
const isStar = containsStar(t?.ResTarget?.val);
|
|
160
|
+
if (isStar)
|
|
161
|
+
continue;
|
|
162
|
+
const nullable = computeTargetNullable(t, scope);
|
|
163
|
+
result.set(colName, nullable);
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
function runTargets(targets, rowDesc, scope) {
|
|
168
|
+
const referencedTables = [];
|
|
169
|
+
for (const a of scope.aliases.values()) {
|
|
170
|
+
if (a.kind === "table")
|
|
171
|
+
referencedTables.push({ schema: a.schema, name: a.relname });
|
|
172
|
+
}
|
|
173
|
+
const nullables = new Array(rowDesc.length).fill(true);
|
|
174
|
+
if (scope.hasStar || targets.length !== rowDesc.length) {
|
|
175
|
+
for (let i = 0; i < rowDesc.length; i++) {
|
|
176
|
+
const f = rowDesc[i];
|
|
177
|
+
nullables[i] = nullableFromRowDescConservative(f, scope);
|
|
178
|
+
}
|
|
179
|
+
return { perColumnNullable: nullables, referencedTables };
|
|
180
|
+
}
|
|
181
|
+
for (let i = 0; i < rowDesc.length; i++) {
|
|
182
|
+
const f = rowDesc[i];
|
|
183
|
+
const target = targets[i];
|
|
184
|
+
const val = target.ResTarget?.val;
|
|
185
|
+
if (f.tableOid !== 0 && f.columnAttr !== 0) {
|
|
186
|
+
const aliasName = aliasOfColumnRef(val);
|
|
187
|
+
const refColName = colNameOfColumnRef(val);
|
|
188
|
+
if (refColName && isNarrowed(scope.forcedNonNull, aliasName ?? undefined, refColName)) {
|
|
189
|
+
nullables[i] = false;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const notNull = scope.schema.isNotNull(f.tableOid, f.columnAttr);
|
|
193
|
+
let joinNullable;
|
|
194
|
+
if (aliasName && scope.aliases.has(aliasName)) {
|
|
195
|
+
joinNullable = scope.aliases.get(aliasName).joinNullable;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
joinNullable = anyAliasNullableForOid(f.tableOid, scope);
|
|
199
|
+
}
|
|
200
|
+
nullables[i] = !(notNull === true && !joinNullable);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
nullables[i] = expressionNullable(val, scope);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { perColumnNullable: nullables, referencedTables };
|
|
207
|
+
}
|
|
208
|
+
function addForcedNonNull(scope, set) {
|
|
209
|
+
for (const k of set)
|
|
210
|
+
scope.forcedNonNull.add(k);
|
|
211
|
+
}
|
|
212
|
+
function computeTargetNullable(target, scope) {
|
|
213
|
+
const val = target?.ResTarget?.val;
|
|
214
|
+
return expressionNullable(val, scope);
|
|
215
|
+
}
|
|
216
|
+
function nullableFromRowDescConservative(f, scope) {
|
|
217
|
+
if (f.tableOid === 0 || f.columnAttr === 0)
|
|
218
|
+
return true;
|
|
219
|
+
const notNull = scope.schema.isNotNull(f.tableOid, f.columnAttr);
|
|
220
|
+
if (notNull !== true)
|
|
221
|
+
return true;
|
|
222
|
+
return anyAliasNullableForOid(f.tableOid, scope);
|
|
223
|
+
}
|
|
224
|
+
function anyAliasNullableForOid(tableOid, scope) {
|
|
225
|
+
const refs = scope.tableRefsByOid.get(tableOid);
|
|
226
|
+
if (!refs || refs.length === 0)
|
|
227
|
+
return true;
|
|
228
|
+
return refs.some((r) => r.joinNullable);
|
|
229
|
+
}
|
|
230
|
+
function walkFrom(node, joinNullable, scope) {
|
|
231
|
+
if (!node)
|
|
232
|
+
return;
|
|
233
|
+
if (node.RangeVar) {
|
|
234
|
+
const v = node.RangeVar;
|
|
235
|
+
const alias = v.alias?.aliasname ?? v.relname;
|
|
236
|
+
const cteCols = scope.cteColumnInfo.get(v.relname);
|
|
237
|
+
if (cteCols) {
|
|
238
|
+
scope.aliases.set(alias, { kind: "cte", joinNullable, columns: cteCols });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const info = {
|
|
242
|
+
kind: "table",
|
|
243
|
+
relname: v.relname,
|
|
244
|
+
joinNullable,
|
|
245
|
+
};
|
|
246
|
+
if (v.schemaname)
|
|
247
|
+
info.schema = v.schemaname;
|
|
248
|
+
scope.aliases.set(alias, info);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (node.JoinExpr) {
|
|
252
|
+
const j = node.JoinExpr;
|
|
253
|
+
let leftNullable = joinNullable;
|
|
254
|
+
let rightNullable = joinNullable;
|
|
255
|
+
switch (j.jointype) {
|
|
256
|
+
case "JOIN_LEFT":
|
|
257
|
+
rightNullable = true;
|
|
258
|
+
break;
|
|
259
|
+
case "JOIN_RIGHT":
|
|
260
|
+
leftNullable = true;
|
|
261
|
+
break;
|
|
262
|
+
case "JOIN_FULL":
|
|
263
|
+
leftNullable = true;
|
|
264
|
+
rightNullable = true;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
walkFrom(j.larg, leftNullable, scope);
|
|
268
|
+
walkFrom(j.rarg, rightNullable, scope);
|
|
269
|
+
if (j.jointype === "JOIN_INNER" && !joinNullable) {
|
|
270
|
+
addForcedNonNull(scope, narrowFromWhere(j.quals));
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (node.RangeSubselect) {
|
|
275
|
+
const alias = node.RangeSubselect.alias?.aliasname;
|
|
276
|
+
if (alias)
|
|
277
|
+
scope.aliases.set(alias, { kind: "subquery", joinNullable, columns: new Map() });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (node.RangeFunction) {
|
|
281
|
+
const alias = node.RangeFunction.alias?.aliasname;
|
|
282
|
+
if (alias)
|
|
283
|
+
scope.aliases.set(alias, { kind: "function", joinNullable });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function aliasOfColumnRef(val) {
|
|
288
|
+
if (!val?.ColumnRef)
|
|
289
|
+
return null;
|
|
290
|
+
const fields = val.ColumnRef.fields;
|
|
291
|
+
if (!Array.isArray(fields) || fields.length < 2)
|
|
292
|
+
return null;
|
|
293
|
+
const first = fields[0]?.String?.sval;
|
|
294
|
+
if (typeof first !== "string")
|
|
295
|
+
return null;
|
|
296
|
+
return first;
|
|
297
|
+
}
|
|
298
|
+
function colNameOfColumnRef(val) {
|
|
299
|
+
if (!val?.ColumnRef)
|
|
300
|
+
return undefined;
|
|
301
|
+
const fields = val.ColumnRef.fields;
|
|
302
|
+
if (!Array.isArray(fields) || fields.length === 0)
|
|
303
|
+
return undefined;
|
|
304
|
+
if (fields.some((f) => f.A_Star !== undefined))
|
|
305
|
+
return undefined;
|
|
306
|
+
return fields[fields.length - 1]?.String?.sval;
|
|
307
|
+
}
|
|
308
|
+
function containsStar(val) {
|
|
309
|
+
if (!val?.ColumnRef)
|
|
310
|
+
return false;
|
|
311
|
+
const fields = val.ColumnRef.fields;
|
|
312
|
+
if (!Array.isArray(fields))
|
|
313
|
+
return false;
|
|
314
|
+
return fields.some((f) => f.A_Star !== undefined);
|
|
315
|
+
}
|
|
316
|
+
function columnRefNullable(fields, scope) {
|
|
317
|
+
let aliasName;
|
|
318
|
+
let colName;
|
|
319
|
+
if (fields.length >= 2) {
|
|
320
|
+
aliasName = fields[0]?.String?.sval;
|
|
321
|
+
colName = fields[fields.length - 1]?.String?.sval;
|
|
322
|
+
}
|
|
323
|
+
else if (fields.length === 1) {
|
|
324
|
+
colName = fields[0]?.String?.sval;
|
|
325
|
+
}
|
|
326
|
+
if (typeof colName !== "string")
|
|
327
|
+
return true;
|
|
328
|
+
if (isNarrowed(scope.forcedNonNull, aliasName, colName))
|
|
329
|
+
return false;
|
|
330
|
+
if (aliasName) {
|
|
331
|
+
const a = scope.aliases.get(aliasName);
|
|
332
|
+
if (!a)
|
|
333
|
+
return true;
|
|
334
|
+
if (a.kind === "cte" || a.kind === "subquery") {
|
|
335
|
+
const inner = a.columns.get(colName);
|
|
336
|
+
if (inner === undefined)
|
|
337
|
+
return true;
|
|
338
|
+
return inner || a.joinNullable;
|
|
339
|
+
}
|
|
340
|
+
if (a.kind !== "table")
|
|
341
|
+
return true;
|
|
342
|
+
const oid = scope.aliasOidByName.get(aliasName);
|
|
343
|
+
if (oid === undefined)
|
|
344
|
+
return true;
|
|
345
|
+
const cols = scope.schema.columnsOf(oid);
|
|
346
|
+
const info = cols?.get(colName);
|
|
347
|
+
if (!info)
|
|
348
|
+
return true;
|
|
349
|
+
return !info.notNull || a.joinNullable;
|
|
350
|
+
}
|
|
351
|
+
const matches = [];
|
|
352
|
+
for (const [name, a] of scope.aliases) {
|
|
353
|
+
if (a.kind === "cte" || a.kind === "subquery") {
|
|
354
|
+
const inner = a.columns.get(colName);
|
|
355
|
+
if (inner === undefined)
|
|
356
|
+
continue;
|
|
357
|
+
matches.push({ alias: name, notNull: !inner, joinNullable: a.joinNullable });
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (a.kind !== "table")
|
|
361
|
+
continue;
|
|
362
|
+
const oid = scope.aliasOidByName.get(name);
|
|
363
|
+
if (oid === undefined)
|
|
364
|
+
continue;
|
|
365
|
+
const info = scope.schema.columnsOf(oid)?.get(colName);
|
|
366
|
+
if (!info)
|
|
367
|
+
continue;
|
|
368
|
+
matches.push({ alias: name, notNull: info.notNull, joinNullable: a.joinNullable });
|
|
369
|
+
}
|
|
370
|
+
if (matches.length !== 1)
|
|
371
|
+
return true;
|
|
372
|
+
const m = matches[0];
|
|
373
|
+
return !m.notNull || m.joinNullable;
|
|
374
|
+
}
|
|
375
|
+
function funcName(call) {
|
|
376
|
+
const names = call?.funcname;
|
|
377
|
+
if (!Array.isArray(names))
|
|
378
|
+
return null;
|
|
379
|
+
const last = names[names.length - 1];
|
|
380
|
+
return last?.String?.sval?.toLowerCase() ?? null;
|
|
381
|
+
}
|
|
382
|
+
const NON_NULL_FUNCS = new Set([
|
|
383
|
+
"now",
|
|
384
|
+
"current_timestamp",
|
|
385
|
+
"current_date",
|
|
386
|
+
"current_time",
|
|
387
|
+
"localtime",
|
|
388
|
+
"localtimestamp",
|
|
389
|
+
"current_user",
|
|
390
|
+
"session_user",
|
|
391
|
+
"user",
|
|
392
|
+
"current_database",
|
|
393
|
+
"current_schema",
|
|
394
|
+
"version",
|
|
395
|
+
"pg_backend_pid",
|
|
396
|
+
"txid_current",
|
|
397
|
+
"random",
|
|
398
|
+
"gen_random_uuid",
|
|
399
|
+
"uuid_generate_v4",
|
|
400
|
+
"length",
|
|
401
|
+
"char_length",
|
|
402
|
+
"character_length",
|
|
403
|
+
"octet_length",
|
|
404
|
+
"concat",
|
|
405
|
+
"concat_ws",
|
|
406
|
+
]);
|
|
407
|
+
const COUNT_FUNCS = new Set(["count"]);
|
|
408
|
+
function expressionNullable(val, scope) {
|
|
409
|
+
if (!val)
|
|
410
|
+
return true;
|
|
411
|
+
if (val.A_Const !== undefined) {
|
|
412
|
+
const c = val.A_Const;
|
|
413
|
+
if (c.isnull === true)
|
|
414
|
+
return true;
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
if (val.ColumnRef) {
|
|
418
|
+
const fields = val.ColumnRef.fields;
|
|
419
|
+
if (!Array.isArray(fields))
|
|
420
|
+
return true;
|
|
421
|
+
if (fields.some((f) => f.A_Star !== undefined))
|
|
422
|
+
return true;
|
|
423
|
+
return columnRefNullable(fields, scope);
|
|
424
|
+
}
|
|
425
|
+
if (val.FuncCall) {
|
|
426
|
+
const name = funcName(val.FuncCall);
|
|
427
|
+
if (name && COUNT_FUNCS.has(name))
|
|
428
|
+
return false;
|
|
429
|
+
if (name && NON_NULL_FUNCS.has(name)) {
|
|
430
|
+
const args = val.FuncCall.args ?? [];
|
|
431
|
+
return args.some((a) => expressionNullable(a, scope));
|
|
432
|
+
}
|
|
433
|
+
if (name === "greatest" || name === "least") {
|
|
434
|
+
const args = val.FuncCall.args ?? [];
|
|
435
|
+
if (args.length === 0)
|
|
436
|
+
return true;
|
|
437
|
+
return args.every((a) => expressionNullable(a, scope));
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
if (val.CoalesceExpr) {
|
|
442
|
+
const args = val.CoalesceExpr.args ?? [];
|
|
443
|
+
if (args.length === 0)
|
|
444
|
+
return true;
|
|
445
|
+
return args.every((a) => expressionNullable(a, scope));
|
|
446
|
+
}
|
|
447
|
+
if (val.MinMaxExpr) {
|
|
448
|
+
const args = val.MinMaxExpr.args ?? [];
|
|
449
|
+
if (args.length === 0)
|
|
450
|
+
return true;
|
|
451
|
+
return args.every((a) => expressionNullable(a, scope));
|
|
452
|
+
}
|
|
453
|
+
if (val.NullIfExpr) {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
if (val.CaseExpr) {
|
|
457
|
+
const c = val.CaseExpr;
|
|
458
|
+
const branches = (c.args ?? []).map((arm) => arm.CaseWhen?.result);
|
|
459
|
+
const hasElse = c.defresult !== undefined && c.defresult !== null;
|
|
460
|
+
if (!hasElse)
|
|
461
|
+
return true;
|
|
462
|
+
const elseExpr = c.defresult;
|
|
463
|
+
return [...branches, elseExpr].some((b) => expressionNullable(b, scope));
|
|
464
|
+
}
|
|
465
|
+
if (val.A_Expr) {
|
|
466
|
+
const e = val.A_Expr;
|
|
467
|
+
return expressionNullable(e.lexpr, scope) || expressionNullable(e.rexpr, scope);
|
|
468
|
+
}
|
|
469
|
+
if (val.SubLink)
|
|
470
|
+
return true;
|
|
471
|
+
if (val.TypeCast) {
|
|
472
|
+
return expressionNullable(val.TypeCast.arg, scope);
|
|
473
|
+
}
|
|
474
|
+
if (val.BoolExpr) {
|
|
475
|
+
const a = val.BoolExpr.args ?? [];
|
|
476
|
+
return a.some((x) => expressionNullable(x, scope));
|
|
477
|
+
}
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const BUILTIN_EXTENSION_TYPES = {
|
|
2
|
+
vector: "number[]",
|
|
3
|
+
halfvec: "number[]",
|
|
4
|
+
sparsevec: "string",
|
|
5
|
+
hstore: "Record<string, string | null>",
|
|
6
|
+
citext: "string",
|
|
7
|
+
ltree: "string",
|
|
8
|
+
lquery: "string",
|
|
9
|
+
ltxtquery: "string",
|
|
10
|
+
};
|
|
11
|
+
export function mergeExtensionTypes(user) {
|
|
12
|
+
if (!user)
|
|
13
|
+
return { ...BUILTIN_EXTENSION_TYPES };
|
|
14
|
+
return { ...BUILTIN_EXTENSION_TYPES, ...user };
|
|
15
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const NULL_REJECTING_OPS = new Set(["=", "!=", "<>", "<", ">", "<=", ">="]);
|
|
2
|
+
export function narrowFromWhere(whereClause) {
|
|
3
|
+
if (!whereClause)
|
|
4
|
+
return new Set();
|
|
5
|
+
return walk(whereClause).forced;
|
|
6
|
+
}
|
|
7
|
+
function emptyInfo() {
|
|
8
|
+
return { forced: new Set(), equalities: [] };
|
|
9
|
+
}
|
|
10
|
+
function forcedInfo(keys) {
|
|
11
|
+
return { forced: new Set(keys), equalities: [] };
|
|
12
|
+
}
|
|
13
|
+
function propagateEqualities(info) {
|
|
14
|
+
if (info.forced.size === 0 || info.equalities.length === 0)
|
|
15
|
+
return info;
|
|
16
|
+
const graph = new Map();
|
|
17
|
+
for (const [left, right] of info.equalities) {
|
|
18
|
+
if (left === right)
|
|
19
|
+
continue;
|
|
20
|
+
const l = graph.get(left) ?? new Set();
|
|
21
|
+
l.add(right);
|
|
22
|
+
graph.set(left, l);
|
|
23
|
+
const r = graph.get(right) ?? new Set();
|
|
24
|
+
r.add(left);
|
|
25
|
+
graph.set(right, r);
|
|
26
|
+
}
|
|
27
|
+
const forced = new Set(info.forced);
|
|
28
|
+
const queue = [...forced];
|
|
29
|
+
for (let i = 0; i < queue.length; i++) {
|
|
30
|
+
const current = queue[i];
|
|
31
|
+
for (const next of graph.get(current) ?? []) {
|
|
32
|
+
if (forced.has(next))
|
|
33
|
+
continue;
|
|
34
|
+
forced.add(next);
|
|
35
|
+
queue.push(next);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { forced, equalities: info.equalities };
|
|
39
|
+
}
|
|
40
|
+
function walk(node) {
|
|
41
|
+
if (!node)
|
|
42
|
+
return emptyInfo();
|
|
43
|
+
if (node.NullTest) {
|
|
44
|
+
if (node.NullTest.nulltesttype === "IS_NOT_NULL") {
|
|
45
|
+
const k = keyOfColumnRef(node.NullTest.arg);
|
|
46
|
+
return k ? forcedInfo([k]) : emptyInfo();
|
|
47
|
+
}
|
|
48
|
+
return emptyInfo();
|
|
49
|
+
}
|
|
50
|
+
if (node.BoolExpr) {
|
|
51
|
+
const op = node.BoolExpr.boolop;
|
|
52
|
+
const args = node.BoolExpr.args ?? [];
|
|
53
|
+
if (op === "AND_EXPR") {
|
|
54
|
+
const out = { forced: new Set(), equalities: [] };
|
|
55
|
+
for (const a of args) {
|
|
56
|
+
const child = walk(a);
|
|
57
|
+
for (const k of child.forced)
|
|
58
|
+
out.forced.add(k);
|
|
59
|
+
out.equalities.push(...child.equalities);
|
|
60
|
+
}
|
|
61
|
+
return propagateEqualities(out);
|
|
62
|
+
}
|
|
63
|
+
if (op === "OR_EXPR") {
|
|
64
|
+
if (args.length === 0)
|
|
65
|
+
return emptyInfo();
|
|
66
|
+
let acc;
|
|
67
|
+
for (const a of args) {
|
|
68
|
+
const s = walk(a).forced;
|
|
69
|
+
if (!acc)
|
|
70
|
+
acc = new Set(s);
|
|
71
|
+
else {
|
|
72
|
+
const next = new Set();
|
|
73
|
+
for (const k of acc)
|
|
74
|
+
if (s.has(k))
|
|
75
|
+
next.add(k);
|
|
76
|
+
acc = next;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return forcedInfo(acc ?? []);
|
|
80
|
+
}
|
|
81
|
+
return emptyInfo();
|
|
82
|
+
}
|
|
83
|
+
if (node.A_Expr) {
|
|
84
|
+
const e = node.A_Expr;
|
|
85
|
+
const kind = e.kind;
|
|
86
|
+
const opName = e.name?.[0]?.String?.sval;
|
|
87
|
+
const lk = keyOfColumnRef(e.lexpr);
|
|
88
|
+
const rk = keyOfColumnRef(e.rexpr);
|
|
89
|
+
if (kind === "AEXPR_OP" && opName && NULL_REJECTING_OPS.has(opName)) {
|
|
90
|
+
const out = new Set();
|
|
91
|
+
const lIsNull = isNullLiteral(e.lexpr);
|
|
92
|
+
const rIsNull = isNullLiteral(e.rexpr);
|
|
93
|
+
if (lk && !rIsNull)
|
|
94
|
+
out.add(lk);
|
|
95
|
+
if (rk && !lIsNull)
|
|
96
|
+
out.add(rk);
|
|
97
|
+
const equalities = opName === "=" && lk && rk ? [[lk, rk]] : [];
|
|
98
|
+
return { forced: out, equalities };
|
|
99
|
+
}
|
|
100
|
+
if (kind === "AEXPR_NOT_DISTINCT" && opName === "=" && lk && rk) {
|
|
101
|
+
return { forced: new Set(), equalities: [[lk, rk]] };
|
|
102
|
+
}
|
|
103
|
+
if (kind === "AEXPR_IN" || kind === "AEXPR_LIKE" || kind === "AEXPR_ILIKE" || kind === "AEXPR_BETWEEN") {
|
|
104
|
+
const k = keyOfColumnRef(e.lexpr);
|
|
105
|
+
return k ? forcedInfo([k]) : emptyInfo();
|
|
106
|
+
}
|
|
107
|
+
return emptyInfo();
|
|
108
|
+
}
|
|
109
|
+
return emptyInfo();
|
|
110
|
+
}
|
|
111
|
+
function keyOfColumnRef(node) {
|
|
112
|
+
if (!node?.ColumnRef)
|
|
113
|
+
return null;
|
|
114
|
+
const fields = node.ColumnRef.fields;
|
|
115
|
+
if (!Array.isArray(fields) || fields.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
if (fields.some((f) => f.A_Star !== undefined))
|
|
118
|
+
return null;
|
|
119
|
+
if (fields.length === 1) {
|
|
120
|
+
const col = fields[0]?.String?.sval;
|
|
121
|
+
return typeof col === "string" ? `|${col}` : null;
|
|
122
|
+
}
|
|
123
|
+
const alias = fields[0]?.String?.sval;
|
|
124
|
+
const col = fields[fields.length - 1]?.String?.sval;
|
|
125
|
+
if (typeof alias !== "string" || typeof col !== "string")
|
|
126
|
+
return null;
|
|
127
|
+
return `${alias}|${col}`;
|
|
128
|
+
}
|
|
129
|
+
function isNullLiteral(node) {
|
|
130
|
+
return node?.A_Const?.isnull === true;
|
|
131
|
+
}
|
|
132
|
+
export function isNarrowed(set, alias, col) {
|
|
133
|
+
if (set.size === 0)
|
|
134
|
+
return false;
|
|
135
|
+
if (alias && set.has(`${alias}|${col}`))
|
|
136
|
+
return true;
|
|
137
|
+
if (set.has(`|${col}`))
|
|
138
|
+
return true;
|
|
139
|
+
if (!alias) {
|
|
140
|
+
const suffix = `|${col}`;
|
|
141
|
+
for (const k of set)
|
|
142
|
+
if (k.endsWith(suffix))
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type TsTypeInfo = {
|
|
2
|
+
ts: string;
|
|
3
|
+
bigint?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export declare function oidToTs(oid: number): TsTypeInfo;
|
|
6
|
+
export declare function isBuiltinOid(oid: number): boolean;
|
|
7
|
+
export declare function builtinArrayOids(): number[];
|
|
8
|
+
export declare function arrayElementOid(oid: number): number | undefined;
|
|
9
|
+
export type ResolveTs = (oid: number) => string;
|
|
10
|
+
export declare function makeResolver(custom: (oid: number) => string | undefined): ResolveTs;
|