@nymphjs/driver-postgresql 1.0.0-beta.8 → 1.0.0-beta.80

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.
@@ -1,19 +1,20 @@
1
- import cp from 'child_process';
2
1
  import { Pool, PoolClient, PoolConfig, QueryResult } from 'pg';
3
2
  import format from 'pg-format';
4
3
  import {
5
4
  NymphDriver,
6
- EntityConstructor,
7
- EntityData,
8
- EntityInterface,
9
- SerializedEntityData,
5
+ type EntityConstructor,
6
+ type EntityData,
7
+ type EntityInterface,
8
+ type EntityInstanceType,
9
+ type SerializedEntityData,
10
+ type FormattedSelector,
11
+ type Options,
12
+ type Selector,
13
+ EntityUniqueConstraintError,
10
14
  InvalidParametersError,
11
15
  NotConfiguredError,
12
16
  QueryFailedError,
13
17
  UnableToConnectError,
14
- FormattedSelector,
15
- Options,
16
- Selector,
17
18
  xor,
18
19
  } from '@nymphjs/nymph';
19
20
  import { makeTableSuffix } from '@nymphjs/guid';
@@ -53,10 +54,44 @@ export default class PostgreSQLDriver extends NymphDriver {
53
54
  return format.literal(input);
54
55
  }
55
56
 
57
+ static escapeNullSequences(input: string) {
58
+ // Postgres doesn't support null bytes in `text`, and it converts strings
59
+ // in JSON to `text`, so we need to escape the escape sequences for null
60
+ // bytes.
61
+ return (
62
+ input
63
+ .replace(/\uFFFD/g, () => '\uFFFD\uFFFD')
64
+ // n so that if there's already an escape, it turns into \n
65
+ // - so that it won't match a \uFFFD that got turned into \uFFFD\uFFFD
66
+ .replace(/\\u0000/g, () => 'nu\uFFFD-')
67
+ .replace(/\\x00/g, () => 'nx\uFFFD-')
68
+ );
69
+ }
70
+
71
+ static unescapeNullSequences(input: string) {
72
+ return input
73
+ .replace(/nu\uFFFD-/g, () => '\\u0000')
74
+ .replace(/nx\uFFFD-/g, () => '\\x00')
75
+ .replace(/\uFFFD\uFFFD/g, () => '\uFFFD');
76
+ }
77
+
78
+ static escapeNulls(input: string) {
79
+ // Postgres doesn't support null bytes in `text`.
80
+ return input
81
+ .replace(/\uFFFD/g, () => '\uFFFD\uFFFD')
82
+ .replace(/\x00/g, () => '-\uFFFD-');
83
+ }
84
+
85
+ static unescapeNulls(input: string) {
86
+ return input
87
+ .replace(/-\uFFFD-/g, () => '\x00')
88
+ .replace(/\uFFFD\uFFFD/g, () => '\uFFFD');
89
+ }
90
+
56
91
  constructor(
57
92
  config: Partial<PostgreSQLDriverConfig>,
58
93
  link?: Pool,
59
- transaction?: PostgreSQLDriverTransaction
94
+ transaction?: PostgreSQLDriverTransaction,
60
95
  ) {
61
96
  super();
62
97
  this.config = { ...defaults, ...config };
@@ -91,18 +126,28 @@ export default class PostgreSQLDriver extends NymphDriver {
91
126
  return new PostgreSQLDriver(
92
127
  this.config,
93
128
  this.link,
94
- this.transaction ?? undefined
129
+ this.transaction ?? undefined,
95
130
  );
96
131
  }
97
132
 
98
- private getConnection(): Promise<PostgreSQLDriverConnection> {
99
- if (this.transaction != null && this.transaction.connection != null) {
133
+ private getConnection(
134
+ outsideTransaction = false,
135
+ ): Promise<PostgreSQLDriverConnection> {
136
+ if (
137
+ this.transaction != null &&
138
+ this.transaction.connection != null &&
139
+ !outsideTransaction
140
+ ) {
100
141
  return Promise.resolve(this.transaction.connection);
101
142
  }
102
143
  return new Promise((resolve, reject) =>
103
144
  this.link.connect((err, client, done) =>
104
- err ? reject(err) : resolve({ client, done })
105
- )
145
+ err
146
+ ? reject(err)
147
+ : client
148
+ ? resolve({ client, done })
149
+ : reject('No client returned from connect.'),
150
+ ),
106
151
  );
107
152
  }
108
153
 
@@ -118,8 +163,12 @@ export default class PostgreSQLDriver extends NymphDriver {
118
163
  const connection: PostgreSQLDriverConnection = await new Promise(
119
164
  (resolve, reject) =>
120
165
  this.link.connect((err, client, done) =>
121
- err ? reject(err) : resolve({ client, done })
122
- )
166
+ err
167
+ ? reject(err)
168
+ : client
169
+ ? resolve({ client, done })
170
+ : reject('No client returned from connect.'),
171
+ ),
123
172
  );
124
173
  await new Promise((resolve, reject) =>
125
174
  connection.client.query('SELECT 1;', [], (err, res) => {
@@ -127,7 +176,7 @@ export default class PostgreSQLDriver extends NymphDriver {
127
176
  reject(err);
128
177
  }
129
178
  resolve(0);
130
- })
179
+ }),
131
180
  );
132
181
  connection.done();
133
182
  }
@@ -148,7 +197,7 @@ export default class PostgreSQLDriver extends NymphDriver {
148
197
  this.postgresqlConfig.database === 'nymph'
149
198
  ) {
150
199
  throw new NotConfiguredError(
151
- "It seems the config hasn't been set up correctly."
200
+ "It seems the config hasn't been set up correctly.",
152
201
  );
153
202
  } else {
154
203
  throw new UnableToConnectError('Could not connect: ' + e?.message);
@@ -190,203 +239,254 @@ export default class PostgreSQLDriver extends NymphDriver {
190
239
  * @param etype The entity type to create a table for. If this is blank, the default tables are created.
191
240
  * @returns True on success, false on failure.
192
241
  */
193
- private createTables(etype: string | null = null) {
242
+ private async createTables(etype: string | null = null) {
243
+ const connection = await this.getConnection(true);
194
244
  if (etype != null) {
195
245
  // Create the entity table.
196
- this.queryRunSync(
246
+ await this.queryRun(
197
247
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
198
- `${this.prefix}entities_${etype}`
248
+ `${this.prefix}entities_${etype}`,
199
249
  )} (
200
250
  "guid" BYTEA NOT NULL,
201
251
  "tags" TEXT[],
202
252
  "cdate" DOUBLE PRECISION NOT NULL,
203
253
  "mdate" DOUBLE PRECISION NOT NULL,
204
254
  PRIMARY KEY ("guid")
205
- ) WITH ( OIDS=FALSE );`
255
+ ) WITH ( OIDS=FALSE );`,
256
+ { connection },
206
257
  );
207
- this.queryRunSync(
258
+ await this.queryRun(
208
259
  `ALTER TABLE ${PostgreSQLDriver.escape(
209
- `${this.prefix}entities_${etype}`
210
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
260
+ `${this.prefix}entities_${etype}`,
261
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
262
+ { connection },
211
263
  );
212
- this.queryRunSync(
264
+ await this.queryRun(
213
265
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
214
- `${this.prefix}entities_${etype}_id_cdate`
215
- )};`
266
+ `${this.prefix}entities_${etype}_id_cdate`,
267
+ )};`,
268
+ { connection },
216
269
  );
217
- this.queryRunSync(
270
+ await this.queryRun(
218
271
  `CREATE INDEX ${PostgreSQLDriver.escape(
219
- `${this.prefix}entities_${etype}_id_cdate`
272
+ `${this.prefix}entities_${etype}_id_cdate`,
220
273
  )} ON ${PostgreSQLDriver.escape(
221
- `${this.prefix}entities_${etype}`
222
- )} USING btree ("cdate");`
274
+ `${this.prefix}entities_${etype}`,
275
+ )} USING btree ("cdate");`,
276
+ { connection },
223
277
  );
224
- this.queryRunSync(
278
+ await this.queryRun(
225
279
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
226
- `${this.prefix}entities_${etype}_id_mdate`
227
- )};`
280
+ `${this.prefix}entities_${etype}_id_mdate`,
281
+ )};`,
282
+ { connection },
228
283
  );
229
- this.queryRunSync(
284
+ await this.queryRun(
230
285
  `CREATE INDEX ${PostgreSQLDriver.escape(
231
- `${this.prefix}entities_${etype}_id_mdate`
286
+ `${this.prefix}entities_${etype}_id_mdate`,
232
287
  )} ON ${PostgreSQLDriver.escape(
233
- `${this.prefix}entities_${etype}`
234
- )} USING btree ("mdate");`
288
+ `${this.prefix}entities_${etype}`,
289
+ )} USING btree ("mdate");`,
290
+ { connection },
235
291
  );
236
- this.queryRunSync(
292
+ await this.queryRun(
237
293
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
238
- `${this.prefix}entities_${etype}_id_tags`
239
- )};`
294
+ `${this.prefix}entities_${etype}_id_tags`,
295
+ )};`,
296
+ { connection },
240
297
  );
241
- this.queryRunSync(
298
+ await this.queryRun(
242
299
  `CREATE INDEX ${PostgreSQLDriver.escape(
243
- `${this.prefix}entities_${etype}_id_tags`
300
+ `${this.prefix}entities_${etype}_id_tags`,
244
301
  )} ON ${PostgreSQLDriver.escape(
245
- `${this.prefix}entities_${etype}`
246
- )} USING gin ("tags");`
302
+ `${this.prefix}entities_${etype}`,
303
+ )} USING gin ("tags");`,
304
+ { connection },
247
305
  );
248
306
  // Create the data table.
249
- this.queryRunSync(
307
+ await this.queryRun(
250
308
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
251
- `${this.prefix}data_${etype}`
309
+ `${this.prefix}data_${etype}`,
252
310
  )} (
253
311
  "guid" BYTEA NOT NULL,
254
312
  "name" TEXT NOT NULL,
255
- "value" TEXT NOT NULL,
313
+ "value" CHARACTER(1) NOT NULL,
314
+ "json" JSONB,
315
+ "string" TEXT,
316
+ "number" DOUBLE PRECISION,
317
+ "truthy" BOOLEAN,
256
318
  PRIMARY KEY ("guid", "name"),
257
319
  FOREIGN KEY ("guid")
258
320
  REFERENCES ${PostgreSQLDriver.escape(
259
- `${this.prefix}entities_${etype}`
321
+ `${this.prefix}entities_${etype}`,
260
322
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
261
- ) WITH ( OIDS=FALSE );`
323
+ ) WITH ( OIDS=FALSE );`,
324
+ { connection },
262
325
  );
263
- this.queryRunSync(
326
+ await this.queryRun(
264
327
  `ALTER TABLE ${PostgreSQLDriver.escape(
265
- `${this.prefix}data_${etype}`
266
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
328
+ `${this.prefix}data_${etype}`,
329
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
330
+ { connection },
267
331
  );
268
- this.queryRunSync(
332
+ await this.queryRun(
269
333
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
270
- `${this.prefix}data_${etype}_id_guid`
271
- )};`
334
+ `${this.prefix}data_${etype}_id_guid`,
335
+ )};`,
336
+ { connection },
272
337
  );
273
- this.queryRunSync(
338
+ await this.queryRun(
274
339
  `CREATE INDEX ${PostgreSQLDriver.escape(
275
- `${this.prefix}data_${etype}_id_guid`
340
+ `${this.prefix}data_${etype}_id_guid`,
276
341
  )} ON ${PostgreSQLDriver.escape(
277
- `${this.prefix}data_${etype}`
278
- )} USING btree ("guid");`
342
+ `${this.prefix}data_${etype}`,
343
+ )} USING btree ("guid");`,
344
+ { connection },
279
345
  );
280
- this.queryRunSync(
346
+ await this.queryRun(
281
347
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
282
- `${this.prefix}data_${etype}_id_name`
283
- )};`
348
+ `${this.prefix}data_${etype}_id_guid_name`,
349
+ )};`,
350
+ { connection },
284
351
  );
285
- this.queryRunSync(
352
+ await this.queryRun(
286
353
  `CREATE INDEX ${PostgreSQLDriver.escape(
287
- `${this.prefix}data_${etype}_id_name`
354
+ `${this.prefix}data_${etype}_id_guid_name`,
288
355
  )} ON ${PostgreSQLDriver.escape(
289
- `${this.prefix}data_${etype}`
290
- )} USING btree ("name");`
356
+ `${this.prefix}data_${etype}`,
357
+ )} USING btree ("guid", "name");`,
358
+ { connection },
291
359
  );
292
- this.queryRunSync(
360
+ await this.queryRun(
293
361
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
294
- `${this.prefix}data_${etype}_id_guid_name__user`
295
- )};`
362
+ `${this.prefix}data_${etype}_id_guid_name__user`,
363
+ )};`,
364
+ { connection },
296
365
  );
297
- this.queryRunSync(
366
+ await this.queryRun(
298
367
  `CREATE INDEX ${PostgreSQLDriver.escape(
299
- `${this.prefix}data_${etype}_id_guid_name__user`
368
+ `${this.prefix}data_${etype}_id_guid_name__user`,
300
369
  )} ON ${PostgreSQLDriver.escape(
301
- `${this.prefix}data_${etype}`
302
- )} USING btree ("guid") WHERE "name" = 'user'::text;`
370
+ `${this.prefix}data_${etype}`,
371
+ )} USING btree ("guid") WHERE "name" = 'user'::text;`,
372
+ { connection },
303
373
  );
304
- this.queryRunSync(
374
+ await this.queryRun(
305
375
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
306
- `${this.prefix}data_${etype}_id_guid_name__group`
307
- )};`
376
+ `${this.prefix}data_${etype}_id_guid_name__group`,
377
+ )};`,
378
+ { connection },
308
379
  );
309
- this.queryRunSync(
380
+ await this.queryRun(
310
381
  `CREATE INDEX ${PostgreSQLDriver.escape(
311
- `${this.prefix}data_${etype}_id_guid_name__group`
382
+ `${this.prefix}data_${etype}_id_guid_name__group`,
312
383
  )} ON ${PostgreSQLDriver.escape(
313
- `${this.prefix}data_${etype}`
314
- )} USING btree ("guid") WHERE "name" = 'group'::text;`
384
+ `${this.prefix}data_${etype}`,
385
+ )} USING btree ("guid") WHERE "name" = 'group'::text;`,
386
+ { connection },
315
387
  );
316
- // Create the data comparisons table.
317
- this.queryRunSync(
318
- `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
319
- `${this.prefix}comparisons_${etype}`
320
- )} (
321
- "guid" BYTEA NOT NULL,
322
- "name" TEXT NOT NULL,
323
- "truthy" BOOLEAN,
324
- "string" TEXT,
325
- "number" DOUBLE PRECISION,
326
- PRIMARY KEY ("guid", "name"),
327
- FOREIGN KEY ("guid")
328
- REFERENCES ${PostgreSQLDriver.escape(
329
- `${this.prefix}entities_${etype}`
330
- )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
331
- ) WITH ( OIDS=FALSE );`
388
+ await this.queryRun(
389
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
390
+ `${this.prefix}data_${etype}_id_name`,
391
+ )};`,
392
+ { connection },
332
393
  );
333
- this.queryRunSync(
334
- `ALTER TABLE ${PostgreSQLDriver.escape(
335
- `${this.prefix}comparisons_${etype}`
336
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
394
+ await this.queryRun(
395
+ `CREATE INDEX ${PostgreSQLDriver.escape(
396
+ `${this.prefix}data_${etype}_id_name`,
397
+ )} ON ${PostgreSQLDriver.escape(
398
+ `${this.prefix}data_${etype}`,
399
+ )} USING btree ("name");`,
400
+ { connection },
401
+ );
402
+ await this.queryRun(
403
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
404
+ `${this.prefix}data_${etype}_id_name_string`,
405
+ )};`,
406
+ { connection },
407
+ );
408
+ await this.queryRun(
409
+ `CREATE INDEX ${PostgreSQLDriver.escape(
410
+ `${this.prefix}data_${etype}_id_name_string`,
411
+ )} ON ${PostgreSQLDriver.escape(
412
+ `${this.prefix}data_${etype}`,
413
+ )} USING btree ("name", LEFT("string", 512));`,
414
+ { connection },
415
+ );
416
+ await this.queryRun(
417
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
418
+ `${this.prefix}data_${etype}_id_name_number`,
419
+ )};`,
420
+ { connection },
421
+ );
422
+ await this.queryRun(
423
+ `CREATE INDEX ${PostgreSQLDriver.escape(
424
+ `${this.prefix}data_${etype}_id_name_number`,
425
+ )} ON ${PostgreSQLDriver.escape(
426
+ `${this.prefix}data_${etype}`,
427
+ )} USING btree ("name", "number");`,
428
+ { connection },
337
429
  );
338
- this.queryRunSync(
430
+ await this.queryRun(
339
431
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
340
- `${this.prefix}comparisons_${etype}_id_guid`
341
- )};`
432
+ `${this.prefix}data_${etype}_id_name_truthy`,
433
+ )};`,
434
+ { connection },
342
435
  );
343
- this.queryRunSync(
436
+ await this.queryRun(
344
437
  `CREATE INDEX ${PostgreSQLDriver.escape(
345
- `${this.prefix}comparisons_${etype}_id_guid`
438
+ `${this.prefix}data_${etype}_id_name_truthy`,
346
439
  )} ON ${PostgreSQLDriver.escape(
347
- `${this.prefix}comparisons_${etype}`
348
- )} USING btree ("guid");`
440
+ `${this.prefix}data_${etype}`,
441
+ )} USING btree ("name") WHERE "truthy" = TRUE;`,
442
+ { connection },
349
443
  );
350
- this.queryRunSync(
444
+ await this.queryRun(
351
445
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
352
- `${this.prefix}comparisons_${etype}_id_name`
353
- )};`
446
+ `${this.prefix}data_${etype}_id_name_falsy`,
447
+ )};`,
448
+ { connection },
354
449
  );
355
- this.queryRunSync(
450
+ await this.queryRun(
356
451
  `CREATE INDEX ${PostgreSQLDriver.escape(
357
- `${this.prefix}comparisons_${etype}_id_name`
452
+ `${this.prefix}data_${etype}_id_name_falsy`,
358
453
  )} ON ${PostgreSQLDriver.escape(
359
- `${this.prefix}comparisons_${etype}`
360
- )} USING btree ("name");`
454
+ `${this.prefix}data_${etype}`,
455
+ )} USING btree ("name") WHERE "truthy" <> TRUE;`,
456
+ { connection },
361
457
  );
362
- this.queryRunSync(
458
+ await this.queryRun(
363
459
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
364
- `${this.prefix}comparisons_${etype}_id_guid_name_truthy`
365
- )};`
460
+ `${this.prefix}data_${etype}_id_string`,
461
+ )};`,
462
+ { connection },
366
463
  );
367
- this.queryRunSync(
464
+ await this.queryRun(
368
465
  `CREATE INDEX ${PostgreSQLDriver.escape(
369
- `${this.prefix}comparisons_${etype}_id_guid_name_truthy`
466
+ `${this.prefix}data_${etype}_id_string`,
370
467
  )} ON ${PostgreSQLDriver.escape(
371
- `${this.prefix}comparisons_${etype}`
372
- )} USING btree ("guid", "name") WHERE "truthy" = TRUE;`
468
+ `${this.prefix}data_${etype}`,
469
+ )} USING gin ("string" gin_trgm_ops);`,
470
+ { connection },
373
471
  );
374
- this.queryRunSync(
472
+ await this.queryRun(
375
473
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
376
- `${this.prefix}comparisons_${etype}_id_guid_name_falsy`
377
- )};`
474
+ `${this.prefix}data_${etype}_id_json`,
475
+ )};`,
476
+ { connection },
378
477
  );
379
- this.queryRunSync(
478
+ await this.queryRun(
380
479
  `CREATE INDEX ${PostgreSQLDriver.escape(
381
- `${this.prefix}comparisons_${etype}_id_guid_name_falsy`
480
+ `${this.prefix}data_${etype}_id_json`,
382
481
  )} ON ${PostgreSQLDriver.escape(
383
- `${this.prefix}comparisons_${etype}`
384
- )} USING btree ("guid", "name") WHERE "truthy" <> TRUE;`
482
+ `${this.prefix}data_${etype}`,
483
+ )} USING gin ("json");`,
484
+ { connection },
385
485
  );
386
486
  // Create the references table.
387
- this.queryRunSync(
487
+ await this.queryRun(
388
488
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
389
- `${this.prefix}references_${etype}`
489
+ `${this.prefix}references_${etype}`,
390
490
  )} (
391
491
  "guid" BYTEA NOT NULL,
392
492
  "name" TEXT NOT NULL,
@@ -394,74 +494,106 @@ export default class PostgreSQLDriver extends NymphDriver {
394
494
  PRIMARY KEY ("guid", "name", "reference"),
395
495
  FOREIGN KEY ("guid")
396
496
  REFERENCES ${PostgreSQLDriver.escape(
397
- `${this.prefix}entities_${etype}`
497
+ `${this.prefix}entities_${etype}`,
398
498
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
399
- ) WITH ( OIDS=FALSE );`
499
+ ) WITH ( OIDS=FALSE );`,
500
+ { connection },
400
501
  );
401
- this.queryRunSync(
502
+ await this.queryRun(
402
503
  `ALTER TABLE ${PostgreSQLDriver.escape(
403
- `${this.prefix}references_${etype}`
404
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
504
+ `${this.prefix}references_${etype}`,
505
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
506
+ { connection },
405
507
  );
406
- this.queryRunSync(
508
+ await this.queryRun(
407
509
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
408
- `${this.prefix}references_${etype}_id_guid`
409
- )};`
510
+ `${this.prefix}references_${etype}_id_guid`,
511
+ )};`,
512
+ { connection },
410
513
  );
411
- this.queryRunSync(
514
+ await this.queryRun(
412
515
  `CREATE INDEX ${PostgreSQLDriver.escape(
413
- `${this.prefix}references_${etype}_id_guid`
516
+ `${this.prefix}references_${etype}_id_guid`,
414
517
  )} ON ${PostgreSQLDriver.escape(
415
- `${this.prefix}references_${etype}`
416
- )} USING btree ("guid");`
518
+ `${this.prefix}references_${etype}`,
519
+ )} USING btree ("guid");`,
520
+ { connection },
417
521
  );
418
- this.queryRunSync(
522
+ await this.queryRun(
419
523
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
420
- `${this.prefix}references_${etype}_id_name`
421
- )};`
524
+ `${this.prefix}references_${etype}_id_name`,
525
+ )};`,
526
+ { connection },
422
527
  );
423
- this.queryRunSync(
528
+ await this.queryRun(
424
529
  `CREATE INDEX ${PostgreSQLDriver.escape(
425
- `${this.prefix}references_${etype}_id_name`
530
+ `${this.prefix}references_${etype}_id_name`,
426
531
  )} ON ${PostgreSQLDriver.escape(
427
- `${this.prefix}references_${etype}`
428
- )} USING btree ("name");`
532
+ `${this.prefix}references_${etype}`,
533
+ )} USING btree ("name");`,
534
+ { connection },
429
535
  );
430
- this.queryRunSync(
536
+ await this.queryRun(
431
537
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
432
- `${this.prefix}references_${etype}_id_reference`
433
- )};`
538
+ `${this.prefix}references_${etype}_id_name_reference`,
539
+ )};`,
540
+ { connection },
434
541
  );
435
- this.queryRunSync(
542
+ await this.queryRun(
436
543
  `CREATE INDEX ${PostgreSQLDriver.escape(
437
- `${this.prefix}references_${etype}_id_reference`
544
+ `${this.prefix}references_${etype}_id_name_reference`,
438
545
  )} ON ${PostgreSQLDriver.escape(
439
- `${this.prefix}references_${etype}`
440
- )} USING btree ("reference");`
546
+ `${this.prefix}references_${etype}`,
547
+ )} USING btree ("name", "reference");`,
548
+ { connection },
549
+ );
550
+ // Create the unique strings table.
551
+ await this.queryRun(
552
+ `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
553
+ `${this.prefix}uniques_${etype}`,
554
+ )} (
555
+ "guid" BYTEA NOT NULL,
556
+ "unique" TEXT NOT NULL UNIQUE,
557
+ PRIMARY KEY ("guid", "unique"),
558
+ FOREIGN KEY ("guid")
559
+ REFERENCES ${PostgreSQLDriver.escape(
560
+ `${this.prefix}entities_${etype}`,
561
+ )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
562
+ ) WITH ( OIDS=FALSE );`,
563
+ { connection },
441
564
  );
442
565
  } else {
566
+ // Add trigram extensions.
567
+ try {
568
+ await this.queryRun(`CREATE EXTENSION pg_trgm;`, { connection });
569
+ } catch (e: any) {
570
+ // Ignore errors.
571
+ }
443
572
  // Create the UID table.
444
- this.queryRunSync(
573
+ await this.queryRun(
445
574
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
446
- `${this.prefix}uids`
575
+ `${this.prefix}uids`,
447
576
  )} (
448
577
  "name" TEXT NOT NULL,
449
578
  "cur_uid" BIGINT NOT NULL,
450
579
  PRIMARY KEY ("name")
451
- ) WITH ( OIDS = FALSE );`
580
+ ) WITH ( OIDS = FALSE );`,
581
+ { connection },
452
582
  );
453
- this.queryRunSync(
583
+ await this.queryRun(
454
584
  `ALTER TABLE ${PostgreSQLDriver.escape(
455
- `${this.prefix}uids`
456
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
585
+ `${this.prefix}uids`,
586
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
587
+ { connection },
457
588
  );
458
589
  }
590
+ connection.done();
459
591
  return true;
460
592
  }
461
593
 
462
594
  private translateQuery(
463
595
  origQuery: string,
464
- origParams: { [k: string]: any }
596
+ origParams: { [k: string]: any },
465
597
  ): { query: string; params: any[] } {
466
598
  const params: any[] = [];
467
599
  let query = origQuery;
@@ -481,56 +613,34 @@ export default class PostgreSQLDriver extends NymphDriver {
481
613
  private async query<T extends () => any>(
482
614
  runQuery: T,
483
615
  query: string,
484
- etypes: string[] = []
616
+ etypes: string[] = [],
485
617
  // @ts-ignore: The return type of T is a promise.
486
618
  ): ReturnType<T> {
487
619
  try {
620
+ this.nymph.config.debugInfo('postgresql:query', query);
488
621
  return await runQuery();
489
622
  } catch (e: any) {
490
623
  const errorCode = e?.code;
491
- if (errorCode === '42P01' && this.createTables()) {
624
+ if (errorCode === '42P01' && (await this.createTables())) {
492
625
  // If the tables don't exist yet, create them.
493
626
  for (let etype of etypes) {
494
- this.createTables(etype);
627
+ await this.createTables(etype);
495
628
  }
496
629
  try {
497
630
  return await runQuery();
498
631
  } catch (e2: any) {
499
632
  throw new QueryFailedError(
500
633
  'Query failed: ' + e2?.code + ' - ' + e2?.message,
501
- query
502
- );
503
- }
504
- } else {
505
- throw e;
506
- }
507
- }
508
- }
509
-
510
- private querySync<T extends () => any>(
511
- runQuery: T,
512
- query: string,
513
- etypes: string[] = []
514
- ): ReturnType<T> {
515
- try {
516
- return runQuery();
517
- } catch (e: any) {
518
- const errorCode = e?.code;
519
- if (errorCode === '42P01' && this.createTables()) {
520
- // If the tables don't exist yet, create them.
521
- for (let etype of etypes) {
522
- this.createTables(etype);
523
- }
524
- try {
525
- return runQuery();
526
- } catch (e2: any) {
527
- throw new QueryFailedError(
528
- 'Query failed: ' + e2?.code + ' - ' + e2?.message,
529
- query
634
+ query,
530
635
  );
531
636
  }
637
+ } else if (errorCode === '23505') {
638
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
532
639
  } else {
533
- throw e;
640
+ throw new QueryFailedError(
641
+ 'Query failed: ' + e?.code + ' - ' + e?.message,
642
+ query,
643
+ );
534
644
  }
535
645
  }
536
646
  }
@@ -543,11 +653,11 @@ export default class PostgreSQLDriver extends NymphDriver {
543
653
  }: {
544
654
  etypes?: string[];
545
655
  params?: { [k: string]: any };
546
- } = {}
656
+ } = {},
547
657
  ) {
548
658
  const { query: newQuery, params: newParams } = this.translateQuery(
549
659
  query,
550
- params
660
+ params,
551
661
  );
552
662
  return this.query(
553
663
  async () => {
@@ -558,64 +668,17 @@ export default class PostgreSQLDriver extends NymphDriver {
558
668
  .query(newQuery, newParams)
559
669
  .then(
560
670
  (results) => resolve(results),
561
- (error) => reject(error)
671
+ (error) => reject(error),
562
672
  );
563
673
  } catch (e) {
564
674
  reject(e);
565
675
  }
566
- }
676
+ },
567
677
  );
568
678
  return results.rows;
569
679
  },
570
680
  `${query} -- ${JSON.stringify(params)}`,
571
- etypes
572
- );
573
- }
574
-
575
- private queryIterSync(
576
- query: string,
577
- {
578
- etypes = [],
579
- params = {},
580
- }: { etypes?: string[]; params?: { [k: string]: any } } = {}
581
- ) {
582
- const { query: newQuery, params: newParams } = this.translateQuery(
583
- query,
584
- params
585
- );
586
- return this.querySync(
587
- () => {
588
- const output = cp.spawnSync(
589
- process.argv0,
590
- [__dirname + '/runPostgresqlSync.js'],
591
- {
592
- input: JSON.stringify({
593
- postgresqlConfig: this.postgresqlConfig,
594
- query: newQuery,
595
- params: newParams,
596
- }),
597
- timeout: 30000,
598
- maxBuffer: 100 * 1024 * 1024,
599
- encoding: 'utf8',
600
- windowsHide: true,
601
- }
602
- );
603
- try {
604
- return JSON.parse(output.stdout).rows;
605
- } catch (e) {
606
- // Do nothing.
607
- }
608
- if (output.status === 0) {
609
- throw new Error('Unknown parse error.');
610
- }
611
- const err = JSON.parse(output.stderr);
612
- const e = new Error(err.name);
613
- for (const name in err) {
614
- (e as any)[name] = err[name];
615
- }
616
- },
617
- `${query} -- ${JSON.stringify(params)}`,
618
- etypes
681
+ etypes,
619
682
  );
620
683
  }
621
684
 
@@ -627,11 +690,11 @@ export default class PostgreSQLDriver extends NymphDriver {
627
690
  }: {
628
691
  etypes?: string[];
629
692
  params?: { [k: string]: any };
630
- } = {}
693
+ } = {},
631
694
  ) {
632
695
  const { query: newQuery, params: newParams } = this.translateQuery(
633
696
  query,
634
- params
697
+ params,
635
698
  );
636
699
  return this.query(
637
700
  async () => {
@@ -642,17 +705,17 @@ export default class PostgreSQLDriver extends NymphDriver {
642
705
  .query(newQuery, newParams)
643
706
  .then(
644
707
  (results) => resolve(results),
645
- (error) => reject(error)
708
+ (error) => reject(error),
646
709
  );
647
710
  } catch (e) {
648
711
  reject(e);
649
712
  }
650
- }
713
+ },
651
714
  );
652
715
  return results.rows[0];
653
716
  },
654
717
  `${query} -- ${JSON.stringify(params)}`,
655
- etypes
718
+ etypes,
656
719
  );
657
720
  }
658
721
 
@@ -661,90 +724,47 @@ export default class PostgreSQLDriver extends NymphDriver {
661
724
  {
662
725
  etypes = [],
663
726
  params = {},
727
+ connection,
664
728
  }: {
665
729
  etypes?: string[];
666
730
  params?: { [k: string]: any };
667
- } = {}
731
+ connection?: PostgreSQLDriverConnection;
732
+ } = {},
668
733
  ) {
669
734
  const { query: newQuery, params: newParams } = this.translateQuery(
670
735
  query,
671
- params
736
+ params,
672
737
  );
673
738
  return this.query(
674
739
  async () => {
675
740
  const results: QueryResult<any> = await new Promise(
676
741
  (resolve, reject) => {
677
742
  try {
678
- (this.transaction?.connection?.client ?? this.link)
743
+ (
744
+ (connection ?? this.transaction?.connection)?.client ??
745
+ this.link
746
+ )
679
747
  .query(newQuery, newParams)
680
748
  .then(
681
749
  (results) => resolve(results),
682
- (error) => reject(error)
750
+ (error) => reject(error),
683
751
  );
684
752
  } catch (e) {
685
753
  reject(e);
686
754
  }
687
- }
755
+ },
688
756
  );
689
757
  return { rowCount: results.rowCount ?? 0 };
690
758
  },
691
759
  `${query} -- ${JSON.stringify(params)}`,
692
- etypes
693
- );
694
- }
695
-
696
- private queryRunSync(
697
- query: string,
698
- {
699
- etypes = [],
700
- params = {},
701
- }: { etypes?: string[]; params?: { [k: string]: any } } = {}
702
- ) {
703
- const { query: newQuery, params: newParams } = this.translateQuery(
704
- query,
705
- params
706
- );
707
- return this.querySync(
708
- () => {
709
- const output = cp.spawnSync(
710
- process.argv0,
711
- [__dirname + '/runPostgresqlSync.js'],
712
- {
713
- input: JSON.stringify({
714
- postgresqlConfig: this.postgresqlConfig,
715
- query: newQuery,
716
- params: newParams,
717
- }),
718
- timeout: 30000,
719
- maxBuffer: 100 * 1024 * 1024,
720
- encoding: 'utf8',
721
- windowsHide: true,
722
- }
723
- );
724
- try {
725
- const results = JSON.parse(output.stdout);
726
- return { rowCount: results.rowCount ?? 0 };
727
- } catch (e) {
728
- // Do nothing.
729
- }
730
- if (output.status === 0) {
731
- throw new Error('Unknown parse error.');
732
- }
733
- const err = JSON.parse(output.stderr);
734
- const e = new Error(err.name);
735
- for (const name in err) {
736
- (e as any)[name] = err[name];
737
- }
738
- },
739
- `${query} -- ${JSON.stringify(params)}`,
740
- etypes
760
+ etypes,
741
761
  );
742
762
  }
743
763
 
744
764
  public async commit(name: string) {
745
765
  if (name == null || typeof name !== 'string' || name.length === 0) {
746
766
  throw new InvalidParametersError(
747
- 'Transaction commit attempted without a name.'
767
+ 'Transaction commit attempted without a name.',
748
768
  );
749
769
  }
750
770
  if (!this.transaction || this.transaction.count === 0) {
@@ -764,7 +784,7 @@ export default class PostgreSQLDriver extends NymphDriver {
764
784
 
765
785
  public async deleteEntityByID(
766
786
  guid: string,
767
- className?: EntityConstructor | string | null
787
+ className?: EntityConstructor | string | null,
768
788
  ) {
769
789
  let EntityClass: EntityConstructor;
770
790
  if (typeof className === 'string' || className == null) {
@@ -778,58 +798,60 @@ export default class PostgreSQLDriver extends NymphDriver {
778
798
  try {
779
799
  await this.queryRun(
780
800
  `DELETE FROM ${PostgreSQLDriver.escape(
781
- `${this.prefix}entities_${etype}`
801
+ `${this.prefix}entities_${etype}`,
782
802
  )} WHERE "guid"=decode(@guid, 'hex');`,
783
803
  {
784
804
  etypes: [etype],
785
805
  params: {
786
806
  guid,
787
807
  },
788
- }
808
+ },
789
809
  );
790
810
  await this.queryRun(
791
811
  `DELETE FROM ${PostgreSQLDriver.escape(
792
- `${this.prefix}data_${etype}`
812
+ `${this.prefix}data_${etype}`,
793
813
  )} WHERE "guid"=decode(@guid, 'hex');`,
794
814
  {
795
815
  etypes: [etype],
796
816
  params: {
797
817
  guid,
798
818
  },
799
- }
819
+ },
800
820
  );
801
821
  await this.queryRun(
802
822
  `DELETE FROM ${PostgreSQLDriver.escape(
803
- `${this.prefix}comparisons_${etype}`
823
+ `${this.prefix}references_${etype}`,
804
824
  )} WHERE "guid"=decode(@guid, 'hex');`,
805
825
  {
806
826
  etypes: [etype],
807
827
  params: {
808
828
  guid,
809
829
  },
810
- }
830
+ },
811
831
  );
812
832
  await this.queryRun(
813
833
  `DELETE FROM ${PostgreSQLDriver.escape(
814
- `${this.prefix}references_${etype}`
834
+ `${this.prefix}uniques_${etype}`,
815
835
  )} WHERE "guid"=decode(@guid, 'hex');`,
816
836
  {
817
837
  etypes: [etype],
818
838
  params: {
819
839
  guid,
820
840
  },
821
- }
841
+ },
822
842
  );
823
- await this.commit('nymph-delete');
824
- // Remove any cached versions of this entity.
825
- if (this.nymph.config.cache) {
826
- this.cleanCache(guid);
827
- }
828
- return true;
829
843
  } catch (e: any) {
844
+ this.nymph.config.debugError('postgresql', `Delete entity error: "${e}"`);
830
845
  await this.rollback('nymph-delete');
831
846
  throw e;
832
847
  }
848
+
849
+ await this.commit('nymph-delete');
850
+ // Remove any cached versions of this entity.
851
+ if (this.nymph.config.cache) {
852
+ this.cleanCache(guid);
853
+ }
854
+ return true;
833
855
  }
834
856
 
835
857
  public async deleteUID(name: string) {
@@ -838,103 +860,145 @@ export default class PostgreSQLDriver extends NymphDriver {
838
860
  }
839
861
  await this.queryRun(
840
862
  `DELETE FROM ${PostgreSQLDriver.escape(
841
- `${this.prefix}uids`
863
+ `${this.prefix}uids`,
842
864
  )} WHERE "name"=@name;`,
843
865
  {
844
866
  params: {
845
867
  name,
846
868
  },
847
- }
869
+ },
848
870
  );
849
871
  return true;
850
872
  }
851
873
 
852
- protected async exportEntities(writeLine: (line: string) => void) {
853
- writeLine('#nex2');
854
- writeLine('# Nymph Entity Exchange v2');
855
- writeLine('# http://nymph.io');
856
- writeLine('#');
857
- writeLine('# Generation Time: ' + new Date().toLocaleString());
858
- writeLine('');
874
+ public async *exportDataIterator(): AsyncGenerator<
875
+ { type: 'comment' | 'uid' | 'entity'; content: string },
876
+ void,
877
+ false | undefined
878
+ > {
879
+ if (
880
+ yield {
881
+ type: 'comment',
882
+ content: `#nex2
883
+ # Nymph Entity Exchange v2
884
+ # http://nymph.io
885
+ #
886
+ # Generation Time: ${new Date().toLocaleString()}
887
+ `,
888
+ }
889
+ ) {
890
+ return;
891
+ }
892
+
893
+ if (
894
+ yield {
895
+ type: 'comment',
896
+ content: `
897
+
898
+ #
899
+ # UIDs
900
+ #
859
901
 
860
- writeLine('#');
861
- writeLine('# UIDs');
862
- writeLine('#');
863
- writeLine('');
902
+ `,
903
+ }
904
+ ) {
905
+ return;
906
+ }
864
907
 
865
908
  // Export UIDs.
866
909
  let uids = await this.queryIter(
867
910
  `SELECT * FROM ${PostgreSQLDriver.escape(
868
- `${this.prefix}uids`
869
- )} ORDER BY "name";`
911
+ `${this.prefix}uids`,
912
+ )} ORDER BY "name";`,
870
913
  );
871
914
  for (const uid of uids) {
872
- writeLine(`<${uid.name}>[${uid.cur_uid}]`);
915
+ if (yield { type: 'uid', content: `<${uid.name}>[${uid.cur_uid}]\n` }) {
916
+ return;
917
+ }
873
918
  }
874
919
 
875
- writeLine('');
876
- writeLine('#');
877
- writeLine('# Entities');
878
- writeLine('#');
879
- writeLine('');
920
+ if (
921
+ yield {
922
+ type: 'comment',
923
+ content: `
924
+
925
+ #
926
+ # Entities
927
+ #
928
+
929
+ `,
930
+ }
931
+ ) {
932
+ return;
933
+ }
880
934
 
881
935
  // Get the etypes.
882
936
  const tables = await this.queryIter(
883
- 'SELECT relname FROM pg_stat_user_tables ORDER BY relname;'
937
+ 'SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @prefix;',
938
+ {
939
+ params: {
940
+ db: this.config.database,
941
+ prefix: this.prefix + 'entities_' + '%',
942
+ },
943
+ },
884
944
  );
885
945
  const etypes = [];
886
- for (const tableRow of tables) {
887
- const table = tableRow.relname;
888
- if (table.startsWith(this.prefix + 'entities_')) {
889
- etypes.push(table.substr((this.prefix + 'entities_').length));
890
- }
946
+ for (const table of tables) {
947
+ etypes.push(table.table_name.substr((this.prefix + 'entities_').length));
891
948
  }
892
949
 
893
950
  for (const etype of etypes) {
894
951
  // Export entities.
895
952
  const dataIterator = (
896
953
  await this.queryIter(
897
- `SELECT encode(e."guid", 'hex') AS "guid", e."tags", e."cdate", e."mdate", d."name" AS "dname", d."value" AS "dvalue", c."string", c."number"
954
+ `SELECT encode(e."guid", 'hex') AS "guid", e."tags", e."cdate", e."mdate", d."name", d."value", d."json", d."string", d."number"
898
955
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} e
899
956
  LEFT JOIN ${PostgreSQLDriver.escape(
900
- `${this.prefix}data_${etype}`
957
+ `${this.prefix}data_${etype}`,
901
958
  )} d ON e."guid"=d."guid"
902
- INNER JOIN ${PostgreSQLDriver.escape(
903
- `${this.prefix}comparisons_${etype}`
904
- )} c ON d."guid"=c."guid" AND d."name"=c."name"
905
- ORDER BY e."guid";`
959
+ ORDER BY e."guid";`,
906
960
  )
907
961
  )[Symbol.iterator]();
908
962
  let datum = dataIterator.next();
909
963
  while (!datum.done) {
910
964
  const guid = datum.value.guid;
911
- const tags = datum.value.tags.join(',');
965
+ const tags = datum.value.tags.filter((tag: string) => tag).join(',');
912
966
  const cdate = datum.value.cdate;
913
967
  const mdate = datum.value.mdate;
914
- writeLine(`{${guid}}<${etype}>[${tags}]`);
915
- writeLine(`\tcdate=${JSON.stringify(cdate)}`);
916
- writeLine(`\tmdate=${JSON.stringify(mdate)}`);
917
- if (datum.value.dname != null) {
968
+ let currentEntityExport: string[] = [];
969
+ currentEntityExport.push(`{${guid}}<${etype}>[${tags}]`);
970
+ currentEntityExport.push(`\tcdate=${JSON.stringify(cdate)}`);
971
+ currentEntityExport.push(`\tmdate=${JSON.stringify(mdate)}`);
972
+ if (datum.value.name != null) {
918
973
  // This do will keep going and adding the data until the
919
974
  // next entity is reached. $row will end on the next entity.
920
975
  do {
921
976
  const value =
922
- datum.value.dvalue === 'N'
977
+ datum.value.value === 'N'
923
978
  ? JSON.stringify(Number(datum.value.number))
924
- : datum.value.dvalue === 'S'
925
- ? JSON.stringify(datum.value.string)
926
- : datum.value.dvalue;
927
- writeLine(`\t${datum.value.dname}=${value}`);
979
+ : datum.value.value === 'S'
980
+ ? JSON.stringify(
981
+ PostgreSQLDriver.unescapeNulls(datum.value.string),
982
+ )
983
+ : datum.value.value === 'J'
984
+ ? PostgreSQLDriver.unescapeNullSequences(
985
+ JSON.stringify(datum.value.json),
986
+ )
987
+ : datum.value.value;
988
+ currentEntityExport.push(`\t${datum.value.name}=${value}`);
928
989
  datum = dataIterator.next();
929
990
  } while (!datum.done && datum.value.guid === guid);
930
991
  } else {
931
992
  // Make sure that datum is incremented :)
932
993
  datum = dataIterator.next();
933
994
  }
995
+ currentEntityExport.push('');
996
+
997
+ if (yield { type: 'entity', content: currentEntityExport.join('\n') }) {
998
+ return;
999
+ }
934
1000
  }
935
1001
  }
936
-
937
- return;
938
1002
  }
939
1003
 
940
1004
  /**
@@ -955,21 +1019,22 @@ export default class PostgreSQLDriver extends NymphDriver {
955
1019
  params: { [k: string]: any } = {},
956
1020
  subquery = false,
957
1021
  tableSuffix = '',
958
- etypes: string[] = []
1022
+ etypes: string[] = [],
1023
+ guidSelector: string | undefined = undefined,
959
1024
  ) {
960
1025
  if (typeof options.class?.alterOptions === 'function') {
961
1026
  options = options.class.alterOptions(options);
962
1027
  }
963
1028
  const eTable = `e${tableSuffix}`;
964
1029
  const dTable = `d${tableSuffix}`;
965
- const cTable = `c${tableSuffix}`;
966
1030
  const fTable = `f${tableSuffix}`;
967
1031
  const ieTable = `ie${tableSuffix}`;
968
1032
  const countTable = `count${tableSuffix}`;
1033
+ const sTable = `s${tableSuffix}`;
969
1034
  const sort = options.sort ?? 'cdate';
970
1035
  const queryParts = this.iterateSelectorsForQuery(
971
1036
  formattedSelectors,
972
- (key, value, typeIsOr, typeIsNot) => {
1037
+ ({ key, value, typeIsOr, typeIsNot }) => {
973
1038
  const clauseNot = key.startsWith('!');
974
1039
  let curQuery = '';
975
1040
  for (const curValue of value) {
@@ -1013,12 +1078,12 @@ export default class PostgreSQLDriver extends NymphDriver {
1013
1078
  }
1014
1079
  const name = `param${++count.i}`;
1015
1080
  curQuery +=
1016
- ieTable +
1017
- '."guid" ' +
1018
1081
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1019
- 'IN (SELECT "guid" FROM ' +
1082
+ 'EXISTS (SELECT "guid" FROM ' +
1020
1083
  PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1021
- ' WHERE "name"=@' +
1084
+ ' WHERE "guid"=' +
1085
+ ieTable +
1086
+ '."guid" AND "name"=@' +
1022
1087
  name +
1023
1088
  ')';
1024
1089
  params[name] = curVar;
@@ -1048,12 +1113,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1048
1113
  const name = `param${++count.i}`;
1049
1114
  curQuery +=
1050
1115
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1116
+ 'EXISTS (SELECT "guid" FROM ' +
1117
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1118
+ ' WHERE "guid"=' +
1051
1119
  ieTable +
1052
- '."guid" IN (SELECT "guid" FROM ' +
1053
- PostgreSQLDriver.escape(
1054
- this.prefix + 'comparisons_' + etype
1055
- ) +
1056
- ' WHERE "name"=@' +
1120
+ '."guid" AND "name"=@' +
1057
1121
  name +
1058
1122
  ' AND "truthy"=TRUE)';
1059
1123
  params[name] = curVar;
@@ -1098,12 +1162,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1098
1162
  const value = `param${++count.i}`;
1099
1163
  curQuery +=
1100
1164
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1165
+ 'EXISTS (SELECT "guid" FROM ' +
1166
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1167
+ ' WHERE "guid"=' +
1101
1168
  ieTable +
1102
- '."guid" IN (SELECT "guid" FROM ' +
1103
- PostgreSQLDriver.escape(
1104
- this.prefix + 'comparisons_' + etype
1105
- ) +
1106
- ' WHERE "name"=@' +
1169
+ '."guid" AND "name"=@' +
1107
1170
  name +
1108
1171
  ' AND "number"=@' +
1109
1172
  value +
@@ -1118,18 +1181,19 @@ export default class PostgreSQLDriver extends NymphDriver {
1118
1181
  const value = `param${++count.i}`;
1119
1182
  curQuery +=
1120
1183
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1184
+ 'EXISTS (SELECT "guid" FROM ' +
1185
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1186
+ ' WHERE "guid"=' +
1121
1187
  ieTable +
1122
- '."guid" IN (SELECT "guid" FROM ' +
1123
- PostgreSQLDriver.escape(
1124
- this.prefix + 'comparisons_' + etype
1125
- ) +
1126
- ' WHERE "name"=@' +
1188
+ '."guid" AND "name"=@' +
1127
1189
  name +
1128
- ' AND "string"=@' +
1129
- value +
1190
+ ' AND "string"=' +
1191
+ (curValue[1].length < 512
1192
+ ? 'LEFT(@' + value + ', 512)'
1193
+ : '@' + value) +
1130
1194
  ')';
1131
1195
  params[name] = curValue[0];
1132
- params[value] = curValue[1];
1196
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
1133
1197
  } else {
1134
1198
  if (curQuery) {
1135
1199
  curQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -1147,16 +1211,17 @@ export default class PostgreSQLDriver extends NymphDriver {
1147
1211
  const value = `param${++count.i}`;
1148
1212
  curQuery +=
1149
1213
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1150
- ieTable +
1151
- '."guid" IN (SELECT "guid" FROM ' +
1214
+ 'EXISTS (SELECT "guid" FROM ' +
1152
1215
  PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1153
- ' WHERE "name"=@' +
1216
+ ' WHERE "guid"=' +
1217
+ ieTable +
1218
+ '."guid" AND "name"=@' +
1154
1219
  name +
1155
- ' AND "value"=@' +
1220
+ ' AND "json"=@' +
1156
1221
  value +
1157
1222
  ')';
1158
1223
  params[name] = curValue[0];
1159
- params[value] = svalue;
1224
+ params[value] = PostgreSQLDriver.escapeNullSequences(svalue);
1160
1225
  }
1161
1226
  break;
1162
1227
  case 'contain':
@@ -1169,7 +1234,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1169
1234
  curQuery +=
1170
1235
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1171
1236
  ieTable +
1172
- '."cdate"=' +
1237
+ '."cdate"=@' +
1173
1238
  cdate;
1174
1239
  params[cdate] = isNaN(Number(curValue[1]))
1175
1240
  ? null
@@ -1183,7 +1248,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1183
1248
  curQuery +=
1184
1249
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1185
1250
  ieTable +
1186
- '."mdate"=' +
1251
+ '."mdate"=@' +
1187
1252
  mdate;
1188
1253
  params[mdate] = isNaN(Number(curValue[1]))
1189
1254
  ? null
@@ -1194,57 +1259,34 @@ export default class PostgreSQLDriver extends NymphDriver {
1194
1259
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1195
1260
  }
1196
1261
  let svalue: string;
1197
- let stringValue: string;
1198
1262
  if (
1199
1263
  curValue[1] instanceof Object &&
1200
1264
  typeof curValue[1].toReference === 'function'
1201
1265
  ) {
1202
1266
  svalue = JSON.stringify(curValue[1].toReference());
1203
- stringValue = `${curValue[1].toReference()}`;
1204
- } else {
1267
+ } else if (
1268
+ typeof curValue[1] === 'string' ||
1269
+ typeof curValue[1] === 'number'
1270
+ ) {
1205
1271
  svalue = JSON.stringify(curValue[1]);
1206
- stringValue = `${curValue[1]}`;
1272
+ } else {
1273
+ svalue = JSON.stringify([curValue[1]]);
1207
1274
  }
1208
1275
  const name = `param${++count.i}`;
1209
1276
  const value = `param${++count.i}`;
1210
- if (typeof curValue[1] === 'string') {
1211
- const stringParam = `param${++count.i}`;
1212
- curQuery +=
1213
- (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1214
- '(' +
1215
- ieTable +
1216
- '."guid" IN (SELECT "guid" FROM ' +
1217
- PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1218
- ' WHERE "name"=@' +
1219
- name +
1220
- ' AND position(@' +
1221
- value +
1222
- ' IN "value")>0) OR ' +
1223
- ieTable +
1224
- '."guid" IN (SELECT "guid" FROM ' +
1225
- PostgreSQLDriver.escape(
1226
- this.prefix + 'comparisons_' + etype
1227
- ) +
1228
- ' WHERE "name"=@' +
1229
- name +
1230
- ' AND "string"=@' +
1231
- stringParam +
1232
- '))';
1233
- params[stringParam] = stringValue;
1234
- } else {
1235
- curQuery +=
1236
- (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1237
- ieTable +
1238
- '."guid" IN (SELECT "guid" FROM ' +
1239
- PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1240
- ' WHERE "name"=@' +
1241
- name +
1242
- ' AND position(@' +
1243
- value +
1244
- ' IN "value")>0)';
1245
- }
1277
+ curQuery +=
1278
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1279
+ 'EXISTS (SELECT "guid" FROM ' +
1280
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1281
+ ' WHERE "guid"=' +
1282
+ ieTable +
1283
+ '."guid" AND "name"=@' +
1284
+ name +
1285
+ ' AND "json" @> @' +
1286
+ value +
1287
+ ')';
1246
1288
  params[name] = curValue[0];
1247
- params[value] = svalue;
1289
+ params[value] = PostgreSQLDriver.escapeNullSequences(svalue);
1248
1290
  }
1249
1291
  break;
1250
1292
  case 'match':
@@ -1285,18 +1327,17 @@ export default class PostgreSQLDriver extends NymphDriver {
1285
1327
  const value = `param${++count.i}`;
1286
1328
  curQuery +=
1287
1329
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1330
+ 'EXISTS (SELECT "guid" FROM ' +
1331
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1332
+ ' WHERE "guid"=' +
1288
1333
  ieTable +
1289
- '."guid" IN (SELECT "guid" FROM ' +
1290
- PostgreSQLDriver.escape(
1291
- this.prefix + 'comparisons_' + etype
1292
- ) +
1293
- ' WHERE "name"=@' +
1334
+ '."guid" AND "name"=@' +
1294
1335
  name +
1295
1336
  ' AND "string" ~ @' +
1296
1337
  value +
1297
1338
  ')';
1298
1339
  params[name] = curValue[0];
1299
- params[value] = curValue[1];
1340
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
1300
1341
  }
1301
1342
  break;
1302
1343
  case 'imatch':
@@ -1337,18 +1378,17 @@ export default class PostgreSQLDriver extends NymphDriver {
1337
1378
  const value = `param${++count.i}`;
1338
1379
  curQuery +=
1339
1380
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1381
+ 'EXISTS (SELECT "guid" FROM ' +
1382
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1383
+ ' WHERE "guid"=' +
1340
1384
  ieTable +
1341
- '."guid" IN (SELECT "guid" FROM ' +
1342
- PostgreSQLDriver.escape(
1343
- this.prefix + 'comparisons_' + etype
1344
- ) +
1345
- ' WHERE "name"=@' +
1385
+ '."guid" AND "name"=@' +
1346
1386
  name +
1347
1387
  ' AND "string" ~* @' +
1348
1388
  value +
1349
1389
  ')';
1350
1390
  params[name] = curValue[0];
1351
- params[value] = curValue[1];
1391
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
1352
1392
  }
1353
1393
  break;
1354
1394
  case 'like':
@@ -1389,18 +1429,17 @@ export default class PostgreSQLDriver extends NymphDriver {
1389
1429
  const value = `param${++count.i}`;
1390
1430
  curQuery +=
1391
1431
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1432
+ 'EXISTS (SELECT "guid" FROM ' +
1433
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1434
+ ' WHERE "guid"=' +
1392
1435
  ieTable +
1393
- '."guid" IN (SELECT "guid" FROM ' +
1394
- PostgreSQLDriver.escape(
1395
- this.prefix + 'comparisons_' + etype
1396
- ) +
1397
- ' WHERE "name"=@' +
1436
+ '."guid" AND "name"=@' +
1398
1437
  name +
1399
1438
  ' AND "string" LIKE @' +
1400
1439
  value +
1401
1440
  ')';
1402
1441
  params[name] = curValue[0];
1403
- params[value] = curValue[1];
1442
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
1404
1443
  }
1405
1444
  break;
1406
1445
  case 'ilike':
@@ -1441,18 +1480,17 @@ export default class PostgreSQLDriver extends NymphDriver {
1441
1480
  const value = `param${++count.i}`;
1442
1481
  curQuery +=
1443
1482
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1483
+ 'EXISTS (SELECT "guid" FROM ' +
1484
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1485
+ ' WHERE "guid"=' +
1444
1486
  ieTable +
1445
- '."guid" IN (SELECT "guid" FROM ' +
1446
- PostgreSQLDriver.escape(
1447
- this.prefix + 'comparisons_' + etype
1448
- ) +
1449
- ' WHERE "name"=@' +
1487
+ '."guid" AND "name"=@' +
1450
1488
  name +
1451
1489
  ' AND "string" ILIKE @' +
1452
1490
  value +
1453
1491
  ')';
1454
1492
  params[name] = curValue[0];
1455
- params[value] = curValue[1];
1493
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
1456
1494
  }
1457
1495
  break;
1458
1496
  case 'gt':
@@ -1493,12 +1531,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1493
1531
  const value = `param${++count.i}`;
1494
1532
  curQuery +=
1495
1533
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1534
+ 'EXISTS (SELECT "guid" FROM ' +
1535
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1536
+ ' WHERE "guid"=' +
1496
1537
  ieTable +
1497
- '."guid" IN (SELECT "guid" FROM ' +
1498
- PostgreSQLDriver.escape(
1499
- this.prefix + 'comparisons_' + etype
1500
- ) +
1501
- ' WHERE "name"=@' +
1538
+ '."guid" AND "name"=@' +
1502
1539
  name +
1503
1540
  ' AND "number">@' +
1504
1541
  value +
@@ -1547,12 +1584,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1547
1584
  const value = `param${++count.i}`;
1548
1585
  curQuery +=
1549
1586
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1587
+ 'EXISTS (SELECT "guid" FROM ' +
1588
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1589
+ ' WHERE "guid"=' +
1550
1590
  ieTable +
1551
- '."guid" IN (SELECT "guid" FROM ' +
1552
- PostgreSQLDriver.escape(
1553
- this.prefix + 'comparisons_' + etype
1554
- ) +
1555
- ' WHERE "name"=@' +
1591
+ '."guid" AND "name"=@' +
1556
1592
  name +
1557
1593
  ' AND "number">=@' +
1558
1594
  value +
@@ -1601,12 +1637,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1601
1637
  const value = `param${++count.i}`;
1602
1638
  curQuery +=
1603
1639
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1640
+ 'EXISTS (SELECT "guid" FROM ' +
1641
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1642
+ ' WHERE "guid"=' +
1604
1643
  ieTable +
1605
- '."guid" IN (SELECT "guid" FROM ' +
1606
- PostgreSQLDriver.escape(
1607
- this.prefix + 'comparisons_' + etype
1608
- ) +
1609
- ' WHERE "name"=@' +
1644
+ '."guid" AND "name"=@' +
1610
1645
  name +
1611
1646
  ' AND "number"<@' +
1612
1647
  value +
@@ -1655,12 +1690,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1655
1690
  const value = `param${++count.i}`;
1656
1691
  curQuery +=
1657
1692
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1693
+ 'EXISTS (SELECT "guid" FROM ' +
1694
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1695
+ ' WHERE "guid"=' +
1658
1696
  ieTable +
1659
- '."guid" IN (SELECT "guid" FROM ' +
1660
- PostgreSQLDriver.escape(
1661
- this.prefix + 'comparisons_' + etype
1662
- ) +
1663
- ' WHERE "name"=@' +
1697
+ '."guid" AND "name"=@' +
1664
1698
  name +
1665
1699
  ' AND "number"<=@' +
1666
1700
  value +
@@ -1688,10 +1722,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1688
1722
  const guid = `param${++count.i}`;
1689
1723
  curQuery +=
1690
1724
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1691
- ieTable +
1692
- '."guid" IN (SELECT "guid" FROM ' +
1725
+ 'EXISTS (SELECT "guid" FROM ' +
1693
1726
  PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1694
- ' WHERE "name"=@' +
1727
+ ' WHERE "guid"=' +
1728
+ ieTable +
1729
+ '."guid" AND "name"=@' +
1695
1730
  name +
1696
1731
  ' AND "reference"=decode(@' +
1697
1732
  guid +
@@ -1709,7 +1744,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1709
1744
  params,
1710
1745
  true,
1711
1746
  tableSuffix,
1712
- etypes
1747
+ etypes,
1713
1748
  );
1714
1749
  if (curQuery) {
1715
1750
  curQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -1722,9 +1757,10 @@ export default class PostgreSQLDriver extends NymphDriver {
1722
1757
  break;
1723
1758
  case 'qref':
1724
1759
  case '!qref':
1760
+ const referenceTableSuffix = makeTableSuffix();
1725
1761
  const [qrefOptions, ...qrefSelectors] = curValue[1] as [
1726
1762
  Options,
1727
- ...FormattedSelector[]
1763
+ ...FormattedSelector[],
1728
1764
  ];
1729
1765
  const QrefEntityClass = qrefOptions.class as EntityConstructor;
1730
1766
  etypes.push(QrefEntityClass.ETYPE);
@@ -1736,7 +1772,8 @@ export default class PostgreSQLDriver extends NymphDriver {
1736
1772
  params,
1737
1773
  false,
1738
1774
  makeTableSuffix(),
1739
- etypes
1775
+ etypes,
1776
+ 'r' + referenceTableSuffix + '."reference"',
1740
1777
  );
1741
1778
  if (curQuery) {
1742
1779
  curQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -1744,12 +1781,19 @@ export default class PostgreSQLDriver extends NymphDriver {
1744
1781
  const qrefName = `param${++count.i}`;
1745
1782
  curQuery +=
1746
1783
  (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1747
- ieTable +
1748
- '."guid" IN (SELECT "guid" FROM ' +
1784
+ 'EXISTS (SELECT "guid" FROM ' +
1749
1785
  PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1750
- ' WHERE "name"=@' +
1786
+ ' r' +
1787
+ referenceTableSuffix +
1788
+ ' WHERE r' +
1789
+ referenceTableSuffix +
1790
+ '."guid"=' +
1791
+ ieTable +
1792
+ '."guid" AND r' +
1793
+ referenceTableSuffix +
1794
+ '."name"=@' +
1751
1795
  qrefName +
1752
- ' AND "reference" IN (' +
1796
+ ' AND EXISTS (' +
1753
1797
  qrefQuery.query +
1754
1798
  '))';
1755
1799
  params[qrefName] = curValue[0];
@@ -1757,22 +1801,42 @@ export default class PostgreSQLDriver extends NymphDriver {
1757
1801
  }
1758
1802
  }
1759
1803
  return curQuery;
1760
- }
1804
+ },
1761
1805
  );
1762
1806
 
1763
1807
  let sortBy: string;
1808
+ let sortByInner: string;
1809
+ let sortJoin = '';
1810
+ let sortJoinInner = '';
1811
+ const order = options.reverse ? ' DESC' : '';
1764
1812
  switch (sort) {
1765
1813
  case 'mdate':
1766
- sortBy = '"mdate"';
1814
+ sortBy = `${eTable}."mdate"${order}`;
1815
+ sortByInner = `${ieTable}."mdate"${order}`;
1767
1816
  break;
1768
1817
  case 'cdate':
1818
+ sortBy = `${eTable}."cdate"${order}`;
1819
+ sortByInner = `${ieTable}."cdate"${order}`;
1820
+ break;
1769
1821
  default:
1770
- sortBy = '"cdate"';
1822
+ const name = `param${++count.i}`;
1823
+ sortJoin = `LEFT JOIN (
1824
+ SELECT "guid", "string", "number"
1825
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'data_' + etype)}
1826
+ WHERE "name"=@${name}
1827
+ ORDER BY "number"${order}, "string"${order}
1828
+ ) ${sTable} ON ${eTable}."guid"=${sTable}."guid"`;
1829
+ sortJoinInner = `LEFT JOIN (
1830
+ SELECT "guid", "string", "number"
1831
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'data_' + etype)}
1832
+ WHERE "name"=@${name}
1833
+ ORDER BY "number"${order}, "string"${order}
1834
+ ) ${sTable} ON ${ieTable}."guid"=${sTable}."guid"`;
1835
+ sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
1836
+ sortByInner = sortBy;
1837
+ params[name] = sort;
1771
1838
  break;
1772
1839
  }
1773
- if (options.reverse) {
1774
- sortBy += ' DESC';
1775
- }
1776
1840
 
1777
1841
  let query: string;
1778
1842
  if (queryParts.length) {
@@ -1782,31 +1846,34 @@ export default class PostgreSQLDriver extends NymphDriver {
1782
1846
  let limit = '';
1783
1847
  if ('limit' in options) {
1784
1848
  limit = ` LIMIT ${Math.floor(
1785
- isNaN(Number(options.limit)) ? 0 : Number(options.limit)
1849
+ isNaN(Number(options.limit)) ? 0 : Number(options.limit),
1786
1850
  )}`;
1787
1851
  }
1788
1852
  let offset = '';
1789
1853
  if ('offset' in options) {
1790
1854
  offset = ` OFFSET ${Math.floor(
1791
- isNaN(Number(options.offset)) ? 0 : Number(options.offset)
1855
+ isNaN(Number(options.offset)) ? 0 : Number(options.offset),
1792
1856
  )}`;
1793
1857
  }
1794
1858
  const whereClause = queryParts.join(') AND (');
1859
+ const guidClause = guidSelector
1860
+ ? `${ieTable}."guid"=${guidSelector} AND `
1861
+ : '';
1795
1862
  if (options.return === 'count') {
1796
1863
  if (limit || offset) {
1797
1864
  query = `SELECT COUNT(${countTable}."guid") AS "count" FROM (
1798
- SELECT COUNT(${ieTable}."guid") AS "guid"
1865
+ SELECT ${ieTable}."guid" AS "guid"
1799
1866
  FROM ${PostgreSQLDriver.escape(
1800
- `${this.prefix}entities_${etype}`
1867
+ `${this.prefix}entities_${etype}`,
1801
1868
  )} ${ieTable}
1802
- WHERE (${whereClause})${limit}${offset}
1869
+ WHERE ${guidClause}(${whereClause})${limit}${offset}
1803
1870
  ) ${countTable}`;
1804
1871
  } else {
1805
1872
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1806
1873
  FROM ${PostgreSQLDriver.escape(
1807
- `${this.prefix}entities_${etype}`
1874
+ `${this.prefix}entities_${etype}`,
1808
1875
  )} ${ieTable}
1809
- WHERE (${whereClause})`;
1876
+ WHERE ${guidClause}(${whereClause})`;
1810
1877
  }
1811
1878
  } else if (options.return === 'guid') {
1812
1879
  const guidColumn =
@@ -1815,10 +1882,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1815
1882
  : `${ieTable}."guid"`;
1816
1883
  query = `SELECT ${guidColumn} AS "guid"
1817
1884
  FROM ${PostgreSQLDriver.escape(
1818
- `${this.prefix}entities_${etype}`
1885
+ `${this.prefix}entities_${etype}`,
1819
1886
  )} ${ieTable}
1820
- WHERE (${whereClause})
1821
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1887
+ ${sortJoinInner}
1888
+ WHERE ${guidClause}(${whereClause})
1889
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1822
1890
  } else {
1823
1891
  query = `SELECT
1824
1892
  encode(${eTable}."guid", 'hex') AS "guid",
@@ -1827,26 +1895,26 @@ export default class PostgreSQLDriver extends NymphDriver {
1827
1895
  ${eTable}."mdate",
1828
1896
  ${dTable}."name",
1829
1897
  ${dTable}."value",
1830
- ${cTable}."string",
1831
- ${cTable}."number"
1898
+ ${dTable}."json",
1899
+ ${dTable}."string",
1900
+ ${dTable}."number"
1832
1901
  FROM ${PostgreSQLDriver.escape(
1833
- `${this.prefix}entities_${etype}`
1902
+ `${this.prefix}entities_${etype}`,
1834
1903
  )} ${eTable}
1835
1904
  LEFT JOIN ${PostgreSQLDriver.escape(
1836
- `${this.prefix}data_${etype}`
1905
+ `${this.prefix}data_${etype}`,
1837
1906
  )} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1838
- INNER JOIN ${PostgreSQLDriver.escape(
1839
- `${this.prefix}comparisons_${etype}`
1840
- )} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1907
+ ${sortJoin}
1841
1908
  INNER JOIN (
1842
1909
  SELECT ${ieTable}."guid"
1843
1910
  FROM ${PostgreSQLDriver.escape(
1844
- `${this.prefix}entities_${etype}`
1911
+ `${this.prefix}entities_${etype}`,
1845
1912
  )} ${ieTable}
1846
- WHERE (${whereClause})
1847
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1913
+ ${sortJoinInner}
1914
+ WHERE ${guidClause}(${whereClause})
1915
+ ORDER BY ${sortByInner}${limit}${offset}
1848
1916
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1849
- ORDER BY ${eTable}.${sortBy}`;
1917
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1850
1918
  }
1851
1919
  }
1852
1920
  } else {
@@ -1856,28 +1924,31 @@ export default class PostgreSQLDriver extends NymphDriver {
1856
1924
  let limit = '';
1857
1925
  if ('limit' in options) {
1858
1926
  limit = ` LIMIT ${Math.floor(
1859
- isNaN(Number(options.limit)) ? 0 : Number(options.limit)
1927
+ isNaN(Number(options.limit)) ? 0 : Number(options.limit),
1860
1928
  )}`;
1861
1929
  }
1862
1930
  let offset = '';
1863
1931
  if ('offset' in options) {
1864
1932
  offset = ` OFFSET ${Math.floor(
1865
- isNaN(Number(options.offset)) ? 0 : Number(options.offset)
1933
+ isNaN(Number(options.offset)) ? 0 : Number(options.offset),
1866
1934
  )}`;
1867
1935
  }
1936
+ const guidClause = guidSelector
1937
+ ? ` WHERE ${ieTable}."guid"=${guidSelector}`
1938
+ : '';
1868
1939
  if (options.return === 'count') {
1869
1940
  if (limit || offset) {
1870
1941
  query = `SELECT COUNT(${countTable}."guid") AS "count" FROM (
1871
- SELECT COUNT(${ieTable}."guid") AS "guid"
1942
+ SELECT ${ieTable}."guid" AS "guid"
1872
1943
  FROM ${PostgreSQLDriver.escape(
1873
- `${this.prefix}entities_${etype}`
1874
- )} ${ieTable}${limit}${offset}
1944
+ `${this.prefix}entities_${etype}`,
1945
+ )} ${ieTable}${guidClause}${limit}${offset}
1875
1946
  ) ${countTable}`;
1876
1947
  } else {
1877
1948
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1878
1949
  FROM ${PostgreSQLDriver.escape(
1879
- `${this.prefix}entities_${etype}`
1880
- )} ${ieTable}`;
1950
+ `${this.prefix}entities_${etype}`,
1951
+ )} ${ieTable}${guidClause}`;
1881
1952
  }
1882
1953
  } else if (options.return === 'guid') {
1883
1954
  const guidColumn =
@@ -1886,9 +1957,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1886
1957
  : `${ieTable}."guid"`;
1887
1958
  query = `SELECT ${guidColumn} AS "guid"
1888
1959
  FROM ${PostgreSQLDriver.escape(
1889
- `${this.prefix}entities_${etype}`
1960
+ `${this.prefix}entities_${etype}`,
1890
1961
  )} ${ieTable}
1891
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1962
+ ${sortJoinInner}
1963
+ ${guidClause}
1964
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1892
1965
  } else {
1893
1966
  if (limit || offset) {
1894
1967
  query = `SELECT
@@ -1898,25 +1971,26 @@ export default class PostgreSQLDriver extends NymphDriver {
1898
1971
  ${eTable}."mdate",
1899
1972
  ${dTable}."name",
1900
1973
  ${dTable}."value",
1901
- ${cTable}."string",
1902
- ${cTable}."number"
1974
+ ${dTable}."json",
1975
+ ${dTable}."string",
1976
+ ${dTable}."number"
1903
1977
  FROM ${PostgreSQLDriver.escape(
1904
- `${this.prefix}entities_${etype}`
1978
+ `${this.prefix}entities_${etype}`,
1905
1979
  )} ${eTable}
1906
1980
  LEFT JOIN ${PostgreSQLDriver.escape(
1907
- `${this.prefix}data_${etype}`
1981
+ `${this.prefix}data_${etype}`,
1908
1982
  )} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1909
- INNER JOIN ${PostgreSQLDriver.escape(
1910
- `${this.prefix}comparisons_${etype}`
1911
- )} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1983
+ ${sortJoin}
1912
1984
  INNER JOIN (
1913
1985
  SELECT ${ieTable}."guid"
1914
1986
  FROM ${PostgreSQLDriver.escape(
1915
- `${this.prefix}entities_${etype}`
1987
+ `${this.prefix}entities_${etype}`,
1916
1988
  )} ${ieTable}
1917
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1989
+ ${sortJoinInner}
1990
+ ${guidClause}
1991
+ ORDER BY ${sortByInner}${limit}${offset}
1918
1992
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1919
- ORDER BY ${eTable}.${sortBy}`;
1993
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1920
1994
  } else {
1921
1995
  query = `SELECT
1922
1996
  encode(${eTable}."guid", 'hex') AS "guid",
@@ -1925,18 +1999,18 @@ export default class PostgreSQLDriver extends NymphDriver {
1925
1999
  ${eTable}."mdate",
1926
2000
  ${dTable}."name",
1927
2001
  ${dTable}."value",
1928
- ${cTable}."string",
1929
- ${cTable}."number"
2002
+ ${dTable}."json",
2003
+ ${dTable}."string",
2004
+ ${dTable}."number"
1930
2005
  FROM ${PostgreSQLDriver.escape(
1931
- `${this.prefix}entities_${etype}`
2006
+ `${this.prefix}entities_${etype}`,
1932
2007
  )} ${eTable}
1933
2008
  LEFT JOIN ${PostgreSQLDriver.escape(
1934
- `${this.prefix}data_${etype}`
2009
+ `${this.prefix}data_${etype}`,
1935
2010
  )} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1936
- INNER JOIN ${PostgreSQLDriver.escape(
1937
- `${this.prefix}comparisons_${etype}`
1938
- )} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1939
- ORDER BY ${eTable}.${sortBy}`;
2011
+ ${sortJoin}
2012
+ ${guidSelector ? `WHERE ${eTable}."guid"=${guidSelector}` : ''}
2013
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1940
2014
  }
1941
2015
  }
1942
2016
  }
@@ -1956,43 +2030,23 @@ export default class PostgreSQLDriver extends NymphDriver {
1956
2030
  protected performQuery(
1957
2031
  options: Options,
1958
2032
  formattedSelectors: FormattedSelector[],
1959
- etype: string
2033
+ etype: string,
1960
2034
  ): {
1961
2035
  result: any;
1962
2036
  } {
1963
2037
  const { query, params, etypes } = this.makeEntityQuery(
1964
2038
  options,
1965
2039
  formattedSelectors,
1966
- etype
2040
+ etype,
1967
2041
  );
1968
2042
  const result = this.queryIter(query, { etypes, params }).then((val) =>
1969
- val[Symbol.iterator]()
2043
+ val[Symbol.iterator](),
1970
2044
  );
1971
2045
  return {
1972
2046
  result,
1973
2047
  };
1974
2048
  }
1975
2049
 
1976
- protected performQuerySync(
1977
- options: Options,
1978
- formattedSelectors: FormattedSelector[],
1979
- etype: string
1980
- ): {
1981
- result: any;
1982
- } {
1983
- const { query, params, etypes } = this.makeEntityQuery(
1984
- options,
1985
- formattedSelectors,
1986
- etype
1987
- );
1988
- const result = (this.queryIterSync(query, { etypes, params }) || [])[
1989
- Symbol.iterator
1990
- ]();
1991
- return {
1992
- result,
1993
- };
1994
- }
1995
-
1996
2050
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
1997
2051
  options: Options<T> & { return: 'count' },
1998
2052
  ...selectors: Selector[]
@@ -2004,17 +2058,17 @@ export default class PostgreSQLDriver extends NymphDriver {
2004
2058
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
2005
2059
  options?: Options<T>,
2006
2060
  ...selectors: Selector[]
2007
- ): Promise<ReturnType<T['factorySync']>[]>;
2061
+ ): Promise<EntityInstanceType<T>[]>;
2008
2062
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
2009
2063
  options: Options<T> = {},
2010
2064
  ...selectors: Selector[]
2011
- ): Promise<ReturnType<T['factorySync']>[] | string[] | number> {
2012
- const { result: resultPromise, process } = this.getEntitesRowLike<T>(
2065
+ ): Promise<EntityInstanceType<T>[] | string[] | number> {
2066
+ const { result: resultPromise, process } = this.getEntitiesRowLike<T>(
2013
2067
  // @ts-ignore: options is correct here.
2014
2068
  options,
2015
2069
  selectors,
2016
- (options, formattedSelectors, etype) =>
2017
- this.performQuery(options, formattedSelectors, etype),
2070
+ ({ options, selectors, etype }) =>
2071
+ this.performQuery(options, selectors, etype),
2018
2072
  () => {
2019
2073
  const next: any = result.next();
2020
2074
  return next.done ? null : next.value;
@@ -2023,7 +2077,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2023
2077
  (row) => Number(row.count),
2024
2078
  (row) => row.guid,
2025
2079
  (row) => ({
2026
- tags: row.tags,
2080
+ tags: row.tags.filter((tag: string) => tag),
2027
2081
  cdate: isNaN(Number(row.cdate)) ? null : Number(row.cdate),
2028
2082
  mdate: isNaN(Number(row.mdate)) ? null : Number(row.mdate),
2029
2083
  }),
@@ -2033,9 +2087,11 @@ export default class PostgreSQLDriver extends NymphDriver {
2033
2087
  row.value === 'N'
2034
2088
  ? JSON.stringify(Number(row.number))
2035
2089
  : row.value === 'S'
2036
- ? JSON.stringify(row.string)
2090
+ ? JSON.stringify(PostgreSQLDriver.unescapeNulls(row.string))
2091
+ : row.value === 'J'
2092
+ ? PostgreSQLDriver.unescapeNullSequences(JSON.stringify(row.json))
2037
2093
  : row.value,
2038
- })
2094
+ }),
2039
2095
  );
2040
2096
 
2041
2097
  const result = await resultPromise;
@@ -2046,252 +2102,228 @@ export default class PostgreSQLDriver extends NymphDriver {
2046
2102
  return value;
2047
2103
  }
2048
2104
 
2049
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2050
- options: Options<T> & { return: 'count' },
2051
- ...selectors: Selector[]
2052
- ): number;
2053
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2054
- options: Options<T> & { return: 'guid' },
2055
- ...selectors: Selector[]
2056
- ): string[];
2057
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2058
- options?: Options<T>,
2059
- ...selectors: Selector[]
2060
- ): ReturnType<T['factorySync']>[];
2061
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2062
- options: Options<T> = {},
2063
- ...selectors: Selector[]
2064
- ): ReturnType<T['factorySync']>[] | string[] | number {
2065
- const { result, process } = this.getEntitesRowLike<T>(
2066
- // @ts-ignore: options is correct here.
2067
- options,
2068
- selectors,
2069
- (options, formattedSelectors, etype) =>
2070
- this.performQuerySync(options, formattedSelectors, etype),
2071
- () => {
2072
- const next: any = result.next();
2073
- return next.done ? null : next.value;
2074
- },
2075
- () => undefined,
2076
- (row) => Number(row.count),
2077
- (row) => row.guid,
2078
- (row) => ({
2079
- tags: row.tags,
2080
- cdate: isNaN(Number(row.cdate)) ? null : Number(row.cdate),
2081
- mdate: isNaN(Number(row.mdate)) ? null : Number(row.mdate),
2082
- }),
2083
- (row) => ({
2084
- name: row.name,
2085
- svalue:
2086
- row.value === 'N'
2087
- ? JSON.stringify(Number(row.number))
2088
- : row.value === 'S'
2089
- ? JSON.stringify(row.string)
2090
- : row.value,
2091
- })
2092
- );
2093
- const value = process();
2094
- if (value instanceof Error) {
2095
- throw value;
2096
- }
2097
- return value;
2098
- }
2099
-
2100
2105
  public async getUID(name: string) {
2101
2106
  if (name == null) {
2102
2107
  throw new InvalidParametersError('Name not given for UID.');
2103
2108
  }
2104
2109
  const result = await this.queryGet(
2105
2110
  `SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(
2106
- `${this.prefix}uids`
2111
+ `${this.prefix}uids`,
2107
2112
  )} WHERE "name"=@name;`,
2108
2113
  {
2109
2114
  params: {
2110
2115
  name: name,
2111
2116
  },
2112
- }
2117
+ },
2113
2118
  );
2114
2119
  return result?.cur_uid == null ? null : Number(result.cur_uid);
2115
2120
  }
2116
2121
 
2117
- public async import(filename: string) {
2122
+ public async importEntity({
2123
+ guid,
2124
+ cdate,
2125
+ mdate,
2126
+ tags,
2127
+ sdata,
2128
+ etype,
2129
+ }: {
2130
+ guid: string;
2131
+ cdate: number;
2132
+ mdate: number;
2133
+ tags: string[];
2134
+ sdata: SerializedEntityData;
2135
+ etype: string;
2136
+ }) {
2118
2137
  try {
2119
- const result = await this.importFromFile(
2120
- filename,
2121
- async (guid, tags, sdata, etype) => {
2122
- await this.queryRun(
2123
- `DELETE FROM ${PostgreSQLDriver.escape(
2124
- `${this.prefix}entities_${etype}`
2125
- )} WHERE "guid"=decode(@guid, 'hex');`,
2126
- {
2127
- etypes: [etype],
2128
- params: {
2129
- guid,
2130
- },
2131
- }
2132
- );
2133
- await this.queryRun(
2138
+ let promises = [];
2139
+ promises.push(
2140
+ this.queryRun(
2141
+ `DELETE FROM ${PostgreSQLDriver.escape(
2142
+ `${this.prefix}entities_${etype}`,
2143
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2144
+ {
2145
+ etypes: [etype],
2146
+ params: {
2147
+ guid,
2148
+ },
2149
+ },
2150
+ ),
2151
+ );
2152
+ promises.push(
2153
+ this.queryRun(
2154
+ `DELETE FROM ${PostgreSQLDriver.escape(
2155
+ `${this.prefix}data_${etype}`,
2156
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2157
+ {
2158
+ etypes: [etype],
2159
+ params: {
2160
+ guid,
2161
+ },
2162
+ },
2163
+ ),
2164
+ );
2165
+ promises.push(
2166
+ this.queryRun(
2167
+ `DELETE FROM ${PostgreSQLDriver.escape(
2168
+ `${this.prefix}references_${etype}`,
2169
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2170
+ {
2171
+ etypes: [etype],
2172
+ params: {
2173
+ guid,
2174
+ },
2175
+ },
2176
+ ),
2177
+ );
2178
+ promises.push(
2179
+ this.queryRun(
2180
+ `DELETE FROM ${PostgreSQLDriver.escape(
2181
+ `${this.prefix}uniques_${etype}`,
2182
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2183
+ {
2184
+ etypes: [etype],
2185
+ params: {
2186
+ guid,
2187
+ },
2188
+ },
2189
+ ),
2190
+ );
2191
+ await Promise.all(promises);
2192
+ promises = [];
2193
+ await this.queryRun(
2194
+ `INSERT INTO ${PostgreSQLDriver.escape(
2195
+ `${this.prefix}entities_${etype}`,
2196
+ )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`,
2197
+ {
2198
+ etypes: [etype],
2199
+ params: {
2200
+ guid,
2201
+ tags,
2202
+ cdate: isNaN(cdate) ? null : cdate,
2203
+ mdate: isNaN(mdate) ? null : mdate,
2204
+ },
2205
+ },
2206
+ );
2207
+ for (const name in sdata) {
2208
+ const value = sdata[name];
2209
+ const uvalue = JSON.parse(value);
2210
+ if (value === undefined) {
2211
+ continue;
2212
+ }
2213
+ const storageValue =
2214
+ typeof uvalue === 'number'
2215
+ ? 'N'
2216
+ : typeof uvalue === 'string'
2217
+ ? 'S'
2218
+ : 'J';
2219
+ const jsonValue =
2220
+ storageValue === 'J'
2221
+ ? PostgreSQLDriver.escapeNullSequences(value)
2222
+ : null;
2223
+ promises.push(
2224
+ this.queryRun(
2134
2225
  `INSERT INTO ${PostgreSQLDriver.escape(
2135
- `${this.prefix}entities_${etype}`
2136
- )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`,
2226
+ `${this.prefix}data_${etype}`,
2227
+ )} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (decode(@guid, 'hex'), @name, @storageValue, @jsonValue, @string, @number, @truthy);`,
2137
2228
  {
2138
2229
  etypes: [etype],
2139
2230
  params: {
2140
2231
  guid,
2141
- tags,
2142
- cdate: isNaN(Number(JSON.parse(sdata.cdate)))
2143
- ? null
2144
- : Number(JSON.parse(sdata.cdate)),
2145
- mdate: isNaN(Number(JSON.parse(sdata.mdate)))
2146
- ? null
2147
- : Number(JSON.parse(sdata.mdate)),
2232
+ name,
2233
+ storageValue,
2234
+ jsonValue,
2235
+ string:
2236
+ storageValue === 'J'
2237
+ ? null
2238
+ : PostgreSQLDriver.escapeNulls(`${uvalue}`),
2239
+ number: isNaN(Number(uvalue)) ? null : Number(uvalue),
2240
+ truthy: !!uvalue,
2148
2241
  },
2149
- }
2150
- );
2151
- const promises = [];
2152
- promises.push(
2153
- this.queryRun(
2154
- `DELETE FROM ${PostgreSQLDriver.escape(
2155
- `${this.prefix}data_${etype}`
2156
- )} WHERE "guid"=decode(@guid, 'hex');`,
2157
- {
2158
- etypes: [etype],
2159
- params: {
2160
- guid,
2161
- },
2162
- }
2163
- )
2164
- );
2165
- promises.push(
2166
- this.queryRun(
2167
- `DELETE FROM ${PostgreSQLDriver.escape(
2168
- `${this.prefix}comparisons_${etype}`
2169
- )} WHERE "guid"=decode(@guid, 'hex');`,
2170
- {
2171
- etypes: [etype],
2172
- params: {
2173
- guid,
2174
- },
2175
- }
2176
- )
2177
- );
2242
+ },
2243
+ ),
2244
+ );
2245
+ const references = this.findReferences(value);
2246
+ for (const reference of references) {
2178
2247
  promises.push(
2179
2248
  this.queryRun(
2180
- `DELETE FROM ${PostgreSQLDriver.escape(
2181
- `${this.prefix}references_${etype}`
2182
- )} WHERE "guid"=decode(@guid, 'hex');`,
2249
+ `INSERT INTO ${PostgreSQLDriver.escape(
2250
+ `${this.prefix}references_${etype}`,
2251
+ )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2183
2252
  {
2184
2253
  etypes: [etype],
2185
2254
  params: {
2186
2255
  guid,
2256
+ name,
2257
+ reference,
2187
2258
  },
2188
- }
2189
- )
2190
- );
2191
- await Promise.all(promises);
2192
- delete sdata.cdate;
2193
- delete sdata.mdate;
2194
- for (const name in sdata) {
2195
- const value = sdata[name];
2196
- const uvalue = JSON.parse(value);
2197
- if (value === undefined) {
2198
- continue;
2199
- }
2200
- const storageValue =
2201
- typeof uvalue === 'number'
2202
- ? 'N'
2203
- : typeof uvalue === 'string'
2204
- ? 'S'
2205
- : value;
2206
- const promises = [];
2207
- promises.push(
2208
- this.queryRun(
2209
- `INSERT INTO ${PostgreSQLDriver.escape(
2210
- `${this.prefix}data_${etype}`
2211
- )} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`,
2212
- {
2213
- etypes: [etype],
2214
- params: {
2215
- guid,
2216
- name,
2217
- storageValue,
2218
- },
2219
- }
2220
- )
2221
- );
2222
- promises.push(
2223
- this.queryRun(
2224
- `INSERT INTO ${PostgreSQLDriver.escape(
2225
- `${this.prefix}comparisons_${etype}`
2226
- )} ("guid", "name", "truthy", "string", "number") VALUES (decode(@guid, 'hex'), @name, @truthy, @string, @number);`,
2227
- {
2228
- etypes: [etype],
2229
- params: {
2230
- guid,
2231
- name,
2232
- truthy: !!uvalue,
2233
- string: `${uvalue}`,
2234
- number: isNaN(Number(uvalue)) ? null : Number(uvalue),
2235
- },
2236
- }
2237
- )
2238
- );
2239
- const references = this.findReferences(value);
2240
- for (const reference of references) {
2241
- promises.push(
2242
- this.queryRun(
2243
- `INSERT INTO ${PostgreSQLDriver.escape(
2244
- `${this.prefix}references_${etype}`
2245
- )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2246
- {
2247
- etypes: [etype],
2248
- params: {
2249
- guid,
2250
- name,
2251
- reference,
2252
- },
2253
- }
2254
- )
2255
- );
2256
- }
2257
- }
2258
- await Promise.all(promises);
2259
- },
2260
- async (name, curUid) => {
2261
- await this.queryRun(
2262
- `DELETE FROM ${PostgreSQLDriver.escape(
2263
- `${this.prefix}uids`
2264
- )} WHERE "name"=@name;`,
2265
- {
2266
- params: {
2267
- name,
2268
2259
  },
2269
- }
2260
+ ),
2270
2261
  );
2271
- await this.queryRun(
2262
+ }
2263
+ }
2264
+ const uniques = await this.nymph
2265
+ .getEntityClassByEtype(etype)
2266
+ .getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
2267
+ for (const unique of uniques) {
2268
+ promises.push(
2269
+ this.queryRun(
2272
2270
  `INSERT INTO ${PostgreSQLDriver.escape(
2273
- `${this.prefix}uids`
2274
- )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2271
+ `${this.prefix}uniques_${etype}`,
2272
+ )} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`,
2275
2273
  {
2274
+ etypes: [etype],
2276
2275
  params: {
2277
- name,
2278
- curUid,
2276
+ guid,
2277
+ unique,
2279
2278
  },
2279
+ },
2280
+ ).catch((e: any) => {
2281
+ if (e instanceof EntityUniqueConstraintError) {
2282
+ this.nymph.config.debugError(
2283
+ 'postgresql',
2284
+ `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2285
+ );
2280
2286
  }
2281
- );
2287
+ return e;
2288
+ }),
2289
+ );
2290
+ }
2291
+ await Promise.all(promises);
2292
+ } catch (e: any) {
2293
+ this.nymph.config.debugError('postgresql', `Import entity error: "${e}"`);
2294
+ throw e;
2295
+ }
2296
+ }
2297
+
2298
+ public async importUID({ name, value }: { name: string; value: number }) {
2299
+ try {
2300
+ await this.internalTransaction(`nymph-import-uid-${name}`);
2301
+ await this.queryRun(
2302
+ `DELETE FROM ${PostgreSQLDriver.escape(
2303
+ `${this.prefix}uids`,
2304
+ )} WHERE "name"=@name;`,
2305
+ {
2306
+ params: {
2307
+ name,
2308
+ },
2282
2309
  },
2283
- async () => {
2284
- await this.internalTransaction('nymph-import');
2310
+ );
2311
+ await this.queryRun(
2312
+ `INSERT INTO ${PostgreSQLDriver.escape(
2313
+ `${this.prefix}uids`,
2314
+ )} ("name", "cur_uid") VALUES (@name, @value);`,
2315
+ {
2316
+ params: {
2317
+ name,
2318
+ value,
2319
+ },
2285
2320
  },
2286
- async () => {
2287
- await this.commit('nymph-import');
2288
- }
2289
2321
  );
2290
-
2291
- return result;
2322
+ await this.commit(`nymph-import-uid-${name}`);
2292
2323
  } catch (e: any) {
2293
- await this.rollback('nymph-import');
2294
- return false;
2324
+ this.nymph.config.debugError('postgresql', `Import UID error: "${e}"`);
2325
+ await this.rollback(`nymph-import-uid-${name}`);
2326
+ throw e;
2295
2327
  }
2296
2328
  }
2297
2329
 
@@ -2300,52 +2332,54 @@ export default class PostgreSQLDriver extends NymphDriver {
2300
2332
  throw new InvalidParametersError('Name not given for UID.');
2301
2333
  }
2302
2334
  await this.internalTransaction('nymph-newuid');
2335
+ let curUid: number | undefined = undefined;
2303
2336
  try {
2304
2337
  const lock = await this.queryGet(
2305
2338
  `SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(
2306
- `${this.prefix}uids`
2339
+ `${this.prefix}uids`,
2307
2340
  )} WHERE "name"=@name FOR UPDATE;`,
2308
2341
  {
2309
2342
  params: {
2310
2343
  name,
2311
2344
  },
2312
- }
2345
+ },
2313
2346
  );
2314
- let curUid: number | undefined =
2315
- lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
2347
+ curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
2316
2348
  if (curUid == null) {
2317
2349
  curUid = 1;
2318
2350
  await this.queryRun(
2319
2351
  `INSERT INTO ${PostgreSQLDriver.escape(
2320
- `${this.prefix}uids`
2352
+ `${this.prefix}uids`,
2321
2353
  )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2322
2354
  {
2323
2355
  params: {
2324
2356
  name,
2325
2357
  curUid,
2326
2358
  },
2327
- }
2359
+ },
2328
2360
  );
2329
2361
  } else {
2330
2362
  curUid++;
2331
2363
  await this.queryRun(
2332
2364
  `UPDATE ${PostgreSQLDriver.escape(
2333
- `${this.prefix}uids`
2365
+ `${this.prefix}uids`,
2334
2366
  )} SET "cur_uid"=@curUid WHERE "name"=@name;`,
2335
2367
  {
2336
2368
  params: {
2337
2369
  name,
2338
2370
  curUid,
2339
2371
  },
2340
- }
2372
+ },
2341
2373
  );
2342
2374
  }
2343
- await this.commit('nymph-newuid');
2344
- return curUid;
2345
2375
  } catch (e: any) {
2376
+ this.nymph.config.debugError('postgresql', `New UID error: "${e}"`);
2346
2377
  await this.rollback('nymph-newuid');
2347
2378
  throw e;
2348
2379
  }
2380
+
2381
+ await this.commit('nymph-newuid');
2382
+ return curUid;
2349
2383
  }
2350
2384
 
2351
2385
  public async renameUID(oldName: string, newName: string) {
@@ -2354,14 +2388,14 @@ export default class PostgreSQLDriver extends NymphDriver {
2354
2388
  }
2355
2389
  await this.queryRun(
2356
2390
  `UPDATE ${PostgreSQLDriver.escape(
2357
- `${this.prefix}uids`
2391
+ `${this.prefix}uids`,
2358
2392
  )} SET "name"=@newName WHERE "name"=@oldName;`,
2359
2393
  {
2360
2394
  params: {
2361
2395
  newName,
2362
2396
  oldName,
2363
2397
  },
2364
- }
2398
+ },
2365
2399
  );
2366
2400
  return true;
2367
2401
  }
@@ -2369,7 +2403,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2369
2403
  public async rollback(name: string) {
2370
2404
  if (name == null || typeof name !== 'string' || name.length === 0) {
2371
2405
  throw new InvalidParametersError(
2372
- 'Transaction rollback attempted without a name.'
2406
+ 'Transaction rollback attempted without a name.',
2373
2407
  );
2374
2408
  }
2375
2409
  if (!this.transaction || this.transaction.count === 0) {
@@ -2377,7 +2411,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2377
2411
  return true;
2378
2412
  }
2379
2413
  await this.queryRun(
2380
- `ROLLBACK TO SAVEPOINT ${PostgreSQLDriver.escape(name)};`
2414
+ `ROLLBACK TO SAVEPOINT ${PostgreSQLDriver.escape(name)};`,
2381
2415
  );
2382
2416
  this.transaction.count--;
2383
2417
  if (this.transaction.count === 0) {
@@ -2394,12 +2428,13 @@ export default class PostgreSQLDriver extends NymphDriver {
2394
2428
  guid: string,
2395
2429
  data: EntityData,
2396
2430
  sdata: SerializedEntityData,
2397
- etype: string
2431
+ uniques: string[],
2432
+ etype: string,
2398
2433
  ) => {
2399
2434
  const runInsertQuery = async (
2400
2435
  name: string,
2401
2436
  value: any,
2402
- svalue: string
2437
+ svalue: string,
2403
2438
  ) => {
2404
2439
  if (value === undefined) {
2405
2440
  return;
@@ -2409,46 +2444,40 @@ export default class PostgreSQLDriver extends NymphDriver {
2409
2444
  ? 'N'
2410
2445
  : typeof value === 'string'
2411
2446
  ? 'S'
2412
- : svalue;
2447
+ : 'J';
2448
+ const jsonValue =
2449
+ storageValue === 'J'
2450
+ ? PostgreSQLDriver.escapeNullSequences(svalue)
2451
+ : null;
2413
2452
  const promises = [];
2414
2453
  promises.push(
2415
2454
  this.queryRun(
2416
2455
  `INSERT INTO ${PostgreSQLDriver.escape(
2417
- `${this.prefix}data_${etype}`
2418
- )} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`,
2456
+ `${this.prefix}data_${etype}`,
2457
+ )} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (decode(@guid, 'hex'), @name, @storageValue, @jsonValue, @string, @number, @truthy);`,
2419
2458
  {
2420
2459
  etypes: [etype],
2421
2460
  params: {
2422
2461
  guid,
2423
2462
  name,
2424
2463
  storageValue,
2425
- },
2426
- }
2427
- )
2428
- );
2429
- promises.push(
2430
- this.queryRun(
2431
- `INSERT INTO ${PostgreSQLDriver.escape(
2432
- `${this.prefix}comparisons_${etype}`
2433
- )} ("guid", "name", "truthy", "string", "number") VALUES (decode(@guid, 'hex'), @name, @truthy, @string, @number);`,
2434
- {
2435
- etypes: [etype],
2436
- params: {
2437
- guid,
2438
- name,
2439
- truthy: !!value,
2440
- string: `${value}`,
2464
+ jsonValue,
2465
+ string:
2466
+ storageValue === 'J'
2467
+ ? null
2468
+ : PostgreSQLDriver.escapeNulls(`${value}`),
2441
2469
  number: isNaN(Number(value)) ? null : Number(value),
2470
+ truthy: !!value,
2442
2471
  },
2443
- }
2444
- )
2472
+ },
2473
+ ),
2445
2474
  );
2446
2475
  const references = this.findReferences(svalue);
2447
2476
  for (const reference of references) {
2448
2477
  promises.push(
2449
2478
  this.queryRun(
2450
2479
  `INSERT INTO ${PostgreSQLDriver.escape(
2451
- `${this.prefix}references_${etype}`
2480
+ `${this.prefix}references_${etype}`,
2452
2481
  )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2453
2482
  {
2454
2483
  etypes: [etype],
@@ -2457,12 +2486,36 @@ export default class PostgreSQLDriver extends NymphDriver {
2457
2486
  name,
2458
2487
  reference,
2459
2488
  },
2460
- }
2461
- )
2489
+ },
2490
+ ),
2462
2491
  );
2463
2492
  }
2464
2493
  await Promise.all(promises);
2465
2494
  };
2495
+ for (const unique of uniques) {
2496
+ try {
2497
+ await this.queryRun(
2498
+ `INSERT INTO ${PostgreSQLDriver.escape(
2499
+ `${this.prefix}uniques_${etype}`,
2500
+ )} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`,
2501
+ {
2502
+ etypes: [etype],
2503
+ params: {
2504
+ guid,
2505
+ unique,
2506
+ },
2507
+ },
2508
+ );
2509
+ } catch (e: any) {
2510
+ if (e instanceof EntityUniqueConstraintError) {
2511
+ this.nymph.config.debugError(
2512
+ 'postgresql',
2513
+ `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2514
+ );
2515
+ }
2516
+ throw e;
2517
+ }
2518
+ }
2466
2519
  for (const name in data) {
2467
2520
  await runInsertQuery(name, data[name], JSON.stringify(data[name]));
2468
2521
  }
@@ -2470,13 +2523,20 @@ export default class PostgreSQLDriver extends NymphDriver {
2470
2523
  await runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
2471
2524
  }
2472
2525
  };
2526
+ let inTransaction = false;
2473
2527
  try {
2474
2528
  const result = await this.saveEntityRowLike(
2475
2529
  entity,
2476
- async (_entity, guid, tags, data, sdata, cdate, etype) => {
2530
+ async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
2531
+ if (
2532
+ Object.keys(data).length === 0 &&
2533
+ Object.keys(sdata).length === 0
2534
+ ) {
2535
+ return false;
2536
+ }
2477
2537
  await this.queryRun(
2478
2538
  `INSERT INTO ${PostgreSQLDriver.escape(
2479
- `${this.prefix}entities_${etype}`
2539
+ `${this.prefix}entities_${etype}`,
2480
2540
  )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @cdate);`,
2481
2541
  {
2482
2542
  etypes: [etype],
@@ -2485,69 +2545,75 @@ export default class PostgreSQLDriver extends NymphDriver {
2485
2545
  tags,
2486
2546
  cdate,
2487
2547
  },
2488
- }
2548
+ },
2489
2549
  );
2490
- await insertData(guid, data, sdata, etype);
2550
+ await insertData(guid, data, sdata, uniques, etype);
2491
2551
  return true;
2492
2552
  },
2493
- async (entity, guid, tags, data, sdata, mdate, etype) => {
2553
+ async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
2554
+ if (
2555
+ Object.keys(data).length === 0 &&
2556
+ Object.keys(sdata).length === 0
2557
+ ) {
2558
+ return false;
2559
+ }
2494
2560
  const promises = [];
2495
2561
  promises.push(
2496
2562
  this.queryRun(
2497
2563
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2498
- `${this.prefix}entities_${etype}`
2564
+ `${this.prefix}entities_${etype}`,
2499
2565
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2500
2566
  {
2501
2567
  etypes: [etype],
2502
2568
  params: {
2503
2569
  guid,
2504
2570
  },
2505
- }
2506
- )
2571
+ },
2572
+ ),
2507
2573
  );
2508
2574
  promises.push(
2509
2575
  this.queryRun(
2510
2576
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2511
- `${this.prefix}data_${etype}`
2577
+ `${this.prefix}data_${etype}`,
2512
2578
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2513
2579
  {
2514
2580
  etypes: [etype],
2515
2581
  params: {
2516
2582
  guid,
2517
2583
  },
2518
- }
2519
- )
2584
+ },
2585
+ ),
2520
2586
  );
2521
2587
  promises.push(
2522
2588
  this.queryRun(
2523
2589
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2524
- `${this.prefix}comparisons_${etype}`
2590
+ `${this.prefix}references_${etype}`,
2525
2591
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2526
2592
  {
2527
2593
  etypes: [etype],
2528
2594
  params: {
2529
2595
  guid,
2530
2596
  },
2531
- }
2532
- )
2597
+ },
2598
+ ),
2533
2599
  );
2534
2600
  promises.push(
2535
2601
  this.queryRun(
2536
2602
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2537
- `${this.prefix}references_${etype}`
2603
+ `${this.prefix}uniques_${etype}`,
2538
2604
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2539
2605
  {
2540
2606
  etypes: [etype],
2541
2607
  params: {
2542
2608
  guid,
2543
2609
  },
2544
- }
2545
- )
2610
+ },
2611
+ ),
2546
2612
  );
2547
2613
  await Promise.all(promises);
2548
2614
  const info = await this.queryRun(
2549
2615
  `UPDATE ${PostgreSQLDriver.escape(
2550
- `${this.prefix}entities_${etype}`
2616
+ `${this.prefix}entities_${etype}`,
2551
2617
  )} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=decode(@guid, 'hex') AND "mdate" <= @emdate;`,
2552
2618
  {
2553
2619
  etypes: [etype],
@@ -2557,7 +2623,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2557
2623
  guid,
2558
2624
  emdate: isNaN(Number(entity.mdate)) ? 0 : Number(entity.mdate),
2559
2625
  },
2560
- }
2626
+ },
2561
2627
  );
2562
2628
  let success = false;
2563
2629
  if (info.rowCount === 1) {
@@ -2565,64 +2631,71 @@ export default class PostgreSQLDriver extends NymphDriver {
2565
2631
  promises.push(
2566
2632
  this.queryRun(
2567
2633
  `DELETE FROM ${PostgreSQLDriver.escape(
2568
- `${this.prefix}data_${etype}`
2634
+ `${this.prefix}data_${etype}`,
2569
2635
  )} WHERE "guid"=decode(@guid, 'hex');`,
2570
2636
  {
2571
2637
  etypes: [etype],
2572
2638
  params: {
2573
2639
  guid,
2574
2640
  },
2575
- }
2576
- )
2641
+ },
2642
+ ),
2577
2643
  );
2578
2644
  promises.push(
2579
2645
  this.queryRun(
2580
2646
  `DELETE FROM ${PostgreSQLDriver.escape(
2581
- `${this.prefix}comparisons_${etype}`
2647
+ `${this.prefix}references_${etype}`,
2582
2648
  )} WHERE "guid"=decode(@guid, 'hex');`,
2583
2649
  {
2584
2650
  etypes: [etype],
2585
2651
  params: {
2586
2652
  guid,
2587
2653
  },
2588
- }
2589
- )
2654
+ },
2655
+ ),
2590
2656
  );
2591
2657
  promises.push(
2592
2658
  this.queryRun(
2593
2659
  `DELETE FROM ${PostgreSQLDriver.escape(
2594
- `${this.prefix}references_${etype}`
2660
+ `${this.prefix}uniques_${etype}`,
2595
2661
  )} WHERE "guid"=decode(@guid, 'hex');`,
2596
2662
  {
2597
2663
  etypes: [etype],
2598
2664
  params: {
2599
2665
  guid,
2600
2666
  },
2601
- }
2602
- )
2667
+ },
2668
+ ),
2603
2669
  );
2604
2670
  await Promise.all(promises);
2605
- await insertData(guid, data, sdata, etype);
2671
+ await insertData(guid, data, sdata, uniques, etype);
2606
2672
  success = true;
2607
2673
  }
2608
2674
  return success;
2609
2675
  },
2610
2676
  async () => {
2611
2677
  await this.internalTransaction('nymph-save');
2678
+ inTransaction = true;
2612
2679
  },
2613
2680
  async (success) => {
2614
- if (success) {
2615
- await this.commit('nymph-save');
2616
- } else {
2617
- await this.rollback('nymph-save');
2681
+ if (inTransaction) {
2682
+ inTransaction = false;
2683
+ if (success) {
2684
+ await this.commit('nymph-save');
2685
+ } else {
2686
+ await this.rollback('nymph-save');
2687
+ }
2618
2688
  }
2619
2689
  return success;
2620
- }
2690
+ },
2621
2691
  );
2622
2692
 
2623
2693
  return result;
2624
2694
  } catch (e: any) {
2625
- await this.rollback('nymph-save');
2695
+ this.nymph.config.debugError('postgresql', `Save entity error: "${e}"`);
2696
+ if (inTransaction) {
2697
+ await this.rollback('nymph-save');
2698
+ }
2626
2699
  throw e;
2627
2700
  }
2628
2701
  }
@@ -2635,38 +2708,39 @@ export default class PostgreSQLDriver extends NymphDriver {
2635
2708
  try {
2636
2709
  await this.queryRun(
2637
2710
  `DELETE FROM ${PostgreSQLDriver.escape(
2638
- `${this.prefix}uids`
2711
+ `${this.prefix}uids`,
2639
2712
  )} WHERE "name"=@name;`,
2640
2713
  {
2641
2714
  params: {
2642
2715
  name,
2643
2716
  curUid,
2644
2717
  },
2645
- }
2718
+ },
2646
2719
  );
2647
2720
  await this.queryRun(
2648
2721
  `INSERT INTO ${PostgreSQLDriver.escape(
2649
- `${this.prefix}uids`
2722
+ `${this.prefix}uids`,
2650
2723
  )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2651
2724
  {
2652
2725
  params: {
2653
2726
  name,
2654
2727
  curUid,
2655
2728
  },
2656
- }
2729
+ },
2657
2730
  );
2658
- await this.commit('nymph-setuid');
2659
- return true;
2660
2731
  } catch (e: any) {
2661
2732
  await this.rollback('nymph-setuid');
2662
2733
  throw e;
2663
2734
  }
2735
+
2736
+ await this.commit('nymph-setuid');
2737
+ return true;
2664
2738
  }
2665
2739
 
2666
- private async internalTransaction(name: string) {
2740
+ protected async internalTransaction(name: string) {
2667
2741
  if (name == null || typeof name !== 'string' || name.length === 0) {
2668
2742
  throw new InvalidParametersError(
2669
- 'Transaction start attempted without a name.'
2743
+ 'Transaction start attempted without a name.',
2670
2744
  );
2671
2745
  }
2672
2746
 
@@ -2688,7 +2762,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2688
2762
  }
2689
2763
 
2690
2764
  public async startTransaction(name: string) {
2691
- const inTransaction = this.inTransaction();
2765
+ const inTransaction = await this.inTransaction();
2692
2766
  const transaction = await this.internalTransaction(name);
2693
2767
  if (!inTransaction) {
2694
2768
  this.transaction = null;
@@ -2699,4 +2773,29 @@ export default class PostgreSQLDriver extends NymphDriver {
2699
2773
 
2700
2774
  return nymph;
2701
2775
  }
2776
+
2777
+ public async needsMigration(): Promise<boolean> {
2778
+ const table = await this.queryGet(
2779
+ 'SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @prefix LIMIT 1;',
2780
+ {
2781
+ params: {
2782
+ db: this.config.database,
2783
+ prefix: this.prefix + 'data_' + '%',
2784
+ },
2785
+ },
2786
+ );
2787
+ if (table?.table_name) {
2788
+ const result = await this.queryGet(
2789
+ 'SELECT 1 AS "exists" FROM "information_schema"."columns" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name"=@table AND "column_name"=\'json\';',
2790
+ {
2791
+ params: {
2792
+ db: this.config.database,
2793
+ table: table.table_name,
2794
+ },
2795
+ },
2796
+ );
2797
+ return !result?.exists;
2798
+ }
2799
+ return false;
2800
+ }
2702
2801
  }