@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/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
- } else if (op === "$all" || op === "$any") {
127
- node[3] = simplify(node[3]);
128
- } else if (op === "$has") {
129
- node[3] = node[3].map((subnode) => simplify(subnode));
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
- if (!node[1].length)
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
- function defaultColumnType(_) {
173
- return "any";
174
- }
175
- function getCompatibleTypes(value) {
176
- if (value === null)
177
- return "any";
178
- if (Array.isArray(value))
179
- return "array";
180
- if (typeof value === "object")
181
- return "jsonb";
182
- if (typeof value === "number")
183
- return "numeric";
184
- if (typeof value === "string")
185
- return "text";
186
- }
187
- function getSql(filter, getLookupSql, getColumnType = defaultColumnType) {
188
- function lookup(string, type) {
189
- if (string.substr(0, 3) === "el$")
190
- return sql__default["default"]`"${sql.raw(string)}"`;
191
- return getLookupSql(string, type);
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
- function binop(op, left, right) {
194
- const lType = left.substr(0, 3) === "el$" ? "any" : getColumnType(left);
195
- const rType = getCompatibleTypes(right);
196
- if (lType === "any" || rType === "any" || rType === lType) {
197
- return sql__default["default"]`${lookup(left)} ${sql.raw(op)} ${right}`;
198
- } else {
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
- switch (ast[0]) {
204
- case "$eq":
205
- if (ast[2] === null)
206
- return sql__default["default"]`${lookup(ast[1])} IS NULL`;
207
- return binop("=", ast[1], ast[2]);
208
- case "$neq":
209
- if (ast[2] === null)
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 getSelectCols = (table) => {
275
- return sql__default["default"]`to_jsonb("${sql.raw(table)}")`;
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(([name]) => name !== options.verCol && name[0] !== "$").concat([[options.verCol, nowTimestamp]]).forEach(([col, val]) => {
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 instanceof sql.Sql || typeof val !== "object" || !val ? val : sql__default["default"]`${JSON.stringify(stripAttributes(val))}::jsonb`);
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(([name]) => name !== options.idCol && name[0] !== "$").map(([name, value]) => {
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
- const lookup = (prop, type) => {
330
- const [prefix2, ...suffix] = common.encodePath(prop);
331
- const op = type === "text" ? sql__default["default"]`#>>` : sql__default["default"]`#>`;
332
- return suffix.length ? sql__default["default"]`"${sql.raw(prefix2)}" ${op} ${suffix}` : sql__default["default"]`"${sql.raw(prefix2)}"`;
333
- };
334
- const getType = (prop) => {
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, lookup, getType));
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 patch(object, arg, options) {
431
+ function getSingleSql(arg, options) {
421
432
  const { table, idCol } = options;
422
- const { where, meta } = common.isPlainObject(arg) ? getArgSql(arg, options) : { where: [sql__default["default"]`"${sql.raw(idCol)}" = ${arg}`], meta: getIdMeta(options) };
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 ${common.isPlainObject(arg) ? sql__default["default"]`"${sql.raw(idCol)}" = (
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
- let result = await this.query(sql2);
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
- let res = await this.query(sql2);
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
- const getByArgs = async (args) => {
496
- const result = await this.readSql(selectByArgs(args, tableOptions));
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
- promises.push(getByArgs(__spreadValues(__spreadValues({}, args), childArgs)));
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
- promises.push(getByArgs(args));
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
- for (const node of change) {
595
+ const sqls = change.map((node) => {
596
+ const arg = common.decodeArgs(node);
537
597
  if (common.isRange(node)) {
538
- throw Error(node.key === node.end ? "pg_write.delete_unsupported" : "pg_write.write_range_unsupported");
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 ? addToQuery(put(object, arg, tableOptions)) : addToQuery(patch(object, arg, tableOptions));
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, links, connection }) => (store) => {
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
- links: links || {}
630
+ schema
570
631
  };
571
632
  const defaultDb = new Db(connection);
572
633
  function read(query, options, next) {
573
- const _a = options, { transactionDb = defaultDb } = _a, readOpts = __objRest(_a, ["transactionDb"]);
574
- const readPromise = transactionDb.read(query, tableOpts, readOpts);
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 _a = options, { transactionDb = defaultDb } = _a, writeOpts = __objRest(_a, ["transactionDb"]);
583
- const writePromise = transactionDb.write(change, tableOpts, writeOpts);
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]) => {