@graffy/pg 0.15.25-alpha.3 → 0.15.25-alpha.5
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/package.json +3 -8
- package/index.cjs +0 -711
package/package.json
CHANGED
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
"name": "@graffy/pg",
|
|
3
3
|
"description": "The standard Postgres module for Graffy. Each instance this module mounts a Postgres table as a Graffy subtree.",
|
|
4
4
|
"author": "aravind (https://github.com/aravindet)",
|
|
5
|
-
"version": "0.15.25-alpha.
|
|
6
|
-
"main": "./index.
|
|
7
|
-
"exports": {
|
|
8
|
-
"import": "./index.mjs",
|
|
9
|
-
"require": "./index.cjs"
|
|
10
|
-
},
|
|
11
|
-
"module": "./index.mjs",
|
|
5
|
+
"version": "0.15.25-alpha.5",
|
|
6
|
+
"main": "./index.mjs",
|
|
12
7
|
"types": "./types/index.d.ts",
|
|
13
8
|
"repository": {
|
|
14
9
|
"type": "git",
|
|
@@ -16,7 +11,7 @@
|
|
|
16
11
|
},
|
|
17
12
|
"license": "Apache-2.0",
|
|
18
13
|
"dependencies": {
|
|
19
|
-
"@graffy/common": "0.15.25-alpha.
|
|
14
|
+
"@graffy/common": "0.15.25-alpha.5",
|
|
20
15
|
"sql-template-tag": "^5.0.3",
|
|
21
16
|
"debug": "^4.3.3"
|
|
22
17
|
},
|
package/index.cjs
DELETED
|
@@ -1,711 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
-
const common = require("@graffy/common");
|
|
4
|
-
const pg$1 = require("pg");
|
|
5
|
-
const sql = require("sql-template-tag");
|
|
6
|
-
const debug = require("debug");
|
|
7
|
-
const _interopDefaultLegacy = (e) => e && typeof e === "object" && "default" in e ? e : { default: e };
|
|
8
|
-
const pg__default = /* @__PURE__ */ _interopDefaultLegacy(pg$1);
|
|
9
|
-
const sql__default = /* @__PURE__ */ _interopDefaultLegacy(sql);
|
|
10
|
-
const debug__default = /* @__PURE__ */ _interopDefaultLegacy(debug);
|
|
11
|
-
const valid = {
|
|
12
|
-
$eq: true,
|
|
13
|
-
$lt: true,
|
|
14
|
-
$gt: true,
|
|
15
|
-
$lte: true,
|
|
16
|
-
$gte: true,
|
|
17
|
-
$re: true,
|
|
18
|
-
$ire: true,
|
|
19
|
-
$text: true,
|
|
20
|
-
$and: true,
|
|
21
|
-
$or: true,
|
|
22
|
-
$any: true,
|
|
23
|
-
$all: true,
|
|
24
|
-
$has: true,
|
|
25
|
-
$cts: true,
|
|
26
|
-
$ctd: true
|
|
27
|
-
};
|
|
28
|
-
const inverse = {
|
|
29
|
-
$eq: "$neq",
|
|
30
|
-
$neq: "$eq",
|
|
31
|
-
$in: "$nin",
|
|
32
|
-
$nin: "$in",
|
|
33
|
-
$lt: "$gte",
|
|
34
|
-
$gte: "$lt",
|
|
35
|
-
$gt: "$lte",
|
|
36
|
-
$lte: "$gt"
|
|
37
|
-
};
|
|
38
|
-
function getAst(filter) {
|
|
39
|
-
return simplify(construct(filter));
|
|
40
|
-
}
|
|
41
|
-
function construct(node, prop, op) {
|
|
42
|
-
if (!node || typeof node !== "object" || prop && op) {
|
|
43
|
-
if (op && prop)
|
|
44
|
-
return [op, prop, node];
|
|
45
|
-
if (prop)
|
|
46
|
-
return ["$eq", prop, node];
|
|
47
|
-
throw Error("pgast.expected_prop_before:" + JSON.stringify(node));
|
|
48
|
-
}
|
|
49
|
-
if (Array.isArray(node)) {
|
|
50
|
-
return ["$or", node.map((item) => construct(item, prop, op))];
|
|
51
|
-
}
|
|
52
|
-
return [
|
|
53
|
-
"$and",
|
|
54
|
-
Object.entries(node).map(([key, val]) => {
|
|
55
|
-
if (key === "$or" || key === "$and") {
|
|
56
|
-
return [key, construct(val, prop, op)[1]];
|
|
57
|
-
}
|
|
58
|
-
if (key === "$not") {
|
|
59
|
-
return [key, construct(val, prop, op)];
|
|
60
|
-
}
|
|
61
|
-
if (key[0] === "$") {
|
|
62
|
-
if (!valid[key])
|
|
63
|
-
throw Error("pgast.invalid_op:" + key);
|
|
64
|
-
if (op)
|
|
65
|
-
throw Error("pgast.unexpected_op:" + op + " before:" + key);
|
|
66
|
-
if (!prop)
|
|
67
|
-
throw Error("pgast.expected_prop_before:" + key);
|
|
68
|
-
return construct(val, prop, key);
|
|
69
|
-
}
|
|
70
|
-
if (prop) {
|
|
71
|
-
if (key[0] === ".")
|
|
72
|
-
return construct(val, prop + key);
|
|
73
|
-
throw Error("pgast.unexpected_prop: " + key);
|
|
74
|
-
}
|
|
75
|
-
return construct(val, key);
|
|
76
|
-
})
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
function simplify(node) {
|
|
80
|
-
const op = node[0];
|
|
81
|
-
if (op === "$and" || op === "$or") {
|
|
82
|
-
node[1] = node[1].map((subnode) => simplify(subnode));
|
|
83
|
-
} else if (op === "$not") {
|
|
84
|
-
node[1] = simplify(node[1]);
|
|
85
|
-
}
|
|
86
|
-
if (op === "$and") {
|
|
87
|
-
if (!node[1].length)
|
|
88
|
-
return true;
|
|
89
|
-
if (node[1].includes(false))
|
|
90
|
-
return false;
|
|
91
|
-
node[1] = node[1].filter((item) => item !== true);
|
|
92
|
-
} else if (op === "$or") {
|
|
93
|
-
if (!node[1].length)
|
|
94
|
-
return false;
|
|
95
|
-
if (node[1].includes(true))
|
|
96
|
-
return true;
|
|
97
|
-
node[1] = node[1].filter((item) => item !== false);
|
|
98
|
-
} else if (op === "$not" && typeof node[1] === "boolean") {
|
|
99
|
-
return !node[1];
|
|
100
|
-
}
|
|
101
|
-
if (op === "$or") {
|
|
102
|
-
const { eqmap, noneq, change } = node[1].reduce(
|
|
103
|
-
(acc, item) => {
|
|
104
|
-
if (item[0] !== "$eq") {
|
|
105
|
-
acc.noneq.push(item);
|
|
106
|
-
} else if (acc.eqmap[item[1]]) {
|
|
107
|
-
acc.change = true;
|
|
108
|
-
acc.eqmap[item[1]].push(item[2]);
|
|
109
|
-
return acc;
|
|
110
|
-
} else {
|
|
111
|
-
acc.eqmap[item[1]] = [item[2]];
|
|
112
|
-
}
|
|
113
|
-
return acc;
|
|
114
|
-
},
|
|
115
|
-
{ eqmap: {}, noneq: [], change: false }
|
|
116
|
-
);
|
|
117
|
-
if (change) {
|
|
118
|
-
node[1] = [
|
|
119
|
-
...noneq,
|
|
120
|
-
...Object.entries(eqmap).map(
|
|
121
|
-
([prop, val]) => val.length > 1 ? ["$in", prop, val] : ["$eq", prop, val[0]]
|
|
122
|
-
)
|
|
123
|
-
];
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if ((op === "$and" || op === "$or") && node[1].length === 1) {
|
|
127
|
-
return node[1][0];
|
|
128
|
-
}
|
|
129
|
-
if (op === "$not") {
|
|
130
|
-
const [subop, ...subargs] = node[1];
|
|
131
|
-
const invop = inverse[subop];
|
|
132
|
-
return invop ? [invop, ...subargs] : node;
|
|
133
|
-
}
|
|
134
|
-
return node;
|
|
135
|
-
}
|
|
136
|
-
const getJsonBuildTrusted = (variadic) => {
|
|
137
|
-
const args = sql.join(
|
|
138
|
-
Object.entries(variadic).map(([name, value]) => {
|
|
139
|
-
return sql__default.default`'${sql.raw(name)}', ${getJsonBuildValue(value)}`;
|
|
140
|
-
})
|
|
141
|
-
);
|
|
142
|
-
return sql__default.default`jsonb_build_object(${args})`;
|
|
143
|
-
};
|
|
144
|
-
const getJsonBuildValue = (value) => {
|
|
145
|
-
if (value instanceof sql.Sql)
|
|
146
|
-
return value;
|
|
147
|
-
if (typeof value === "string")
|
|
148
|
-
return sql__default.default`${value}::text`;
|
|
149
|
-
return sql__default.default`${JSON.stringify(stripAttributes(value))}::jsonb`;
|
|
150
|
-
};
|
|
151
|
-
const lookup = (prop) => {
|
|
152
|
-
const [prefix, ...suffix] = common.encodePath(prop);
|
|
153
|
-
return suffix.length ? sql__default.default`"${sql.raw(prefix)}" #> ${suffix}` : sql__default.default`"${sql.raw(prefix)}"`;
|
|
154
|
-
};
|
|
155
|
-
const lookupNumeric = (prop) => {
|
|
156
|
-
const [prefix, ...suffix] = common.encodePath(prop);
|
|
157
|
-
return suffix.length ? sql__default.default`CASE WHEN "${sql.raw(prefix)}" #> ${suffix} = 'null'::jsonb THEN 0 ELSE ("${sql.raw(
|
|
158
|
-
prefix
|
|
159
|
-
)}" #> ${suffix})::numeric END` : sql__default.default`"${sql.raw(prefix)}"`;
|
|
160
|
-
};
|
|
161
|
-
const aggSql = {
|
|
162
|
-
$sum: (prop) => sql__default.default`sum((${lookupNumeric(prop)})::numeric)`,
|
|
163
|
-
$card: (prop) => sql__default.default`count(distinct(${lookup(prop)}))`,
|
|
164
|
-
$avg: (prop) => sql__default.default`avg((${lookupNumeric(prop)})::numeric)`,
|
|
165
|
-
$max: (prop) => sql__default.default`max((${lookupNumeric(prop)})::numeric)`,
|
|
166
|
-
$min: (prop) => sql__default.default`min((${lookupNumeric(prop)})::numeric)`
|
|
167
|
-
};
|
|
168
|
-
const getSelectCols = (table, projection = null) => {
|
|
169
|
-
if (!projection)
|
|
170
|
-
return sql__default.default`*`;
|
|
171
|
-
const sqls = [];
|
|
172
|
-
for (const key in projection) {
|
|
173
|
-
if (key === "$count") {
|
|
174
|
-
sqls.push(sql__default.default`count(*) AS "$count"`);
|
|
175
|
-
} else if (aggSql[key]) {
|
|
176
|
-
const subSqls = [];
|
|
177
|
-
for (const prop in projection[key]) {
|
|
178
|
-
subSqls.push(sql__default.default`${prop}::text, ${aggSql[key](prop)}`);
|
|
179
|
-
}
|
|
180
|
-
sqls.push(
|
|
181
|
-
sql__default.default`jsonb_build_object(${sql.join(subSqls, ", ")}) AS "${sql.raw(key)}"`
|
|
182
|
-
);
|
|
183
|
-
} else {
|
|
184
|
-
sqls.push(sql__default.default`"${sql.raw(key)}"`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return sql.join(sqls, ", ");
|
|
188
|
-
};
|
|
189
|
-
function vertexSql(array, nullValue) {
|
|
190
|
-
return sql__default.default`array[${sql.join(
|
|
191
|
-
array.map((num) => num === null ? nullValue : num)
|
|
192
|
-
)}]::float8[]`;
|
|
193
|
-
}
|
|
194
|
-
function cubeLiteralSql(value) {
|
|
195
|
-
if (!Array.isArray(value) || !value.length || Array.isArray(value[0]) && value.length !== 2) {
|
|
196
|
-
throw Error("pg.castValue_bad_cube" + JSON.stringify(value));
|
|
197
|
-
}
|
|
198
|
-
return Array.isArray(value[0]) ? sql__default.default`cube(${vertexSql(value[0], sql__default.default`'-Infinity'`)}, ${vertexSql(
|
|
199
|
-
value[1],
|
|
200
|
-
sql__default.default`'Infinity'`
|
|
201
|
-
)})` : sql__default.default`cube(${vertexSql(value, 0)})`;
|
|
202
|
-
}
|
|
203
|
-
function castValue(value, type, name, isPut) {
|
|
204
|
-
if (!type)
|
|
205
|
-
throw Error("pg.write_no_column " + name);
|
|
206
|
-
if (value instanceof sql.Sql)
|
|
207
|
-
return value;
|
|
208
|
-
if (value === null)
|
|
209
|
-
return sql__default.default`NULL`;
|
|
210
|
-
if (type === "jsonb") {
|
|
211
|
-
return isPut ? JSON.stringify(stripAttributes(value)) : sql__default.default`jsonb_strip_nulls(${getJsonUpdate(value, name, [])})`;
|
|
212
|
-
}
|
|
213
|
-
if (type === "cube")
|
|
214
|
-
return cubeLiteralSql(value);
|
|
215
|
-
return value;
|
|
216
|
-
}
|
|
217
|
-
const getInsert = (row, options) => {
|
|
218
|
-
const cols = [];
|
|
219
|
-
const vals = [];
|
|
220
|
-
Object.entries(row).filter(([col]) => col !== options.verCol && col[0] !== "$").concat([[options.verCol, sql__default.default`default`]]).forEach(([col, val]) => {
|
|
221
|
-
cols.push(sql__default.default`"${sql.raw(col)}"`);
|
|
222
|
-
vals.push(castValue(val, options.schema.types[col], col, row.$put));
|
|
223
|
-
});
|
|
224
|
-
return { cols: sql.join(cols, ", "), vals: sql.join(vals, ", ") };
|
|
225
|
-
};
|
|
226
|
-
const getUpdates = (row, options) => {
|
|
227
|
-
return sql.join(
|
|
228
|
-
Object.entries(row).filter(([col]) => col !== options.idCol && col[0] !== "$").map(
|
|
229
|
-
([col, val]) => sql__default.default`"${sql.raw(col)}" = ${castValue(
|
|
230
|
-
val,
|
|
231
|
-
options.schema.types[col],
|
|
232
|
-
col,
|
|
233
|
-
row.$put
|
|
234
|
-
)}`
|
|
235
|
-
).concat(sql__default.default`"${sql.raw(options.verCol)}" = default`),
|
|
236
|
-
", "
|
|
237
|
-
);
|
|
238
|
-
};
|
|
239
|
-
function getJsonUpdate(object, col, path) {
|
|
240
|
-
if (!object || typeof object !== "object" || Array.isArray(object) || object.$put) {
|
|
241
|
-
return getJsonBuildValue(object);
|
|
242
|
-
}
|
|
243
|
-
const curr = sql__default.default`"${sql.raw(col)}"${path.length ? sql__default.default`#>${path}` : sql.empty}`;
|
|
244
|
-
if (common.isEmpty(object))
|
|
245
|
-
return curr;
|
|
246
|
-
return sql__default.default`(case jsonb_typeof(${curr})
|
|
247
|
-
when 'object' then ${curr}
|
|
248
|
-
else '{}'::jsonb
|
|
249
|
-
end) || jsonb_build_object(${sql.join(
|
|
250
|
-
Object.entries(object).map(
|
|
251
|
-
([key, value]) => sql__default.default`${key}::text, ${getJsonUpdate(value, col, path.concat(key))}`
|
|
252
|
-
),
|
|
253
|
-
", "
|
|
254
|
-
)})`;
|
|
255
|
-
}
|
|
256
|
-
function stripAttributes(object) {
|
|
257
|
-
if (typeof object !== "object" || !object)
|
|
258
|
-
return object;
|
|
259
|
-
if (Array.isArray(object)) {
|
|
260
|
-
return object.map((item) => stripAttributes(item));
|
|
261
|
-
}
|
|
262
|
-
return Object.entries(object).reduce((out, [key, val]) => {
|
|
263
|
-
if (key === "$put")
|
|
264
|
-
return out;
|
|
265
|
-
out[key] = stripAttributes(val);
|
|
266
|
-
return out;
|
|
267
|
-
}, {});
|
|
268
|
-
}
|
|
269
|
-
const opSql = {
|
|
270
|
-
$and: `AND`,
|
|
271
|
-
$or: `OR`,
|
|
272
|
-
$not: sql__default.default`NOT`,
|
|
273
|
-
$eq: sql__default.default`=`,
|
|
274
|
-
$neq: sql__default.default`<>`,
|
|
275
|
-
$in: sql__default.default`IN`,
|
|
276
|
-
$nin: sql__default.default`NOT IN`,
|
|
277
|
-
$lt: sql__default.default`<`,
|
|
278
|
-
$lte: sql__default.default`<=`,
|
|
279
|
-
$gt: sql__default.default`>`,
|
|
280
|
-
$gte: sql__default.default`>=`,
|
|
281
|
-
$re: sql__default.default`~`,
|
|
282
|
-
$ire: sql__default.default`~*`,
|
|
283
|
-
$cts: sql__default.default`@>`,
|
|
284
|
-
$ctd: sql__default.default`<@`
|
|
285
|
-
};
|
|
286
|
-
function getBinarySql(lhs, type, op, value, textLhs) {
|
|
287
|
-
if (value === null && op === "$eq")
|
|
288
|
-
return sql__default.default`${lhs} IS NULL`;
|
|
289
|
-
if (value === null && op === "$neq")
|
|
290
|
-
return sql__default.default`${lhs} IS NOT NULL`;
|
|
291
|
-
const sqlOp = opSql[op];
|
|
292
|
-
if (!sqlOp)
|
|
293
|
-
throw Error("pg.getSql_unknown_operator " + op);
|
|
294
|
-
if (op === "$in" || op === "$nin") {
|
|
295
|
-
if (type === "jsonb" && typeof value[0] === "string")
|
|
296
|
-
lhs = textLhs;
|
|
297
|
-
return sql__default.default`${lhs} ${sqlOp} (${sql.join(value)})`;
|
|
298
|
-
}
|
|
299
|
-
if (op === "$re" || op === "$ire") {
|
|
300
|
-
if (type === "jsonb") {
|
|
301
|
-
lhs = textLhs;
|
|
302
|
-
} else if (type !== "text") {
|
|
303
|
-
lhs = sql__default.default`(${lhs})::text`;
|
|
304
|
-
}
|
|
305
|
-
return sql__default.default`${lhs} ${sqlOp} ${String(value)}`;
|
|
306
|
-
}
|
|
307
|
-
if (type === "jsonb") {
|
|
308
|
-
if (typeof value === "string") {
|
|
309
|
-
return sql__default.default`${textLhs} ${sqlOp} ${value}`;
|
|
310
|
-
}
|
|
311
|
-
return sql__default.default`${lhs} ${sqlOp} ${JSON.stringify(value)}::jsonb`;
|
|
312
|
-
}
|
|
313
|
-
if (type === "cube")
|
|
314
|
-
return sql__default.default`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
|
|
315
|
-
return sql__default.default`${lhs} ${sqlOp} ${value}`;
|
|
316
|
-
}
|
|
317
|
-
function getSql(filter, options) {
|
|
318
|
-
function getNodeSql(ast) {
|
|
319
|
-
if (typeof ast === "boolean")
|
|
320
|
-
return ast;
|
|
321
|
-
const op = ast[0];
|
|
322
|
-
if (op === "$and" || op === "$or") {
|
|
323
|
-
return sql__default.default`(${sql.join(
|
|
324
|
-
ast[1].map((node) => getNodeSql(node)),
|
|
325
|
-
`) ${opSql[op]} (`
|
|
326
|
-
)})`;
|
|
327
|
-
} else if (op === "$not") {
|
|
328
|
-
return sql__default.default`${opSql[op]} (${getNodeSql(ast[1])})`;
|
|
329
|
-
}
|
|
330
|
-
const [prefix, ...suffix] = common.encodePath(ast[1]);
|
|
331
|
-
const { types: types2 } = options.schema;
|
|
332
|
-
if (!types2[prefix])
|
|
333
|
-
throw Error("pg.no_column " + prefix);
|
|
334
|
-
if (types2[prefix] === "jsonb") {
|
|
335
|
-
const [lhs, textLhs] = suffix.length ? [
|
|
336
|
-
sql__default.default`"${sql.raw(prefix)}" #> ${suffix}`,
|
|
337
|
-
sql__default.default`"${sql.raw(prefix)}" #>> ${suffix}`
|
|
338
|
-
] : [sql__default.default`"${sql.raw(prefix)}"`, sql__default.default`"${sql.raw(prefix)}" #>> '{}'`];
|
|
339
|
-
return getBinarySql(lhs, "jsonb", op, ast[2], textLhs);
|
|
340
|
-
} else {
|
|
341
|
-
if (suffix.length)
|
|
342
|
-
throw Error("pg.lookup_not_jsonb " + prefix);
|
|
343
|
-
return getBinarySql(sql__default.default`"${sql.raw(prefix)}"`, types2[prefix], op, ast[2]);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return getNodeSql(getAst(filter));
|
|
347
|
-
}
|
|
348
|
-
const getIdMeta = ({ idCol, verDefault }) => sql__default.default`"${sql.raw(idCol)}" AS "$key", ${sql.raw(verDefault)} AS "$ver"`;
|
|
349
|
-
const getArgMeta = (key, { prefix, idCol, verDefault }) => sql__default.default`
|
|
350
|
-
${key} AS "$key",
|
|
351
|
-
${sql.raw(verDefault)} AS "$ver",
|
|
352
|
-
array[
|
|
353
|
-
${sql.join(prefix.map((k) => sql__default.default`${k}::text`))},
|
|
354
|
-
"${sql.raw(idCol)}"
|
|
355
|
-
]::text[] AS "$ref"
|
|
356
|
-
`;
|
|
357
|
-
const getAggMeta = (key, { verDefault }) => sql__default.default`${key} AS "$key", ${sql.raw(verDefault)} AS "$ver"`;
|
|
358
|
-
function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $cursor: _, ...rest }, options) {
|
|
359
|
-
const { $order, $group, ...filter } = rest;
|
|
360
|
-
const { prefix, idCol } = options;
|
|
361
|
-
const meta = (key2) => $group ? getAggMeta(key2, options) : getArgMeta(key2, options);
|
|
362
|
-
const hasRangeArg = $before || $after || $since || $until || $first || $last || $all;
|
|
363
|
-
if ($order && $group) {
|
|
364
|
-
throw Error("pg_arg.order_and_group_unsupported in " + prefix);
|
|
365
|
-
}
|
|
366
|
-
if (($order || $group && $group !== true) && !hasRangeArg) {
|
|
367
|
-
throw Error("pg_arg.range_arg_expected in " + prefix);
|
|
368
|
-
}
|
|
369
|
-
const baseKey = sql__default.default`${JSON.stringify(rest)}::jsonb`;
|
|
370
|
-
const where = [];
|
|
371
|
-
if (!common.isEmpty(filter))
|
|
372
|
-
where.push(getSql(filter, options));
|
|
373
|
-
if (!hasRangeArg)
|
|
374
|
-
return { meta: meta(baseKey), where, limit: 1 };
|
|
375
|
-
const groupCols = Array.isArray($group) && $group.length && $group.map(lookup);
|
|
376
|
-
const group = groupCols ? sql.join(groupCols, ", ") : void 0;
|
|
377
|
-
const orderCols = ($order || [idCol]).map(
|
|
378
|
-
(orderItem) => orderItem[0] === "!" ? sql__default.default`-(${lookup(orderItem.slice(1))})::float8` : lookup(orderItem)
|
|
379
|
-
);
|
|
380
|
-
Object.entries({ $after, $before, $since, $until }).forEach(
|
|
381
|
-
([name, value]) => {
|
|
382
|
-
if (value)
|
|
383
|
-
where.push(getBoundCond(orderCols, value, name));
|
|
384
|
-
}
|
|
385
|
-
);
|
|
386
|
-
const order = !$group && sql.join(
|
|
387
|
-
($order || [idCol]).map(
|
|
388
|
-
(orderItem) => orderItem[0] === "!" ? sql__default.default`${lookup(orderItem.slice(1))} ${$last ? sql__default.default`ASC` : sql__default.default`DESC`}` : sql__default.default`${lookup(orderItem)} ${$last ? sql__default.default`DESC` : sql__default.default`ASC`}`
|
|
389
|
-
),
|
|
390
|
-
`, `
|
|
391
|
-
);
|
|
392
|
-
const cursorKey = getJsonBuildTrusted({
|
|
393
|
-
$cursor: $group === true ? sql__default.default`''` : sql__default.default`jsonb_build_array(${sql.join(groupCols || orderCols)})`
|
|
394
|
-
});
|
|
395
|
-
const key = sql__default.default`(${baseKey} || ${cursorKey})`;
|
|
396
|
-
return {
|
|
397
|
-
meta: meta(key),
|
|
398
|
-
where,
|
|
399
|
-
order,
|
|
400
|
-
group,
|
|
401
|
-
limit: $first || $last
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
function getBoundCond(orderCols, bound, kind) {
|
|
405
|
-
if (!Array.isArray(bound)) {
|
|
406
|
-
throw Error("pg_arg.bad_query bound : " + JSON.stringify(bound));
|
|
407
|
-
}
|
|
408
|
-
const lhs = orderCols[0];
|
|
409
|
-
const rhs = bound[0];
|
|
410
|
-
if (orderCols.length > 1 && bound.length > 1) {
|
|
411
|
-
const subCond = getBoundCond(orderCols.slice(1), bound.slice(1), kind);
|
|
412
|
-
switch (kind) {
|
|
413
|
-
case "$after":
|
|
414
|
-
case "$since":
|
|
415
|
-
return sql__default.default`${lhs} > ${rhs} OR ${lhs} = ${rhs} AND (${subCond})`;
|
|
416
|
-
case "$before":
|
|
417
|
-
case "$until":
|
|
418
|
-
return sql__default.default`${lhs} < ${rhs} OR ${lhs} = ${rhs} AND (${subCond})`;
|
|
419
|
-
}
|
|
420
|
-
} else {
|
|
421
|
-
switch (kind) {
|
|
422
|
-
case "$after":
|
|
423
|
-
return sql__default.default`${lhs} > ${rhs}`;
|
|
424
|
-
case "$since":
|
|
425
|
-
return sql__default.default`${lhs} >= ${rhs}`;
|
|
426
|
-
case "$before":
|
|
427
|
-
return sql__default.default`${lhs} < ${rhs}`;
|
|
428
|
-
case "$until":
|
|
429
|
-
return sql__default.default`${lhs} <= ${rhs}`;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
const MAX_LIMIT = 4096;
|
|
434
|
-
function selectByArgs(args, projection, options) {
|
|
435
|
-
const { table } = options;
|
|
436
|
-
const { where, order, group, limit, meta } = getArgSql(args, options);
|
|
437
|
-
const clampedLimit = Math.min(MAX_LIMIT, limit || MAX_LIMIT);
|
|
438
|
-
return sql__default.default`
|
|
439
|
-
SELECT
|
|
440
|
-
${getSelectCols(table, projection)}, ${meta}
|
|
441
|
-
FROM "${sql.raw(table)}"
|
|
442
|
-
${where.length ? sql__default.default`WHERE ${sql.join(where, ` AND `)}` : sql.empty}
|
|
443
|
-
${group ? sql__default.default`GROUP BY ${group}` : sql.empty}
|
|
444
|
-
${order ? sql__default.default`ORDER BY ${order}` : sql.empty}
|
|
445
|
-
LIMIT ${clampedLimit}
|
|
446
|
-
`;
|
|
447
|
-
}
|
|
448
|
-
function selectByIds(ids, projection, options) {
|
|
449
|
-
const { table, idCol } = options;
|
|
450
|
-
return sql__default.default`
|
|
451
|
-
SELECT
|
|
452
|
-
${getSelectCols(table, projection)}, ${getIdMeta(options)}
|
|
453
|
-
FROM "${sql.raw(table)}"
|
|
454
|
-
WHERE "${sql.raw(idCol)}" IN (${sql.join(ids)})
|
|
455
|
-
`;
|
|
456
|
-
}
|
|
457
|
-
function getSingleSql(arg, options) {
|
|
458
|
-
const { table, idCol } = options;
|
|
459
|
-
if (!common.isPlainObject(arg)) {
|
|
460
|
-
return {
|
|
461
|
-
where: sql__default.default`"${sql.raw(idCol)}" = ${arg}`,
|
|
462
|
-
meta: getIdMeta(options)
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
const { where, meta } = getArgSql(arg, options);
|
|
466
|
-
if (!where || !where.length)
|
|
467
|
-
throw Error("pg_write.no_condition");
|
|
468
|
-
return {
|
|
469
|
-
where: sql__default.default`"${sql.raw(idCol)}" = (
|
|
470
|
-
SELECT "${sql.raw(idCol)}"
|
|
471
|
-
FROM "${sql.raw(table)}"
|
|
472
|
-
WHERE ${sql.join(where, ` AND `)}
|
|
473
|
-
LIMIT 1
|
|
474
|
-
)`,
|
|
475
|
-
meta
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function patch(object, arg, options) {
|
|
479
|
-
const { table } = options;
|
|
480
|
-
const { where, meta } = getSingleSql(arg, options);
|
|
481
|
-
const row = object;
|
|
482
|
-
return sql__default.default`
|
|
483
|
-
UPDATE "${sql.raw(table)}" SET ${getUpdates(row, options)}
|
|
484
|
-
WHERE ${where}
|
|
485
|
-
RETURNING ${getSelectCols()}, ${meta}`;
|
|
486
|
-
}
|
|
487
|
-
function put(object, arg, options) {
|
|
488
|
-
const { idCol, table } = options;
|
|
489
|
-
const row = object;
|
|
490
|
-
let meta, conflictTarget;
|
|
491
|
-
if (common.isPlainObject(arg)) {
|
|
492
|
-
({ meta } = getArgSql(arg, options));
|
|
493
|
-
conflictTarget = sql.join(Object.keys(arg).map((col) => sql__default.default`"${sql.raw(col)}"`));
|
|
494
|
-
} else {
|
|
495
|
-
meta = getIdMeta(options);
|
|
496
|
-
conflictTarget = sql__default.default`"${sql.raw(idCol)}"`;
|
|
497
|
-
}
|
|
498
|
-
const { cols, vals } = getInsert(row, options);
|
|
499
|
-
return sql__default.default`
|
|
500
|
-
INSERT INTO "${sql.raw(table)}" (${cols}) VALUES (${vals})
|
|
501
|
-
ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
|
|
502
|
-
RETURNING ${getSelectCols()}, ${meta}`;
|
|
503
|
-
}
|
|
504
|
-
function del(arg, options) {
|
|
505
|
-
const { table } = options;
|
|
506
|
-
const { where } = getSingleSql(arg, options);
|
|
507
|
-
return sql__default.default`
|
|
508
|
-
DELETE FROM "${sql.raw(table)}"
|
|
509
|
-
WHERE ${where}
|
|
510
|
-
RETURNING ${arg} "$key"`;
|
|
511
|
-
}
|
|
512
|
-
const log = debug__default.default("graffy:pg:db");
|
|
513
|
-
const { Pool, Client, types } = pg__default.default;
|
|
514
|
-
class Db {
|
|
515
|
-
constructor(connection) {
|
|
516
|
-
if (typeof connection === "object" && connection && (connection instanceof Pool || connection instanceof Client)) {
|
|
517
|
-
this.client = connection;
|
|
518
|
-
} else {
|
|
519
|
-
this.client = new Pool(connection);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async query(sql2) {
|
|
523
|
-
log("Making SQL query: " + sql2.text, sql2.values);
|
|
524
|
-
try {
|
|
525
|
-
sql2.types = {
|
|
526
|
-
getTypeParser: (oid, format) => {
|
|
527
|
-
if (oid === types.builtins.INT8) {
|
|
528
|
-
return (value) => parseInt(value, 10);
|
|
529
|
-
}
|
|
530
|
-
return types.getTypeParser(oid, format);
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
return await this.client.query(sql2);
|
|
534
|
-
} catch (e) {
|
|
535
|
-
const message = [
|
|
536
|
-
e.message,
|
|
537
|
-
e.detail,
|
|
538
|
-
e.hint,
|
|
539
|
-
e.where,
|
|
540
|
-
sql2.text,
|
|
541
|
-
JSON.stringify(sql2.values)
|
|
542
|
-
].filter(Boolean).join("; ");
|
|
543
|
-
throw Error("pg.sql_error " + message);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
async readSql(sql2) {
|
|
547
|
-
const result = (await this.query(sql2)).rows;
|
|
548
|
-
log("Read result", result);
|
|
549
|
-
return result;
|
|
550
|
-
}
|
|
551
|
-
async writeSql(sql2) {
|
|
552
|
-
const res = await this.query(sql2);
|
|
553
|
-
log("Rows written", res.rowCount);
|
|
554
|
-
if (!res.rowCount) {
|
|
555
|
-
throw Error("pg.nothing_written " + sql2.text + " with " + sql2.values);
|
|
556
|
-
}
|
|
557
|
-
return res.rows[0];
|
|
558
|
-
}
|
|
559
|
-
async ensureSchema(tableOptions) {
|
|
560
|
-
if (tableOptions.schema)
|
|
561
|
-
return;
|
|
562
|
-
const { table, verCol } = tableOptions;
|
|
563
|
-
const tableSchema = (await this.query(sql__default.default`
|
|
564
|
-
SELECT table_schema
|
|
565
|
-
FROM information_schema.tables
|
|
566
|
-
WHERE table_name = ${table}
|
|
567
|
-
ORDER BY array_position(current_schemas(false)::text[], table_schema::text) ASC
|
|
568
|
-
LIMIT 1`)).rows[0].table_schema;
|
|
569
|
-
const types2 = (await this.query(sql__default.default`
|
|
570
|
-
SELECT jsonb_object_agg(column_name, udt_name) AS column_types
|
|
571
|
-
FROM information_schema.columns
|
|
572
|
-
WHERE
|
|
573
|
-
table_name = ${table} AND
|
|
574
|
-
table_schema = ${tableSchema}`)).rows[0].column_types;
|
|
575
|
-
if (!types2)
|
|
576
|
-
throw Error(`pg.missing_table ${table}`);
|
|
577
|
-
const verDefault = (await this.query(sql__default.default`
|
|
578
|
-
SELECT column_default
|
|
579
|
-
FROM information_schema.columns
|
|
580
|
-
WHERE
|
|
581
|
-
table_name = ${table} AND
|
|
582
|
-
table_schema = ${tableSchema} AND
|
|
583
|
-
column_name = ${verCol}`)).rows[0].column_default;
|
|
584
|
-
if (!verDefault) {
|
|
585
|
-
throw Error(`pg.verCol_without_default ${verCol}`);
|
|
586
|
-
}
|
|
587
|
-
log("ensureSchema", types2);
|
|
588
|
-
tableOptions.schema = { types: types2 };
|
|
589
|
-
tableOptions.verDefault = verDefault;
|
|
590
|
-
}
|
|
591
|
-
async read(rootQuery, tableOptions) {
|
|
592
|
-
const idQueries = {};
|
|
593
|
-
const promises = [];
|
|
594
|
-
const results = [];
|
|
595
|
-
const { prefix } = tableOptions;
|
|
596
|
-
await this.ensureSchema(tableOptions);
|
|
597
|
-
const getByArgs = async (args, projection) => {
|
|
598
|
-
const result = await this.readSql(
|
|
599
|
-
selectByArgs(args, projection, tableOptions)
|
|
600
|
-
);
|
|
601
|
-
const wrappedGraph = common.encodeGraph(common.wrapObject(result, prefix));
|
|
602
|
-
log("getByArgs", wrappedGraph);
|
|
603
|
-
common.merge(results, wrappedGraph);
|
|
604
|
-
};
|
|
605
|
-
const getByIds = async () => {
|
|
606
|
-
const result = await this.readSql(
|
|
607
|
-
selectByIds(Object.keys(idQueries), null, tableOptions)
|
|
608
|
-
);
|
|
609
|
-
result.forEach((object) => {
|
|
610
|
-
const wrappedGraph = common.encodeGraph(common.wrapObject(object, prefix));
|
|
611
|
-
log("getByIds", wrappedGraph);
|
|
612
|
-
common.merge(results, wrappedGraph);
|
|
613
|
-
});
|
|
614
|
-
};
|
|
615
|
-
const query = common.unwrap(rootQuery, prefix);
|
|
616
|
-
for (const node of query) {
|
|
617
|
-
const args = common.decodeArgs(node);
|
|
618
|
-
if (common.isPlainObject(args)) {
|
|
619
|
-
if (node.prefix) {
|
|
620
|
-
for (const childNode of node.children) {
|
|
621
|
-
const childArgs = common.decodeArgs(childNode);
|
|
622
|
-
const projection = childNode.children ? common.decodeQuery(childNode.children) : null;
|
|
623
|
-
promises.push(getByArgs({ ...args, ...childArgs }, projection));
|
|
624
|
-
}
|
|
625
|
-
} else {
|
|
626
|
-
const projection = node.children ? common.decodeQuery(node.children) : null;
|
|
627
|
-
promises.push(getByArgs(args, projection));
|
|
628
|
-
}
|
|
629
|
-
} else {
|
|
630
|
-
idQueries[node.key] = node.children;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
if (!common.isEmpty(idQueries))
|
|
634
|
-
promises.push(getByIds());
|
|
635
|
-
await Promise.all(promises);
|
|
636
|
-
log("dbRead", rootQuery, results);
|
|
637
|
-
return common.finalize(results, common.wrap(query, prefix));
|
|
638
|
-
}
|
|
639
|
-
async write(rootChange, tableOptions) {
|
|
640
|
-
const { prefix } = tableOptions;
|
|
641
|
-
await this.ensureSchema(tableOptions);
|
|
642
|
-
const change = common.unwrap(rootChange, prefix);
|
|
643
|
-
const sqls = change.map((node) => {
|
|
644
|
-
const arg = common.decodeArgs(node);
|
|
645
|
-
if (common.isRange(node)) {
|
|
646
|
-
if (node.key === node.end)
|
|
647
|
-
return del(arg, tableOptions);
|
|
648
|
-
throw Error("pg_write.write_range_unsupported");
|
|
649
|
-
}
|
|
650
|
-
const object = common.decodeGraph(node.children);
|
|
651
|
-
if (common.isPlainObject(arg)) {
|
|
652
|
-
common.mergeObject(object, arg);
|
|
653
|
-
} else {
|
|
654
|
-
object.id = arg;
|
|
655
|
-
}
|
|
656
|
-
if (object.$put && object.$put !== true) {
|
|
657
|
-
throw Error("pg_write.partial_put_unsupported");
|
|
658
|
-
}
|
|
659
|
-
return object.$put ? put(object, arg, tableOptions) : patch(object, arg, tableOptions);
|
|
660
|
-
});
|
|
661
|
-
const result = [];
|
|
662
|
-
await Promise.all(
|
|
663
|
-
sqls.map(
|
|
664
|
-
(sql2) => this.writeSql(sql2).then((object) => {
|
|
665
|
-
common.merge(result, common.encodeGraph(common.wrapObject(object, prefix)));
|
|
666
|
-
})
|
|
667
|
-
)
|
|
668
|
-
);
|
|
669
|
-
log("dbWrite", rootChange, result);
|
|
670
|
-
return result;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
const pg = ({ table, idCol, verCol, connection, schema, verDefault }) => (store) => {
|
|
674
|
-
store.on("read", read);
|
|
675
|
-
store.on("write", write);
|
|
676
|
-
const prefix = store.path;
|
|
677
|
-
const tableOpts = {
|
|
678
|
-
prefix,
|
|
679
|
-
table: table || prefix[prefix.length - 1] || "default",
|
|
680
|
-
idCol: idCol || "id",
|
|
681
|
-
verCol: verCol || "updatedAt",
|
|
682
|
-
schema,
|
|
683
|
-
verDefault
|
|
684
|
-
};
|
|
685
|
-
const defaultDb = new Db(connection);
|
|
686
|
-
function read(query, options, next) {
|
|
687
|
-
const { pgClient } = options;
|
|
688
|
-
const db = pgClient ? new Db(pgClient) : defaultDb;
|
|
689
|
-
const readPromise = db.read(query, tableOpts);
|
|
690
|
-
const remainingQuery = common.remove(query, prefix);
|
|
691
|
-
const nextPromise = next(remainingQuery);
|
|
692
|
-
return Promise.all([readPromise, nextPromise]).then(
|
|
693
|
-
([readRes, nextRes]) => {
|
|
694
|
-
return common.merge(readRes, nextRes);
|
|
695
|
-
}
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
function write(change, options, next) {
|
|
699
|
-
const { pgClient } = options;
|
|
700
|
-
const db = pgClient ? new Db(pgClient) : defaultDb;
|
|
701
|
-
const writePromise = db.write(change, tableOpts);
|
|
702
|
-
const remainingChange = common.remove(change, prefix);
|
|
703
|
-
const nextPromise = next(remainingChange);
|
|
704
|
-
return Promise.all([writePromise, nextPromise]).then(
|
|
705
|
-
([writeRes, nextRes]) => {
|
|
706
|
-
return common.merge(writeRes, nextRes);
|
|
707
|
-
}
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
};
|
|
711
|
-
exports.pg = pg;
|