@graffy/pg 0.15.12 → 0.15.14-alpha.1
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/Readme.md +131 -3
- package/index.cjs +225 -162
- package/index.mjs +226 -163
- package/package.json +7 -5
- package/types/Db.d.ts +1 -0
- package/types/filter/getSql.d.ts +1 -3
- package/types/index.d.ts +2 -2
- package/types/sql/clauses.d.ts +2 -1
- package/types/sql/getArgSql.d.ts +2 -1
- package/types/sql/getMeta.d.ts +1 -0
- package/types/sql/select.d.ts +2 -2
- package/types/sql/upsert.d.ts +1 -0
package/index.cjs
CHANGED
|
@@ -53,8 +53,7 @@ const valid = {
|
|
|
53
53
|
$all: true,
|
|
54
54
|
$has: true,
|
|
55
55
|
$cts: true,
|
|
56
|
-
$ctd: true
|
|
57
|
-
$ovl: true
|
|
56
|
+
$ctd: true
|
|
58
57
|
};
|
|
59
58
|
const inverse = {
|
|
60
59
|
$eq: "$neq",
|
|
@@ -67,10 +66,8 @@ const inverse = {
|
|
|
67
66
|
$lte: "$gt"
|
|
68
67
|
};
|
|
69
68
|
function getAst(filter) {
|
|
70
|
-
counter = 0;
|
|
71
69
|
return simplify(construct(filter));
|
|
72
70
|
}
|
|
73
|
-
let counter;
|
|
74
71
|
function construct(node, prop, op) {
|
|
75
72
|
if (!node || typeof node !== "object" || prop && op) {
|
|
76
73
|
if (op && prop)
|
|
@@ -91,14 +88,6 @@ function construct(node, prop, op) {
|
|
|
91
88
|
if (key === "$not") {
|
|
92
89
|
return [key, construct(val, prop, op)];
|
|
93
90
|
}
|
|
94
|
-
if (key === "$any" || key === "$all") {
|
|
95
|
-
const elkey = "el$" + counter++;
|
|
96
|
-
return [key, prop, elkey, construct(val, elkey)];
|
|
97
|
-
}
|
|
98
|
-
if (key === "$has") {
|
|
99
|
-
const elkey = "el$" + counter++;
|
|
100
|
-
return [key, prop, elkey, construct(val, elkey)[1]];
|
|
101
|
-
}
|
|
102
91
|
if (key[0] === "$") {
|
|
103
92
|
if (!valid[key])
|
|
104
93
|
throw Error("pgast.invalid_op:" + key);
|
|
@@ -123,10 +112,21 @@ function simplify(node) {
|
|
|
123
112
|
node[1] = node[1].map((subnode) => simplify(subnode));
|
|
124
113
|
} else if (op === "$not") {
|
|
125
114
|
node[1] = simplify(node[1]);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
}
|
|
116
|
+
if (op === "$and") {
|
|
117
|
+
if (!node[1].length)
|
|
118
|
+
return true;
|
|
119
|
+
if (node[1].includes(false))
|
|
120
|
+
return false;
|
|
121
|
+
node[1] = node[1].filter((item) => item !== true);
|
|
122
|
+
} else if (op === "$or") {
|
|
123
|
+
if (!node[1].length)
|
|
124
|
+
return false;
|
|
125
|
+
if (node[1].includes(true))
|
|
126
|
+
return true;
|
|
127
|
+
node[1] = node[1].filter((item) => item !== false);
|
|
128
|
+
} else if (op === "$not" && typeof node[1] === "boolean") {
|
|
129
|
+
return !node[1];
|
|
130
130
|
}
|
|
131
131
|
if (op === "$or") {
|
|
132
132
|
const { eqmap, noneq, change } = node[1].reduce((acc, item) => {
|
|
@@ -148,112 +148,75 @@ function simplify(node) {
|
|
|
148
148
|
];
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
if (op === "$and" || op === "$or") {
|
|
152
|
-
|
|
153
|
-
throw Error("pgast.expected_children:" + op);
|
|
154
|
-
return node[1].length === 1 ? node[1][0] : node;
|
|
151
|
+
if ((op === "$and" || op === "$or") && node[1].length === 1) {
|
|
152
|
+
return node[1][0];
|
|
155
153
|
}
|
|
156
154
|
if (op === "$not") {
|
|
157
155
|
const [subop, ...subargs] = node[1];
|
|
158
156
|
const invop = inverse[subop];
|
|
159
157
|
return invop ? [invop, ...subargs] : node;
|
|
160
158
|
}
|
|
161
|
-
if (op === "$any" || op === "$all") {
|
|
162
|
-
const [_, list, elkey, subnode] = node;
|
|
163
|
-
const [subop, elk, val] = subnode;
|
|
164
|
-
return (subop === "$eq" || subop === "$in") && elkey === elk ? [op === "$any" ? "$ovl" : "$ctd", list, subop === "$eq" ? [val] : val] : node;
|
|
165
|
-
}
|
|
166
|
-
if (op === "$has") {
|
|
167
|
-
const [_, list, elkey, limbs] = node;
|
|
168
|
-
return limbs.every(([subop, elk]) => subop === "$eq" && elk === elkey) ? ["$cts", list, limbs.map(([_op, _prop, val]) => val)] : node;
|
|
169
|
-
}
|
|
170
159
|
return node;
|
|
171
160
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return
|
|
161
|
+
const opSql = {
|
|
162
|
+
$and: `AND`,
|
|
163
|
+
$or: `OR`,
|
|
164
|
+
$not: sql__default["default"]`NOT`,
|
|
165
|
+
$eq: sql__default["default"]`=`,
|
|
166
|
+
$neq: sql__default["default"]`<>`,
|
|
167
|
+
$in: sql__default["default"]`IN`,
|
|
168
|
+
$nin: sql__default["default"]`NOT IN`,
|
|
169
|
+
$lt: sql__default["default"]`<`,
|
|
170
|
+
$lte: sql__default["default"]`<=`,
|
|
171
|
+
$gt: sql__default["default"]`>`,
|
|
172
|
+
$gte: sql__default["default"]`>=`,
|
|
173
|
+
$re: sql__default["default"]`~`,
|
|
174
|
+
$ire: sql__default["default"]`~*`,
|
|
175
|
+
$cts: sql__default["default"]`@>`,
|
|
176
|
+
$ctd: sql__default["default"]`<@`
|
|
177
|
+
};
|
|
178
|
+
function castValue$1(value, type, op) {
|
|
179
|
+
if (value === null && op === "$eq")
|
|
180
|
+
return sql__default["default"]`IS NULL`;
|
|
181
|
+
if (value === null && op === "$neq")
|
|
182
|
+
return sql__default["default"]`IS NOT NULL`;
|
|
183
|
+
const sqlOp = opSql[op];
|
|
184
|
+
if (!sqlOp)
|
|
185
|
+
throw Error("pg.getSql_unknown_operator " + op);
|
|
186
|
+
if (op === "$in" || op === "$nin") {
|
|
187
|
+
return sql__default["default"]`${sqlOp} (${sql.join(value)})`;
|
|
192
188
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return sql__default["default"]`(${lookup(left, rType)})::${sql.raw(rType)} ${sql.raw(op)} ${right}`;
|
|
189
|
+
if (type === "jsonb") {
|
|
190
|
+
return sql__default["default"]`${sqlOp} ${JSON.stringify(value)}::jsonb`;
|
|
191
|
+
}
|
|
192
|
+
if (type === "cube") {
|
|
193
|
+
if (!Array.isArray(value) || value.length !== 2 || !Array.isArray(value[0])) {
|
|
194
|
+
throw Error("pg.castValue_bad_cube" + JSON.stringify(value));
|
|
200
195
|
}
|
|
196
|
+
return sql__default["default"]`${sqlOp} cube(${sql.join(value)})`;
|
|
201
197
|
}
|
|
198
|
+
return sql__default["default"]`${sqlOp} ${value}`;
|
|
199
|
+
}
|
|
200
|
+
function getSql(filter, options) {
|
|
202
201
|
function getNodeSql(ast) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return sql__default["default"]`${lookup(ast[1])} IS NOT NULL`;
|
|
211
|
-
return binop("<>", ast[1], ast[2]);
|
|
212
|
-
case "$lt":
|
|
213
|
-
return binop("<", ast[1], ast[2]);
|
|
214
|
-
case "$lte":
|
|
215
|
-
return binop("<=", ast[1], ast[2]);
|
|
216
|
-
case "$gt":
|
|
217
|
-
return binop(">", ast[1], ast[2]);
|
|
218
|
-
case "$gte":
|
|
219
|
-
return binop(">=", ast[1], ast[2]);
|
|
220
|
-
case "$re":
|
|
221
|
-
return binop("~", ast[1], ast[2]);
|
|
222
|
-
case "$ire":
|
|
223
|
-
return binop("~*", ast[1], ast[2]);
|
|
224
|
-
case "$in":
|
|
225
|
-
return sql__default["default"]`${lookup(ast[1])} IN (${sql.join(ast[2])})`;
|
|
226
|
-
case "$nin":
|
|
227
|
-
return sql__default["default"]`${lookup(ast[1])} NOT IN (${sql.join(ast[2])})`;
|
|
228
|
-
case "$cts":
|
|
229
|
-
return sql__default["default"]`${lookup(ast[1])} @> ${ast[2]}`;
|
|
230
|
-
case "$ctd":
|
|
231
|
-
return sql__default["default"]`${lookup(ast[1])} <@ ${ast[2]}`;
|
|
232
|
-
case "$ovl":
|
|
233
|
-
switch (getColumnType(ast[1])) {
|
|
234
|
-
case "jsonb":
|
|
235
|
-
case "any":
|
|
236
|
-
return sql__default["default"]`${lookup(ast[1])} ?| ${Array.isArray(ast[2]) ? ast[2] : Object.keys(ast[2])}`;
|
|
237
|
-
case "array":
|
|
238
|
-
return sql__default["default"]`${lookup(ast[1])} && ${ast[2]}`;
|
|
239
|
-
default:
|
|
240
|
-
throw Error("pg.getSql_ovl_unknown_column_type");
|
|
241
|
-
}
|
|
242
|
-
case "$and":
|
|
243
|
-
return sql__default["default"]`(${sql.join(ast[1].map((node) => getNodeSql(node)), `) AND (`)})`;
|
|
244
|
-
case "$or":
|
|
245
|
-
return sql__default["default"]`(${sql.join(ast[1].map((node) => getNodeSql(node)), `) OR (`)})`;
|
|
246
|
-
case "$not":
|
|
247
|
-
return sql__default["default"]`NOT (${getNodeSql(ast[1])})`;
|
|
248
|
-
case "$any":
|
|
249
|
-
return sql__default["default"]`(SELECT bool_or(${getNodeSql(ast[3])}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
|
|
250
|
-
case "$all":
|
|
251
|
-
return sql__default["default"]`(SELECT bool_and(${getNodeSql(ast[3])}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
|
|
252
|
-
case "$has":
|
|
253
|
-
return sql__default["default"]`(SELECT bool_or(${sql.join(ast[3].map((node) => getNodeSql(node)), `) AND bool_or(`)}) FROM UNNEST(${lookup(ast[1])}) ${lookup(ast[2])})`;
|
|
254
|
-
default:
|
|
255
|
-
throw Error("pg.getSql_unknown_operator: " + ast[0]);
|
|
202
|
+
if (typeof ast === "boolean")
|
|
203
|
+
return ast;
|
|
204
|
+
const op = ast[0];
|
|
205
|
+
if (op === "$and" || op === "$or") {
|
|
206
|
+
return sql__default["default"]`(${sql.join(ast[1].map((node) => getNodeSql(node)), `) ${opSql[op]} (`)})`;
|
|
207
|
+
} else if (op === "$not") {
|
|
208
|
+
return sql__default["default"]`${opSql[op]} (${getNodeSql(ast[1])})`;
|
|
256
209
|
}
|
|
210
|
+
const [prefix, ...suffix] = common.encodePath(ast[1]);
|
|
211
|
+
const { types } = options.schema;
|
|
212
|
+
if (!types[prefix])
|
|
213
|
+
throw Error("pg.no_column " + prefix);
|
|
214
|
+
if (suffix.length && types[prefix] !== "jsonb") {
|
|
215
|
+
throw Error("pg.lookup_not_jsonb " + prefix);
|
|
216
|
+
}
|
|
217
|
+
const [lhs, type] = suffix.length ? [sql__default["default"]`"${sql.raw(prefix)}" #> ${suffix}`, "jsonb"] : [sql__default["default"]`"${sql.raw(prefix)}"`, types[prefix]];
|
|
218
|
+
const rhs = castValue$1(ast[2], type, op);
|
|
219
|
+
return sql__default["default"]`${lhs} ${rhs}`;
|
|
257
220
|
}
|
|
258
221
|
return getNodeSql(getAst(filter));
|
|
259
222
|
}
|
|
@@ -271,22 +234,68 @@ const getJsonBuildValue = (value) => {
|
|
|
271
234
|
return sql__default["default"]`${value}::text`;
|
|
272
235
|
return sql__default["default"]`${JSON.stringify(stripAttributes(value))}::jsonb`;
|
|
273
236
|
};
|
|
274
|
-
const
|
|
275
|
-
|
|
237
|
+
const lookup = (prop) => {
|
|
238
|
+
const [prefix, ...suffix] = common.encodePath(prop);
|
|
239
|
+
return suffix.length ? sql__default["default"]`"${sql.raw(prefix)}" #> ${suffix}` : sql__default["default"]`"${sql.raw(prefix)}"`;
|
|
240
|
+
};
|
|
241
|
+
const aggSql = {
|
|
242
|
+
$sum: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`,
|
|
243
|
+
$card: (prop) => sql__default["default"]`count(distinct(${lookup(prop)}))`,
|
|
244
|
+
$avg: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`,
|
|
245
|
+
$max: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`,
|
|
246
|
+
$min: (prop) => sql__default["default"]`sum((${lookup(prop)})::numeric)`
|
|
276
247
|
};
|
|
248
|
+
const getSelectCols = (table, projection = null) => {
|
|
249
|
+
if (!projection)
|
|
250
|
+
return sql__default["default"]`to_jsonb("${sql.raw(table)}")`;
|
|
251
|
+
const sqls = [];
|
|
252
|
+
for (const key in projection) {
|
|
253
|
+
if (key === "$count") {
|
|
254
|
+
sqls.push(sql__default["default"]`'$count', count(*)`);
|
|
255
|
+
} else if (aggSql[key]) {
|
|
256
|
+
const subSqls = [];
|
|
257
|
+
for (const prop in projection[key]) {
|
|
258
|
+
subSqls.push(sql__default["default"]`${prop}::text, ${aggSql[key](prop)}`);
|
|
259
|
+
}
|
|
260
|
+
sqls.push(sql__default["default"]`${key}::text, jsonb_build_object(${sql.join(subSqls, ", ")})`);
|
|
261
|
+
} else {
|
|
262
|
+
sqls.push(sql__default["default"]`${key}::text, "${sql.raw(key)}"`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return sql__default["default"]`jsonb_build_object(${sql.join(sqls, ", ")})`;
|
|
266
|
+
};
|
|
267
|
+
function infJoin(array) {
|
|
268
|
+
return sql__default["default"]`array[${sql.join(array.map((num) => num === Infinity ? sql__default["default"]`'Infinity'` : num === -Infinity ? sql__default["default"]`'-Infinity'` : num))}]::float8[]`;
|
|
269
|
+
}
|
|
270
|
+
function castValue(value, type, name) {
|
|
271
|
+
if (!type)
|
|
272
|
+
throw Error("pg.write_no_column " + name);
|
|
273
|
+
if (value instanceof sql.Sql)
|
|
274
|
+
return value;
|
|
275
|
+
if (value === null)
|
|
276
|
+
return sql__default["default"]`NULL`;
|
|
277
|
+
if (type === "jsonb") {
|
|
278
|
+
return value.$put ? JSON.stringify(stripAttributes(value)) : sql__default["default"]`jsonb_strip_nulls(${getJsonUpdate(value, name, [])})`;
|
|
279
|
+
}
|
|
280
|
+
if (type === "cube") {
|
|
281
|
+
if (!Array.isArray(value) || !value.length || Array.isArray(value[0]) && value.length !== 2) {
|
|
282
|
+
throw Error("pg.castValue_bad_cube" + JSON.stringify(value));
|
|
283
|
+
}
|
|
284
|
+
return Array.isArray(value[0]) ? sql__default["default"]`cube(${infJoin(value[0])}, ${infJoin(value[1])})` : sql__default["default"]`cube(${infJoin(value)})`;
|
|
285
|
+
}
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
277
288
|
const getInsert = (row, options) => {
|
|
278
289
|
const cols = [];
|
|
279
290
|
const vals = [];
|
|
280
|
-
Object.entries(row).filter(([
|
|
291
|
+
Object.entries(row).filter(([col]) => col !== options.verCol && col[0] !== "$").concat([[options.verCol, nowTimestamp]]).forEach(([col, val]) => {
|
|
281
292
|
cols.push(sql__default["default"]`"${sql.raw(col)}"`);
|
|
282
|
-
vals.push(val
|
|
293
|
+
vals.push(castValue(val, options.schema.types[col], col));
|
|
283
294
|
});
|
|
284
295
|
return { cols: sql.join(cols, ", "), vals: sql.join(vals, ", ") };
|
|
285
296
|
};
|
|
286
297
|
const getUpdates = (row, options) => {
|
|
287
|
-
return sql.join(Object.entries(row).filter(([
|
|
288
|
-
return sql__default["default"]`"${sql.raw(name)}" = ${value instanceof sql.Sql || typeof value !== "object" || !value ? value : !value.$put ? sql__default["default"]`jsonb_strip_nulls(${getJsonUpdate(value, name, [])})` : sql__default["default"]`${JSON.stringify(stripAttributes(value))}::jsonb`}`;
|
|
289
|
-
}).concat(sql__default["default"]`"${sql.raw(options.verCol)}" = ${nowTimestamp}`), ", ");
|
|
298
|
+
return sql.join(Object.entries(row).filter(([col]) => col !== options.idCol && col[0] !== "$").map(([col, val]) => sql__default["default"]`"${sql.raw(col)}" = ${castValue(val, options.schema.types[col], col)}`).concat(sql__default["default"]`"${sql.raw(options.verCol)}" = ${nowTimestamp}`), ", ");
|
|
290
299
|
};
|
|
291
300
|
function getJsonUpdate(_a, col, path) {
|
|
292
301
|
var _b = _a, { $put } = _b, object = __objRest(_b, ["$put"]);
|
|
@@ -322,29 +331,29 @@ const getArgMeta = (key, prefix, idCol) => getJsonBuildObject({
|
|
|
322
331
|
$ref: sql__default["default"]`jsonb_build_array(${sql.join(prefix.map((k) => sql__default["default"]`${k}::text`))}, "${sql.raw(idCol)}")`,
|
|
323
332
|
$ver: nowTimestamp
|
|
324
333
|
});
|
|
334
|
+
const getAggMeta = (key, $group) => getJsonBuildObject({
|
|
335
|
+
$key: sql.join([key, getJsonBuildObject({ $group })].filter(Boolean), " || "),
|
|
336
|
+
$ver: nowTimestamp
|
|
337
|
+
});
|
|
325
338
|
function getArgSql(_c, options) {
|
|
326
339
|
var _d = _c, { $first, $last, $after, $before, $since, $until, $all, $cursor: _ } = _d, rest = __objRest(_d, ["$first", "$last", "$after", "$before", "$since", "$until", "$all", "$cursor"]);
|
|
327
|
-
const _a = rest, { $order } = _a, filter = __objRest(_a, ["$order"]);
|
|
340
|
+
const _a = rest, { $order, $group } = _a, filter = __objRest(_a, ["$order", "$group"]);
|
|
328
341
|
const { prefix, idCol } = options;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
const [_prefix, ...suffix] = common.encodePath(prop);
|
|
336
|
-
return suffix.length ? "jsonb" : "any";
|
|
337
|
-
};
|
|
338
|
-
const meta = (key2) => getArgMeta(key2, prefix, idCol);
|
|
342
|
+
if ($order && $group) {
|
|
343
|
+
throw Error("pg_arg.order_and_group_unsupported in " + prefix);
|
|
344
|
+
}
|
|
345
|
+
const meta = (key2) => $group ? getAggMeta(key2, $group) : getArgMeta(key2, prefix, idCol);
|
|
346
|
+
const groupCols = Array.isArray($group) && $group.length && $group.map(lookup);
|
|
347
|
+
const group = groupCols ? sql.join(groupCols, ", ") : void 0;
|
|
339
348
|
const hasRangeArg = $before || $after || $since || $until || $first || $last || $all || $order;
|
|
340
349
|
let key;
|
|
341
350
|
const where = [];
|
|
342
351
|
if (!common.isEmpty(filter)) {
|
|
343
|
-
where.push(getSql(filter,
|
|
352
|
+
where.push(getSql(filter, options));
|
|
344
353
|
key = sql__default["default"]`${JSON.stringify(filter)}::jsonb`;
|
|
345
354
|
}
|
|
346
355
|
if (!hasRangeArg)
|
|
347
|
-
return { meta: meta(key), where, limit: 1 };
|
|
356
|
+
return { meta: meta(key), where, group, limit: 1 };
|
|
348
357
|
if (common.isEmpty(rest)) {
|
|
349
358
|
throw Error("pg_arg.pagination_only_unsupported in " + prefix);
|
|
350
359
|
}
|
|
@@ -355,13 +364,14 @@ function getArgSql(_c, options) {
|
|
|
355
364
|
});
|
|
356
365
|
const orderQuery = $order && getJsonBuildObject({ $order: sql__default["default"]`${JSON.stringify($order)}::jsonb` });
|
|
357
366
|
const cursorQuery = getJsonBuildObject({
|
|
358
|
-
$cursor: sql__default["default"]`jsonb_build_array(${sql.join(orderCols)})`
|
|
367
|
+
$cursor: sql__default["default"]`jsonb_build_array(${sql.join(groupCols || orderCols)})`
|
|
359
368
|
});
|
|
360
369
|
key = sql__default["default"]`(${sql.join([key, orderQuery, cursorQuery].filter(Boolean), ` || `)})`;
|
|
361
370
|
return {
|
|
362
371
|
meta: meta(key),
|
|
363
372
|
where,
|
|
364
|
-
order: sql.join(orderCols.map((col) => sql__default["default"]`${col} ${$last ? sql__default["default"]`DESC` : sql__default["default"]`ASC`}`), `, `),
|
|
373
|
+
order: $order && sql.join(orderCols.map((col) => sql__default["default"]`${col} ${$last ? sql__default["default"]`DESC` : sql__default["default"]`ASC`}`), `, `),
|
|
374
|
+
group,
|
|
365
375
|
limit: $first || $last
|
|
366
376
|
};
|
|
367
377
|
}
|
|
@@ -395,39 +405,57 @@ function getBoundCond(orderCols, bound, kind) {
|
|
|
395
405
|
}
|
|
396
406
|
}
|
|
397
407
|
const MAX_LIMIT = 4096;
|
|
398
|
-
function selectByArgs(args, options) {
|
|
408
|
+
function selectByArgs(args, projection, options) {
|
|
399
409
|
const { table } = options;
|
|
400
|
-
const { where, order, limit, meta } = getArgSql(args, options);
|
|
410
|
+
const { where, order, group, limit, meta } = getArgSql(args, options);
|
|
401
411
|
const clampedLimit = Math.min(MAX_LIMIT, limit || MAX_LIMIT);
|
|
402
412
|
return sql__default["default"]`
|
|
403
413
|
SELECT
|
|
404
|
-
${getSelectCols(table)} || ${meta}
|
|
414
|
+
${getSelectCols(table, projection)} || ${meta}
|
|
405
415
|
FROM "${sql.raw(table)}"
|
|
406
416
|
${where.length ? sql__default["default"]`WHERE ${sql.join(where, ` AND `)}` : sql.empty}
|
|
417
|
+
${group ? sql__default["default"]`GROUP BY ${group}` : sql.empty}
|
|
407
418
|
${order ? sql__default["default"]`ORDER BY ${order}` : sql.empty}
|
|
408
419
|
LIMIT ${clampedLimit}
|
|
409
420
|
`;
|
|
410
421
|
}
|
|
411
|
-
function selectByIds(ids, options) {
|
|
422
|
+
function selectByIds(ids, projection, options) {
|
|
412
423
|
const { table, idCol } = options;
|
|
413
424
|
return sql__default["default"]`
|
|
414
425
|
SELECT
|
|
415
|
-
${getSelectCols(table)} || ${getIdMeta(options)}
|
|
426
|
+
${getSelectCols(table, projection)} || ${getIdMeta(options)}
|
|
416
427
|
FROM "${sql.raw(table)}"
|
|
417
428
|
WHERE "${sql.raw(idCol)}" IN (${sql.join(ids)})
|
|
418
429
|
`;
|
|
419
430
|
}
|
|
420
|
-
function
|
|
431
|
+
function getSingleSql(arg, options) {
|
|
421
432
|
const { table, idCol } = options;
|
|
422
|
-
|
|
433
|
+
if (!common.isPlainObject(arg)) {
|
|
434
|
+
return {
|
|
435
|
+
where: sql__default["default"]`"${sql.raw(idCol)}" = ${arg}`,
|
|
436
|
+
meta: getIdMeta(options)
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const { where, meta } = getArgSql(arg, options);
|
|
423
440
|
if (!where || !where.length)
|
|
424
441
|
throw Error("pg_write.no_condition");
|
|
442
|
+
return {
|
|
443
|
+
where: sql__default["default"]`"${sql.raw(idCol)}" = (
|
|
444
|
+
SELECT "${sql.raw(idCol)}"
|
|
445
|
+
FROM "${sql.raw(table)}"
|
|
446
|
+
WHERE ${sql.join(where, ` AND `)}
|
|
447
|
+
LIMIT 1
|
|
448
|
+
)`,
|
|
449
|
+
meta
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function patch(object, arg, options) {
|
|
453
|
+
const { table } = options;
|
|
454
|
+
const { where, meta } = getSingleSql(arg, options);
|
|
425
455
|
const row = object;
|
|
426
456
|
return sql__default["default"]`
|
|
427
457
|
UPDATE "${sql.raw(table)}" SET ${getUpdates(row, options)}
|
|
428
|
-
WHERE ${
|
|
429
|
-
SELECT "${sql.raw(idCol)}" FROM "${sql.raw(table)}"
|
|
430
|
-
WHERE ${sql.join(where, ` AND `)} LIMIT 1)` : sql.join(where, ` AND `)}
|
|
458
|
+
WHERE ${where}
|
|
431
459
|
RETURNING (${getSelectCols(table)} || ${meta})`;
|
|
432
460
|
}
|
|
433
461
|
function put(object, arg, options) {
|
|
@@ -447,6 +475,14 @@ function put(object, arg, options) {
|
|
|
447
475
|
ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
|
|
448
476
|
RETURNING (${getSelectCols(table)} || ${meta})`;
|
|
449
477
|
}
|
|
478
|
+
function del(arg, options) {
|
|
479
|
+
const { table } = options;
|
|
480
|
+
const { where } = getSingleSql(arg, options);
|
|
481
|
+
return sql__default["default"]`
|
|
482
|
+
DELETE FROM "${sql.raw(table)}"
|
|
483
|
+
WHERE ${where}
|
|
484
|
+
RETURNING (${getJsonBuildObject({ $key: arg })})`;
|
|
485
|
+
}
|
|
450
486
|
const log = debug__default["default"]("graffy:pg:db");
|
|
451
487
|
class Db {
|
|
452
488
|
constructor(connection) {
|
|
@@ -474,32 +510,54 @@ class Db {
|
|
|
474
510
|
}
|
|
475
511
|
}
|
|
476
512
|
async readSql(sql2) {
|
|
477
|
-
|
|
478
|
-
result = result.rows.flat();
|
|
513
|
+
const result = (await this.query(sql2)).rows.flat();
|
|
479
514
|
log("Read result", result);
|
|
480
515
|
return result;
|
|
481
516
|
}
|
|
482
517
|
async writeSql(sql2) {
|
|
483
|
-
|
|
518
|
+
const res = await this.query(sql2);
|
|
484
519
|
log("Rows written", res.rowCount);
|
|
485
520
|
if (!res.rowCount) {
|
|
486
521
|
throw Error("pg.nothing_written " + sql2.text + " with " + sql2.values);
|
|
487
522
|
}
|
|
488
523
|
return res.rows[0][0];
|
|
489
524
|
}
|
|
525
|
+
async ensureSchema(tableOptions) {
|
|
526
|
+
if (tableOptions.schema)
|
|
527
|
+
return;
|
|
528
|
+
const { table } = tableOptions;
|
|
529
|
+
const types = (await this.query(sql__default["default"]`SELECT jsonb_object_agg(
|
|
530
|
+
column_name,
|
|
531
|
+
udt_name
|
|
532
|
+
)
|
|
533
|
+
FROM information_schema.columns
|
|
534
|
+
WHERE
|
|
535
|
+
table_name = ${table} AND
|
|
536
|
+
table_schema = (
|
|
537
|
+
SELECT table_schema
|
|
538
|
+
FROM information_schema.tables
|
|
539
|
+
WHERE table_name = ${table}
|
|
540
|
+
ORDER BY array_position(current_schemas(false)::text[], table_schema::text) ASC
|
|
541
|
+
LIMIT 1)`)).rows[0][0];
|
|
542
|
+
if (!types)
|
|
543
|
+
throw Error(`pg.missing_table ${table}`);
|
|
544
|
+
log("ensureSchema", types);
|
|
545
|
+
tableOptions.schema = { types };
|
|
546
|
+
}
|
|
490
547
|
async read(rootQuery, tableOptions) {
|
|
491
548
|
const idQueries = {};
|
|
492
549
|
const promises = [];
|
|
493
550
|
const results = [];
|
|
494
551
|
const { prefix } = tableOptions;
|
|
495
|
-
|
|
496
|
-
|
|
552
|
+
await this.ensureSchema(tableOptions);
|
|
553
|
+
const getByArgs = async (args, projection) => {
|
|
554
|
+
const result = await this.readSql(selectByArgs(args, projection, tableOptions));
|
|
497
555
|
const wrappedGraph = common.encodeGraph(common.wrapObject(result, prefix));
|
|
498
556
|
log("getByArgs", wrappedGraph);
|
|
499
557
|
common.merge(results, wrappedGraph);
|
|
500
558
|
};
|
|
501
559
|
const getByIds = async () => {
|
|
502
|
-
const result = await this.readSql(selectByIds(Object.keys(idQueries), tableOptions));
|
|
560
|
+
const result = await this.readSql(selectByIds(Object.keys(idQueries), null, tableOptions));
|
|
503
561
|
result.forEach((object) => {
|
|
504
562
|
const wrappedGraph = common.encodeGraph(common.wrapObject(object, prefix));
|
|
505
563
|
log("getByIds", wrappedGraph);
|
|
@@ -513,10 +571,12 @@ class Db {
|
|
|
513
571
|
if (node.prefix) {
|
|
514
572
|
for (const childNode of node.children) {
|
|
515
573
|
const childArgs = common.decodeArgs(childNode);
|
|
516
|
-
|
|
574
|
+
const projection = childNode.children ? common.decodeQuery(childNode.children) : null;
|
|
575
|
+
promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs), projection));
|
|
517
576
|
}
|
|
518
577
|
} else {
|
|
519
|
-
|
|
578
|
+
const projection = node.children ? common.decodeQuery(node.children) : null;
|
|
579
|
+
promises.push(getByArgs(args, projection));
|
|
520
580
|
}
|
|
521
581
|
} else {
|
|
522
582
|
idQueries[node.key] = node.children;
|
|
@@ -529,15 +589,16 @@ class Db {
|
|
|
529
589
|
return common.slice(common.finalize(results, common.wrap(query, prefix)), rootQuery).known || [];
|
|
530
590
|
}
|
|
531
591
|
async write(rootChange, tableOptions) {
|
|
532
|
-
const sqls = [];
|
|
533
|
-
const addToQuery = (sql2) => sqls.push(sql2);
|
|
534
592
|
const { prefix } = tableOptions;
|
|
593
|
+
await this.ensureSchema(tableOptions);
|
|
535
594
|
const change = common.unwrap(rootChange, prefix);
|
|
536
|
-
|
|
595
|
+
const sqls = change.map((node) => {
|
|
596
|
+
const arg = common.decodeArgs(node);
|
|
537
597
|
if (common.isRange(node)) {
|
|
538
|
-
|
|
598
|
+
if (node.key === node.end)
|
|
599
|
+
return del(arg, tableOptions);
|
|
600
|
+
throw Error("pg_write.write_range_unsupported");
|
|
539
601
|
}
|
|
540
|
-
const arg = common.decodeArgs(node);
|
|
541
602
|
const object = common.decodeGraph(node.children);
|
|
542
603
|
if (common.isPlainObject(arg)) {
|
|
543
604
|
common.mergeObject(object, arg);
|
|
@@ -547,8 +608,8 @@ class Db {
|
|
|
547
608
|
if (object.$put && object.$put !== true) {
|
|
548
609
|
throw Error("pg_write.partial_put_unsupported");
|
|
549
610
|
}
|
|
550
|
-
object.$put ?
|
|
551
|
-
}
|
|
611
|
+
return object.$put ? put(object, arg, tableOptions) : patch(object, arg, tableOptions);
|
|
612
|
+
});
|
|
552
613
|
const result = [];
|
|
553
614
|
await Promise.all(sqls.map((sql2) => this.writeSql(sql2).then((object) => {
|
|
554
615
|
common.merge(result, common.encodeGraph(common.wrapObject(object, prefix)));
|
|
@@ -557,7 +618,7 @@ class Db {
|
|
|
557
618
|
return result;
|
|
558
619
|
}
|
|
559
620
|
}
|
|
560
|
-
const pg = ({ table, idCol, verCol,
|
|
621
|
+
const pg = ({ table, idCol, verCol, connection, schema }) => (store) => {
|
|
561
622
|
store.on("read", read);
|
|
562
623
|
store.on("write", write);
|
|
563
624
|
const prefix = store.path;
|
|
@@ -566,12 +627,13 @@ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
|
|
|
566
627
|
table: table || prefix[prefix.length - 1] || "default",
|
|
567
628
|
idCol: idCol || "id",
|
|
568
629
|
verCol: verCol || "updatedAt",
|
|
569
|
-
|
|
630
|
+
schema
|
|
570
631
|
};
|
|
571
632
|
const defaultDb = new Db(connection);
|
|
572
633
|
function read(query, options, next) {
|
|
573
|
-
const
|
|
574
|
-
const
|
|
634
|
+
const { pgClient } = options;
|
|
635
|
+
const db = pgClient ? new Db(pgClient) : defaultDb;
|
|
636
|
+
const readPromise = db.read(query, tableOpts);
|
|
575
637
|
const remainingQuery = common.remove(query, prefix);
|
|
576
638
|
const nextPromise = next(remainingQuery);
|
|
577
639
|
return Promise.all([readPromise, nextPromise]).then(([readRes, nextRes]) => {
|
|
@@ -579,8 +641,9 @@ const pg = ({ table, idCol, verCol, links, connection }) => (store) => {
|
|
|
579
641
|
});
|
|
580
642
|
}
|
|
581
643
|
function write(change, options, next) {
|
|
582
|
-
const
|
|
583
|
-
const
|
|
644
|
+
const { pgClient } = options;
|
|
645
|
+
const db = pgClient ? new Db(pgClient) : defaultDb;
|
|
646
|
+
const writePromise = db.write(change, tableOpts);
|
|
584
647
|
const remainingChange = common.remove(change, prefix);
|
|
585
648
|
const nextPromise = next(remainingChange);
|
|
586
649
|
return Promise.all([writePromise, nextPromise]).then(([writeRes, nextRes]) => {
|