@graffy/pg 0.16.20-alpha.12 → 0.16.20-alpha.13

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 CHANGED
@@ -88,16 +88,13 @@ const getJsonBuildTrusted = (variadic) => {
88
88
  return sql`jsonb_build_object(${args})`;
89
89
  };
90
90
  const getJsonBuildValue = (value) => {
91
- if (value instanceof Sql)
92
- return value;
93
- if (typeof value === "string")
94
- return sql`${value}::text`;
91
+ if (value instanceof Sql) return value;
92
+ if (typeof value === "string") return sql`${value}::text`;
95
93
  return sql`${JSON.stringify(stripAttributes(value))}::jsonb`;
96
94
  };
97
95
  const lookup = (prop, options) => {
98
96
  const [prefix, ...suffix] = prop.split(".");
99
- if (!suffix.length)
100
- return sql`"${raw(prefix)}"`;
97
+ if (!suffix.length) return sql`"${raw(prefix)}"`;
101
98
  const { types: types2 } = options.schema;
102
99
  if (types2[prefix] === "jsonb") {
103
100
  return sql`"${raw(prefix)}" #> ${suffix}`;
@@ -119,9 +116,25 @@ const aggSql = {
119
116
  $max: (prop) => sql`max((${lookupNumeric(prop)})::numeric)`,
120
117
  $min: (prop) => sql`min((${lookupNumeric(prop)})::numeric)`
121
118
  };
119
+ const getOptimisedJsonBuild = (object, path = [], parentPropertyName, options) => {
120
+ const propertyNames = Object.keys(object);
121
+ const propertyPath = [...path, parentPropertyName];
122
+ const buildConfig = [];
123
+ propertyNames.forEach((propertyName) => {
124
+ const property = object[propertyName];
125
+ if (typeof property === "object") {
126
+ const childJSONBuild = getOptimisedJsonBuild(property, propertyPath, propertyName, options);
127
+ buildConfig.push(sql`${propertyName}::text, jsonb_build_object(${join(childJSONBuild, ", ")})`);
128
+ } else {
129
+ if (property === true) {
130
+ buildConfig.push(sql`${propertyName}::text, ${lookup([...propertyPath, propertyName].join("."), options)}`);
131
+ }
132
+ }
133
+ });
134
+ return buildConfig;
135
+ };
122
136
  const getSelectCols = (options, projection = null) => {
123
- if (!projection)
124
- return sql`*`;
137
+ if (!projection) return sql`*`;
125
138
  const sqls = [];
126
139
  for (const key in projection) {
127
140
  if (key === "$count") {
@@ -135,7 +148,12 @@ const getSelectCols = (options, projection = null) => {
135
148
  sql`jsonb_build_object(${join(subSqls, ", ")}) AS "${raw(key)}"`
136
149
  );
137
150
  } else {
138
- sqls.push(sql`"${raw(key)}"`);
151
+ if (typeof projection[key] === "object") {
152
+ const optimisedJsonBuild = getOptimisedJsonBuild(projection[key], [], key, options);
153
+ sqls.push(sql`jsonb_build_object(${join(optimisedJsonBuild, ", ")}) AS "${raw(key)}"`);
154
+ } else {
155
+ sqls.push(sql`"${raw(key)}"`);
156
+ }
139
157
  }
140
158
  }
141
159
  return join(sqls, ", ");
@@ -155,45 +173,14 @@ function cubeLiteralSql(value) {
155
173
  )})` : sql`cube(${vertexSql(value, 0)})`;
156
174
  }
157
175
  function castValue(value, type, name, isPut) {
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`;
176
+ if (!type) throw Error(`pg.write_no_column ${name}`);
177
+ if (value instanceof Sql) return value;
178
+ if (value === null) return sql`NULL`;
164
179
  if (type === "jsonb") {
165
- return buildJsonPartial(value, isPut);
180
+ return isPut ? JSON.stringify(stripAttributes(value)) : getJsonUpdate(value, name, [])[0];
166
181
  }
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;
182
+ if (type === "cube") return cubeLiteralSql(value);
183
+ return value;
197
184
  }
198
185
  const getInsert = (rows, options) => {
199
186
  const { verCol, schema } = options;
@@ -212,8 +199,7 @@ const getInsert = (rows, options) => {
212
199
  for (const row of rows) {
213
200
  const rowVals = Array(cols.length).fill(sql`default`);
214
201
  for (const col of cols) {
215
- if (col === verCol || !(col in row))
216
- continue;
202
+ if (col === verCol || !(col in row)) continue;
217
203
  const ix = colIx[col];
218
204
  colUsed[ix] = true;
219
205
  rowVals[ix] = castValue(row[col], schema.types[col], col, row.$put);
@@ -246,21 +232,53 @@ const getUpdates = (row, options) => {
246
232
  ", "
247
233
  );
248
234
  };
235
+ function getJsonUpdate(object, col, path) {
236
+ if (!object || typeof object !== "object" || Array.isArray(object) || object.$put) {
237
+ const patch2 = stripAttributes(object);
238
+ return [sql`${JSON.stringify(patch2)}::jsonb`, patch2 === null];
239
+ }
240
+ if ("$val" in object) {
241
+ const value = object.$val === true ? stripAttributes(object) : object.$val;
242
+ return [sql`${JSON.stringify({ $val: value })}::jsonb`, false];
243
+ }
244
+ const curr = sql`"${raw(col)}"${path.length ? sql`#>${path}` : empty}`;
245
+ if (common.isEmpty(object)) return [curr, false];
246
+ const baseSql = sql`case jsonb_typeof(${curr})
247
+ when 'object' then ${curr}
248
+ else '{}'::jsonb
249
+ end`;
250
+ let maybeNull = true;
251
+ let hasNulls = false;
252
+ const patchSqls = Object.entries(object).map(([key, value]) => {
253
+ const [valSql, nullable] = getJsonUpdate(value, col, path.concat(key));
254
+ maybeNull && (maybeNull = nullable);
255
+ hasNulls || (hasNulls = nullable);
256
+ return sql`${key}::text, ${valSql}`;
257
+ });
258
+ let clause = sql`${baseSql} || jsonb_build_object(${join(patchSqls, ", ")})`;
259
+ if (hasNulls) {
260
+ clause = sql`(select jsonb_object_agg(key, value)
261
+ from jsonb_each(${clause}) where value <> 'null'::jsonb)`;
262
+ }
263
+ if (maybeNull) {
264
+ clause = sql`nullif(${clause}, '{}'::jsonb)`;
265
+ }
266
+ return [clause, maybeNull];
267
+ }
249
268
  function stripAttributes(object) {
250
- if (typeof object !== "object" || !object)
251
- return object;
252
- if (Array.isArray(object))
269
+ if (typeof object !== "object" || !object) return object;
270
+ if (Array.isArray(object)) {
253
271
  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;
262
272
  }
263
- return Object.keys(res).length ? res : null;
273
+ return Object.entries(object).reduce(
274
+ (out, [key, val]) => {
275
+ if (key === "$put" || val === null) return out;
276
+ if (out === null) out = {};
277
+ out[key] = stripAttributes(val);
278
+ return out;
279
+ },
280
+ null
281
+ );
264
282
  }
265
283
  const valid = {
266
284
  $eq: true,
@@ -295,27 +313,21 @@ function getAst(filter) {
295
313
  return simplify(construct(filter));
296
314
  }
297
315
  function isValidSubQuery(node) {
298
- if (!node || typeof node !== "object")
299
- return false;
316
+ if (!node || typeof node !== "object") return false;
300
317
  const keys = Object.keys(node);
301
318
  for (const key of keys) {
302
- if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
303
- return false;
304
- if (key[0] !== "$")
305
- return true;
319
+ if (key[0] === "$" && !["$and", "$or", "$not"].includes(key)) return false;
320
+ if (key[0] !== "$") return true;
306
321
  }
307
322
  for (const key in node) {
308
- if (!isValidSubQuery(node[key]))
309
- return false;
323
+ if (!isValidSubQuery(node[key])) return false;
310
324
  }
311
325
  return false;
312
326
  }
313
327
  function construct(node, prop, op) {
314
328
  if (!node || typeof node !== "object" || prop && op) {
315
- if (op && prop)
316
- return [op, prop, node];
317
- if (prop)
318
- return ["$eq", prop, node];
329
+ if (op && prop) return [op, prop, node];
330
+ if (prop) return ["$eq", prop, node];
319
331
  throw Error(`pgast.expected_prop_before:${JSON.stringify(node)}`);
320
332
  }
321
333
  if (Array.isArray(node)) {
@@ -334,12 +346,9 @@ function construct(node, prop, op) {
334
346
  return [key, construct(val, prop, op)];
335
347
  }
336
348
  if (key[0] === "$") {
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}`);
349
+ if (!valid[key]) throw Error(`pgast.invalid_op:${key}`);
350
+ if (op) throw Error(`pgast.unexpected_op:${op} before:${key}`);
351
+ if (!prop) throw Error(`pgast.expected_prop_before:${key}`);
343
352
  return construct(val, prop, key);
344
353
  }
345
354
  return construct(val, key);
@@ -356,16 +365,12 @@ function simplify(node) {
356
365
  node[2] = simplify(node[2]);
357
366
  }
358
367
  if (op === "$and") {
359
- if (!node[1].length)
360
- return true;
361
- if (node[1].includes(false))
362
- return false;
368
+ if (!node[1].length) return true;
369
+ if (node[1].includes(false)) return false;
363
370
  node[1] = node[1].filter((item) => item !== true);
364
371
  } else if (op === "$or") {
365
- if (!node[1].length)
366
- return false;
367
- if (node[1].includes(true))
368
- return true;
372
+ if (!node[1].length) return false;
373
+ if (node[1].includes(true)) return true;
369
374
  node[1] = node[1].filter((item) => item !== false);
370
375
  } else if (op === "$not" && typeof node[1] === "boolean") {
371
376
  return !node[1];
@@ -426,16 +431,12 @@ const opSql = {
426
431
  $keyctd: sql`?&`
427
432
  };
428
433
  function getBinarySql(lhs, type, op, value, textLhs) {
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`;
434
+ if (value === null && op === "$eq") return sql`${lhs} IS NULL`;
435
+ if (value === null && op === "$neq") return sql`${lhs} IS NOT NULL`;
433
436
  const sqlOp = opSql[op];
434
- if (!sqlOp)
435
- throw Error(`pg.getSql_unknown_operator ${op}`);
437
+ if (!sqlOp) throw Error(`pg.getSql_unknown_operator ${op}`);
436
438
  if (op === "$in" || op === "$nin") {
437
- if (type === "jsonb" && typeof value[0] === "string")
438
- lhs = textLhs;
439
+ if (type === "jsonb" && typeof value[0] === "string") lhs = textLhs;
439
440
  return sql`${lhs} ${sqlOp} (${join(value)})`;
440
441
  }
441
442
  if (op === "$re" || op === "$ire") {
@@ -454,13 +455,11 @@ function getBinarySql(lhs, type, op, value, textLhs) {
454
455
  return sql`${lhs} ${sqlOp} ${value}::text[]`;
455
456
  return sql`${lhs} ${sqlOp} ${JSON.stringify(value)}::jsonb`;
456
457
  }
457
- if (type === "cube")
458
- return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
458
+ if (type === "cube") return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
459
459
  return sql`${lhs} ${sqlOp} ${value}`;
460
460
  }
461
461
  function getNodeSql(ast, options) {
462
- if (typeof ast === "boolean")
463
- return ast;
462
+ if (typeof ast === "boolean") return ast;
464
463
  const op = ast[0];
465
464
  if (op === "$and" || op === "$or") {
466
465
  return sql`(${join(
@@ -473,8 +472,7 @@ function getNodeSql(ast, options) {
473
472
  }
474
473
  if (op === "$sub") {
475
474
  const joinName = ast[1];
476
- if (!options.joins[joinName])
477
- throw Error(`pg.no_join ${joinName}`);
475
+ if (!options.joins[joinName]) throw Error(`pg.no_join ${joinName}`);
478
476
  const { idCol, schema } = options;
479
477
  const joinOptions = options.joins[joinName];
480
478
  const { table: joinTable, refCol } = options.joins[joinName];
@@ -484,8 +482,7 @@ function getNodeSql(ast, options) {
484
482
  }
485
483
  const [prefix, ...suffix] = ast[1].split(".");
486
484
  const { types: types2 = {} } = options.schema;
487
- if (!types2[prefix])
488
- throw Error(`pg.no_column ${prefix}`);
485
+ if (!types2[prefix]) throw Error(`pg.no_column ${prefix}`);
489
486
  if (types2[prefix] === "jsonb") {
490
487
  const [lhs, textLhs] = suffix.length ? [
491
488
  sql`"${raw(prefix)}" #> ${suffix}`,
@@ -493,8 +490,7 @@ function getNodeSql(ast, options) {
493
490
  ] : [sql`"${raw(prefix)}"`, sql`"${raw(prefix)}" #>> '{}'`];
494
491
  return getBinarySql(lhs, "jsonb", op, ast[2], textLhs);
495
492
  }
496
- if (suffix.length)
497
- throw Error(`pg.lookup_not_jsonb ${prefix}`);
493
+ if (suffix.length) throw Error(`pg.lookup_not_jsonb ${prefix}`);
498
494
  return getBinarySql(sql`"${raw(prefix)}"`, types2[prefix], op, ast[2]);
499
495
  }
500
496
  function getSql(filter, options) {
@@ -524,8 +520,7 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
524
520
  }
525
521
  const baseKey = sql`${JSON.stringify(rest)}::jsonb`;
526
522
  const where = [];
527
- if (!common.isEmpty(filter))
528
- where.push(getSql(filter, options));
523
+ if (!common.isEmpty(filter)) where.push(getSql(filter, options));
529
524
  if (!hasRangeArg)
530
525
  return {
531
526
  meta: meta(baseKey),
@@ -540,8 +535,7 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
540
535
  );
541
536
  Object.entries({ $after, $before, $since, $until }).forEach(
542
537
  ([name, value]) => {
543
- if (value)
544
- where.push(getBoundCond(orderCols, value, name));
538
+ if (value) where.push(getBoundCond(orderCols, value, name));
545
539
  }
546
540
  );
547
541
  const order = !$group && join(
@@ -625,8 +619,7 @@ function getSingleSql(arg, options) {
625
619
  };
626
620
  }
627
621
  const { where, meta } = getArgSql(arg, options);
628
- if (!(where == null ? void 0 : where.length))
629
- throw Error("pg_write.no_condition");
622
+ if (!(where == null ? void 0 : where.length)) throw Error("pg_write.no_condition");
630
623
  return {
631
624
  where: sql`"${raw(idCol)}" = (
632
625
  SELECT "${raw(idCol)}"
@@ -747,8 +740,7 @@ class Db {
747
740
  avoid this query in every operation.
748
741
  */
749
742
  async ensureSchema(tableOptions, typeOids) {
750
- if (tableOptions.schema)
751
- return;
743
+ if (tableOptions.schema) return;
752
744
  const { table, verCol, joins } = tableOptions;
753
745
  const tableInfoSchema = (await this.query(sql`
754
746
  SELECT table_schema, table_type
@@ -764,8 +756,7 @@ class Db {
764
756
  WHERE
765
757
  table_name = ${table} AND
766
758
  table_schema = ${tableSchema}`)).rows[0].column_types;
767
- if (!types2)
768
- throw Error(`pg.missing_table ${table}`);
759
+ if (!types2) throw Error(`pg.missing_table ${table}`);
769
760
  typeOids = typeOids || (await this.query(sql`
770
761
  SELECT jsonb_object_agg(typname, oid) AS type_oids
771
762
  FROM pg_type
@@ -834,8 +825,7 @@ class Db {
834
825
  idQueries[args] = node.children;
835
826
  }
836
827
  }
837
- if (!common.isEmpty(idQueries))
838
- promises.push(getByIds());
828
+ if (!common.isEmpty(idQueries)) promises.push(getByIds());
839
829
  await Promise.all(promises);
840
830
  log("dbRead", rootQuery, results);
841
831
  return common.finalize(results, common.wrap(query, prefix));
@@ -872,8 +862,7 @@ class Db {
872
862
  sqls.push(patch(object, arg, tableOptions));
873
863
  }
874
864
  }
875
- if (puts.length)
876
- sqls.push(...put(puts, tableOptions));
865
+ if (puts.length) sqls.push(...put(puts, tableOptions));
877
866
  const result = [];
878
867
  await Promise.all(
879
868
  sqls.map(
package/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { isPlainObject, isEmpty, encodePath, unwrap, decodeArgs, decodeQuery, finalize, wrap, isRange, cmp, decodeGraph, mergeObject, wrapObject, merge, encodeGraph, remove } from "@graffy/common";
1
+ import { isEmpty, isPlainObject, 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,16 +86,13 @@ const getJsonBuildTrusted = (variadic) => {
86
86
  return sql`jsonb_build_object(${args})`;
87
87
  };
88
88
  const getJsonBuildValue = (value) => {
89
- if (value instanceof Sql)
90
- return value;
91
- if (typeof value === "string")
92
- return sql`${value}::text`;
89
+ if (value instanceof Sql) return value;
90
+ if (typeof value === "string") return sql`${value}::text`;
93
91
  return sql`${JSON.stringify(stripAttributes(value))}::jsonb`;
94
92
  };
95
93
  const lookup = (prop, options) => {
96
94
  const [prefix, ...suffix] = prop.split(".");
97
- if (!suffix.length)
98
- return sql`"${raw(prefix)}"`;
95
+ if (!suffix.length) return sql`"${raw(prefix)}"`;
99
96
  const { types: types2 } = options.schema;
100
97
  if (types2[prefix] === "jsonb") {
101
98
  return sql`"${raw(prefix)}" #> ${suffix}`;
@@ -117,9 +114,25 @@ const aggSql = {
117
114
  $max: (prop) => sql`max((${lookupNumeric(prop)})::numeric)`,
118
115
  $min: (prop) => sql`min((${lookupNumeric(prop)})::numeric)`
119
116
  };
117
+ const getOptimisedJsonBuild = (object, path = [], parentPropertyName, options) => {
118
+ const propertyNames = Object.keys(object);
119
+ const propertyPath = [...path, parentPropertyName];
120
+ const buildConfig = [];
121
+ propertyNames.forEach((propertyName) => {
122
+ const property = object[propertyName];
123
+ if (typeof property === "object") {
124
+ const childJSONBuild = getOptimisedJsonBuild(property, propertyPath, propertyName, options);
125
+ buildConfig.push(sql`${propertyName}::text, jsonb_build_object(${join(childJSONBuild, ", ")})`);
126
+ } else {
127
+ if (property === true) {
128
+ buildConfig.push(sql`${propertyName}::text, ${lookup([...propertyPath, propertyName].join("."), options)}`);
129
+ }
130
+ }
131
+ });
132
+ return buildConfig;
133
+ };
120
134
  const getSelectCols = (options, projection = null) => {
121
- if (!projection)
122
- return sql`*`;
135
+ if (!projection) return sql`*`;
123
136
  const sqls = [];
124
137
  for (const key in projection) {
125
138
  if (key === "$count") {
@@ -133,7 +146,12 @@ const getSelectCols = (options, projection = null) => {
133
146
  sql`jsonb_build_object(${join(subSqls, ", ")}) AS "${raw(key)}"`
134
147
  );
135
148
  } else {
136
- sqls.push(sql`"${raw(key)}"`);
149
+ if (typeof projection[key] === "object") {
150
+ const optimisedJsonBuild = getOptimisedJsonBuild(projection[key], [], key, options);
151
+ sqls.push(sql`jsonb_build_object(${join(optimisedJsonBuild, ", ")}) AS "${raw(key)}"`);
152
+ } else {
153
+ sqls.push(sql`"${raw(key)}"`);
154
+ }
137
155
  }
138
156
  }
139
157
  return join(sqls, ", ");
@@ -153,45 +171,14 @@ function cubeLiteralSql(value) {
153
171
  )})` : sql`cube(${vertexSql(value, 0)})`;
154
172
  }
155
173
  function castValue(value, type, name, isPut) {
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`;
174
+ if (!type) throw Error(`pg.write_no_column ${name}`);
175
+ if (value instanceof Sql) return value;
176
+ if (value === null) return sql`NULL`;
162
177
  if (type === "jsonb") {
163
- return buildJsonPartial(value, isPut);
178
+ return isPut ? JSON.stringify(stripAttributes(value)) : getJsonUpdate(value, name, [])[0];
164
179
  }
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;
180
+ if (type === "cube") return cubeLiteralSql(value);
181
+ return value;
195
182
  }
196
183
  const getInsert = (rows, options) => {
197
184
  const { verCol, schema } = options;
@@ -210,8 +197,7 @@ const getInsert = (rows, options) => {
210
197
  for (const row of rows) {
211
198
  const rowVals = Array(cols.length).fill(sql`default`);
212
199
  for (const col of cols) {
213
- if (col === verCol || !(col in row))
214
- continue;
200
+ if (col === verCol || !(col in row)) continue;
215
201
  const ix = colIx[col];
216
202
  colUsed[ix] = true;
217
203
  rowVals[ix] = castValue(row[col], schema.types[col], col, row.$put);
@@ -244,21 +230,53 @@ const getUpdates = (row, options) => {
244
230
  ", "
245
231
  );
246
232
  };
233
+ function getJsonUpdate(object, col, path) {
234
+ if (!object || typeof object !== "object" || Array.isArray(object) || object.$put) {
235
+ const patch2 = stripAttributes(object);
236
+ return [sql`${JSON.stringify(patch2)}::jsonb`, patch2 === null];
237
+ }
238
+ if ("$val" in object) {
239
+ const value = object.$val === true ? stripAttributes(object) : object.$val;
240
+ return [sql`${JSON.stringify({ $val: value })}::jsonb`, false];
241
+ }
242
+ const curr = sql`"${raw(col)}"${path.length ? sql`#>${path}` : empty}`;
243
+ if (isEmpty(object)) return [curr, false];
244
+ const baseSql = sql`case jsonb_typeof(${curr})
245
+ when 'object' then ${curr}
246
+ else '{}'::jsonb
247
+ end`;
248
+ let maybeNull = true;
249
+ let hasNulls = false;
250
+ const patchSqls = Object.entries(object).map(([key, value]) => {
251
+ const [valSql, nullable] = getJsonUpdate(value, col, path.concat(key));
252
+ maybeNull && (maybeNull = nullable);
253
+ hasNulls || (hasNulls = nullable);
254
+ return sql`${key}::text, ${valSql}`;
255
+ });
256
+ let clause = sql`${baseSql} || jsonb_build_object(${join(patchSqls, ", ")})`;
257
+ if (hasNulls) {
258
+ clause = sql`(select jsonb_object_agg(key, value)
259
+ from jsonb_each(${clause}) where value <> 'null'::jsonb)`;
260
+ }
261
+ if (maybeNull) {
262
+ clause = sql`nullif(${clause}, '{}'::jsonb)`;
263
+ }
264
+ return [clause, maybeNull];
265
+ }
247
266
  function stripAttributes(object) {
248
- if (typeof object !== "object" || !object)
249
- return object;
250
- if (Array.isArray(object))
267
+ if (typeof object !== "object" || !object) return object;
268
+ if (Array.isArray(object)) {
251
269
  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;
260
270
  }
261
- return Object.keys(res).length ? res : null;
271
+ return Object.entries(object).reduce(
272
+ (out, [key, val]) => {
273
+ if (key === "$put" || val === null) return out;
274
+ if (out === null) out = {};
275
+ out[key] = stripAttributes(val);
276
+ return out;
277
+ },
278
+ null
279
+ );
262
280
  }
263
281
  const valid = {
264
282
  $eq: true,
@@ -293,27 +311,21 @@ function getAst(filter) {
293
311
  return simplify(construct(filter));
294
312
  }
295
313
  function isValidSubQuery(node) {
296
- if (!node || typeof node !== "object")
297
- return false;
314
+ if (!node || typeof node !== "object") return false;
298
315
  const keys = Object.keys(node);
299
316
  for (const key of keys) {
300
- if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
301
- return false;
302
- if (key[0] !== "$")
303
- return true;
317
+ if (key[0] === "$" && !["$and", "$or", "$not"].includes(key)) return false;
318
+ if (key[0] !== "$") return true;
304
319
  }
305
320
  for (const key in node) {
306
- if (!isValidSubQuery(node[key]))
307
- return false;
321
+ if (!isValidSubQuery(node[key])) return false;
308
322
  }
309
323
  return false;
310
324
  }
311
325
  function construct(node, prop, op) {
312
326
  if (!node || typeof node !== "object" || prop && op) {
313
- if (op && prop)
314
- return [op, prop, node];
315
- if (prop)
316
- return ["$eq", prop, node];
327
+ if (op && prop) return [op, prop, node];
328
+ if (prop) return ["$eq", prop, node];
317
329
  throw Error(`pgast.expected_prop_before:${JSON.stringify(node)}`);
318
330
  }
319
331
  if (Array.isArray(node)) {
@@ -332,12 +344,9 @@ function construct(node, prop, op) {
332
344
  return [key, construct(val, prop, op)];
333
345
  }
334
346
  if (key[0] === "$") {
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}`);
347
+ if (!valid[key]) throw Error(`pgast.invalid_op:${key}`);
348
+ if (op) throw Error(`pgast.unexpected_op:${op} before:${key}`);
349
+ if (!prop) throw Error(`pgast.expected_prop_before:${key}`);
341
350
  return construct(val, prop, key);
342
351
  }
343
352
  return construct(val, key);
@@ -354,16 +363,12 @@ function simplify(node) {
354
363
  node[2] = simplify(node[2]);
355
364
  }
356
365
  if (op === "$and") {
357
- if (!node[1].length)
358
- return true;
359
- if (node[1].includes(false))
360
- return false;
366
+ if (!node[1].length) return true;
367
+ if (node[1].includes(false)) return false;
361
368
  node[1] = node[1].filter((item) => item !== true);
362
369
  } else if (op === "$or") {
363
- if (!node[1].length)
364
- return false;
365
- if (node[1].includes(true))
366
- return true;
370
+ if (!node[1].length) return false;
371
+ if (node[1].includes(true)) return true;
367
372
  node[1] = node[1].filter((item) => item !== false);
368
373
  } else if (op === "$not" && typeof node[1] === "boolean") {
369
374
  return !node[1];
@@ -424,16 +429,12 @@ const opSql = {
424
429
  $keyctd: sql`?&`
425
430
  };
426
431
  function getBinarySql(lhs, type, op, value, textLhs) {
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`;
432
+ if (value === null && op === "$eq") return sql`${lhs} IS NULL`;
433
+ if (value === null && op === "$neq") return sql`${lhs} IS NOT NULL`;
431
434
  const sqlOp = opSql[op];
432
- if (!sqlOp)
433
- throw Error(`pg.getSql_unknown_operator ${op}`);
435
+ if (!sqlOp) throw Error(`pg.getSql_unknown_operator ${op}`);
434
436
  if (op === "$in" || op === "$nin") {
435
- if (type === "jsonb" && typeof value[0] === "string")
436
- lhs = textLhs;
437
+ if (type === "jsonb" && typeof value[0] === "string") lhs = textLhs;
437
438
  return sql`${lhs} ${sqlOp} (${join(value)})`;
438
439
  }
439
440
  if (op === "$re" || op === "$ire") {
@@ -452,13 +453,11 @@ function getBinarySql(lhs, type, op, value, textLhs) {
452
453
  return sql`${lhs} ${sqlOp} ${value}::text[]`;
453
454
  return sql`${lhs} ${sqlOp} ${JSON.stringify(value)}::jsonb`;
454
455
  }
455
- if (type === "cube")
456
- return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
456
+ if (type === "cube") return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
457
457
  return sql`${lhs} ${sqlOp} ${value}`;
458
458
  }
459
459
  function getNodeSql(ast, options) {
460
- if (typeof ast === "boolean")
461
- return ast;
460
+ if (typeof ast === "boolean") return ast;
462
461
  const op = ast[0];
463
462
  if (op === "$and" || op === "$or") {
464
463
  return sql`(${join(
@@ -471,8 +470,7 @@ function getNodeSql(ast, options) {
471
470
  }
472
471
  if (op === "$sub") {
473
472
  const joinName = ast[1];
474
- if (!options.joins[joinName])
475
- throw Error(`pg.no_join ${joinName}`);
473
+ if (!options.joins[joinName]) throw Error(`pg.no_join ${joinName}`);
476
474
  const { idCol, schema } = options;
477
475
  const joinOptions = options.joins[joinName];
478
476
  const { table: joinTable, refCol } = options.joins[joinName];
@@ -482,8 +480,7 @@ function getNodeSql(ast, options) {
482
480
  }
483
481
  const [prefix, ...suffix] = ast[1].split(".");
484
482
  const { types: types2 = {} } = options.schema;
485
- if (!types2[prefix])
486
- throw Error(`pg.no_column ${prefix}`);
483
+ if (!types2[prefix]) throw Error(`pg.no_column ${prefix}`);
487
484
  if (types2[prefix] === "jsonb") {
488
485
  const [lhs, textLhs] = suffix.length ? [
489
486
  sql`"${raw(prefix)}" #> ${suffix}`,
@@ -491,8 +488,7 @@ function getNodeSql(ast, options) {
491
488
  ] : [sql`"${raw(prefix)}"`, sql`"${raw(prefix)}" #>> '{}'`];
492
489
  return getBinarySql(lhs, "jsonb", op, ast[2], textLhs);
493
490
  }
494
- if (suffix.length)
495
- throw Error(`pg.lookup_not_jsonb ${prefix}`);
491
+ if (suffix.length) throw Error(`pg.lookup_not_jsonb ${prefix}`);
496
492
  return getBinarySql(sql`"${raw(prefix)}"`, types2[prefix], op, ast[2]);
497
493
  }
498
494
  function getSql(filter, options) {
@@ -522,8 +518,7 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
522
518
  }
523
519
  const baseKey = sql`${JSON.stringify(rest)}::jsonb`;
524
520
  const where = [];
525
- if (!isEmpty(filter))
526
- where.push(getSql(filter, options));
521
+ if (!isEmpty(filter)) where.push(getSql(filter, options));
527
522
  if (!hasRangeArg)
528
523
  return {
529
524
  meta: meta(baseKey),
@@ -538,8 +533,7 @@ function getArgSql({ $first, $last, $after, $before, $since, $until, $all, $curs
538
533
  );
539
534
  Object.entries({ $after, $before, $since, $until }).forEach(
540
535
  ([name, value]) => {
541
- if (value)
542
- where.push(getBoundCond(orderCols, value, name));
536
+ if (value) where.push(getBoundCond(orderCols, value, name));
543
537
  }
544
538
  );
545
539
  const order = !$group && join(
@@ -623,8 +617,7 @@ function getSingleSql(arg, options) {
623
617
  };
624
618
  }
625
619
  const { where, meta } = getArgSql(arg, options);
626
- if (!(where == null ? void 0 : where.length))
627
- throw Error("pg_write.no_condition");
620
+ if (!(where == null ? void 0 : where.length)) throw Error("pg_write.no_condition");
628
621
  return {
629
622
  where: sql`"${raw(idCol)}" = (
630
623
  SELECT "${raw(idCol)}"
@@ -745,8 +738,7 @@ class Db {
745
738
  avoid this query in every operation.
746
739
  */
747
740
  async ensureSchema(tableOptions, typeOids) {
748
- if (tableOptions.schema)
749
- return;
741
+ if (tableOptions.schema) return;
750
742
  const { table, verCol, joins } = tableOptions;
751
743
  const tableInfoSchema = (await this.query(sql`
752
744
  SELECT table_schema, table_type
@@ -762,8 +754,7 @@ class Db {
762
754
  WHERE
763
755
  table_name = ${table} AND
764
756
  table_schema = ${tableSchema}`)).rows[0].column_types;
765
- if (!types2)
766
- throw Error(`pg.missing_table ${table}`);
757
+ if (!types2) throw Error(`pg.missing_table ${table}`);
767
758
  typeOids = typeOids || (await this.query(sql`
768
759
  SELECT jsonb_object_agg(typname, oid) AS type_oids
769
760
  FROM pg_type
@@ -832,8 +823,7 @@ class Db {
832
823
  idQueries[args] = node.children;
833
824
  }
834
825
  }
835
- if (!isEmpty(idQueries))
836
- promises.push(getByIds());
826
+ if (!isEmpty(idQueries)) promises.push(getByIds());
837
827
  await Promise.all(promises);
838
828
  log("dbRead", rootQuery, results);
839
829
  return finalize(results, wrap(query, prefix));
@@ -870,8 +860,7 @@ class Db {
870
860
  sqls.push(patch(object, arg, tableOptions));
871
861
  }
872
862
  }
873
- if (puts.length)
874
- sqls.push(...put(puts, tableOptions));
863
+ if (puts.length) sqls.push(...put(puts, tableOptions));
875
864
  const result = [];
876
865
  await Promise.all(
877
866
  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.12",
5
+ "version": "0.16.20-alpha.13",
6
6
  "main": "./index.cjs",
7
7
  "exports": {
8
8
  "import": "./index.mjs",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "license": "Apache-2.0",
18
18
  "dependencies": {
19
- "@graffy/common": "0.16.20-alpha.12",
19
+ "@graffy/common": "0.16.20-alpha.13",
20
20
  "debug": "^4.3.3"
21
21
  },
22
22
  "peerDependencies": {
package/types/Db.d.ts CHANGED
@@ -6,8 +6,8 @@ export default class Db {
6
6
  writeSql(sql: any, tableOptions: any): Promise<any>;
7
7
  ensureSchema(tableOptions: any, typeOids: any): Promise<void>;
8
8
  read(rootQuery: any, tableOptions: any): Promise<{
9
- key: Uint8Array;
10
- end: Uint8Array;
9
+ key: Uint8Array<ArrayBuffer>;
10
+ end: Uint8Array<ArrayBuffer>;
11
11
  version: number;
12
12
  }[]>;
13
13
  write(rootChange: any, tableOptions: any): Promise<any[]>;
@@ -22,4 +22,4 @@ export default function getArgSql({ $first, $last, $after, $before, $since, $unt
22
22
  /**
23
23
  * Uses the args object (typically passed in the $key attribute)
24
24
  */
25
- export type Sql = import('sql-template-tag').Sql;
25
+ export type Sql = import("sql-template-tag").Sql;