@nymphjs/driver-postgresql 1.0.0-beta.6 → 1.0.0-beta.60

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 };
@@ -82,14 +83,31 @@ export default class PostgreSQLDriver extends NymphDriver {
82
83
  }
83
84
  }
84
85
 
86
+ /**
87
+ * This is used internally by Nymph. Don't call it yourself.
88
+ *
89
+ * @returns A clone of this instance.
90
+ */
91
+ public clone() {
92
+ return new PostgreSQLDriver(
93
+ this.config,
94
+ this.link,
95
+ this.transaction ?? undefined,
96
+ );
97
+ }
98
+
85
99
  private getConnection(): Promise<PostgreSQLDriverConnection> {
86
100
  if (this.transaction != null && this.transaction.connection != null) {
87
101
  return Promise.resolve(this.transaction.connection);
88
102
  }
89
103
  return new Promise((resolve, reject) =>
90
104
  this.link.connect((err, client, done) =>
91
- err ? reject(err) : resolve({ client, done })
92
- )
105
+ err
106
+ ? reject(err)
107
+ : client
108
+ ? resolve({ client, done })
109
+ : reject('No client returned from connect.'),
110
+ ),
93
111
  );
94
112
  }
95
113
 
@@ -105,8 +123,12 @@ export default class PostgreSQLDriver extends NymphDriver {
105
123
  const connection: PostgreSQLDriverConnection = await new Promise(
106
124
  (resolve, reject) =>
107
125
  this.link.connect((err, client, done) =>
108
- err ? reject(err) : resolve({ client, done })
109
- )
126
+ err
127
+ ? reject(err)
128
+ : client
129
+ ? resolve({ client, done })
130
+ : reject('No client returned from connect.'),
131
+ ),
110
132
  );
111
133
  await new Promise((resolve, reject) =>
112
134
  connection.client.query('SELECT 1;', [], (err, res) => {
@@ -114,7 +136,7 @@ export default class PostgreSQLDriver extends NymphDriver {
114
136
  reject(err);
115
137
  }
116
138
  resolve(0);
117
- })
139
+ }),
118
140
  );
119
141
  connection.done();
120
142
  }
@@ -135,7 +157,7 @@ export default class PostgreSQLDriver extends NymphDriver {
135
157
  this.postgresqlConfig.database === 'nymph'
136
158
  ) {
137
159
  throw new NotConfiguredError(
138
- "It seems the config hasn't been set up correctly."
160
+ "It seems the config hasn't been set up correctly.",
139
161
  );
140
162
  } else {
141
163
  throw new UnableToConnectError('Could not connect: ' + e?.message);
@@ -177,65 +199,65 @@ export default class PostgreSQLDriver extends NymphDriver {
177
199
  * @param etype The entity type to create a table for. If this is blank, the default tables are created.
178
200
  * @returns True on success, false on failure.
179
201
  */
180
- private createTables(etype: string | null = null) {
202
+ private async createTables(etype: string | null = null) {
181
203
  if (etype != null) {
182
204
  // Create the entity table.
183
- this.queryRunSync(
205
+ await this.queryRun(
184
206
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
185
- `${this.prefix}entities_${etype}`
207
+ `${this.prefix}entities_${etype}`,
186
208
  )} (
187
209
  "guid" BYTEA NOT NULL,
188
210
  "tags" TEXT[],
189
211
  "cdate" DOUBLE PRECISION NOT NULL,
190
212
  "mdate" DOUBLE PRECISION NOT NULL,
191
213
  PRIMARY KEY ("guid")
192
- ) WITH ( OIDS=FALSE );`
214
+ ) WITH ( OIDS=FALSE );`,
193
215
  );
194
- this.queryRunSync(
216
+ await this.queryRun(
195
217
  `ALTER TABLE ${PostgreSQLDriver.escape(
196
- `${this.prefix}entities_${etype}`
197
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
218
+ `${this.prefix}entities_${etype}`,
219
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
198
220
  );
199
- this.queryRunSync(
221
+ await this.queryRun(
200
222
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
201
- `${this.prefix}entities_${etype}_id_cdate`
202
- )};`
223
+ `${this.prefix}entities_${etype}_id_cdate`,
224
+ )};`,
203
225
  );
204
- this.queryRunSync(
226
+ await this.queryRun(
205
227
  `CREATE INDEX ${PostgreSQLDriver.escape(
206
- `${this.prefix}entities_${etype}_id_cdate`
228
+ `${this.prefix}entities_${etype}_id_cdate`,
207
229
  )} ON ${PostgreSQLDriver.escape(
208
- `${this.prefix}entities_${etype}`
209
- )} USING btree ("cdate");`
230
+ `${this.prefix}entities_${etype}`,
231
+ )} USING btree ("cdate");`,
210
232
  );
211
- this.queryRunSync(
233
+ await this.queryRun(
212
234
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
213
- `${this.prefix}entities_${etype}_id_mdate`
214
- )};`
235
+ `${this.prefix}entities_${etype}_id_mdate`,
236
+ )};`,
215
237
  );
216
- this.queryRunSync(
238
+ await this.queryRun(
217
239
  `CREATE INDEX ${PostgreSQLDriver.escape(
218
- `${this.prefix}entities_${etype}_id_mdate`
240
+ `${this.prefix}entities_${etype}_id_mdate`,
219
241
  )} ON ${PostgreSQLDriver.escape(
220
- `${this.prefix}entities_${etype}`
221
- )} USING btree ("mdate");`
242
+ `${this.prefix}entities_${etype}`,
243
+ )} USING btree ("mdate");`,
222
244
  );
223
- this.queryRunSync(
245
+ await this.queryRun(
224
246
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
225
- `${this.prefix}entities_${etype}_id_tags`
226
- )};`
247
+ `${this.prefix}entities_${etype}_id_tags`,
248
+ )};`,
227
249
  );
228
- this.queryRunSync(
250
+ await this.queryRun(
229
251
  `CREATE INDEX ${PostgreSQLDriver.escape(
230
- `${this.prefix}entities_${etype}_id_tags`
252
+ `${this.prefix}entities_${etype}_id_tags`,
231
253
  )} ON ${PostgreSQLDriver.escape(
232
- `${this.prefix}entities_${etype}`
233
- )} USING gin ("tags");`
254
+ `${this.prefix}entities_${etype}`,
255
+ )} USING gin ("tags");`,
234
256
  );
235
257
  // Create the data table.
236
- this.queryRunSync(
258
+ await this.queryRun(
237
259
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
238
- `${this.prefix}data_${etype}`
260
+ `${this.prefix}data_${etype}`,
239
261
  )} (
240
262
  "guid" BYTEA NOT NULL,
241
263
  "name" TEXT NOT NULL,
@@ -243,67 +265,67 @@ export default class PostgreSQLDriver extends NymphDriver {
243
265
  PRIMARY KEY ("guid", "name"),
244
266
  FOREIGN KEY ("guid")
245
267
  REFERENCES ${PostgreSQLDriver.escape(
246
- `${this.prefix}entities_${etype}`
268
+ `${this.prefix}entities_${etype}`,
247
269
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
248
- ) WITH ( OIDS=FALSE );`
270
+ ) WITH ( OIDS=FALSE );`,
249
271
  );
250
- this.queryRunSync(
272
+ await this.queryRun(
251
273
  `ALTER TABLE ${PostgreSQLDriver.escape(
252
- `${this.prefix}data_${etype}`
253
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
274
+ `${this.prefix}data_${etype}`,
275
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
254
276
  );
255
- this.queryRunSync(
277
+ await this.queryRun(
256
278
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
257
- `${this.prefix}data_${etype}_id_guid`
258
- )};`
279
+ `${this.prefix}data_${etype}_id_guid`,
280
+ )};`,
259
281
  );
260
- this.queryRunSync(
282
+ await this.queryRun(
261
283
  `CREATE INDEX ${PostgreSQLDriver.escape(
262
- `${this.prefix}data_${etype}_id_guid`
284
+ `${this.prefix}data_${etype}_id_guid`,
263
285
  )} ON ${PostgreSQLDriver.escape(
264
- `${this.prefix}data_${etype}`
265
- )} USING btree ("guid");`
286
+ `${this.prefix}data_${etype}`,
287
+ )} USING btree ("guid");`,
266
288
  );
267
- this.queryRunSync(
289
+ await this.queryRun(
268
290
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
269
- `${this.prefix}data_${etype}_id_name`
270
- )};`
291
+ `${this.prefix}data_${etype}_id_name`,
292
+ )};`,
271
293
  );
272
- this.queryRunSync(
294
+ await this.queryRun(
273
295
  `CREATE INDEX ${PostgreSQLDriver.escape(
274
- `${this.prefix}data_${etype}_id_name`
296
+ `${this.prefix}data_${etype}_id_name`,
275
297
  )} ON ${PostgreSQLDriver.escape(
276
- `${this.prefix}data_${etype}`
277
- )} USING btree ("name");`
298
+ `${this.prefix}data_${etype}`,
299
+ )} USING btree ("name");`,
278
300
  );
279
- this.queryRunSync(
301
+ await this.queryRun(
280
302
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
281
- `${this.prefix}data_${etype}_id_guid_name__user`
282
- )};`
303
+ `${this.prefix}data_${etype}_id_guid_name__user`,
304
+ )};`,
283
305
  );
284
- this.queryRunSync(
306
+ await this.queryRun(
285
307
  `CREATE INDEX ${PostgreSQLDriver.escape(
286
- `${this.prefix}data_${etype}_id_guid_name__user`
308
+ `${this.prefix}data_${etype}_id_guid_name__user`,
287
309
  )} ON ${PostgreSQLDriver.escape(
288
- `${this.prefix}data_${etype}`
289
- )} USING btree ("guid") WHERE "name" = 'user'::text;`
310
+ `${this.prefix}data_${etype}`,
311
+ )} USING btree ("guid") WHERE "name" = 'user'::text;`,
290
312
  );
291
- this.queryRunSync(
313
+ await this.queryRun(
292
314
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
293
- `${this.prefix}data_${etype}_id_guid_name__group`
294
- )};`
315
+ `${this.prefix}data_${etype}_id_guid_name__group`,
316
+ )};`,
295
317
  );
296
- this.queryRunSync(
318
+ await this.queryRun(
297
319
  `CREATE INDEX ${PostgreSQLDriver.escape(
298
- `${this.prefix}data_${etype}_id_guid_name__group`
320
+ `${this.prefix}data_${etype}_id_guid_name__group`,
299
321
  )} ON ${PostgreSQLDriver.escape(
300
- `${this.prefix}data_${etype}`
301
- )} USING btree ("guid") WHERE "name" = 'group'::text;`
322
+ `${this.prefix}data_${etype}`,
323
+ )} USING btree ("guid") WHERE "name" = 'group'::text;`,
302
324
  );
303
325
  // Create the data comparisons table.
304
- this.queryRunSync(
326
+ await this.queryRun(
305
327
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
306
- `${this.prefix}comparisons_${etype}`
328
+ `${this.prefix}comparisons_${etype}`,
307
329
  )} (
308
330
  "guid" BYTEA NOT NULL,
309
331
  "name" TEXT NOT NULL,
@@ -313,67 +335,67 @@ export default class PostgreSQLDriver extends NymphDriver {
313
335
  PRIMARY KEY ("guid", "name"),
314
336
  FOREIGN KEY ("guid")
315
337
  REFERENCES ${PostgreSQLDriver.escape(
316
- `${this.prefix}entities_${etype}`
338
+ `${this.prefix}entities_${etype}`,
317
339
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
318
- ) WITH ( OIDS=FALSE );`
340
+ ) WITH ( OIDS=FALSE );`,
319
341
  );
320
- this.queryRunSync(
342
+ await this.queryRun(
321
343
  `ALTER TABLE ${PostgreSQLDriver.escape(
322
- `${this.prefix}comparisons_${etype}`
323
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
344
+ `${this.prefix}comparisons_${etype}`,
345
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
324
346
  );
325
- this.queryRunSync(
347
+ await this.queryRun(
326
348
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
327
- `${this.prefix}comparisons_${etype}_id_guid`
328
- )};`
349
+ `${this.prefix}comparisons_${etype}_id_guid`,
350
+ )};`,
329
351
  );
330
- this.queryRunSync(
352
+ await this.queryRun(
331
353
  `CREATE INDEX ${PostgreSQLDriver.escape(
332
- `${this.prefix}comparisons_${etype}_id_guid`
354
+ `${this.prefix}comparisons_${etype}_id_guid`,
333
355
  )} ON ${PostgreSQLDriver.escape(
334
- `${this.prefix}comparisons_${etype}`
335
- )} USING btree ("guid");`
356
+ `${this.prefix}comparisons_${etype}`,
357
+ )} USING btree ("guid");`,
336
358
  );
337
- this.queryRunSync(
359
+ await this.queryRun(
338
360
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
339
- `${this.prefix}comparisons_${etype}_id_name`
340
- )};`
361
+ `${this.prefix}comparisons_${etype}_id_name`,
362
+ )};`,
341
363
  );
342
- this.queryRunSync(
364
+ await this.queryRun(
343
365
  `CREATE INDEX ${PostgreSQLDriver.escape(
344
- `${this.prefix}comparisons_${etype}_id_name`
366
+ `${this.prefix}comparisons_${etype}_id_name`,
345
367
  )} ON ${PostgreSQLDriver.escape(
346
- `${this.prefix}comparisons_${etype}`
347
- )} USING btree ("name");`
368
+ `${this.prefix}comparisons_${etype}`,
369
+ )} USING btree ("name");`,
348
370
  );
349
- this.queryRunSync(
371
+ await this.queryRun(
350
372
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
351
- `${this.prefix}comparisons_${etype}_id_guid_name_truthy`
352
- )};`
373
+ `${this.prefix}comparisons_${etype}_id_guid_name_truthy`,
374
+ )};`,
353
375
  );
354
- this.queryRunSync(
376
+ await this.queryRun(
355
377
  `CREATE INDEX ${PostgreSQLDriver.escape(
356
- `${this.prefix}comparisons_${etype}_id_guid_name_truthy`
378
+ `${this.prefix}comparisons_${etype}_id_guid_name_truthy`,
357
379
  )} ON ${PostgreSQLDriver.escape(
358
- `${this.prefix}comparisons_${etype}`
359
- )} USING btree ("guid", "name") WHERE "truthy" = TRUE;`
380
+ `${this.prefix}comparisons_${etype}`,
381
+ )} USING btree ("guid", "name") WHERE "truthy" = TRUE;`,
360
382
  );
361
- this.queryRunSync(
383
+ await this.queryRun(
362
384
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
363
- `${this.prefix}comparisons_${etype}_id_guid_name_falsy`
364
- )};`
385
+ `${this.prefix}comparisons_${etype}_id_guid_name_falsy`,
386
+ )};`,
365
387
  );
366
- this.queryRunSync(
388
+ await this.queryRun(
367
389
  `CREATE INDEX ${PostgreSQLDriver.escape(
368
- `${this.prefix}comparisons_${etype}_id_guid_name_falsy`
390
+ `${this.prefix}comparisons_${etype}_id_guid_name_falsy`,
369
391
  )} ON ${PostgreSQLDriver.escape(
370
- `${this.prefix}comparisons_${etype}`
371
- )} USING btree ("guid", "name") WHERE "truthy" <> TRUE;`
392
+ `${this.prefix}comparisons_${etype}`,
393
+ )} USING btree ("guid", "name") WHERE "truthy" <> TRUE;`,
372
394
  );
373
395
  // Create the references table.
374
- this.queryRunSync(
396
+ await this.queryRun(
375
397
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
376
- `${this.prefix}references_${etype}`
398
+ `${this.prefix}references_${etype}`,
377
399
  )} (
378
400
  "guid" BYTEA NOT NULL,
379
401
  "name" TEXT NOT NULL,
@@ -381,66 +403,80 @@ export default class PostgreSQLDriver extends NymphDriver {
381
403
  PRIMARY KEY ("guid", "name", "reference"),
382
404
  FOREIGN KEY ("guid")
383
405
  REFERENCES ${PostgreSQLDriver.escape(
384
- `${this.prefix}entities_${etype}`
406
+ `${this.prefix}entities_${etype}`,
385
407
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
386
- ) WITH ( OIDS=FALSE );`
408
+ ) WITH ( OIDS=FALSE );`,
387
409
  );
388
- this.queryRunSync(
410
+ await this.queryRun(
389
411
  `ALTER TABLE ${PostgreSQLDriver.escape(
390
- `${this.prefix}references_${etype}`
391
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
412
+ `${this.prefix}references_${etype}`,
413
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
392
414
  );
393
- this.queryRunSync(
415
+ await this.queryRun(
394
416
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
395
- `${this.prefix}references_${etype}_id_guid`
396
- )};`
417
+ `${this.prefix}references_${etype}_id_guid`,
418
+ )};`,
397
419
  );
398
- this.queryRunSync(
420
+ await this.queryRun(
399
421
  `CREATE INDEX ${PostgreSQLDriver.escape(
400
- `${this.prefix}references_${etype}_id_guid`
422
+ `${this.prefix}references_${etype}_id_guid`,
401
423
  )} ON ${PostgreSQLDriver.escape(
402
- `${this.prefix}references_${etype}`
403
- )} USING btree ("guid");`
424
+ `${this.prefix}references_${etype}`,
425
+ )} USING btree ("guid");`,
404
426
  );
405
- this.queryRunSync(
427
+ await this.queryRun(
406
428
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
407
- `${this.prefix}references_${etype}_id_name`
408
- )};`
429
+ `${this.prefix}references_${etype}_id_name`,
430
+ )};`,
409
431
  );
410
- this.queryRunSync(
432
+ await this.queryRun(
411
433
  `CREATE INDEX ${PostgreSQLDriver.escape(
412
- `${this.prefix}references_${etype}_id_name`
434
+ `${this.prefix}references_${etype}_id_name`,
413
435
  )} ON ${PostgreSQLDriver.escape(
414
- `${this.prefix}references_${etype}`
415
- )} USING btree ("name");`
436
+ `${this.prefix}references_${etype}`,
437
+ )} USING btree ("name");`,
416
438
  );
417
- this.queryRunSync(
439
+ await this.queryRun(
418
440
  `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
419
- `${this.prefix}references_${etype}_id_reference`
420
- )};`
441
+ `${this.prefix}references_${etype}_id_reference`,
442
+ )};`,
421
443
  );
422
- this.queryRunSync(
444
+ await this.queryRun(
423
445
  `CREATE INDEX ${PostgreSQLDriver.escape(
424
- `${this.prefix}references_${etype}_id_reference`
446
+ `${this.prefix}references_${etype}_id_reference`,
425
447
  )} ON ${PostgreSQLDriver.escape(
426
- `${this.prefix}references_${etype}`
427
- )} USING btree ("reference");`
448
+ `${this.prefix}references_${etype}`,
449
+ )} USING btree ("reference");`,
450
+ );
451
+ // Create the unique strings table.
452
+ await this.queryRun(
453
+ `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
454
+ `${this.prefix}uniques_${etype}`,
455
+ )} (
456
+ "guid" BYTEA NOT NULL,
457
+ "unique" TEXT NOT NULL UNIQUE,
458
+ PRIMARY KEY ("guid", "unique"),
459
+ FOREIGN KEY ("guid")
460
+ REFERENCES ${PostgreSQLDriver.escape(
461
+ `${this.prefix}entities_${etype}`,
462
+ )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
463
+ ) WITH ( OIDS=FALSE );`,
428
464
  );
429
465
  } else {
430
466
  // Create the UID table.
431
- this.queryRunSync(
467
+ await this.queryRun(
432
468
  `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
433
- `${this.prefix}uids`
469
+ `${this.prefix}uids`,
434
470
  )} (
435
471
  "name" TEXT NOT NULL,
436
472
  "cur_uid" BIGINT NOT NULL,
437
473
  PRIMARY KEY ("name")
438
- ) WITH ( OIDS = FALSE );`
474
+ ) WITH ( OIDS = FALSE );`,
439
475
  );
440
- this.queryRunSync(
476
+ await this.queryRun(
441
477
  `ALTER TABLE ${PostgreSQLDriver.escape(
442
- `${this.prefix}uids`
443
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`
478
+ `${this.prefix}uids`,
479
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
444
480
  );
445
481
  }
446
482
  return true;
@@ -448,7 +484,7 @@ export default class PostgreSQLDriver extends NymphDriver {
448
484
 
449
485
  private translateQuery(
450
486
  origQuery: string,
451
- origParams: { [k: string]: any }
487
+ origParams: { [k: string]: any },
452
488
  ): { query: string; params: any[] } {
453
489
  const params: any[] = [];
454
490
  let query = origQuery;
@@ -468,56 +504,33 @@ export default class PostgreSQLDriver extends NymphDriver {
468
504
  private async query<T extends () => any>(
469
505
  runQuery: T,
470
506
  query: string,
471
- etypes: string[] = []
507
+ etypes: string[] = [],
472
508
  // @ts-ignore: The return type of T is a promise.
473
509
  ): ReturnType<T> {
474
510
  try {
475
511
  return await runQuery();
476
512
  } catch (e: any) {
477
513
  const errorCode = e?.code;
478
- if (errorCode === '42P01' && this.createTables()) {
514
+ if (errorCode === '42P01' && (await this.createTables())) {
479
515
  // If the tables don't exist yet, create them.
480
516
  for (let etype of etypes) {
481
- this.createTables(etype);
517
+ await this.createTables(etype);
482
518
  }
483
519
  try {
484
520
  return await runQuery();
485
521
  } catch (e2: any) {
486
522
  throw new QueryFailedError(
487
523
  'Query failed: ' + e2?.code + ' - ' + e2?.message,
488
- query
524
+ query,
489
525
  );
490
526
  }
527
+ } else if (errorCode === '23505') {
528
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
491
529
  } else {
492
- throw e;
493
- }
494
- }
495
- }
496
-
497
- private querySync<T extends () => any>(
498
- runQuery: T,
499
- query: string,
500
- etypes: string[] = []
501
- ): ReturnType<T> {
502
- try {
503
- return runQuery();
504
- } catch (e: any) {
505
- const errorCode = e?.code;
506
- if (errorCode === '42P01' && this.createTables()) {
507
- // If the tables don't exist yet, create them.
508
- for (let etype of etypes) {
509
- this.createTables(etype);
510
- }
511
- try {
512
- return runQuery();
513
- } catch (e2: any) {
514
- throw new QueryFailedError(
515
- 'Query failed: ' + e2?.code + ' - ' + e2?.message,
516
- query
517
- );
518
- }
519
- } else {
520
- throw e;
530
+ throw new QueryFailedError(
531
+ 'Query failed: ' + e?.code + ' - ' + e?.message,
532
+ query,
533
+ );
521
534
  }
522
535
  }
523
536
  }
@@ -530,11 +543,11 @@ export default class PostgreSQLDriver extends NymphDriver {
530
543
  }: {
531
544
  etypes?: string[];
532
545
  params?: { [k: string]: any };
533
- } = {}
546
+ } = {},
534
547
  ) {
535
548
  const { query: newQuery, params: newParams } = this.translateQuery(
536
549
  query,
537
- params
550
+ params,
538
551
  );
539
552
  return this.query(
540
553
  async () => {
@@ -545,64 +558,17 @@ export default class PostgreSQLDriver extends NymphDriver {
545
558
  .query(newQuery, newParams)
546
559
  .then(
547
560
  (results) => resolve(results),
548
- (error) => reject(error)
561
+ (error) => reject(error),
549
562
  );
550
563
  } catch (e) {
551
564
  reject(e);
552
565
  }
553
- }
566
+ },
554
567
  );
555
568
  return results.rows;
556
569
  },
557
570
  `${query} -- ${JSON.stringify(params)}`,
558
- etypes
559
- );
560
- }
561
-
562
- private queryIterSync(
563
- query: string,
564
- {
565
- etypes = [],
566
- params = {},
567
- }: { etypes?: string[]; params?: { [k: string]: any } } = {}
568
- ) {
569
- const { query: newQuery, params: newParams } = this.translateQuery(
570
- query,
571
- params
572
- );
573
- return this.querySync(
574
- () => {
575
- const output = cp.spawnSync(
576
- process.argv0,
577
- [__dirname + '/runPostgresqlSync.js'],
578
- {
579
- input: JSON.stringify({
580
- postgresqlConfig: this.postgresqlConfig,
581
- query: newQuery,
582
- params: newParams,
583
- }),
584
- timeout: 30000,
585
- maxBuffer: 100 * 1024 * 1024,
586
- encoding: 'utf8',
587
- windowsHide: true,
588
- }
589
- );
590
- try {
591
- return JSON.parse(output.stdout).rows;
592
- } catch (e) {
593
- // Do nothing.
594
- }
595
- if (output.status === 0) {
596
- throw new Error('Unknown parse error.');
597
- }
598
- const err = JSON.parse(output.stderr);
599
- const e = new Error(err.name);
600
- for (const name in err) {
601
- (e as any)[name] = err[name];
602
- }
603
- },
604
- `${query} -- ${JSON.stringify(params)}`,
605
- etypes
571
+ etypes,
606
572
  );
607
573
  }
608
574
 
@@ -614,11 +580,11 @@ export default class PostgreSQLDriver extends NymphDriver {
614
580
  }: {
615
581
  etypes?: string[];
616
582
  params?: { [k: string]: any };
617
- } = {}
583
+ } = {},
618
584
  ) {
619
585
  const { query: newQuery, params: newParams } = this.translateQuery(
620
586
  query,
621
- params
587
+ params,
622
588
  );
623
589
  return this.query(
624
590
  async () => {
@@ -629,17 +595,17 @@ export default class PostgreSQLDriver extends NymphDriver {
629
595
  .query(newQuery, newParams)
630
596
  .then(
631
597
  (results) => resolve(results),
632
- (error) => reject(error)
598
+ (error) => reject(error),
633
599
  );
634
600
  } catch (e) {
635
601
  reject(e);
636
602
  }
637
- }
603
+ },
638
604
  );
639
605
  return results.rows[0];
640
606
  },
641
607
  `${query} -- ${JSON.stringify(params)}`,
642
- etypes
608
+ etypes,
643
609
  );
644
610
  }
645
611
 
@@ -651,11 +617,11 @@ export default class PostgreSQLDriver extends NymphDriver {
651
617
  }: {
652
618
  etypes?: string[];
653
619
  params?: { [k: string]: any };
654
- } = {}
620
+ } = {},
655
621
  ) {
656
622
  const { query: newQuery, params: newParams } = this.translateQuery(
657
623
  query,
658
- params
624
+ params,
659
625
  );
660
626
  return this.query(
661
627
  async () => {
@@ -666,72 +632,24 @@ export default class PostgreSQLDriver extends NymphDriver {
666
632
  .query(newQuery, newParams)
667
633
  .then(
668
634
  (results) => resolve(results),
669
- (error) => reject(error)
635
+ (error) => reject(error),
670
636
  );
671
637
  } catch (e) {
672
638
  reject(e);
673
639
  }
674
- }
640
+ },
675
641
  );
676
642
  return { rowCount: results.rowCount ?? 0 };
677
643
  },
678
644
  `${query} -- ${JSON.stringify(params)}`,
679
- etypes
680
- );
681
- }
682
-
683
- private queryRunSync(
684
- query: string,
685
- {
686
- etypes = [],
687
- params = {},
688
- }: { etypes?: string[]; params?: { [k: string]: any } } = {}
689
- ) {
690
- const { query: newQuery, params: newParams } = this.translateQuery(
691
- query,
692
- params
693
- );
694
- return this.querySync(
695
- () => {
696
- const output = cp.spawnSync(
697
- process.argv0,
698
- [__dirname + '/runPostgresqlSync.js'],
699
- {
700
- input: JSON.stringify({
701
- postgresqlConfig: this.postgresqlConfig,
702
- query: newQuery,
703
- params: newParams,
704
- }),
705
- timeout: 30000,
706
- maxBuffer: 100 * 1024 * 1024,
707
- encoding: 'utf8',
708
- windowsHide: true,
709
- }
710
- );
711
- try {
712
- const results = JSON.parse(output.stdout);
713
- return { rowCount: results.rowCount ?? 0 };
714
- } catch (e) {
715
- // Do nothing.
716
- }
717
- if (output.status === 0) {
718
- throw new Error('Unknown parse error.');
719
- }
720
- const err = JSON.parse(output.stderr);
721
- const e = new Error(err.name);
722
- for (const name in err) {
723
- (e as any)[name] = err[name];
724
- }
725
- },
726
- `${query} -- ${JSON.stringify(params)}`,
727
- etypes
645
+ etypes,
728
646
  );
729
647
  }
730
648
 
731
649
  public async commit(name: string) {
732
650
  if (name == null || typeof name !== 'string' || name.length === 0) {
733
651
  throw new InvalidParametersError(
734
- 'Transaction commit attempted without a name.'
652
+ 'Transaction commit attempted without a name.',
735
653
  );
736
654
  }
737
655
  if (!this.transaction || this.transaction.count === 0) {
@@ -751,7 +669,7 @@ export default class PostgreSQLDriver extends NymphDriver {
751
669
 
752
670
  public async deleteEntityByID(
753
671
  guid: string,
754
- className?: EntityConstructor | string | null
672
+ className?: EntityConstructor | string | null,
755
673
  ) {
756
674
  let EntityClass: EntityConstructor;
757
675
  if (typeof className === 'string' || className == null) {
@@ -765,58 +683,71 @@ export default class PostgreSQLDriver extends NymphDriver {
765
683
  try {
766
684
  await this.queryRun(
767
685
  `DELETE FROM ${PostgreSQLDriver.escape(
768
- `${this.prefix}entities_${etype}`
686
+ `${this.prefix}entities_${etype}`,
769
687
  )} WHERE "guid"=decode(@guid, 'hex');`,
770
688
  {
771
689
  etypes: [etype],
772
690
  params: {
773
691
  guid,
774
692
  },
775
- }
693
+ },
776
694
  );
777
695
  await this.queryRun(
778
696
  `DELETE FROM ${PostgreSQLDriver.escape(
779
- `${this.prefix}data_${etype}`
697
+ `${this.prefix}data_${etype}`,
780
698
  )} WHERE "guid"=decode(@guid, 'hex');`,
781
699
  {
782
700
  etypes: [etype],
783
701
  params: {
784
702
  guid,
785
703
  },
786
- }
704
+ },
787
705
  );
788
706
  await this.queryRun(
789
707
  `DELETE FROM ${PostgreSQLDriver.escape(
790
- `${this.prefix}comparisons_${etype}`
708
+ `${this.prefix}comparisons_${etype}`,
791
709
  )} WHERE "guid"=decode(@guid, 'hex');`,
792
710
  {
793
711
  etypes: [etype],
794
712
  params: {
795
713
  guid,
796
714
  },
797
- }
715
+ },
798
716
  );
799
717
  await this.queryRun(
800
718
  `DELETE FROM ${PostgreSQLDriver.escape(
801
- `${this.prefix}references_${etype}`
719
+ `${this.prefix}references_${etype}`,
802
720
  )} WHERE "guid"=decode(@guid, 'hex');`,
803
721
  {
804
722
  etypes: [etype],
805
723
  params: {
806
724
  guid,
807
725
  },
808
- }
726
+ },
727
+ );
728
+ await this.queryRun(
729
+ `DELETE FROM ${PostgreSQLDriver.escape(
730
+ `${this.prefix}uniques_${etype}`,
731
+ )} WHERE "guid"=decode(@guid, 'hex');`,
732
+ {
733
+ etypes: [etype],
734
+ params: {
735
+ guid,
736
+ },
737
+ },
809
738
  );
810
- await this.commit('nymph-delete');
811
- // Remove any cached versions of this entity.
812
- if (this.nymph.config.cache) {
813
- this.cleanCache(guid);
814
- }
815
- return true;
816
739
  } catch (e: any) {
740
+ this.nymph.config.debugError('postgresql', `Delete entity error: "${e}"`);
817
741
  await this.rollback('nymph-delete');
818
742
  throw e;
819
743
  }
744
+
745
+ await this.commit('nymph-delete');
746
+ // Remove any cached versions of this entity.
747
+ if (this.nymph.config.cache) {
748
+ this.cleanCache(guid);
749
+ }
750
+ return true;
820
751
  }
821
752
 
822
753
  public async deleteUID(name: string) {
@@ -825,13 +756,13 @@ export default class PostgreSQLDriver extends NymphDriver {
825
756
  }
826
757
  await this.queryRun(
827
758
  `DELETE FROM ${PostgreSQLDriver.escape(
828
- `${this.prefix}uids`
759
+ `${this.prefix}uids`,
829
760
  )} WHERE "name"=@name;`,
830
761
  {
831
762
  params: {
832
763
  name,
833
764
  },
834
- }
765
+ },
835
766
  );
836
767
  return true;
837
768
  }
@@ -852,8 +783,8 @@ export default class PostgreSQLDriver extends NymphDriver {
852
783
  // Export UIDs.
853
784
  let uids = await this.queryIter(
854
785
  `SELECT * FROM ${PostgreSQLDriver.escape(
855
- `${this.prefix}uids`
856
- )} ORDER BY "name";`
786
+ `${this.prefix}uids`,
787
+ )} ORDER BY "name";`,
857
788
  );
858
789
  for (const uid of uids) {
859
790
  writeLine(`<${uid.name}>[${uid.cur_uid}]`);
@@ -867,7 +798,7 @@ export default class PostgreSQLDriver extends NymphDriver {
867
798
 
868
799
  // Get the etypes.
869
800
  const tables = await this.queryIter(
870
- 'SELECT relname FROM pg_stat_user_tables ORDER BY relname;'
801
+ 'SELECT relname FROM pg_stat_user_tables ORDER BY relname;',
871
802
  );
872
803
  const etypes = [];
873
804
  for (const tableRow of tables) {
@@ -884,12 +815,12 @@ export default class PostgreSQLDriver extends NymphDriver {
884
815
  `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"
885
816
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} e
886
817
  LEFT JOIN ${PostgreSQLDriver.escape(
887
- `${this.prefix}data_${etype}`
818
+ `${this.prefix}data_${etype}`,
888
819
  )} d ON e."guid"=d."guid"
889
820
  INNER JOIN ${PostgreSQLDriver.escape(
890
- `${this.prefix}comparisons_${etype}`
821
+ `${this.prefix}comparisons_${etype}`,
891
822
  )} c ON d."guid"=c."guid" AND d."name"=c."name"
892
- ORDER BY e."guid";`
823
+ ORDER BY e."guid";`,
893
824
  )
894
825
  )[Symbol.iterator]();
895
826
  let datum = dataIterator.next();
@@ -942,7 +873,7 @@ export default class PostgreSQLDriver extends NymphDriver {
942
873
  params: { [k: string]: any } = {},
943
874
  subquery = false,
944
875
  tableSuffix = '',
945
- etypes: string[] = []
876
+ etypes: string[] = [],
946
877
  ) {
947
878
  if (typeof options.class?.alterOptions === 'function') {
948
879
  options = options.class.alterOptions(options);
@@ -953,10 +884,11 @@ export default class PostgreSQLDriver extends NymphDriver {
953
884
  const fTable = `f${tableSuffix}`;
954
885
  const ieTable = `ie${tableSuffix}`;
955
886
  const countTable = `count${tableSuffix}`;
887
+ const sTable = `s${tableSuffix}`;
956
888
  const sort = options.sort ?? 'cdate';
957
889
  const queryParts = this.iterateSelectorsForQuery(
958
890
  formattedSelectors,
959
- (key, value, typeIsOr, typeIsNot) => {
891
+ ({ key, value, typeIsOr, typeIsNot }) => {
960
892
  const clauseNot = key.startsWith('!');
961
893
  let curQuery = '';
962
894
  for (const curValue of value) {
@@ -1038,7 +970,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1038
970
  ieTable +
1039
971
  '."guid" IN (SELECT "guid" FROM ' +
1040
972
  PostgreSQLDriver.escape(
1041
- this.prefix + 'comparisons_' + etype
973
+ this.prefix + 'comparisons_' + etype,
1042
974
  ) +
1043
975
  ' WHERE "name"=@' +
1044
976
  name +
@@ -1088,7 +1020,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1088
1020
  ieTable +
1089
1021
  '."guid" IN (SELECT "guid" FROM ' +
1090
1022
  PostgreSQLDriver.escape(
1091
- this.prefix + 'comparisons_' + etype
1023
+ this.prefix + 'comparisons_' + etype,
1092
1024
  ) +
1093
1025
  ' WHERE "name"=@' +
1094
1026
  name +
@@ -1108,7 +1040,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1108
1040
  ieTable +
1109
1041
  '."guid" IN (SELECT "guid" FROM ' +
1110
1042
  PostgreSQLDriver.escape(
1111
- this.prefix + 'comparisons_' + etype
1043
+ this.prefix + 'comparisons_' + etype,
1112
1044
  ) +
1113
1045
  ' WHERE "name"=@' +
1114
1046
  name +
@@ -1210,7 +1142,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1210
1142
  ieTable +
1211
1143
  '."guid" IN (SELECT "guid" FROM ' +
1212
1144
  PostgreSQLDriver.escape(
1213
- this.prefix + 'comparisons_' + etype
1145
+ this.prefix + 'comparisons_' + etype,
1214
1146
  ) +
1215
1147
  ' WHERE "name"=@' +
1216
1148
  name +
@@ -1275,7 +1207,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1275
1207
  ieTable +
1276
1208
  '."guid" IN (SELECT "guid" FROM ' +
1277
1209
  PostgreSQLDriver.escape(
1278
- this.prefix + 'comparisons_' + etype
1210
+ this.prefix + 'comparisons_' + etype,
1279
1211
  ) +
1280
1212
  ' WHERE "name"=@' +
1281
1213
  name +
@@ -1327,7 +1259,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1327
1259
  ieTable +
1328
1260
  '."guid" IN (SELECT "guid" FROM ' +
1329
1261
  PostgreSQLDriver.escape(
1330
- this.prefix + 'comparisons_' + etype
1262
+ this.prefix + 'comparisons_' + etype,
1331
1263
  ) +
1332
1264
  ' WHERE "name"=@' +
1333
1265
  name +
@@ -1379,7 +1311,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1379
1311
  ieTable +
1380
1312
  '."guid" IN (SELECT "guid" FROM ' +
1381
1313
  PostgreSQLDriver.escape(
1382
- this.prefix + 'comparisons_' + etype
1314
+ this.prefix + 'comparisons_' + etype,
1383
1315
  ) +
1384
1316
  ' WHERE "name"=@' +
1385
1317
  name +
@@ -1431,7 +1363,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1431
1363
  ieTable +
1432
1364
  '."guid" IN (SELECT "guid" FROM ' +
1433
1365
  PostgreSQLDriver.escape(
1434
- this.prefix + 'comparisons_' + etype
1366
+ this.prefix + 'comparisons_' + etype,
1435
1367
  ) +
1436
1368
  ' WHERE "name"=@' +
1437
1369
  name +
@@ -1483,7 +1415,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1483
1415
  ieTable +
1484
1416
  '."guid" IN (SELECT "guid" FROM ' +
1485
1417
  PostgreSQLDriver.escape(
1486
- this.prefix + 'comparisons_' + etype
1418
+ this.prefix + 'comparisons_' + etype,
1487
1419
  ) +
1488
1420
  ' WHERE "name"=@' +
1489
1421
  name +
@@ -1537,7 +1469,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1537
1469
  ieTable +
1538
1470
  '."guid" IN (SELECT "guid" FROM ' +
1539
1471
  PostgreSQLDriver.escape(
1540
- this.prefix + 'comparisons_' + etype
1472
+ this.prefix + 'comparisons_' + etype,
1541
1473
  ) +
1542
1474
  ' WHERE "name"=@' +
1543
1475
  name +
@@ -1591,7 +1523,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1591
1523
  ieTable +
1592
1524
  '."guid" IN (SELECT "guid" FROM ' +
1593
1525
  PostgreSQLDriver.escape(
1594
- this.prefix + 'comparisons_' + etype
1526
+ this.prefix + 'comparisons_' + etype,
1595
1527
  ) +
1596
1528
  ' WHERE "name"=@' +
1597
1529
  name +
@@ -1645,7 +1577,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1645
1577
  ieTable +
1646
1578
  '."guid" IN (SELECT "guid" FROM ' +
1647
1579
  PostgreSQLDriver.escape(
1648
- this.prefix + 'comparisons_' + etype
1580
+ this.prefix + 'comparisons_' + etype,
1649
1581
  ) +
1650
1582
  ' WHERE "name"=@' +
1651
1583
  name +
@@ -1696,7 +1628,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1696
1628
  params,
1697
1629
  true,
1698
1630
  tableSuffix,
1699
- etypes
1631
+ etypes,
1700
1632
  );
1701
1633
  if (curQuery) {
1702
1634
  curQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -1711,7 +1643,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1711
1643
  case '!qref':
1712
1644
  const [qrefOptions, ...qrefSelectors] = curValue[1] as [
1713
1645
  Options,
1714
- ...FormattedSelector[]
1646
+ ...FormattedSelector[],
1715
1647
  ];
1716
1648
  const QrefEntityClass = qrefOptions.class as EntityConstructor;
1717
1649
  etypes.push(QrefEntityClass.ETYPE);
@@ -1723,7 +1655,7 @@ export default class PostgreSQLDriver extends NymphDriver {
1723
1655
  params,
1724
1656
  false,
1725
1657
  makeTableSuffix(),
1726
- etypes
1658
+ etypes,
1727
1659
  );
1728
1660
  if (curQuery) {
1729
1661
  curQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -1744,22 +1676,46 @@ export default class PostgreSQLDriver extends NymphDriver {
1744
1676
  }
1745
1677
  }
1746
1678
  return curQuery;
1747
- }
1679
+ },
1748
1680
  );
1749
1681
 
1750
1682
  let sortBy: string;
1683
+ let sortByInner: string;
1684
+ let sortJoin = '';
1685
+ let sortJoinInner = '';
1686
+ const order = options.reverse ? ' DESC' : '';
1751
1687
  switch (sort) {
1752
1688
  case 'mdate':
1753
- sortBy = '"mdate"';
1689
+ sortBy = `${eTable}."mdate"${order}`;
1690
+ sortByInner = `${ieTable}."mdate"${order}`;
1754
1691
  break;
1755
1692
  case 'cdate':
1693
+ sortBy = `${eTable}."cdate"${order}`;
1694
+ sortByInner = `${ieTable}."cdate"${order}`;
1695
+ break;
1756
1696
  default:
1757
- sortBy = '"cdate"';
1697
+ const name = `param${++count.i}`;
1698
+ sortJoin = `LEFT JOIN (
1699
+ SELECT "guid", "string", "number"
1700
+ FROM ${PostgreSQLDriver.escape(
1701
+ this.prefix + 'comparisons_' + etype,
1702
+ )}
1703
+ WHERE "name"=@${name}
1704
+ ORDER BY "number"${order}, "string"${order}
1705
+ ) ${sTable} ON ${eTable}."guid"=${sTable}."guid"`;
1706
+ sortJoinInner = `LEFT JOIN (
1707
+ SELECT "guid", "string", "number"
1708
+ FROM ${PostgreSQLDriver.escape(
1709
+ this.prefix + 'comparisons_' + etype,
1710
+ )}
1711
+ WHERE "name"=@${name}
1712
+ ORDER BY "number"${order}, "string"${order}
1713
+ ) ${sTable} ON ${ieTable}."guid"=${sTable}."guid"`;
1714
+ sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
1715
+ sortByInner = sortBy;
1716
+ params[name] = sort;
1758
1717
  break;
1759
1718
  }
1760
- if (options.reverse) {
1761
- sortBy += ' DESC';
1762
- }
1763
1719
 
1764
1720
  let query: string;
1765
1721
  if (queryParts.length) {
@@ -1769,13 +1725,13 @@ export default class PostgreSQLDriver extends NymphDriver {
1769
1725
  let limit = '';
1770
1726
  if ('limit' in options) {
1771
1727
  limit = ` LIMIT ${Math.floor(
1772
- isNaN(Number(options.limit)) ? 0 : Number(options.limit)
1728
+ isNaN(Number(options.limit)) ? 0 : Number(options.limit),
1773
1729
  )}`;
1774
1730
  }
1775
1731
  let offset = '';
1776
1732
  if ('offset' in options) {
1777
1733
  offset = ` OFFSET ${Math.floor(
1778
- isNaN(Number(options.offset)) ? 0 : Number(options.offset)
1734
+ isNaN(Number(options.offset)) ? 0 : Number(options.offset),
1779
1735
  )}`;
1780
1736
  }
1781
1737
  const whereClause = queryParts.join(') AND (');
@@ -1784,14 +1740,14 @@ export default class PostgreSQLDriver extends NymphDriver {
1784
1740
  query = `SELECT COUNT(${countTable}."guid") AS "count" FROM (
1785
1741
  SELECT COUNT(${ieTable}."guid") AS "guid"
1786
1742
  FROM ${PostgreSQLDriver.escape(
1787
- `${this.prefix}entities_${etype}`
1743
+ `${this.prefix}entities_${etype}`,
1788
1744
  )} ${ieTable}
1789
1745
  WHERE (${whereClause})${limit}${offset}
1790
1746
  ) ${countTable}`;
1791
1747
  } else {
1792
1748
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1793
1749
  FROM ${PostgreSQLDriver.escape(
1794
- `${this.prefix}entities_${etype}`
1750
+ `${this.prefix}entities_${etype}`,
1795
1751
  )} ${ieTable}
1796
1752
  WHERE (${whereClause})`;
1797
1753
  }
@@ -1802,10 +1758,11 @@ export default class PostgreSQLDriver extends NymphDriver {
1802
1758
  : `${ieTable}."guid"`;
1803
1759
  query = `SELECT ${guidColumn} AS "guid"
1804
1760
  FROM ${PostgreSQLDriver.escape(
1805
- `${this.prefix}entities_${etype}`
1761
+ `${this.prefix}entities_${etype}`,
1806
1762
  )} ${ieTable}
1763
+ ${sortJoinInner}
1807
1764
  WHERE (${whereClause})
1808
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1765
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1809
1766
  } else {
1810
1767
  query = `SELECT
1811
1768
  encode(${eTable}."guid", 'hex') AS "guid",
@@ -1817,23 +1774,25 @@ export default class PostgreSQLDriver extends NymphDriver {
1817
1774
  ${cTable}."string",
1818
1775
  ${cTable}."number"
1819
1776
  FROM ${PostgreSQLDriver.escape(
1820
- `${this.prefix}entities_${etype}`
1777
+ `${this.prefix}entities_${etype}`,
1821
1778
  )} ${eTable}
1822
1779
  LEFT JOIN ${PostgreSQLDriver.escape(
1823
- `${this.prefix}data_${etype}`
1780
+ `${this.prefix}data_${etype}`,
1824
1781
  )} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1825
1782
  INNER JOIN ${PostgreSQLDriver.escape(
1826
- `${this.prefix}comparisons_${etype}`
1783
+ `${this.prefix}comparisons_${etype}`,
1827
1784
  )} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1785
+ ${sortJoin}
1828
1786
  INNER JOIN (
1829
1787
  SELECT ${ieTable}."guid"
1830
1788
  FROM ${PostgreSQLDriver.escape(
1831
- `${this.prefix}entities_${etype}`
1789
+ `${this.prefix}entities_${etype}`,
1832
1790
  )} ${ieTable}
1791
+ ${sortJoinInner}
1833
1792
  WHERE (${whereClause})
1834
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1793
+ ORDER BY ${sortByInner}${limit}${offset}
1835
1794
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1836
- ORDER BY ${eTable}.${sortBy}`;
1795
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1837
1796
  }
1838
1797
  }
1839
1798
  } else {
@@ -1843,13 +1802,13 @@ export default class PostgreSQLDriver extends NymphDriver {
1843
1802
  let limit = '';
1844
1803
  if ('limit' in options) {
1845
1804
  limit = ` LIMIT ${Math.floor(
1846
- isNaN(Number(options.limit)) ? 0 : Number(options.limit)
1805
+ isNaN(Number(options.limit)) ? 0 : Number(options.limit),
1847
1806
  )}`;
1848
1807
  }
1849
1808
  let offset = '';
1850
1809
  if ('offset' in options) {
1851
1810
  offset = ` OFFSET ${Math.floor(
1852
- isNaN(Number(options.offset)) ? 0 : Number(options.offset)
1811
+ isNaN(Number(options.offset)) ? 0 : Number(options.offset),
1853
1812
  )}`;
1854
1813
  }
1855
1814
  if (options.return === 'count') {
@@ -1857,13 +1816,13 @@ export default class PostgreSQLDriver extends NymphDriver {
1857
1816
  query = `SELECT COUNT(${countTable}."guid") AS "count" FROM (
1858
1817
  SELECT COUNT(${ieTable}."guid") AS "guid"
1859
1818
  FROM ${PostgreSQLDriver.escape(
1860
- `${this.prefix}entities_${etype}`
1819
+ `${this.prefix}entities_${etype}`,
1861
1820
  )} ${ieTable}${limit}${offset}
1862
1821
  ) ${countTable}`;
1863
1822
  } else {
1864
1823
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1865
1824
  FROM ${PostgreSQLDriver.escape(
1866
- `${this.prefix}entities_${etype}`
1825
+ `${this.prefix}entities_${etype}`,
1867
1826
  )} ${ieTable}`;
1868
1827
  }
1869
1828
  } else if (options.return === 'guid') {
@@ -1873,9 +1832,10 @@ export default class PostgreSQLDriver extends NymphDriver {
1873
1832
  : `${ieTable}."guid"`;
1874
1833
  query = `SELECT ${guidColumn} AS "guid"
1875
1834
  FROM ${PostgreSQLDriver.escape(
1876
- `${this.prefix}entities_${etype}`
1835
+ `${this.prefix}entities_${etype}`,
1877
1836
  )} ${ieTable}
1878
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1837
+ ${sortJoinInner}
1838
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1879
1839
  } else {
1880
1840
  if (limit || offset) {
1881
1841
  query = `SELECT
@@ -1888,22 +1848,24 @@ export default class PostgreSQLDriver extends NymphDriver {
1888
1848
  ${cTable}."string",
1889
1849
  ${cTable}."number"
1890
1850
  FROM ${PostgreSQLDriver.escape(
1891
- `${this.prefix}entities_${etype}`
1851
+ `${this.prefix}entities_${etype}`,
1892
1852
  )} ${eTable}
1893
1853
  LEFT JOIN ${PostgreSQLDriver.escape(
1894
- `${this.prefix}data_${etype}`
1854
+ `${this.prefix}data_${etype}`,
1895
1855
  )} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1896
1856
  INNER JOIN ${PostgreSQLDriver.escape(
1897
- `${this.prefix}comparisons_${etype}`
1857
+ `${this.prefix}comparisons_${etype}`,
1898
1858
  )} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1859
+ ${sortJoin}
1899
1860
  INNER JOIN (
1900
1861
  SELECT ${ieTable}."guid"
1901
1862
  FROM ${PostgreSQLDriver.escape(
1902
- `${this.prefix}entities_${etype}`
1863
+ `${this.prefix}entities_${etype}`,
1903
1864
  )} ${ieTable}
1904
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1865
+ ${sortJoinInner}
1866
+ ORDER BY ${sortByInner}${limit}${offset}
1905
1867
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1906
- ORDER BY ${eTable}.${sortBy}`;
1868
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1907
1869
  } else {
1908
1870
  query = `SELECT
1909
1871
  encode(${eTable}."guid", 'hex') AS "guid",
@@ -1915,15 +1877,16 @@ export default class PostgreSQLDriver extends NymphDriver {
1915
1877
  ${cTable}."string",
1916
1878
  ${cTable}."number"
1917
1879
  FROM ${PostgreSQLDriver.escape(
1918
- `${this.prefix}entities_${etype}`
1880
+ `${this.prefix}entities_${etype}`,
1919
1881
  )} ${eTable}
1920
1882
  LEFT JOIN ${PostgreSQLDriver.escape(
1921
- `${this.prefix}data_${etype}`
1883
+ `${this.prefix}data_${etype}`,
1922
1884
  )} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1923
1885
  INNER JOIN ${PostgreSQLDriver.escape(
1924
- `${this.prefix}comparisons_${etype}`
1886
+ `${this.prefix}comparisons_${etype}`,
1925
1887
  )} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1926
- ORDER BY ${eTable}.${sortBy}`;
1888
+ ${sortJoin}
1889
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1927
1890
  }
1928
1891
  }
1929
1892
  }
@@ -1943,38 +1906,18 @@ export default class PostgreSQLDriver extends NymphDriver {
1943
1906
  protected performQuery(
1944
1907
  options: Options,
1945
1908
  formattedSelectors: FormattedSelector[],
1946
- etype: string
1909
+ etype: string,
1947
1910
  ): {
1948
1911
  result: any;
1949
1912
  } {
1950
1913
  const { query, params, etypes } = this.makeEntityQuery(
1951
1914
  options,
1952
1915
  formattedSelectors,
1953
- etype
1916
+ etype,
1954
1917
  );
1955
1918
  const result = this.queryIter(query, { etypes, params }).then((val) =>
1956
- val[Symbol.iterator]()
1957
- );
1958
- return {
1959
- result,
1960
- };
1961
- }
1962
-
1963
- protected performQuerySync(
1964
- options: Options,
1965
- formattedSelectors: FormattedSelector[],
1966
- etype: string
1967
- ): {
1968
- result: any;
1969
- } {
1970
- const { query, params, etypes } = this.makeEntityQuery(
1971
- options,
1972
- formattedSelectors,
1973
- etype
1919
+ val[Symbol.iterator](),
1974
1920
  );
1975
- const result = (this.queryIterSync(query, { etypes, params }) || [])[
1976
- Symbol.iterator
1977
- ]();
1978
1921
  return {
1979
1922
  result,
1980
1923
  };
@@ -1991,17 +1934,17 @@ export default class PostgreSQLDriver extends NymphDriver {
1991
1934
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
1992
1935
  options?: Options<T>,
1993
1936
  ...selectors: Selector[]
1994
- ): Promise<ReturnType<T['factorySync']>[]>;
1937
+ ): Promise<EntityInstanceType<T>[]>;
1995
1938
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
1996
1939
  options: Options<T> = {},
1997
1940
  ...selectors: Selector[]
1998
- ): Promise<ReturnType<T['factorySync']>[] | string[] | number> {
1999
- const { result: resultPromise, process } = this.getEntitesRowLike<T>(
1941
+ ): Promise<EntityInstanceType<T>[] | string[] | number> {
1942
+ const { result: resultPromise, process } = this.getEntitiesRowLike<T>(
2000
1943
  // @ts-ignore: options is correct here.
2001
1944
  options,
2002
1945
  selectors,
2003
- (options, formattedSelectors, etype) =>
2004
- this.performQuery(options, formattedSelectors, etype),
1946
+ ({ options, selectors, etype }) =>
1947
+ this.performQuery(options, selectors, etype),
2005
1948
  () => {
2006
1949
  const next: any = result.next();
2007
1950
  return next.done ? null : next.value;
@@ -2022,7 +1965,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2022
1965
  : row.value === 'S'
2023
1966
  ? JSON.stringify(row.string)
2024
1967
  : row.value,
2025
- })
1968
+ }),
2026
1969
  );
2027
1970
 
2028
1971
  const result = await resultPromise;
@@ -2033,251 +1976,269 @@ export default class PostgreSQLDriver extends NymphDriver {
2033
1976
  return value;
2034
1977
  }
2035
1978
 
2036
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2037
- options: Options<T> & { return: 'count' },
2038
- ...selectors: Selector[]
2039
- ): number;
2040
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2041
- options: Options<T> & { return: 'guid' },
2042
- ...selectors: Selector[]
2043
- ): string[];
2044
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2045
- options?: Options<T>,
2046
- ...selectors: Selector[]
2047
- ): ReturnType<T['factorySync']>[];
2048
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
2049
- options: Options<T> = {},
2050
- ...selectors: Selector[]
2051
- ): ReturnType<T['factorySync']>[] | string[] | number {
2052
- const { result, process } = this.getEntitesRowLike<T>(
2053
- // @ts-ignore: options is correct here.
2054
- options,
2055
- selectors,
2056
- (options, formattedSelectors, etype) =>
2057
- this.performQuerySync(options, formattedSelectors, etype),
2058
- () => {
2059
- const next: any = result.next();
2060
- return next.done ? null : next.value;
2061
- },
2062
- () => undefined,
2063
- (row) => Number(row.count),
2064
- (row) => row.guid,
2065
- (row) => ({
2066
- tags: row.tags,
2067
- cdate: isNaN(Number(row.cdate)) ? null : Number(row.cdate),
2068
- mdate: isNaN(Number(row.mdate)) ? null : Number(row.mdate),
2069
- }),
2070
- (row) => ({
2071
- name: row.name,
2072
- svalue:
2073
- row.value === 'N'
2074
- ? JSON.stringify(Number(row.number))
2075
- : row.value === 'S'
2076
- ? JSON.stringify(row.string)
2077
- : row.value,
2078
- })
2079
- );
2080
- const value = process();
2081
- if (value instanceof Error) {
2082
- throw value;
2083
- }
2084
- return value;
2085
- }
2086
-
2087
1979
  public async getUID(name: string) {
2088
1980
  if (name == null) {
2089
1981
  throw new InvalidParametersError('Name not given for UID.');
2090
1982
  }
2091
1983
  const result = await this.queryGet(
2092
1984
  `SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(
2093
- `${this.prefix}uids`
1985
+ `${this.prefix}uids`,
2094
1986
  )} WHERE "name"=@name;`,
2095
1987
  {
2096
1988
  params: {
2097
1989
  name: name,
2098
1990
  },
2099
- }
1991
+ },
2100
1992
  );
2101
1993
  return result?.cur_uid == null ? null : Number(result.cur_uid);
2102
1994
  }
2103
1995
 
2104
- public async import(filename: string) {
1996
+ public async import(filename: string, transaction?: boolean) {
2105
1997
  try {
2106
1998
  const result = await this.importFromFile(
2107
1999
  filename,
2108
2000
  async (guid, tags, sdata, etype) => {
2109
- await this.queryRun(
2110
- `DELETE FROM ${PostgreSQLDriver.escape(
2111
- `${this.prefix}entities_${etype}`
2112
- )} WHERE "guid"=decode(@guid, 'hex');`,
2113
- {
2114
- etypes: [etype],
2115
- params: {
2116
- guid,
2117
- },
2118
- }
2119
- );
2120
- await this.queryRun(
2121
- `INSERT INTO ${PostgreSQLDriver.escape(
2122
- `${this.prefix}entities_${etype}`
2123
- )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`,
2124
- {
2125
- etypes: [etype],
2126
- params: {
2127
- guid,
2128
- tags,
2129
- cdate: isNaN(Number(JSON.parse(sdata.cdate)))
2130
- ? null
2131
- : Number(JSON.parse(sdata.cdate)),
2132
- mdate: isNaN(Number(JSON.parse(sdata.mdate)))
2133
- ? null
2134
- : Number(JSON.parse(sdata.mdate)),
2135
- },
2136
- }
2137
- );
2138
- const promises = [];
2139
- promises.push(
2140
- this.queryRun(
2141
- `DELETE FROM ${PostgreSQLDriver.escape(
2142
- `${this.prefix}data_${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(
2001
+ try {
2002
+ await this.internalTransaction(`nymph-import-entity-${guid}`);
2003
+
2004
+ const cdate = Number(JSON.parse(sdata.cdate));
2005
+ delete sdata.cdate;
2006
+ const mdate = Number(JSON.parse(sdata.mdate));
2007
+ delete sdata.mdate;
2008
+
2009
+ await this.queryRun(
2154
2010
  `DELETE FROM ${PostgreSQLDriver.escape(
2155
- `${this.prefix}comparisons_${etype}`
2011
+ `${this.prefix}entities_${etype}`,
2156
2012
  )} WHERE "guid"=decode(@guid, 'hex');`,
2157
2013
  {
2158
2014
  etypes: [etype],
2159
2015
  params: {
2160
2016
  guid,
2161
2017
  },
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');`,
2018
+ },
2019
+ );
2020
+ await this.queryRun(
2021
+ `INSERT INTO ${PostgreSQLDriver.escape(
2022
+ `${this.prefix}entities_${etype}`,
2023
+ )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`,
2170
2024
  {
2171
2025
  etypes: [etype],
2172
2026
  params: {
2173
2027
  guid,
2028
+ tags,
2029
+ cdate: isNaN(cdate) ? null : cdate,
2030
+ mdate: isNaN(mdate) ? null : mdate,
2174
2031
  },
2175
- }
2176
- )
2177
- );
2178
- await Promise.all(promises);
2179
- delete sdata.cdate;
2180
- delete sdata.mdate;
2181
- for (const name in sdata) {
2182
- const value = sdata[name];
2183
- const uvalue = JSON.parse(value);
2184
- if (value === undefined) {
2185
- continue;
2186
- }
2187
- const storageValue =
2188
- typeof uvalue === 'number'
2189
- ? 'N'
2190
- : typeof uvalue === 'string'
2191
- ? 'S'
2192
- : value;
2032
+ },
2033
+ );
2193
2034
  const promises = [];
2194
2035
  promises.push(
2195
2036
  this.queryRun(
2196
- `INSERT INTO ${PostgreSQLDriver.escape(
2197
- `${this.prefix}data_${etype}`
2198
- )} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`,
2037
+ `DELETE FROM ${PostgreSQLDriver.escape(
2038
+ `${this.prefix}data_${etype}`,
2039
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2199
2040
  {
2200
2041
  etypes: [etype],
2201
2042
  params: {
2202
2043
  guid,
2203
- name,
2204
- storageValue,
2205
2044
  },
2206
- }
2207
- )
2045
+ },
2046
+ ),
2208
2047
  );
2209
2048
  promises.push(
2210
2049
  this.queryRun(
2211
- `INSERT INTO ${PostgreSQLDriver.escape(
2212
- `${this.prefix}comparisons_${etype}`
2213
- )} ("guid", "name", "truthy", "string", "number") VALUES (decode(@guid, 'hex'), @name, @truthy, @string, @number);`,
2050
+ `DELETE FROM ${PostgreSQLDriver.escape(
2051
+ `${this.prefix}comparisons_${etype}`,
2052
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2214
2053
  {
2215
2054
  etypes: [etype],
2216
2055
  params: {
2217
2056
  guid,
2218
- name,
2219
- truthy: !!uvalue,
2220
- string: `${uvalue}`,
2221
- number: isNaN(Number(uvalue)) ? null : Number(uvalue),
2222
2057
  },
2223
- }
2224
- )
2058
+ },
2059
+ ),
2225
2060
  );
2226
- const references = this.findReferences(value);
2227
- for (const reference of references) {
2061
+ promises.push(
2062
+ this.queryRun(
2063
+ `DELETE FROM ${PostgreSQLDriver.escape(
2064
+ `${this.prefix}references_${etype}`,
2065
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2066
+ {
2067
+ etypes: [etype],
2068
+ params: {
2069
+ guid,
2070
+ },
2071
+ },
2072
+ ),
2073
+ );
2074
+ promises.push(
2075
+ this.queryRun(
2076
+ `DELETE FROM ${PostgreSQLDriver.escape(
2077
+ `${this.prefix}uniques_${etype}`,
2078
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2079
+ {
2080
+ etypes: [etype],
2081
+ params: {
2082
+ guid,
2083
+ },
2084
+ },
2085
+ ),
2086
+ );
2087
+ await Promise.all(promises);
2088
+ for (const name in sdata) {
2089
+ const value = sdata[name];
2090
+ const uvalue = JSON.parse(value);
2091
+ if (value === undefined) {
2092
+ continue;
2093
+ }
2094
+ const storageValue =
2095
+ typeof uvalue === 'number'
2096
+ ? 'N'
2097
+ : typeof uvalue === 'string'
2098
+ ? 'S'
2099
+ : value;
2100
+ const promises = [];
2228
2101
  promises.push(
2229
2102
  this.queryRun(
2230
2103
  `INSERT INTO ${PostgreSQLDriver.escape(
2231
- `${this.prefix}references_${etype}`
2232
- )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2104
+ `${this.prefix}data_${etype}`,
2105
+ )} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`,
2233
2106
  {
2234
2107
  etypes: [etype],
2235
2108
  params: {
2236
2109
  guid,
2237
2110
  name,
2238
- reference,
2111
+ storageValue,
2239
2112
  },
2113
+ },
2114
+ ),
2115
+ );
2116
+ promises.push(
2117
+ this.queryRun(
2118
+ `INSERT INTO ${PostgreSQLDriver.escape(
2119
+ `${this.prefix}comparisons_${etype}`,
2120
+ )} ("guid", "name", "truthy", "string", "number") VALUES (decode(@guid, 'hex'), @name, @truthy, @string, @number);`,
2121
+ {
2122
+ etypes: [etype],
2123
+ params: {
2124
+ guid,
2125
+ name,
2126
+ truthy: !!uvalue,
2127
+ string: `${uvalue}`,
2128
+ number: isNaN(Number(uvalue)) ? null : Number(uvalue),
2129
+ },
2130
+ },
2131
+ ),
2132
+ );
2133
+ const references = this.findReferences(value);
2134
+ for (const reference of references) {
2135
+ promises.push(
2136
+ this.queryRun(
2137
+ `INSERT INTO ${PostgreSQLDriver.escape(
2138
+ `${this.prefix}references_${etype}`,
2139
+ )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2140
+ {
2141
+ etypes: [etype],
2142
+ params: {
2143
+ guid,
2144
+ name,
2145
+ reference,
2146
+ },
2147
+ },
2148
+ ),
2149
+ );
2150
+ }
2151
+ }
2152
+ const uniques = await this.nymph
2153
+ .getEntityClassByEtype(etype)
2154
+ .getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
2155
+ for (const unique of uniques) {
2156
+ promises.push(
2157
+ this.queryRun(
2158
+ `INSERT INTO ${PostgreSQLDriver.escape(
2159
+ `${this.prefix}uniques_${etype}`,
2160
+ )} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`,
2161
+ {
2162
+ etypes: [etype],
2163
+ params: {
2164
+ guid,
2165
+ unique,
2166
+ },
2167
+ },
2168
+ ).catch((e: any) => {
2169
+ if (e instanceof EntityUniqueConstraintError) {
2170
+ this.nymph.config.debugError(
2171
+ 'postgresql',
2172
+ `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2173
+ );
2240
2174
  }
2241
- )
2175
+ return e;
2176
+ }),
2242
2177
  );
2243
2178
  }
2179
+ await Promise.all(promises);
2180
+ await this.commit(`nymph-import-entity-${guid}`);
2181
+ } catch (e: any) {
2182
+ this.nymph.config.debugError(
2183
+ 'postgresql',
2184
+ `Import entity error: "${e}"`,
2185
+ );
2186
+ await this.rollback(`nymph-import-entity-${guid}`);
2187
+ throw e;
2244
2188
  }
2245
- await Promise.all(promises);
2246
2189
  },
2247
2190
  async (name, curUid) => {
2248
- await this.queryRun(
2249
- `DELETE FROM ${PostgreSQLDriver.escape(
2250
- `${this.prefix}uids`
2251
- )} WHERE "name"=@name;`,
2252
- {
2253
- params: {
2254
- name,
2191
+ try {
2192
+ await this.internalTransaction(`nymph-import-uid-${name}`);
2193
+ await this.queryRun(
2194
+ `DELETE FROM ${PostgreSQLDriver.escape(
2195
+ `${this.prefix}uids`,
2196
+ )} WHERE "name"=@name;`,
2197
+ {
2198
+ params: {
2199
+ name,
2200
+ },
2255
2201
  },
2256
- }
2257
- );
2258
- await this.queryRun(
2259
- `INSERT INTO ${PostgreSQLDriver.escape(
2260
- `${this.prefix}uids`
2261
- )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2262
- {
2263
- params: {
2264
- name,
2265
- curUid,
2202
+ );
2203
+ await this.queryRun(
2204
+ `INSERT INTO ${PostgreSQLDriver.escape(
2205
+ `${this.prefix}uids`,
2206
+ )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2207
+ {
2208
+ params: {
2209
+ name,
2210
+ curUid,
2211
+ },
2266
2212
  },
2267
- }
2268
- );
2213
+ );
2214
+ await this.commit(`nymph-import-uid-${name}`);
2215
+ } catch (e: any) {
2216
+ this.nymph.config.debugError(
2217
+ 'postgresql',
2218
+ `Import UID error: "${e}"`,
2219
+ );
2220
+ await this.rollback(`nymph-import-uid-${name}`);
2221
+ throw e;
2222
+ }
2269
2223
  },
2270
2224
  async () => {
2271
- await this.internalTransaction('nymph-import');
2225
+ if (transaction) {
2226
+ await this.internalTransaction('nymph-import');
2227
+ }
2272
2228
  },
2273
2229
  async () => {
2274
- await this.commit('nymph-import');
2275
- }
2230
+ if (transaction) {
2231
+ await this.commit('nymph-import');
2232
+ }
2233
+ },
2276
2234
  );
2277
2235
 
2278
2236
  return result;
2279
2237
  } catch (e: any) {
2280
- await this.rollback('nymph-import');
2238
+ this.nymph.config.debugError('postgresql', `Import error: "${e}"`);
2239
+ if (transaction) {
2240
+ await this.rollback('nymph-import');
2241
+ }
2281
2242
  return false;
2282
2243
  }
2283
2244
  }
@@ -2287,52 +2248,54 @@ export default class PostgreSQLDriver extends NymphDriver {
2287
2248
  throw new InvalidParametersError('Name not given for UID.');
2288
2249
  }
2289
2250
  await this.internalTransaction('nymph-newuid');
2251
+ let curUid: number | undefined = undefined;
2290
2252
  try {
2291
2253
  const lock = await this.queryGet(
2292
2254
  `SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(
2293
- `${this.prefix}uids`
2255
+ `${this.prefix}uids`,
2294
2256
  )} WHERE "name"=@name FOR UPDATE;`,
2295
2257
  {
2296
2258
  params: {
2297
2259
  name,
2298
2260
  },
2299
- }
2261
+ },
2300
2262
  );
2301
- let curUid: number | undefined =
2302
- lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
2263
+ curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
2303
2264
  if (curUid == null) {
2304
2265
  curUid = 1;
2305
2266
  await this.queryRun(
2306
2267
  `INSERT INTO ${PostgreSQLDriver.escape(
2307
- `${this.prefix}uids`
2268
+ `${this.prefix}uids`,
2308
2269
  )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2309
2270
  {
2310
2271
  params: {
2311
2272
  name,
2312
2273
  curUid,
2313
2274
  },
2314
- }
2275
+ },
2315
2276
  );
2316
2277
  } else {
2317
2278
  curUid++;
2318
2279
  await this.queryRun(
2319
2280
  `UPDATE ${PostgreSQLDriver.escape(
2320
- `${this.prefix}uids`
2281
+ `${this.prefix}uids`,
2321
2282
  )} SET "cur_uid"=@curUid WHERE "name"=@name;`,
2322
2283
  {
2323
2284
  params: {
2324
2285
  name,
2325
2286
  curUid,
2326
2287
  },
2327
- }
2288
+ },
2328
2289
  );
2329
2290
  }
2330
- await this.commit('nymph-newuid');
2331
- return curUid;
2332
2291
  } catch (e: any) {
2292
+ this.nymph.config.debugError('postgresql', `New UID error: "${e}"`);
2333
2293
  await this.rollback('nymph-newuid');
2334
2294
  throw e;
2335
2295
  }
2296
+
2297
+ await this.commit('nymph-newuid');
2298
+ return curUid;
2336
2299
  }
2337
2300
 
2338
2301
  public async renameUID(oldName: string, newName: string) {
@@ -2341,14 +2304,14 @@ export default class PostgreSQLDriver extends NymphDriver {
2341
2304
  }
2342
2305
  await this.queryRun(
2343
2306
  `UPDATE ${PostgreSQLDriver.escape(
2344
- `${this.prefix}uids`
2307
+ `${this.prefix}uids`,
2345
2308
  )} SET "name"=@newName WHERE "name"=@oldName;`,
2346
2309
  {
2347
2310
  params: {
2348
2311
  newName,
2349
2312
  oldName,
2350
2313
  },
2351
- }
2314
+ },
2352
2315
  );
2353
2316
  return true;
2354
2317
  }
@@ -2356,7 +2319,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2356
2319
  public async rollback(name: string) {
2357
2320
  if (name == null || typeof name !== 'string' || name.length === 0) {
2358
2321
  throw new InvalidParametersError(
2359
- 'Transaction rollback attempted without a name.'
2322
+ 'Transaction rollback attempted without a name.',
2360
2323
  );
2361
2324
  }
2362
2325
  if (!this.transaction || this.transaction.count === 0) {
@@ -2364,7 +2327,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2364
2327
  return true;
2365
2328
  }
2366
2329
  await this.queryRun(
2367
- `ROLLBACK TO SAVEPOINT ${PostgreSQLDriver.escape(name)};`
2330
+ `ROLLBACK TO SAVEPOINT ${PostgreSQLDriver.escape(name)};`,
2368
2331
  );
2369
2332
  this.transaction.count--;
2370
2333
  if (this.transaction.count === 0) {
@@ -2381,12 +2344,13 @@ export default class PostgreSQLDriver extends NymphDriver {
2381
2344
  guid: string,
2382
2345
  data: EntityData,
2383
2346
  sdata: SerializedEntityData,
2384
- etype: string
2347
+ uniques: string[],
2348
+ etype: string,
2385
2349
  ) => {
2386
2350
  const runInsertQuery = async (
2387
2351
  name: string,
2388
2352
  value: any,
2389
- svalue: string
2353
+ svalue: string,
2390
2354
  ) => {
2391
2355
  if (value === undefined) {
2392
2356
  return;
@@ -2401,7 +2365,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2401
2365
  promises.push(
2402
2366
  this.queryRun(
2403
2367
  `INSERT INTO ${PostgreSQLDriver.escape(
2404
- `${this.prefix}data_${etype}`
2368
+ `${this.prefix}data_${etype}`,
2405
2369
  )} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`,
2406
2370
  {
2407
2371
  etypes: [etype],
@@ -2410,13 +2374,13 @@ export default class PostgreSQLDriver extends NymphDriver {
2410
2374
  name,
2411
2375
  storageValue,
2412
2376
  },
2413
- }
2414
- )
2377
+ },
2378
+ ),
2415
2379
  );
2416
2380
  promises.push(
2417
2381
  this.queryRun(
2418
2382
  `INSERT INTO ${PostgreSQLDriver.escape(
2419
- `${this.prefix}comparisons_${etype}`
2383
+ `${this.prefix}comparisons_${etype}`,
2420
2384
  )} ("guid", "name", "truthy", "string", "number") VALUES (decode(@guid, 'hex'), @name, @truthy, @string, @number);`,
2421
2385
  {
2422
2386
  etypes: [etype],
@@ -2427,15 +2391,15 @@ export default class PostgreSQLDriver extends NymphDriver {
2427
2391
  string: `${value}`,
2428
2392
  number: isNaN(Number(value)) ? null : Number(value),
2429
2393
  },
2430
- }
2431
- )
2394
+ },
2395
+ ),
2432
2396
  );
2433
2397
  const references = this.findReferences(svalue);
2434
2398
  for (const reference of references) {
2435
2399
  promises.push(
2436
2400
  this.queryRun(
2437
2401
  `INSERT INTO ${PostgreSQLDriver.escape(
2438
- `${this.prefix}references_${etype}`
2402
+ `${this.prefix}references_${etype}`,
2439
2403
  )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2440
2404
  {
2441
2405
  etypes: [etype],
@@ -2444,12 +2408,36 @@ export default class PostgreSQLDriver extends NymphDriver {
2444
2408
  name,
2445
2409
  reference,
2446
2410
  },
2447
- }
2448
- )
2411
+ },
2412
+ ),
2449
2413
  );
2450
2414
  }
2451
2415
  await Promise.all(promises);
2452
2416
  };
2417
+ for (const unique of uniques) {
2418
+ try {
2419
+ await this.queryRun(
2420
+ `INSERT INTO ${PostgreSQLDriver.escape(
2421
+ `${this.prefix}uniques_${etype}`,
2422
+ )} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`,
2423
+ {
2424
+ etypes: [etype],
2425
+ params: {
2426
+ guid,
2427
+ unique,
2428
+ },
2429
+ },
2430
+ );
2431
+ } catch (e: any) {
2432
+ if (e instanceof EntityUniqueConstraintError) {
2433
+ this.nymph.config.debugError(
2434
+ 'postgresql',
2435
+ `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2436
+ );
2437
+ }
2438
+ throw e;
2439
+ }
2440
+ }
2453
2441
  for (const name in data) {
2454
2442
  await runInsertQuery(name, data[name], JSON.stringify(data[name]));
2455
2443
  }
@@ -2457,13 +2445,20 @@ export default class PostgreSQLDriver extends NymphDriver {
2457
2445
  await runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
2458
2446
  }
2459
2447
  };
2448
+ let inTransaction = false;
2460
2449
  try {
2461
2450
  const result = await this.saveEntityRowLike(
2462
2451
  entity,
2463
- async (_entity, guid, tags, data, sdata, cdate, etype) => {
2452
+ async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
2453
+ if (
2454
+ Object.keys(data).length === 0 &&
2455
+ Object.keys(sdata).length === 0
2456
+ ) {
2457
+ return false;
2458
+ }
2464
2459
  await this.queryRun(
2465
2460
  `INSERT INTO ${PostgreSQLDriver.escape(
2466
- `${this.prefix}entities_${etype}`
2461
+ `${this.prefix}entities_${etype}`,
2467
2462
  )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @cdate);`,
2468
2463
  {
2469
2464
  etypes: [etype],
@@ -2472,69 +2467,88 @@ export default class PostgreSQLDriver extends NymphDriver {
2472
2467
  tags,
2473
2468
  cdate,
2474
2469
  },
2475
- }
2470
+ },
2476
2471
  );
2477
- await insertData(guid, data, sdata, etype);
2472
+ await insertData(guid, data, sdata, uniques, etype);
2478
2473
  return true;
2479
2474
  },
2480
- async (entity, guid, tags, data, sdata, mdate, etype) => {
2475
+ async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
2476
+ if (
2477
+ Object.keys(data).length === 0 &&
2478
+ Object.keys(sdata).length === 0
2479
+ ) {
2480
+ return false;
2481
+ }
2481
2482
  const promises = [];
2482
2483
  promises.push(
2483
2484
  this.queryRun(
2484
2485
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2485
- `${this.prefix}entities_${etype}`
2486
+ `${this.prefix}entities_${etype}`,
2486
2487
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2487
2488
  {
2488
2489
  etypes: [etype],
2489
2490
  params: {
2490
2491
  guid,
2491
2492
  },
2492
- }
2493
- )
2493
+ },
2494
+ ),
2494
2495
  );
2495
2496
  promises.push(
2496
2497
  this.queryRun(
2497
2498
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2498
- `${this.prefix}data_${etype}`
2499
+ `${this.prefix}data_${etype}`,
2499
2500
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2500
2501
  {
2501
2502
  etypes: [etype],
2502
2503
  params: {
2503
2504
  guid,
2504
2505
  },
2505
- }
2506
- )
2506
+ },
2507
+ ),
2507
2508
  );
2508
2509
  promises.push(
2509
2510
  this.queryRun(
2510
2511
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2511
- `${this.prefix}comparisons_${etype}`
2512
+ `${this.prefix}comparisons_${etype}`,
2512
2513
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2513
2514
  {
2514
2515
  etypes: [etype],
2515
2516
  params: {
2516
2517
  guid,
2517
2518
  },
2518
- }
2519
- )
2519
+ },
2520
+ ),
2520
2521
  );
2521
2522
  promises.push(
2522
2523
  this.queryRun(
2523
2524
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
2524
- `${this.prefix}references_${etype}`
2525
+ `${this.prefix}references_${etype}`,
2525
2526
  )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2526
2527
  {
2527
2528
  etypes: [etype],
2528
2529
  params: {
2529
2530
  guid,
2530
2531
  },
2531
- }
2532
- )
2532
+ },
2533
+ ),
2534
+ );
2535
+ promises.push(
2536
+ this.queryRun(
2537
+ `SELECT 1 FROM ${PostgreSQLDriver.escape(
2538
+ `${this.prefix}uniques_${etype}`,
2539
+ )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
2540
+ {
2541
+ etypes: [etype],
2542
+ params: {
2543
+ guid,
2544
+ },
2545
+ },
2546
+ ),
2533
2547
  );
2534
2548
  await Promise.all(promises);
2535
2549
  const info = await this.queryRun(
2536
2550
  `UPDATE ${PostgreSQLDriver.escape(
2537
- `${this.prefix}entities_${etype}`
2551
+ `${this.prefix}entities_${etype}`,
2538
2552
  )} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=decode(@guid, 'hex') AND "mdate" <= @emdate;`,
2539
2553
  {
2540
2554
  etypes: [etype],
@@ -2544,7 +2558,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2544
2558
  guid,
2545
2559
  emdate: isNaN(Number(entity.mdate)) ? 0 : Number(entity.mdate),
2546
2560
  },
2547
- }
2561
+ },
2548
2562
  );
2549
2563
  let success = false;
2550
2564
  if (info.rowCount === 1) {
@@ -2552,64 +2566,84 @@ export default class PostgreSQLDriver extends NymphDriver {
2552
2566
  promises.push(
2553
2567
  this.queryRun(
2554
2568
  `DELETE FROM ${PostgreSQLDriver.escape(
2555
- `${this.prefix}data_${etype}`
2569
+ `${this.prefix}data_${etype}`,
2556
2570
  )} WHERE "guid"=decode(@guid, 'hex');`,
2557
2571
  {
2558
2572
  etypes: [etype],
2559
2573
  params: {
2560
2574
  guid,
2561
2575
  },
2562
- }
2563
- )
2576
+ },
2577
+ ),
2564
2578
  );
2565
2579
  promises.push(
2566
2580
  this.queryRun(
2567
2581
  `DELETE FROM ${PostgreSQLDriver.escape(
2568
- `${this.prefix}comparisons_${etype}`
2582
+ `${this.prefix}comparisons_${etype}`,
2569
2583
  )} WHERE "guid"=decode(@guid, 'hex');`,
2570
2584
  {
2571
2585
  etypes: [etype],
2572
2586
  params: {
2573
2587
  guid,
2574
2588
  },
2575
- }
2576
- )
2589
+ },
2590
+ ),
2577
2591
  );
2578
2592
  promises.push(
2579
2593
  this.queryRun(
2580
2594
  `DELETE FROM ${PostgreSQLDriver.escape(
2581
- `${this.prefix}references_${etype}`
2595
+ `${this.prefix}references_${etype}`,
2582
2596
  )} WHERE "guid"=decode(@guid, 'hex');`,
2583
2597
  {
2584
2598
  etypes: [etype],
2585
2599
  params: {
2586
2600
  guid,
2587
2601
  },
2588
- }
2589
- )
2602
+ },
2603
+ ),
2604
+ );
2605
+ promises.push(
2606
+ this.queryRun(
2607
+ `DELETE FROM ${PostgreSQLDriver.escape(
2608
+ `${this.prefix}uniques_${etype}`,
2609
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2610
+ {
2611
+ etypes: [etype],
2612
+ params: {
2613
+ guid,
2614
+ },
2615
+ },
2616
+ ),
2590
2617
  );
2591
2618
  await Promise.all(promises);
2592
- await insertData(guid, data, sdata, etype);
2619
+ await insertData(guid, data, sdata, uniques, etype);
2593
2620
  success = true;
2594
2621
  }
2595
2622
  return success;
2596
2623
  },
2597
2624
  async () => {
2598
2625
  await this.internalTransaction('nymph-save');
2626
+ inTransaction = true;
2599
2627
  },
2600
2628
  async (success) => {
2601
- if (success) {
2602
- await this.commit('nymph-save');
2603
- } else {
2604
- await this.rollback('nymph-save');
2629
+ if (inTransaction) {
2630
+ inTransaction = false;
2631
+ if (success) {
2632
+ await this.commit('nymph-save');
2633
+ } else {
2634
+ await this.rollback('nymph-save');
2635
+ }
2605
2636
  }
2606
2637
  return success;
2607
- }
2638
+ },
2608
2639
  );
2609
2640
 
2610
2641
  return result;
2611
2642
  } catch (e: any) {
2612
- await this.rollback('nymph-save');
2643
+ this.nymph.config.debugError('postgresql', `Save entity error: "${e}"`);
2644
+ if (inTransaction) {
2645
+ await this.rollback('nymph-save');
2646
+ }
2613
2647
  throw e;
2614
2648
  }
2615
2649
  }
@@ -2622,38 +2656,39 @@ export default class PostgreSQLDriver extends NymphDriver {
2622
2656
  try {
2623
2657
  await this.queryRun(
2624
2658
  `DELETE FROM ${PostgreSQLDriver.escape(
2625
- `${this.prefix}uids`
2659
+ `${this.prefix}uids`,
2626
2660
  )} WHERE "name"=@name;`,
2627
2661
  {
2628
2662
  params: {
2629
2663
  name,
2630
2664
  curUid,
2631
2665
  },
2632
- }
2666
+ },
2633
2667
  );
2634
2668
  await this.queryRun(
2635
2669
  `INSERT INTO ${PostgreSQLDriver.escape(
2636
- `${this.prefix}uids`
2670
+ `${this.prefix}uids`,
2637
2671
  )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2638
2672
  {
2639
2673
  params: {
2640
2674
  name,
2641
2675
  curUid,
2642
2676
  },
2643
- }
2677
+ },
2644
2678
  );
2645
- await this.commit('nymph-setuid');
2646
- return true;
2647
2679
  } catch (e: any) {
2648
2680
  await this.rollback('nymph-setuid');
2649
2681
  throw e;
2650
2682
  }
2683
+
2684
+ await this.commit('nymph-setuid');
2685
+ return true;
2651
2686
  }
2652
2687
 
2653
2688
  private async internalTransaction(name: string) {
2654
2689
  if (name == null || typeof name !== 'string' || name.length === 0) {
2655
2690
  throw new InvalidParametersError(
2656
- 'Transaction start attempted without a name.'
2691
+ 'Transaction start attempted without a name.',
2657
2692
  );
2658
2693
  }
2659
2694
 
@@ -2675,15 +2710,14 @@ export default class PostgreSQLDriver extends NymphDriver {
2675
2710
  }
2676
2711
 
2677
2712
  public async startTransaction(name: string) {
2678
- const inTransaction = this.inTransaction();
2713
+ const inTransaction = await this.inTransaction();
2679
2714
  const transaction = await this.internalTransaction(name);
2680
2715
  if (!inTransaction) {
2681
2716
  this.transaction = null;
2682
2717
  }
2683
2718
 
2684
2719
  const nymph = this.nymph.clone();
2685
- nymph.driver = new PostgreSQLDriver(this.config, this.link, transaction);
2686
- nymph.driver.init(nymph);
2720
+ (nymph.driver as PostgreSQLDriver).transaction = transaction;
2687
2721
 
2688
2722
  return nymph;
2689
2723
  }