@graffy/pg 0.16.20-alpha.11 → 0.16.20-alpha.12
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/index.cjs +117 -84
- package/index.mjs +118 -85
- package/package.json +3 -3
- package/types/sql/getArgSql.d.ts +1 -1
package/index.cjs
CHANGED
|
@@ -88,13 +88,16 @@ const getJsonBuildTrusted = (variadic) => {
|
|
|
88
88
|
return sql`jsonb_build_object(${args})`;
|
|
89
89
|
};
|
|
90
90
|
const getJsonBuildValue = (value) => {
|
|
91
|
-
if (value instanceof Sql)
|
|
92
|
-
|
|
91
|
+
if (value instanceof Sql)
|
|
92
|
+
return value;
|
|
93
|
+
if (typeof value === "string")
|
|
94
|
+
return sql`${value}::text`;
|
|
93
95
|
return sql`${JSON.stringify(stripAttributes(value))}::jsonb`;
|
|
94
96
|
};
|
|
95
97
|
const lookup = (prop, options) => {
|
|
96
98
|
const [prefix, ...suffix] = prop.split(".");
|
|
97
|
-
if (!suffix.length)
|
|
99
|
+
if (!suffix.length)
|
|
100
|
+
return sql`"${raw(prefix)}"`;
|
|
98
101
|
const { types: types2 } = options.schema;
|
|
99
102
|
if (types2[prefix] === "jsonb") {
|
|
100
103
|
return sql`"${raw(prefix)}" #> ${suffix}`;
|
|
@@ -117,7 +120,8 @@ const aggSql = {
|
|
|
117
120
|
$min: (prop) => sql`min((${lookupNumeric(prop)})::numeric)`
|
|
118
121
|
};
|
|
119
122
|
const getSelectCols = (options, projection = null) => {
|
|
120
|
-
if (!projection)
|
|
123
|
+
if (!projection)
|
|
124
|
+
return sql`*`;
|
|
121
125
|
const sqls = [];
|
|
122
126
|
for (const key in projection) {
|
|
123
127
|
if (key === "$count") {
|
|
@@ -151,14 +155,45 @@ function cubeLiteralSql(value) {
|
|
|
151
155
|
)})` : sql`cube(${vertexSql(value, 0)})`;
|
|
152
156
|
}
|
|
153
157
|
function castValue(value, type, name, isPut) {
|
|
154
|
-
if (!type)
|
|
155
|
-
|
|
156
|
-
if (value
|
|
158
|
+
if (!type)
|
|
159
|
+
throw Error(`pg.write_no_column ${name}`);
|
|
160
|
+
if (value instanceof Sql)
|
|
161
|
+
return value;
|
|
162
|
+
if (value === null)
|
|
163
|
+
return sql`NULL`;
|
|
157
164
|
if (type === "jsonb") {
|
|
158
|
-
return
|
|
165
|
+
return buildJsonPartial(value, isPut);
|
|
159
166
|
}
|
|
160
|
-
if (type === "cube")
|
|
161
|
-
|
|
167
|
+
if (type === "cube")
|
|
168
|
+
return cubeLiteralSql(value);
|
|
169
|
+
if (typeof value === "object" && value.$val) {
|
|
170
|
+
return sql`${JSON.stringify(stripAttributes(value))}::jsonb`;
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === "number")
|
|
173
|
+
return value;
|
|
174
|
+
if (typeof value === "boolean")
|
|
175
|
+
return value;
|
|
176
|
+
if (typeof value === "string")
|
|
177
|
+
return sql`${value}`;
|
|
178
|
+
if (Array.isArray(value))
|
|
179
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
180
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
181
|
+
}
|
|
182
|
+
function buildJsonPartial(value, isPut) {
|
|
183
|
+
if (!common.isPlainObject(value)) {
|
|
184
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
185
|
+
}
|
|
186
|
+
if (Object.keys(value).length === 0 && !isPut)
|
|
187
|
+
return sql`NULL`;
|
|
188
|
+
const parts = [];
|
|
189
|
+
for (const k of Object.keys(value)) {
|
|
190
|
+
parts.push(sql`${k}::text, ${buildJsonPartial(value[k], isPut)}`);
|
|
191
|
+
}
|
|
192
|
+
if (!parts.length && isPut)
|
|
193
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
194
|
+
const objSql = sql`jsonb_build_object(${join(parts, ", ")})`;
|
|
195
|
+
const filtered = sql`(select jsonb_object_agg(key, value) from jsonb_each(${objSql}) where value <> 'null'::jsonb)`;
|
|
196
|
+
return filtered;
|
|
162
197
|
}
|
|
163
198
|
const getInsert = (rows, options) => {
|
|
164
199
|
const { verCol, schema } = options;
|
|
@@ -177,7 +212,8 @@ const getInsert = (rows, options) => {
|
|
|
177
212
|
for (const row of rows) {
|
|
178
213
|
const rowVals = Array(cols.length).fill(sql`default`);
|
|
179
214
|
for (const col of cols) {
|
|
180
|
-
if (col === verCol || !(col in row))
|
|
215
|
+
if (col === verCol || !(col in row))
|
|
216
|
+
continue;
|
|
181
217
|
const ix = colIx[col];
|
|
182
218
|
colUsed[ix] = true;
|
|
183
219
|
rowVals[ix] = castValue(row[col], schema.types[col], col, row.$put);
|
|
@@ -210,53 +246,21 @@ const getUpdates = (row, options) => {
|
|
|
210
246
|
", "
|
|
211
247
|
);
|
|
212
248
|
};
|
|
213
|
-
function getJsonUpdate(object, col, path) {
|
|
214
|
-
if (!object || typeof object !== "object" || Array.isArray(object) || object.$put) {
|
|
215
|
-
const patch2 = stripAttributes(object);
|
|
216
|
-
return [sql`${JSON.stringify(patch2)}::jsonb`, patch2 === null];
|
|
217
|
-
}
|
|
218
|
-
if ("$val" in object) {
|
|
219
|
-
const value = object.$val === true ? stripAttributes(object) : object.$val;
|
|
220
|
-
return [sql`${JSON.stringify({ $val: value })}::jsonb`, false];
|
|
221
|
-
}
|
|
222
|
-
const curr = sql`"${raw(col)}"${path.length ? sql`#>${path}` : empty}`;
|
|
223
|
-
if (common.isEmpty(object)) return [curr, false];
|
|
224
|
-
const baseSql = sql`case jsonb_typeof(${curr})
|
|
225
|
-
when 'object' then ${curr}
|
|
226
|
-
else '{}'::jsonb
|
|
227
|
-
end`;
|
|
228
|
-
let maybeNull = true;
|
|
229
|
-
let hasNulls = false;
|
|
230
|
-
const patchSqls = Object.entries(object).map(([key, value]) => {
|
|
231
|
-
const [valSql, nullable] = getJsonUpdate(value, col, path.concat(key));
|
|
232
|
-
maybeNull && (maybeNull = nullable);
|
|
233
|
-
hasNulls || (hasNulls = nullable);
|
|
234
|
-
return sql`${key}::text, ${valSql}`;
|
|
235
|
-
});
|
|
236
|
-
let clause = sql`${baseSql} || jsonb_build_object(${join(patchSqls, ", ")})`;
|
|
237
|
-
if (hasNulls) {
|
|
238
|
-
clause = sql`(select jsonb_object_agg(key, value)
|
|
239
|
-
from jsonb_each(${clause}) where value <> 'null'::jsonb)`;
|
|
240
|
-
}
|
|
241
|
-
if (maybeNull) {
|
|
242
|
-
clause = sql`nullif(${clause}, '{}'::jsonb)`;
|
|
243
|
-
}
|
|
244
|
-
return [clause, maybeNull];
|
|
245
|
-
}
|
|
246
249
|
function stripAttributes(object) {
|
|
247
|
-
if (typeof object !== "object" || !object)
|
|
248
|
-
|
|
250
|
+
if (typeof object !== "object" || !object)
|
|
251
|
+
return object;
|
|
252
|
+
if (Array.isArray(object))
|
|
249
253
|
return object.map((item) => stripAttributes(item));
|
|
254
|
+
const res = {};
|
|
255
|
+
for (const k in object) {
|
|
256
|
+
if (k === "$put")
|
|
257
|
+
continue;
|
|
258
|
+
const val = stripAttributes(object[k]);
|
|
259
|
+
if (val === null)
|
|
260
|
+
continue;
|
|
261
|
+
res[k] = val;
|
|
250
262
|
}
|
|
251
|
-
return Object.
|
|
252
|
-
(out, [key, val]) => {
|
|
253
|
-
if (key === "$put" || val === null) return out;
|
|
254
|
-
if (out === null) out = {};
|
|
255
|
-
out[key] = stripAttributes(val);
|
|
256
|
-
return out;
|
|
257
|
-
},
|
|
258
|
-
null
|
|
259
|
-
);
|
|
263
|
+
return Object.keys(res).length ? res : null;
|
|
260
264
|
}
|
|
261
265
|
const valid = {
|
|
262
266
|
$eq: true,
|
|
@@ -291,21 +295,27 @@ function getAst(filter) {
|
|
|
291
295
|
return simplify(construct(filter));
|
|
292
296
|
}
|
|
293
297
|
function isValidSubQuery(node) {
|
|
294
|
-
if (!node || typeof node !== "object")
|
|
298
|
+
if (!node || typeof node !== "object")
|
|
299
|
+
return false;
|
|
295
300
|
const keys = Object.keys(node);
|
|
296
301
|
for (const key of keys) {
|
|
297
|
-
if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
|
|
298
|
-
|
|
302
|
+
if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
|
|
303
|
+
return false;
|
|
304
|
+
if (key[0] !== "$")
|
|
305
|
+
return true;
|
|
299
306
|
}
|
|
300
307
|
for (const key in node) {
|
|
301
|
-
if (!isValidSubQuery(node[key]))
|
|
308
|
+
if (!isValidSubQuery(node[key]))
|
|
309
|
+
return false;
|
|
302
310
|
}
|
|
303
311
|
return false;
|
|
304
312
|
}
|
|
305
313
|
function construct(node, prop, op) {
|
|
306
314
|
if (!node || typeof node !== "object" || prop && op) {
|
|
307
|
-
if (op && prop)
|
|
308
|
-
|
|
315
|
+
if (op && prop)
|
|
316
|
+
return [op, prop, node];
|
|
317
|
+
if (prop)
|
|
318
|
+
return ["$eq", prop, node];
|
|
309
319
|
throw Error(`pgast.expected_prop_before:${JSON.stringify(node)}`);
|
|
310
320
|
}
|
|
311
321
|
if (Array.isArray(node)) {
|
|
@@ -324,9 +334,12 @@ function construct(node, prop, op) {
|
|
|
324
334
|
return [key, construct(val, prop, op)];
|
|
325
335
|
}
|
|
326
336
|
if (key[0] === "$") {
|
|
327
|
-
if (!valid[key])
|
|
328
|
-
|
|
329
|
-
if (
|
|
337
|
+
if (!valid[key])
|
|
338
|
+
throw Error(`pgast.invalid_op:${key}`);
|
|
339
|
+
if (op)
|
|
340
|
+
throw Error(`pgast.unexpected_op:${op} before:${key}`);
|
|
341
|
+
if (!prop)
|
|
342
|
+
throw Error(`pgast.expected_prop_before:${key}`);
|
|
330
343
|
return construct(val, prop, key);
|
|
331
344
|
}
|
|
332
345
|
return construct(val, key);
|
|
@@ -343,12 +356,16 @@ function simplify(node) {
|
|
|
343
356
|
node[2] = simplify(node[2]);
|
|
344
357
|
}
|
|
345
358
|
if (op === "$and") {
|
|
346
|
-
if (!node[1].length)
|
|
347
|
-
|
|
359
|
+
if (!node[1].length)
|
|
360
|
+
return true;
|
|
361
|
+
if (node[1].includes(false))
|
|
362
|
+
return false;
|
|
348
363
|
node[1] = node[1].filter((item) => item !== true);
|
|
349
364
|
} else if (op === "$or") {
|
|
350
|
-
if (!node[1].length)
|
|
351
|
-
|
|
365
|
+
if (!node[1].length)
|
|
366
|
+
return false;
|
|
367
|
+
if (node[1].includes(true))
|
|
368
|
+
return true;
|
|
352
369
|
node[1] = node[1].filter((item) => item !== false);
|
|
353
370
|
} else if (op === "$not" && typeof node[1] === "boolean") {
|
|
354
371
|
return !node[1];
|
|
@@ -409,12 +426,16 @@ const opSql = {
|
|
|
409
426
|
$keyctd: sql`?&`
|
|
410
427
|
};
|
|
411
428
|
function getBinarySql(lhs, type, op, value, textLhs) {
|
|
412
|
-
if (value === null && op === "$eq")
|
|
413
|
-
|
|
429
|
+
if (value === null && op === "$eq")
|
|
430
|
+
return sql`${lhs} IS NULL`;
|
|
431
|
+
if (value === null && op === "$neq")
|
|
432
|
+
return sql`${lhs} IS NOT NULL`;
|
|
414
433
|
const sqlOp = opSql[op];
|
|
415
|
-
if (!sqlOp)
|
|
434
|
+
if (!sqlOp)
|
|
435
|
+
throw Error(`pg.getSql_unknown_operator ${op}`);
|
|
416
436
|
if (op === "$in" || op === "$nin") {
|
|
417
|
-
if (type === "jsonb" && typeof value[0] === "string")
|
|
437
|
+
if (type === "jsonb" && typeof value[0] === "string")
|
|
438
|
+
lhs = textLhs;
|
|
418
439
|
return sql`${lhs} ${sqlOp} (${join(value)})`;
|
|
419
440
|
}
|
|
420
441
|
if (op === "$re" || op === "$ire") {
|
|
@@ -433,11 +454,13 @@ function getBinarySql(lhs, type, op, value, textLhs) {
|
|
|
433
454
|
return sql`${lhs} ${sqlOp} ${value}::text[]`;
|
|
434
455
|
return sql`${lhs} ${sqlOp} ${JSON.stringify(value)}::jsonb`;
|
|
435
456
|
}
|
|
436
|
-
if (type === "cube")
|
|
457
|
+
if (type === "cube")
|
|
458
|
+
return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
|
|
437
459
|
return sql`${lhs} ${sqlOp} ${value}`;
|
|
438
460
|
}
|
|
439
461
|
function getNodeSql(ast, options) {
|
|
440
|
-
if (typeof ast === "boolean")
|
|
462
|
+
if (typeof ast === "boolean")
|
|
463
|
+
return ast;
|
|
441
464
|
const op = ast[0];
|
|
442
465
|
if (op === "$and" || op === "$or") {
|
|
443
466
|
return sql`(${join(
|
|
@@ -450,7 +473,8 @@ function getNodeSql(ast, options) {
|
|
|
450
473
|
}
|
|
451
474
|
if (op === "$sub") {
|
|
452
475
|
const joinName = ast[1];
|
|
453
|
-
if (!options.joins[joinName])
|
|
476
|
+
if (!options.joins[joinName])
|
|
477
|
+
throw Error(`pg.no_join ${joinName}`);
|
|
454
478
|
const { idCol, schema } = options;
|
|
455
479
|
const joinOptions = options.joins[joinName];
|
|
456
480
|
const { table: joinTable, refCol } = options.joins[joinName];
|
|
@@ -460,7 +484,8 @@ function getNodeSql(ast, options) {
|
|
|
460
484
|
}
|
|
461
485
|
const [prefix, ...suffix] = ast[1].split(".");
|
|
462
486
|
const { types: types2 = {} } = options.schema;
|
|
463
|
-
if (!types2[prefix])
|
|
487
|
+
if (!types2[prefix])
|
|
488
|
+
throw Error(`pg.no_column ${prefix}`);
|
|
464
489
|
if (types2[prefix] === "jsonb") {
|
|
465
490
|
const [lhs, textLhs] = suffix.length ? [
|
|
466
491
|
sql`"${raw(prefix)}" #> ${suffix}`,
|
|
@@ -468,7 +493,8 @@ function getNodeSql(ast, options) {
|
|
|
468
493
|
] : [sql`"${raw(prefix)}"`, sql`"${raw(prefix)}" #>> '{}'`];
|
|
469
494
|
return getBinarySql(lhs, "jsonb", op, ast[2], textLhs);
|
|
470
495
|
}
|
|
471
|
-
if (suffix.length)
|
|
496
|
+
if (suffix.length)
|
|
497
|
+
throw Error(`pg.lookup_not_jsonb ${prefix}`);
|
|
472
498
|
return getBinarySql(sql`"${raw(prefix)}"`, types2[prefix], op, ast[2]);
|
|
473
499
|
}
|
|
474
500
|
function getSql(filter, options) {
|
|
@@ -498,7 +524,8 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
|
|
|
498
524
|
}
|
|
499
525
|
const baseKey = sql`${JSON.stringify(rest)}::jsonb`;
|
|
500
526
|
const where = [];
|
|
501
|
-
if (!common.isEmpty(filter))
|
|
527
|
+
if (!common.isEmpty(filter))
|
|
528
|
+
where.push(getSql(filter, options));
|
|
502
529
|
if (!hasRangeArg)
|
|
503
530
|
return {
|
|
504
531
|
meta: meta(baseKey),
|
|
@@ -513,7 +540,8 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
|
|
|
513
540
|
);
|
|
514
541
|
Object.entries({ $after, $before, $since, $until }).forEach(
|
|
515
542
|
([name, value]) => {
|
|
516
|
-
if (value)
|
|
543
|
+
if (value)
|
|
544
|
+
where.push(getBoundCond(orderCols, value, name));
|
|
517
545
|
}
|
|
518
546
|
);
|
|
519
547
|
const order = !$group && join(
|
|
@@ -597,7 +625,8 @@ function getSingleSql(arg, options) {
|
|
|
597
625
|
};
|
|
598
626
|
}
|
|
599
627
|
const { where, meta } = getArgSql(arg, options);
|
|
600
|
-
if (!(where == null ? void 0 : where.length))
|
|
628
|
+
if (!(where == null ? void 0 : where.length))
|
|
629
|
+
throw Error("pg_write.no_condition");
|
|
601
630
|
return {
|
|
602
631
|
where: sql`"${raw(idCol)}" = (
|
|
603
632
|
SELECT "${raw(idCol)}"
|
|
@@ -718,7 +747,8 @@ class Db {
|
|
|
718
747
|
avoid this query in every operation.
|
|
719
748
|
*/
|
|
720
749
|
async ensureSchema(tableOptions, typeOids) {
|
|
721
|
-
if (tableOptions.schema)
|
|
750
|
+
if (tableOptions.schema)
|
|
751
|
+
return;
|
|
722
752
|
const { table, verCol, joins } = tableOptions;
|
|
723
753
|
const tableInfoSchema = (await this.query(sql`
|
|
724
754
|
SELECT table_schema, table_type
|
|
@@ -734,7 +764,8 @@ class Db {
|
|
|
734
764
|
WHERE
|
|
735
765
|
table_name = ${table} AND
|
|
736
766
|
table_schema = ${tableSchema}`)).rows[0].column_types;
|
|
737
|
-
if (!types2)
|
|
767
|
+
if (!types2)
|
|
768
|
+
throw Error(`pg.missing_table ${table}`);
|
|
738
769
|
typeOids = typeOids || (await this.query(sql`
|
|
739
770
|
SELECT jsonb_object_agg(typname, oid) AS type_oids
|
|
740
771
|
FROM pg_type
|
|
@@ -803,7 +834,8 @@ class Db {
|
|
|
803
834
|
idQueries[args] = node.children;
|
|
804
835
|
}
|
|
805
836
|
}
|
|
806
|
-
if (!common.isEmpty(idQueries))
|
|
837
|
+
if (!common.isEmpty(idQueries))
|
|
838
|
+
promises.push(getByIds());
|
|
807
839
|
await Promise.all(promises);
|
|
808
840
|
log("dbRead", rootQuery, results);
|
|
809
841
|
return common.finalize(results, common.wrap(query, prefix));
|
|
@@ -840,7 +872,8 @@ class Db {
|
|
|
840
872
|
sqls.push(patch(object, arg, tableOptions));
|
|
841
873
|
}
|
|
842
874
|
}
|
|
843
|
-
if (puts.length)
|
|
875
|
+
if (puts.length)
|
|
876
|
+
sqls.push(...put(puts, tableOptions));
|
|
844
877
|
const result = [];
|
|
845
878
|
await Promise.all(
|
|
846
879
|
sqls.map(
|
package/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isPlainObject, isEmpty, encodePath, unwrap, decodeArgs, decodeQuery, finalize, wrap, isRange, cmp, decodeGraph, mergeObject, wrapObject, merge, encodeGraph, remove } from "@graffy/common";
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import pg$1 from "pg";
|
|
4
4
|
class Sql {
|
|
@@ -86,13 +86,16 @@ const getJsonBuildTrusted = (variadic) => {
|
|
|
86
86
|
return sql`jsonb_build_object(${args})`;
|
|
87
87
|
};
|
|
88
88
|
const getJsonBuildValue = (value) => {
|
|
89
|
-
if (value instanceof Sql)
|
|
90
|
-
|
|
89
|
+
if (value instanceof Sql)
|
|
90
|
+
return value;
|
|
91
|
+
if (typeof value === "string")
|
|
92
|
+
return sql`${value}::text`;
|
|
91
93
|
return sql`${JSON.stringify(stripAttributes(value))}::jsonb`;
|
|
92
94
|
};
|
|
93
95
|
const lookup = (prop, options) => {
|
|
94
96
|
const [prefix, ...suffix] = prop.split(".");
|
|
95
|
-
if (!suffix.length)
|
|
97
|
+
if (!suffix.length)
|
|
98
|
+
return sql`"${raw(prefix)}"`;
|
|
96
99
|
const { types: types2 } = options.schema;
|
|
97
100
|
if (types2[prefix] === "jsonb") {
|
|
98
101
|
return sql`"${raw(prefix)}" #> ${suffix}`;
|
|
@@ -115,7 +118,8 @@ const aggSql = {
|
|
|
115
118
|
$min: (prop) => sql`min((${lookupNumeric(prop)})::numeric)`
|
|
116
119
|
};
|
|
117
120
|
const getSelectCols = (options, projection = null) => {
|
|
118
|
-
if (!projection)
|
|
121
|
+
if (!projection)
|
|
122
|
+
return sql`*`;
|
|
119
123
|
const sqls = [];
|
|
120
124
|
for (const key in projection) {
|
|
121
125
|
if (key === "$count") {
|
|
@@ -149,14 +153,45 @@ function cubeLiteralSql(value) {
|
|
|
149
153
|
)})` : sql`cube(${vertexSql(value, 0)})`;
|
|
150
154
|
}
|
|
151
155
|
function castValue(value, type, name, isPut) {
|
|
152
|
-
if (!type)
|
|
153
|
-
|
|
154
|
-
if (value
|
|
156
|
+
if (!type)
|
|
157
|
+
throw Error(`pg.write_no_column ${name}`);
|
|
158
|
+
if (value instanceof Sql)
|
|
159
|
+
return value;
|
|
160
|
+
if (value === null)
|
|
161
|
+
return sql`NULL`;
|
|
155
162
|
if (type === "jsonb") {
|
|
156
|
-
return
|
|
163
|
+
return buildJsonPartial(value, isPut);
|
|
157
164
|
}
|
|
158
|
-
if (type === "cube")
|
|
159
|
-
|
|
165
|
+
if (type === "cube")
|
|
166
|
+
return cubeLiteralSql(value);
|
|
167
|
+
if (typeof value === "object" && value.$val) {
|
|
168
|
+
return sql`${JSON.stringify(stripAttributes(value))}::jsonb`;
|
|
169
|
+
}
|
|
170
|
+
if (typeof value === "number")
|
|
171
|
+
return value;
|
|
172
|
+
if (typeof value === "boolean")
|
|
173
|
+
return value;
|
|
174
|
+
if (typeof value === "string")
|
|
175
|
+
return sql`${value}`;
|
|
176
|
+
if (Array.isArray(value))
|
|
177
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
178
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
179
|
+
}
|
|
180
|
+
function buildJsonPartial(value, isPut) {
|
|
181
|
+
if (!isPlainObject(value)) {
|
|
182
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
183
|
+
}
|
|
184
|
+
if (Object.keys(value).length === 0 && !isPut)
|
|
185
|
+
return sql`NULL`;
|
|
186
|
+
const parts = [];
|
|
187
|
+
for (const k of Object.keys(value)) {
|
|
188
|
+
parts.push(sql`${k}::text, ${buildJsonPartial(value[k], isPut)}`);
|
|
189
|
+
}
|
|
190
|
+
if (!parts.length && isPut)
|
|
191
|
+
return sql`${JSON.stringify(value)}::jsonb`;
|
|
192
|
+
const objSql = sql`jsonb_build_object(${join(parts, ", ")})`;
|
|
193
|
+
const filtered = sql`(select jsonb_object_agg(key, value) from jsonb_each(${objSql}) where value <> 'null'::jsonb)`;
|
|
194
|
+
return filtered;
|
|
160
195
|
}
|
|
161
196
|
const getInsert = (rows, options) => {
|
|
162
197
|
const { verCol, schema } = options;
|
|
@@ -175,7 +210,8 @@ const getInsert = (rows, options) => {
|
|
|
175
210
|
for (const row of rows) {
|
|
176
211
|
const rowVals = Array(cols.length).fill(sql`default`);
|
|
177
212
|
for (const col of cols) {
|
|
178
|
-
if (col === verCol || !(col in row))
|
|
213
|
+
if (col === verCol || !(col in row))
|
|
214
|
+
continue;
|
|
179
215
|
const ix = colIx[col];
|
|
180
216
|
colUsed[ix] = true;
|
|
181
217
|
rowVals[ix] = castValue(row[col], schema.types[col], col, row.$put);
|
|
@@ -208,53 +244,21 @@ const getUpdates = (row, options) => {
|
|
|
208
244
|
", "
|
|
209
245
|
);
|
|
210
246
|
};
|
|
211
|
-
function getJsonUpdate(object, col, path) {
|
|
212
|
-
if (!object || typeof object !== "object" || Array.isArray(object) || object.$put) {
|
|
213
|
-
const patch2 = stripAttributes(object);
|
|
214
|
-
return [sql`${JSON.stringify(patch2)}::jsonb`, patch2 === null];
|
|
215
|
-
}
|
|
216
|
-
if ("$val" in object) {
|
|
217
|
-
const value = object.$val === true ? stripAttributes(object) : object.$val;
|
|
218
|
-
return [sql`${JSON.stringify({ $val: value })}::jsonb`, false];
|
|
219
|
-
}
|
|
220
|
-
const curr = sql`"${raw(col)}"${path.length ? sql`#>${path}` : empty}`;
|
|
221
|
-
if (isEmpty(object)) return [curr, false];
|
|
222
|
-
const baseSql = sql`case jsonb_typeof(${curr})
|
|
223
|
-
when 'object' then ${curr}
|
|
224
|
-
else '{}'::jsonb
|
|
225
|
-
end`;
|
|
226
|
-
let maybeNull = true;
|
|
227
|
-
let hasNulls = false;
|
|
228
|
-
const patchSqls = Object.entries(object).map(([key, value]) => {
|
|
229
|
-
const [valSql, nullable] = getJsonUpdate(value, col, path.concat(key));
|
|
230
|
-
maybeNull && (maybeNull = nullable);
|
|
231
|
-
hasNulls || (hasNulls = nullable);
|
|
232
|
-
return sql`${key}::text, ${valSql}`;
|
|
233
|
-
});
|
|
234
|
-
let clause = sql`${baseSql} || jsonb_build_object(${join(patchSqls, ", ")})`;
|
|
235
|
-
if (hasNulls) {
|
|
236
|
-
clause = sql`(select jsonb_object_agg(key, value)
|
|
237
|
-
from jsonb_each(${clause}) where value <> 'null'::jsonb)`;
|
|
238
|
-
}
|
|
239
|
-
if (maybeNull) {
|
|
240
|
-
clause = sql`nullif(${clause}, '{}'::jsonb)`;
|
|
241
|
-
}
|
|
242
|
-
return [clause, maybeNull];
|
|
243
|
-
}
|
|
244
247
|
function stripAttributes(object) {
|
|
245
|
-
if (typeof object !== "object" || !object)
|
|
246
|
-
|
|
248
|
+
if (typeof object !== "object" || !object)
|
|
249
|
+
return object;
|
|
250
|
+
if (Array.isArray(object))
|
|
247
251
|
return object.map((item) => stripAttributes(item));
|
|
252
|
+
const res = {};
|
|
253
|
+
for (const k in object) {
|
|
254
|
+
if (k === "$put")
|
|
255
|
+
continue;
|
|
256
|
+
const val = stripAttributes(object[k]);
|
|
257
|
+
if (val === null)
|
|
258
|
+
continue;
|
|
259
|
+
res[k] = val;
|
|
248
260
|
}
|
|
249
|
-
return Object.
|
|
250
|
-
(out, [key, val]) => {
|
|
251
|
-
if (key === "$put" || val === null) return out;
|
|
252
|
-
if (out === null) out = {};
|
|
253
|
-
out[key] = stripAttributes(val);
|
|
254
|
-
return out;
|
|
255
|
-
},
|
|
256
|
-
null
|
|
257
|
-
);
|
|
261
|
+
return Object.keys(res).length ? res : null;
|
|
258
262
|
}
|
|
259
263
|
const valid = {
|
|
260
264
|
$eq: true,
|
|
@@ -289,21 +293,27 @@ function getAst(filter) {
|
|
|
289
293
|
return simplify(construct(filter));
|
|
290
294
|
}
|
|
291
295
|
function isValidSubQuery(node) {
|
|
292
|
-
if (!node || typeof node !== "object")
|
|
296
|
+
if (!node || typeof node !== "object")
|
|
297
|
+
return false;
|
|
293
298
|
const keys = Object.keys(node);
|
|
294
299
|
for (const key of keys) {
|
|
295
|
-
if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
|
|
296
|
-
|
|
300
|
+
if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
|
|
301
|
+
return false;
|
|
302
|
+
if (key[0] !== "$")
|
|
303
|
+
return true;
|
|
297
304
|
}
|
|
298
305
|
for (const key in node) {
|
|
299
|
-
if (!isValidSubQuery(node[key]))
|
|
306
|
+
if (!isValidSubQuery(node[key]))
|
|
307
|
+
return false;
|
|
300
308
|
}
|
|
301
309
|
return false;
|
|
302
310
|
}
|
|
303
311
|
function construct(node, prop, op) {
|
|
304
312
|
if (!node || typeof node !== "object" || prop && op) {
|
|
305
|
-
if (op && prop)
|
|
306
|
-
|
|
313
|
+
if (op && prop)
|
|
314
|
+
return [op, prop, node];
|
|
315
|
+
if (prop)
|
|
316
|
+
return ["$eq", prop, node];
|
|
307
317
|
throw Error(`pgast.expected_prop_before:${JSON.stringify(node)}`);
|
|
308
318
|
}
|
|
309
319
|
if (Array.isArray(node)) {
|
|
@@ -322,9 +332,12 @@ function construct(node, prop, op) {
|
|
|
322
332
|
return [key, construct(val, prop, op)];
|
|
323
333
|
}
|
|
324
334
|
if (key[0] === "$") {
|
|
325
|
-
if (!valid[key])
|
|
326
|
-
|
|
327
|
-
if (
|
|
335
|
+
if (!valid[key])
|
|
336
|
+
throw Error(`pgast.invalid_op:${key}`);
|
|
337
|
+
if (op)
|
|
338
|
+
throw Error(`pgast.unexpected_op:${op} before:${key}`);
|
|
339
|
+
if (!prop)
|
|
340
|
+
throw Error(`pgast.expected_prop_before:${key}`);
|
|
328
341
|
return construct(val, prop, key);
|
|
329
342
|
}
|
|
330
343
|
return construct(val, key);
|
|
@@ -341,12 +354,16 @@ function simplify(node) {
|
|
|
341
354
|
node[2] = simplify(node[2]);
|
|
342
355
|
}
|
|
343
356
|
if (op === "$and") {
|
|
344
|
-
if (!node[1].length)
|
|
345
|
-
|
|
357
|
+
if (!node[1].length)
|
|
358
|
+
return true;
|
|
359
|
+
if (node[1].includes(false))
|
|
360
|
+
return false;
|
|
346
361
|
node[1] = node[1].filter((item) => item !== true);
|
|
347
362
|
} else if (op === "$or") {
|
|
348
|
-
if (!node[1].length)
|
|
349
|
-
|
|
363
|
+
if (!node[1].length)
|
|
364
|
+
return false;
|
|
365
|
+
if (node[1].includes(true))
|
|
366
|
+
return true;
|
|
350
367
|
node[1] = node[1].filter((item) => item !== false);
|
|
351
368
|
} else if (op === "$not" && typeof node[1] === "boolean") {
|
|
352
369
|
return !node[1];
|
|
@@ -407,12 +424,16 @@ const opSql = {
|
|
|
407
424
|
$keyctd: sql`?&`
|
|
408
425
|
};
|
|
409
426
|
function getBinarySql(lhs, type, op, value, textLhs) {
|
|
410
|
-
if (value === null && op === "$eq")
|
|
411
|
-
|
|
427
|
+
if (value === null && op === "$eq")
|
|
428
|
+
return sql`${lhs} IS NULL`;
|
|
429
|
+
if (value === null && op === "$neq")
|
|
430
|
+
return sql`${lhs} IS NOT NULL`;
|
|
412
431
|
const sqlOp = opSql[op];
|
|
413
|
-
if (!sqlOp)
|
|
432
|
+
if (!sqlOp)
|
|
433
|
+
throw Error(`pg.getSql_unknown_operator ${op}`);
|
|
414
434
|
if (op === "$in" || op === "$nin") {
|
|
415
|
-
if (type === "jsonb" && typeof value[0] === "string")
|
|
435
|
+
if (type === "jsonb" && typeof value[0] === "string")
|
|
436
|
+
lhs = textLhs;
|
|
416
437
|
return sql`${lhs} ${sqlOp} (${join(value)})`;
|
|
417
438
|
}
|
|
418
439
|
if (op === "$re" || op === "$ire") {
|
|
@@ -431,11 +452,13 @@ function getBinarySql(lhs, type, op, value, textLhs) {
|
|
|
431
452
|
return sql`${lhs} ${sqlOp} ${value}::text[]`;
|
|
432
453
|
return sql`${lhs} ${sqlOp} ${JSON.stringify(value)}::jsonb`;
|
|
433
454
|
}
|
|
434
|
-
if (type === "cube")
|
|
455
|
+
if (type === "cube")
|
|
456
|
+
return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
|
|
435
457
|
return sql`${lhs} ${sqlOp} ${value}`;
|
|
436
458
|
}
|
|
437
459
|
function getNodeSql(ast, options) {
|
|
438
|
-
if (typeof ast === "boolean")
|
|
460
|
+
if (typeof ast === "boolean")
|
|
461
|
+
return ast;
|
|
439
462
|
const op = ast[0];
|
|
440
463
|
if (op === "$and" || op === "$or") {
|
|
441
464
|
return sql`(${join(
|
|
@@ -448,7 +471,8 @@ function getNodeSql(ast, options) {
|
|
|
448
471
|
}
|
|
449
472
|
if (op === "$sub") {
|
|
450
473
|
const joinName = ast[1];
|
|
451
|
-
if (!options.joins[joinName])
|
|
474
|
+
if (!options.joins[joinName])
|
|
475
|
+
throw Error(`pg.no_join ${joinName}`);
|
|
452
476
|
const { idCol, schema } = options;
|
|
453
477
|
const joinOptions = options.joins[joinName];
|
|
454
478
|
const { table: joinTable, refCol } = options.joins[joinName];
|
|
@@ -458,7 +482,8 @@ function getNodeSql(ast, options) {
|
|
|
458
482
|
}
|
|
459
483
|
const [prefix, ...suffix] = ast[1].split(".");
|
|
460
484
|
const { types: types2 = {} } = options.schema;
|
|
461
|
-
if (!types2[prefix])
|
|
485
|
+
if (!types2[prefix])
|
|
486
|
+
throw Error(`pg.no_column ${prefix}`);
|
|
462
487
|
if (types2[prefix] === "jsonb") {
|
|
463
488
|
const [lhs, textLhs] = suffix.length ? [
|
|
464
489
|
sql`"${raw(prefix)}" #> ${suffix}`,
|
|
@@ -466,7 +491,8 @@ function getNodeSql(ast, options) {
|
|
|
466
491
|
] : [sql`"${raw(prefix)}"`, sql`"${raw(prefix)}" #>> '{}'`];
|
|
467
492
|
return getBinarySql(lhs, "jsonb", op, ast[2], textLhs);
|
|
468
493
|
}
|
|
469
|
-
if (suffix.length)
|
|
494
|
+
if (suffix.length)
|
|
495
|
+
throw Error(`pg.lookup_not_jsonb ${prefix}`);
|
|
470
496
|
return getBinarySql(sql`"${raw(prefix)}"`, types2[prefix], op, ast[2]);
|
|
471
497
|
}
|
|
472
498
|
function getSql(filter, options) {
|
|
@@ -496,7 +522,8 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
|
|
|
496
522
|
}
|
|
497
523
|
const baseKey = sql`${JSON.stringify(rest)}::jsonb`;
|
|
498
524
|
const where = [];
|
|
499
|
-
if (!isEmpty(filter))
|
|
525
|
+
if (!isEmpty(filter))
|
|
526
|
+
where.push(getSql(filter, options));
|
|
500
527
|
if (!hasRangeArg)
|
|
501
528
|
return {
|
|
502
529
|
meta: meta(baseKey),
|
|
@@ -511,7 +538,8 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
|
|
|
511
538
|
);
|
|
512
539
|
Object.entries({ $after, $before, $since, $until }).forEach(
|
|
513
540
|
([name, value]) => {
|
|
514
|
-
if (value)
|
|
541
|
+
if (value)
|
|
542
|
+
where.push(getBoundCond(orderCols, value, name));
|
|
515
543
|
}
|
|
516
544
|
);
|
|
517
545
|
const order = !$group && join(
|
|
@@ -595,7 +623,8 @@ function getSingleSql(arg, options) {
|
|
|
595
623
|
};
|
|
596
624
|
}
|
|
597
625
|
const { where, meta } = getArgSql(arg, options);
|
|
598
|
-
if (!(where == null ? void 0 : where.length))
|
|
626
|
+
if (!(where == null ? void 0 : where.length))
|
|
627
|
+
throw Error("pg_write.no_condition");
|
|
599
628
|
return {
|
|
600
629
|
where: sql`"${raw(idCol)}" = (
|
|
601
630
|
SELECT "${raw(idCol)}"
|
|
@@ -716,7 +745,8 @@ class Db {
|
|
|
716
745
|
avoid this query in every operation.
|
|
717
746
|
*/
|
|
718
747
|
async ensureSchema(tableOptions, typeOids) {
|
|
719
|
-
if (tableOptions.schema)
|
|
748
|
+
if (tableOptions.schema)
|
|
749
|
+
return;
|
|
720
750
|
const { table, verCol, joins } = tableOptions;
|
|
721
751
|
const tableInfoSchema = (await this.query(sql`
|
|
722
752
|
SELECT table_schema, table_type
|
|
@@ -732,7 +762,8 @@ class Db {
|
|
|
732
762
|
WHERE
|
|
733
763
|
table_name = ${table} AND
|
|
734
764
|
table_schema = ${tableSchema}`)).rows[0].column_types;
|
|
735
|
-
if (!types2)
|
|
765
|
+
if (!types2)
|
|
766
|
+
throw Error(`pg.missing_table ${table}`);
|
|
736
767
|
typeOids = typeOids || (await this.query(sql`
|
|
737
768
|
SELECT jsonb_object_agg(typname, oid) AS type_oids
|
|
738
769
|
FROM pg_type
|
|
@@ -801,7 +832,8 @@ class Db {
|
|
|
801
832
|
idQueries[args] = node.children;
|
|
802
833
|
}
|
|
803
834
|
}
|
|
804
|
-
if (!isEmpty(idQueries))
|
|
835
|
+
if (!isEmpty(idQueries))
|
|
836
|
+
promises.push(getByIds());
|
|
805
837
|
await Promise.all(promises);
|
|
806
838
|
log("dbRead", rootQuery, results);
|
|
807
839
|
return finalize(results, wrap(query, prefix));
|
|
@@ -838,7 +870,8 @@ class Db {
|
|
|
838
870
|
sqls.push(patch(object, arg, tableOptions));
|
|
839
871
|
}
|
|
840
872
|
}
|
|
841
|
-
if (puts.length)
|
|
873
|
+
if (puts.length)
|
|
874
|
+
sqls.push(...put(puts, tableOptions));
|
|
842
875
|
const result = [];
|
|
843
876
|
await Promise.all(
|
|
844
877
|
sqls.map(
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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.16.20-alpha.
|
|
5
|
+
"version": "0.16.20-alpha.12",
|
|
6
6
|
"main": "./index.cjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
"import": "./index.mjs",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@graffy/common": "0.16.20-alpha.
|
|
20
|
-
"debug": "^4.3.
|
|
19
|
+
"@graffy/common": "0.16.20-alpha.12",
|
|
20
|
+
"debug": "^4.3.3"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"pg": "^8.0.0"
|
package/types/sql/getArgSql.d.ts
CHANGED