@graffy/pg 0.16.7 → 0.16.9-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/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const common = require("@graffy/common");
4
- const pg$1 = require("pg");
5
4
  const debug = require("debug");
5
+ const pg$1 = require("pg");
6
6
  class Sql {
7
7
  constructor(rawStrings, rawValues) {
8
8
  if (rawStrings.length - 1 !== rawValues.length) {
@@ -98,6 +98,22 @@ const inverse = {
98
98
  function getAst(filter) {
99
99
  return simplify(construct(filter));
100
100
  }
101
+ function isValidSubQuery(node) {
102
+ if (!node || typeof node !== "object")
103
+ return false;
104
+ const keys = Object.keys(node);
105
+ for (const key of keys) {
106
+ if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
107
+ return false;
108
+ if (key[0] !== "$")
109
+ return true;
110
+ }
111
+ for (const key in node) {
112
+ if (!isValidSubQuery(node[key]))
113
+ return false;
114
+ }
115
+ return false;
116
+ }
101
117
  function construct(node, prop, op) {
102
118
  if (!node || typeof node !== "object" || prop && op) {
103
119
  if (op && prop)
@@ -109,6 +125,9 @@ function construct(node, prop, op) {
109
125
  if (Array.isArray(node)) {
110
126
  return ["$or", node.map((item) => construct(item, prop, op))];
111
127
  }
128
+ if (prop && isValidSubQuery(node)) {
129
+ return ["$sub", prop, construct(node)];
130
+ }
112
131
  return [
113
132
  "$and",
114
133
  Object.entries(node).map(([key, val]) => {
@@ -127,9 +146,6 @@ function construct(node, prop, op) {
127
146
  throw Error(`pgast.expected_prop_before:${key}`);
128
147
  return construct(val, prop, key);
129
148
  }
130
- if (prop) {
131
- return ["$sub", prop, construct({ [key]: val })];
132
- }
133
149
  return construct(val, key);
134
150
  })
135
151
  ];
@@ -282,14 +298,44 @@ function castValue(value, type, name, isPut) {
282
298
  return cubeLiteralSql(value);
283
299
  return value;
284
300
  }
285
- const getInsert = (row, options) => {
301
+ const getInsert = (rows, options) => {
302
+ const { verCol, schema } = options;
286
303
  const cols = [];
304
+ const colSqls = [];
305
+ const colIx = {};
306
+ const colUsed = [];
307
+ for (const col of Object.keys(options.schema.types)) {
308
+ colIx[col] = cols.length;
309
+ colUsed[cols.length] = false;
310
+ cols.push(col);
311
+ colSqls.push(sql`"${raw(col)}"`);
312
+ }
313
+ colUsed[colIx[verCol]] = true;
287
314
  const vals = [];
288
- Object.entries(row).filter(([col]) => col !== options.verCol && col[0] !== "$").concat([[options.verCol, sql`default`]]).forEach(([col, val]) => {
289
- cols.push(sql`"${raw(col)}"`);
290
- vals.push(castValue(val, options.schema.types[col], col, row.$put));
291
- });
292
- return { cols: join(cols, ", "), vals: join(vals, ", ") };
315
+ for (const row of rows) {
316
+ const rowVals = Array(cols.length).fill(null);
317
+ rowVals[colIx[verCol]] = sql`default`;
318
+ for (const col of cols) {
319
+ if (col === verCol || !(col in row))
320
+ continue;
321
+ const ix = colIx[col];
322
+ colUsed[ix] = true;
323
+ rowVals[ix] = castValue(row[col], schema.types[col], col, row.$put);
324
+ }
325
+ vals.push(rowVals);
326
+ }
327
+ const isUsed = (_, ix) => colUsed[ix];
328
+ return {
329
+ cols: join(colSqls.filter(isUsed), ", "),
330
+ vals: join(
331
+ vals.map((rowVals) => sql`(${join(rowVals.filter(isUsed), ", ")})`),
332
+ ", "
333
+ ),
334
+ updates: join(
335
+ colSqls.map((col, ix) => sql`${col} = "excluded".${col}`).filter(isUsed),
336
+ ", "
337
+ )
338
+ };
293
339
  };
294
340
  const getUpdates = (row, options) => {
295
341
  return join(
@@ -579,23 +625,35 @@ function patch(object, arg, options) {
579
625
  WHERE ${where}
580
626
  RETURNING ${getSelectCols(options)}, ${meta}`;
581
627
  }
582
- function put(object, arg, options) {
628
+ function put(puts, options) {
583
629
  const { idCol, table } = options;
584
- const row = object;
585
- let meta;
586
- let conflictTarget;
587
- if (common.isPlainObject(arg)) {
588
- ({ meta } = getArgSql(arg, options));
589
- conflictTarget = join(Object.keys(arg).map((col) => sql`"${raw(col)}"`));
590
- } else {
591
- meta = getIdMeta(options);
592
- conflictTarget = sql`"${raw(idCol)}"`;
630
+ const sqls = [];
631
+ const addSql = (rows, meta, conflictTarget) => {
632
+ const { cols, vals, updates } = getInsert(rows, options);
633
+ sqls.push(sql`
634
+ INSERT INTO "${raw(table)}" (${cols}) VALUES ${vals}
635
+ ON CONFLICT (${conflictTarget}) DO UPDATE SET ${updates}
636
+ RETURNING ${getSelectCols(options)}, ${meta}`);
637
+ };
638
+ const idRows = [];
639
+ for (const put2 of puts) {
640
+ const [row, arg] = put2;
641
+ if (!common.isPlainObject(arg)) {
642
+ idRows.push(row);
643
+ continue;
644
+ }
645
+ const { meta } = getArgSql(arg, options);
646
+ const conflictTarget = join(
647
+ Object.keys(arg).map((col) => sql`"${raw(col)}"`)
648
+ );
649
+ addSql([row], meta, conflictTarget);
593
650
  }
594
- const { cols, vals } = getInsert(row, options);
595
- return sql`
596
- INSERT INTO "${raw(table)}" (${cols}) VALUES (${vals})
597
- ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
598
- RETURNING ${getSelectCols(options)}, ${meta}`;
651
+ if (idRows.length) {
652
+ const meta = getIdMeta(options);
653
+ const conflictTarget = sql`"${raw(idCol)}"`;
654
+ addSql(idRows, meta, conflictTarget);
655
+ }
656
+ return sqls;
599
657
  }
600
658
  function del(arg, options) {
601
659
  const { table } = options;
@@ -660,7 +718,7 @@ class Db {
660
718
  if (!res.rowCount) {
661
719
  throw Error(`pg.nothing_written ${sql2.text} with ${sql2.values}`);
662
720
  }
663
- return res.rows[0];
721
+ return res.rows;
664
722
  }
665
723
  async ensureSchema(tableOptions, typeOids) {
666
724
  if (tableOptions.schema)
@@ -724,11 +782,11 @@ class Db {
724
782
  selectByIds(Object.keys(idQueries), null, tableOptions),
725
783
  tableOptions
726
784
  );
727
- result.forEach((object) => {
785
+ for (const object of result) {
728
786
  const wrappedGraph = common.encodeGraph(common.wrapObject(object, rawPrefix));
729
787
  log("getByIds", wrappedGraph);
730
788
  common.merge(results, wrappedGraph);
731
- });
789
+ }
732
790
  };
733
791
  const query = common.unwrap(rootQuery, prefix);
734
792
  for (const node of query) {
@@ -759,28 +817,40 @@ class Db {
759
817
  const prefix = common.encodePath(rawPrefix);
760
818
  await this.ensureSchema(tableOptions);
761
819
  const change = common.unwrap(rootChange, prefix);
762
- const sqls = change.map((node) => {
820
+ const puts = [];
821
+ const sqls = [];
822
+ for (const node of change) {
763
823
  const arg = common.decodeArgs(node);
764
824
  if (common.isRange(node)) {
765
- if (common.cmp(node.key, node.end) === 0)
766
- return del(arg, tableOptions);
825
+ if (common.cmp(node.key, node.end) === 0) {
826
+ log("delete", node);
827
+ sqls.push(del(arg, tableOptions));
828
+ continue;
829
+ }
767
830
  throw Error("pg_write.write_range_unsupported");
768
831
  }
769
832
  const object = common.decodeGraph(node.children);
770
833
  if (common.isPlainObject(arg)) {
771
834
  common.mergeObject(object, arg);
772
835
  } else {
773
- object.id = arg;
836
+ object[tableOptions.idCol] = arg;
774
837
  }
775
838
  if (object.$put && object.$put !== true) {
776
839
  throw Error("pg_write.partial_put_unsupported");
777
840
  }
778
- return object.$put ? put(object, arg, tableOptions) : patch(object, arg, tableOptions);
779
- });
841
+ if (object.$put) {
842
+ puts.push([object, arg]);
843
+ } else {
844
+ sqls.push(patch(object, arg, tableOptions));
845
+ }
846
+ }
847
+ if (puts.length)
848
+ sqls.push(...put(puts, tableOptions));
780
849
  const result = [];
781
850
  await Promise.all(
782
851
  sqls.map(
783
852
  (sql2) => this.writeSql(sql2, tableOptions).then((object) => {
853
+ log("returned_object_wrapped", common.wrapObject(object, rawPrefix));
784
854
  common.merge(result, common.encodeGraph(common.wrapObject(object, rawPrefix)));
785
855
  })
786
856
  )
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { isEmpty, isPlainObject, encodePath, unwrap, decodeArgs, decodeQuery, finalize, wrap, isRange, cmp, decodeGraph, mergeObject, merge, encodeGraph, wrapObject, remove } from "@graffy/common";
2
- import pg$1 from "pg";
1
+ import { isEmpty, isPlainObject, encodePath, unwrap, decodeArgs, decodeQuery, finalize, wrap, isRange, cmp, decodeGraph, mergeObject, wrapObject, merge, encodeGraph, remove } from "@graffy/common";
3
2
  import debug from "debug";
3
+ import pg$1 from "pg";
4
4
  class Sql {
5
5
  constructor(rawStrings, rawValues) {
6
6
  if (rawStrings.length - 1 !== rawValues.length) {
@@ -96,6 +96,22 @@ const inverse = {
96
96
  function getAst(filter) {
97
97
  return simplify(construct(filter));
98
98
  }
99
+ function isValidSubQuery(node) {
100
+ if (!node || typeof node !== "object")
101
+ return false;
102
+ const keys = Object.keys(node);
103
+ for (const key of keys) {
104
+ if (key[0] === "$" && !["$and", "$or", "$not"].includes(key))
105
+ return false;
106
+ if (key[0] !== "$")
107
+ return true;
108
+ }
109
+ for (const key in node) {
110
+ if (!isValidSubQuery(node[key]))
111
+ return false;
112
+ }
113
+ return false;
114
+ }
99
115
  function construct(node, prop, op) {
100
116
  if (!node || typeof node !== "object" || prop && op) {
101
117
  if (op && prop)
@@ -107,6 +123,9 @@ function construct(node, prop, op) {
107
123
  if (Array.isArray(node)) {
108
124
  return ["$or", node.map((item) => construct(item, prop, op))];
109
125
  }
126
+ if (prop && isValidSubQuery(node)) {
127
+ return ["$sub", prop, construct(node)];
128
+ }
110
129
  return [
111
130
  "$and",
112
131
  Object.entries(node).map(([key, val]) => {
@@ -125,9 +144,6 @@ function construct(node, prop, op) {
125
144
  throw Error(`pgast.expected_prop_before:${key}`);
126
145
  return construct(val, prop, key);
127
146
  }
128
- if (prop) {
129
- return ["$sub", prop, construct({ [key]: val })];
130
- }
131
147
  return construct(val, key);
132
148
  })
133
149
  ];
@@ -280,14 +296,44 @@ function castValue(value, type, name, isPut) {
280
296
  return cubeLiteralSql(value);
281
297
  return value;
282
298
  }
283
- const getInsert = (row, options) => {
299
+ const getInsert = (rows, options) => {
300
+ const { verCol, schema } = options;
284
301
  const cols = [];
302
+ const colSqls = [];
303
+ const colIx = {};
304
+ const colUsed = [];
305
+ for (const col of Object.keys(options.schema.types)) {
306
+ colIx[col] = cols.length;
307
+ colUsed[cols.length] = false;
308
+ cols.push(col);
309
+ colSqls.push(sql`"${raw(col)}"`);
310
+ }
311
+ colUsed[colIx[verCol]] = true;
285
312
  const vals = [];
286
- Object.entries(row).filter(([col]) => col !== options.verCol && col[0] !== "$").concat([[options.verCol, sql`default`]]).forEach(([col, val]) => {
287
- cols.push(sql`"${raw(col)}"`);
288
- vals.push(castValue(val, options.schema.types[col], col, row.$put));
289
- });
290
- return { cols: join(cols, ", "), vals: join(vals, ", ") };
313
+ for (const row of rows) {
314
+ const rowVals = Array(cols.length).fill(null);
315
+ rowVals[colIx[verCol]] = sql`default`;
316
+ for (const col of cols) {
317
+ if (col === verCol || !(col in row))
318
+ continue;
319
+ const ix = colIx[col];
320
+ colUsed[ix] = true;
321
+ rowVals[ix] = castValue(row[col], schema.types[col], col, row.$put);
322
+ }
323
+ vals.push(rowVals);
324
+ }
325
+ const isUsed = (_, ix) => colUsed[ix];
326
+ return {
327
+ cols: join(colSqls.filter(isUsed), ", "),
328
+ vals: join(
329
+ vals.map((rowVals) => sql`(${join(rowVals.filter(isUsed), ", ")})`),
330
+ ", "
331
+ ),
332
+ updates: join(
333
+ colSqls.map((col, ix) => sql`${col} = "excluded".${col}`).filter(isUsed),
334
+ ", "
335
+ )
336
+ };
291
337
  };
292
338
  const getUpdates = (row, options) => {
293
339
  return join(
@@ -577,23 +623,35 @@ function patch(object, arg, options) {
577
623
  WHERE ${where}
578
624
  RETURNING ${getSelectCols(options)}, ${meta}`;
579
625
  }
580
- function put(object, arg, options) {
626
+ function put(puts, options) {
581
627
  const { idCol, table } = options;
582
- const row = object;
583
- let meta;
584
- let conflictTarget;
585
- if (isPlainObject(arg)) {
586
- ({ meta } = getArgSql(arg, options));
587
- conflictTarget = join(Object.keys(arg).map((col) => sql`"${raw(col)}"`));
588
- } else {
589
- meta = getIdMeta(options);
590
- conflictTarget = sql`"${raw(idCol)}"`;
628
+ const sqls = [];
629
+ const addSql = (rows, meta, conflictTarget) => {
630
+ const { cols, vals, updates } = getInsert(rows, options);
631
+ sqls.push(sql`
632
+ INSERT INTO "${raw(table)}" (${cols}) VALUES ${vals}
633
+ ON CONFLICT (${conflictTarget}) DO UPDATE SET ${updates}
634
+ RETURNING ${getSelectCols(options)}, ${meta}`);
635
+ };
636
+ const idRows = [];
637
+ for (const put2 of puts) {
638
+ const [row, arg] = put2;
639
+ if (!isPlainObject(arg)) {
640
+ idRows.push(row);
641
+ continue;
642
+ }
643
+ const { meta } = getArgSql(arg, options);
644
+ const conflictTarget = join(
645
+ Object.keys(arg).map((col) => sql`"${raw(col)}"`)
646
+ );
647
+ addSql([row], meta, conflictTarget);
591
648
  }
592
- const { cols, vals } = getInsert(row, options);
593
- return sql`
594
- INSERT INTO "${raw(table)}" (${cols}) VALUES (${vals})
595
- ON CONFLICT (${conflictTarget}) DO UPDATE SET (${cols}) = (${vals})
596
- RETURNING ${getSelectCols(options)}, ${meta}`;
649
+ if (idRows.length) {
650
+ const meta = getIdMeta(options);
651
+ const conflictTarget = sql`"${raw(idCol)}"`;
652
+ addSql(idRows, meta, conflictTarget);
653
+ }
654
+ return sqls;
597
655
  }
598
656
  function del(arg, options) {
599
657
  const { table } = options;
@@ -658,7 +716,7 @@ class Db {
658
716
  if (!res.rowCount) {
659
717
  throw Error(`pg.nothing_written ${sql2.text} with ${sql2.values}`);
660
718
  }
661
- return res.rows[0];
719
+ return res.rows;
662
720
  }
663
721
  async ensureSchema(tableOptions, typeOids) {
664
722
  if (tableOptions.schema)
@@ -722,11 +780,11 @@ class Db {
722
780
  selectByIds(Object.keys(idQueries), null, tableOptions),
723
781
  tableOptions
724
782
  );
725
- result.forEach((object) => {
783
+ for (const object of result) {
726
784
  const wrappedGraph = encodeGraph(wrapObject(object, rawPrefix));
727
785
  log("getByIds", wrappedGraph);
728
786
  merge(results, wrappedGraph);
729
- });
787
+ }
730
788
  };
731
789
  const query = unwrap(rootQuery, prefix);
732
790
  for (const node of query) {
@@ -757,28 +815,40 @@ class Db {
757
815
  const prefix = encodePath(rawPrefix);
758
816
  await this.ensureSchema(tableOptions);
759
817
  const change = unwrap(rootChange, prefix);
760
- const sqls = change.map((node) => {
818
+ const puts = [];
819
+ const sqls = [];
820
+ for (const node of change) {
761
821
  const arg = decodeArgs(node);
762
822
  if (isRange(node)) {
763
- if (cmp(node.key, node.end) === 0)
764
- return del(arg, tableOptions);
823
+ if (cmp(node.key, node.end) === 0) {
824
+ log("delete", node);
825
+ sqls.push(del(arg, tableOptions));
826
+ continue;
827
+ }
765
828
  throw Error("pg_write.write_range_unsupported");
766
829
  }
767
830
  const object = decodeGraph(node.children);
768
831
  if (isPlainObject(arg)) {
769
832
  mergeObject(object, arg);
770
833
  } else {
771
- object.id = arg;
834
+ object[tableOptions.idCol] = arg;
772
835
  }
773
836
  if (object.$put && object.$put !== true) {
774
837
  throw Error("pg_write.partial_put_unsupported");
775
838
  }
776
- return object.$put ? put(object, arg, tableOptions) : patch(object, arg, tableOptions);
777
- });
839
+ if (object.$put) {
840
+ puts.push([object, arg]);
841
+ } else {
842
+ sqls.push(patch(object, arg, tableOptions));
843
+ }
844
+ }
845
+ if (puts.length)
846
+ sqls.push(...put(puts, tableOptions));
778
847
  const result = [];
779
848
  await Promise.all(
780
849
  sqls.map(
781
850
  (sql2) => this.writeSql(sql2, tableOptions).then((object) => {
851
+ log("returned_object_wrapped", wrapObject(object, rawPrefix));
782
852
  merge(result, encodeGraph(wrapObject(object, rawPrefix)));
783
853
  })
784
854
  )
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.7",
5
+ "version": "0.16.9-alpha.1",
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.7",
19
+ "@graffy/common": "0.16.9-alpha.1",
20
20
  "debug": "^4.3.3"
21
21
  },
22
22
  "peerDependencies": {
@@ -3,9 +3,10 @@ export function getJsonBuildTrusted(variadic: any): Sql;
3
3
  export function lookup(prop: any, options: any): Sql;
4
4
  export function lookupNumeric(prop: any): Sql;
5
5
  export function getSelectCols(options: any, projection?: any): Sql;
6
- export function getInsert(row: any, options: any): {
6
+ export function getInsert(rows: any, options: any): {
7
7
  cols: Sql;
8
8
  vals: Sql;
9
+ updates: Sql;
9
10
  };
10
11
  export function getUpdates(row: any, options: any): Sql;
11
12
  import { Sql } from "sql-template-tag";
@@ -1,3 +1,3 @@
1
1
  export function patch(object: any, arg: any, options: any): import("sql-template-tag").Sql;
2
- export function put(object: any, arg: any, options: any): import("sql-template-tag").Sql;
2
+ export function put(puts: any, options: any): any[];
3
3
  export function del(arg: any, options: any): import("sql-template-tag").Sql;