@nymphjs/driver-postgresql 1.0.0-beta.7 → 1.0.0-beta.71

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