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

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.
@@ -3,12 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const child_process_1 = __importDefault(require("child_process"));
7
6
  const pg_1 = require("pg");
8
7
  const pg_format_1 = __importDefault(require("pg-format"));
9
8
  const nymph_1 = require("@nymphjs/nymph");
10
9
  const guid_1 = require("@nymphjs/guid");
11
10
  const conf_1 = require("./conf");
11
+ /**
12
+ * The PostgreSQL Nymph database driver.
13
+ */
12
14
  class PostgreSQLDriver extends nymph_1.NymphDriver {
13
15
  static escape(input) {
14
16
  return pg_format_1.default.ident(input);
@@ -41,16 +43,38 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
41
43
  this.connect();
42
44
  }
43
45
  }
46
+ /**
47
+ * This is used internally by Nymph. Don't call it yourself.
48
+ *
49
+ * @returns A clone of this instance.
50
+ */
51
+ clone() {
52
+ return new PostgreSQLDriver(this.config, this.link, this.transaction ?? undefined);
53
+ }
44
54
  getConnection() {
45
55
  if (this.transaction != null && this.transaction.connection != null) {
46
56
  return Promise.resolve(this.transaction.connection);
47
57
  }
48
- return new Promise((resolve, reject) => this.link.connect((err, client, done) => err ? reject(err) : resolve({ client, done })));
58
+ return new Promise((resolve, reject) => this.link.connect((err, client, done) => err
59
+ ? reject(err)
60
+ : client
61
+ ? resolve({ client, done })
62
+ : reject('No client returned from connect.')));
49
63
  }
64
+ /**
65
+ * Connect to the PostgreSQL database.
66
+ *
67
+ * @returns Whether this instance is connected to a PostgreSQL database.
68
+ */
50
69
  async connect() {
70
+ // If we think we're connected, try pinging the server.
51
71
  try {
52
72
  if (this.connected) {
53
- const connection = await new Promise((resolve, reject) => this.link.connect((err, client, done) => err ? reject(err) : resolve({ client, done })));
73
+ const connection = await new Promise((resolve, reject) => this.link.connect((err, client, done) => err
74
+ ? reject(err)
75
+ : client
76
+ ? resolve({ client, done })
77
+ : reject('No client returned from connect.')));
54
78
  await new Promise((resolve, reject) => connection.client.query('SELECT 1;', [], (err, res) => {
55
79
  if (err) {
56
80
  reject(err);
@@ -63,6 +87,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
63
87
  catch (e) {
64
88
  this.connected = false;
65
89
  }
90
+ // Connecting, selecting database
66
91
  if (!this.connected) {
67
92
  try {
68
93
  this.link = new pg_1.Pool(this.postgresqlConfig);
@@ -82,6 +107,11 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
82
107
  }
83
108
  return this.connected;
84
109
  }
110
+ /**
111
+ * Disconnect from the PostgreSQL database.
112
+ *
113
+ * @returns Whether this instance is connected to a PostgreSQL database.
114
+ */
85
115
  async disconnect() {
86
116
  if (this.connected) {
87
117
  await new Promise((resolve) => this.link.end(() => resolve(0)));
@@ -92,26 +122,39 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
92
122
  async inTransaction() {
93
123
  return !!this.transaction;
94
124
  }
125
+ /**
126
+ * Check connection status.
127
+ *
128
+ * @returns Whether this instance is connected to a PostgreSQL database.
129
+ */
95
130
  isConnected() {
96
131
  return this.connected;
97
132
  }
98
- createTables(etype = null) {
133
+ /**
134
+ * Create entity tables in the database.
135
+ *
136
+ * @param etype The entity type to create a table for. If this is blank, the default tables are created.
137
+ * @returns True on success, false on failure.
138
+ */
139
+ async createTables(etype = null) {
99
140
  if (etype != null) {
100
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} (
141
+ // Create the entity table.
142
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} (
101
143
  "guid" BYTEA NOT NULL,
102
144
  "tags" TEXT[],
103
145
  "cdate" DOUBLE PRECISION NOT NULL,
104
146
  "mdate" DOUBLE PRECISION NOT NULL,
105
147
  PRIMARY KEY ("guid")
106
148
  ) WITH ( OIDS=FALSE );`);
107
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
108
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)};`);
109
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("cdate");`);
110
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)};`);
111
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("mdate");`);
112
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)};`);
113
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING gin ("tags");`);
114
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} (
149
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
150
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)};`);
151
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("cdate");`);
152
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)};`);
153
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("mdate");`);
154
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)};`);
155
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING gin ("tags");`);
156
+ // Create the data table.
157
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} (
115
158
  "guid" BYTEA NOT NULL,
116
159
  "name" TEXT NOT NULL,
117
160
  "value" TEXT NOT NULL,
@@ -119,16 +162,17 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
119
162
  FOREIGN KEY ("guid")
120
163
  REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
121
164
  ) WITH ( OIDS=FALSE );`);
122
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
123
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)};`);
124
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid");`);
125
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)};`);
126
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name");`);
127
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__user`)};`);
128
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__user`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name" = 'user'::text;`);
129
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__group`)};`);
130
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__group`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name" = 'group'::text;`);
131
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} (
165
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
166
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)};`);
167
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid");`);
168
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)};`);
169
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name");`);
170
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__user`)};`);
171
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__user`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name" = 'user'::text;`);
172
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__group`)};`);
173
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__group`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name" = 'group'::text;`);
174
+ // Create the data comparisons table.
175
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} (
132
176
  "guid" BYTEA NOT NULL,
133
177
  "name" TEXT NOT NULL,
134
178
  "truthy" BOOLEAN,
@@ -138,16 +182,17 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
138
182
  FOREIGN KEY ("guid")
139
183
  REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
140
184
  ) WITH ( OIDS=FALSE );`);
141
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
142
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid`)};`);
143
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("guid");`);
144
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_name`)};`);
145
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("name");`);
146
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_truthy`)};`);
147
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_truthy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("guid", "name") WHERE "truthy" = TRUE;`);
148
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_falsy`)};`);
149
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_falsy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("guid", "name") WHERE "truthy" <> TRUE;`);
150
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} (
185
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
186
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid`)};`);
187
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("guid");`);
188
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_name`)};`);
189
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("name");`);
190
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_truthy`)};`);
191
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_truthy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("guid", "name") WHERE "truthy" = TRUE;`);
192
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_falsy`)};`);
193
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_falsy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("guid", "name") WHERE "truthy" <> TRUE;`);
194
+ // Create the references table.
195
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} (
151
196
  "guid" BYTEA NOT NULL,
152
197
  "name" TEXT NOT NULL,
153
198
  "reference" BYTEA NOT NULL,
@@ -155,21 +200,30 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
155
200
  FOREIGN KEY ("guid")
156
201
  REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
157
202
  ) WITH ( OIDS=FALSE );`);
158
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
159
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)};`);
160
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("guid");`);
161
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)};`);
162
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("name");`);
163
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)};`);
164
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference");`);
203
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
204
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)};`);
205
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("guid");`);
206
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)};`);
207
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("name");`);
208
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)};`);
209
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference");`);
210
+ // Create the unique strings table.
211
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} (
212
+ "guid" BYTEA NOT NULL,
213
+ "unique" TEXT NOT NULL UNIQUE,
214
+ PRIMARY KEY ("guid", "unique"),
215
+ FOREIGN KEY ("guid")
216
+ REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
217
+ ) WITH ( OIDS=FALSE );`);
165
218
  }
166
219
  else {
167
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uids`)} (
220
+ // Create the UID table.
221
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uids`)} (
168
222
  "name" TEXT NOT NULL,
169
223
  "cur_uid" BIGINT NOT NULL,
170
224
  PRIMARY KEY ("name")
171
225
  ) WITH ( OIDS = FALSE );`);
172
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
226
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
173
227
  }
174
228
  return true;
175
229
  }
@@ -192,9 +246,10 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
192
246
  }
193
247
  catch (e) {
194
248
  const errorCode = e?.code;
195
- if (errorCode === '42P01' && this.createTables()) {
249
+ if (errorCode === '42P01' && (await this.createTables())) {
250
+ // If the tables don't exist yet, create them.
196
251
  for (let etype of etypes) {
197
- this.createTables(etype);
252
+ await this.createTables(etype);
198
253
  }
199
254
  try {
200
255
  return await runQuery();
@@ -203,30 +258,11 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
203
258
  throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
204
259
  }
205
260
  }
206
- else {
207
- throw e;
208
- }
209
- }
210
- }
211
- querySync(runQuery, query, etypes = []) {
212
- try {
213
- return runQuery();
214
- }
215
- catch (e) {
216
- const errorCode = e?.code;
217
- if (errorCode === '42P01' && this.createTables()) {
218
- for (let etype of etypes) {
219
- this.createTables(etype);
220
- }
221
- try {
222
- return runQuery();
223
- }
224
- catch (e2) {
225
- throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
226
- }
261
+ else if (errorCode === '23505') {
262
+ throw new nymph_1.EntityUniqueConstraintError(`Unique constraint violation.`);
227
263
  }
228
264
  else {
229
- throw e;
265
+ throw new nymph_1.QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
230
266
  }
231
267
  }
232
268
  }
@@ -246,35 +282,6 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
246
282
  return results.rows;
247
283
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
248
284
  }
249
- queryIterSync(query, { etypes = [], params = {}, } = {}) {
250
- const { query: newQuery, params: newParams } = this.translateQuery(query, params);
251
- return this.querySync(() => {
252
- const output = child_process_1.default.spawnSync(process.argv0, [__dirname + '/runPostgresqlSync.js'], {
253
- input: JSON.stringify({
254
- postgresqlConfig: this.postgresqlConfig,
255
- query: newQuery,
256
- params: newParams,
257
- }),
258
- timeout: 30000,
259
- maxBuffer: 100 * 1024 * 1024,
260
- encoding: 'utf8',
261
- windowsHide: true,
262
- });
263
- try {
264
- return JSON.parse(output.stdout).rows;
265
- }
266
- catch (e) {
267
- }
268
- if (output.status === 0) {
269
- throw new Error('Unknown parse error.');
270
- }
271
- const err = JSON.parse(output.stderr);
272
- const e = new Error(err.name);
273
- for (const name in err) {
274
- e[name] = err[name];
275
- }
276
- }, `${query} -- ${JSON.stringify(params)}`, etypes);
277
- }
278
285
  queryGet(query, { etypes = [], params = {}, } = {}) {
279
286
  const { query: newQuery, params: newParams } = this.translateQuery(query, params);
280
287
  return this.query(async () => {
@@ -307,36 +314,6 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
307
314
  return { rowCount: results.rowCount ?? 0 };
308
315
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
309
316
  }
310
- queryRunSync(query, { etypes = [], params = {}, } = {}) {
311
- const { query: newQuery, params: newParams } = this.translateQuery(query, params);
312
- return this.querySync(() => {
313
- const output = child_process_1.default.spawnSync(process.argv0, [__dirname + '/runPostgresqlSync.js'], {
314
- input: JSON.stringify({
315
- postgresqlConfig: this.postgresqlConfig,
316
- query: newQuery,
317
- params: newParams,
318
- }),
319
- timeout: 30000,
320
- maxBuffer: 100 * 1024 * 1024,
321
- encoding: 'utf8',
322
- windowsHide: true,
323
- });
324
- try {
325
- const results = JSON.parse(output.stdout);
326
- return { rowCount: results.rowCount ?? 0 };
327
- }
328
- catch (e) {
329
- }
330
- if (output.status === 0) {
331
- throw new Error('Unknown parse error.');
332
- }
333
- const err = JSON.parse(output.stderr);
334
- const e = new Error(err.name);
335
- for (const name in err) {
336
- e[name] = err[name];
337
- }
338
- }, `${query} -- ${JSON.stringify(params)}`, etypes);
339
- }
340
317
  async commit(name) {
341
318
  if (name == null || typeof name !== 'string' || name.length === 0) {
342
319
  throw new nymph_1.InvalidParametersError('Transaction commit attempted without a name.');
@@ -391,16 +368,24 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
391
368
  guid,
392
369
  },
393
370
  });
394
- await this.commit('nymph-delete');
395
- if (this.nymph.config.cache) {
396
- this.cleanCache(guid);
397
- }
398
- return true;
371
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
372
+ etypes: [etype],
373
+ params: {
374
+ guid,
375
+ },
376
+ });
399
377
  }
400
378
  catch (e) {
379
+ this.nymph.config.debugError('postgresql', `Delete entity error: "${e}"`);
401
380
  await this.rollback('nymph-delete');
402
381
  throw e;
403
382
  }
383
+ await this.commit('nymph-delete');
384
+ // Remove any cached versions of this entity.
385
+ if (this.nymph.config.cache) {
386
+ this.cleanCache(guid);
387
+ }
388
+ return true;
404
389
  }
405
390
  async deleteUID(name) {
406
391
  if (!name) {
@@ -424,6 +409,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
424
409
  writeLine('# UIDs');
425
410
  writeLine('#');
426
411
  writeLine('');
412
+ // Export UIDs.
427
413
  let uids = await this.queryIter(`SELECT * FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
428
414
  for (const uid of uids) {
429
415
  writeLine(`<${uid.name}>[${uid.cur_uid}]`);
@@ -433,6 +419,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
433
419
  writeLine('# Entities');
434
420
  writeLine('#');
435
421
  writeLine('');
422
+ // Get the etypes.
436
423
  const tables = await this.queryIter('SELECT relname FROM pg_stat_user_tables ORDER BY relname;');
437
424
  const etypes = [];
438
425
  for (const tableRow of tables) {
@@ -442,6 +429,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
442
429
  }
443
430
  }
444
431
  for (const etype of etypes) {
432
+ // Export entities.
445
433
  const dataIterator = (await this.queryIter(`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"
446
434
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} e
447
435
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} d ON e."guid"=d."guid"
@@ -457,6 +445,8 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
457
445
  writeLine(`\tcdate=${JSON.stringify(cdate)}`);
458
446
  writeLine(`\tmdate=${JSON.stringify(mdate)}`);
459
447
  if (datum.value.dname != null) {
448
+ // This do will keep going and adding the data until the
449
+ // next entity is reached. $row will end on the next entity.
460
450
  do {
461
451
  const value = datum.value.dvalue === 'N'
462
452
  ? JSON.stringify(Number(datum.value.number))
@@ -468,12 +458,23 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
468
458
  } while (!datum.done && datum.value.guid === guid);
469
459
  }
470
460
  else {
461
+ // Make sure that datum is incremented :)
471
462
  datum = dataIterator.next();
472
463
  }
473
464
  }
474
465
  }
475
466
  return;
476
467
  }
468
+ /**
469
+ * Generate the PostgreSQL query.
470
+ * @param options The options array.
471
+ * @param formattedSelectors The formatted selector array.
472
+ * @param etype
473
+ * @param count Used to track internal params.
474
+ * @param params Used to store internal params.
475
+ * @param subquery Whether only a subquery should be returned.
476
+ * @returns The SQL query.
477
+ */
477
478
  makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = []) {
478
479
  if (typeof options.class?.alterOptions === 'function') {
479
480
  options = options.class.alterOptions(options);
@@ -484,8 +485,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
484
485
  const fTable = `f${tableSuffix}`;
485
486
  const ieTable = `ie${tableSuffix}`;
486
487
  const countTable = `count${tableSuffix}`;
488
+ const sTable = `s${tableSuffix}`;
487
489
  const sort = options.sort ?? 'cdate';
488
- const queryParts = this.iterateSelectorsForQuery(formattedSelectors, (key, value, typeIsOr, typeIsNot) => {
490
+ const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => {
489
491
  const clauseNot = key.startsWith('!');
490
492
  let curQuery = '';
491
493
  for (const curValue of value) {
@@ -1255,18 +1257,38 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1255
1257
  return curQuery;
1256
1258
  });
1257
1259
  let sortBy;
1260
+ let sortByInner;
1261
+ let sortJoin = '';
1262
+ let sortJoinInner = '';
1263
+ const order = options.reverse ? ' DESC' : '';
1258
1264
  switch (sort) {
1259
1265
  case 'mdate':
1260
- sortBy = '"mdate"';
1266
+ sortBy = `${eTable}."mdate"${order}`;
1267
+ sortByInner = `${ieTable}."mdate"${order}`;
1261
1268
  break;
1262
1269
  case 'cdate':
1270
+ sortBy = `${eTable}."cdate"${order}`;
1271
+ sortByInner = `${ieTable}."cdate"${order}`;
1272
+ break;
1263
1273
  default:
1264
- sortBy = '"cdate"';
1274
+ const name = `param${++count.i}`;
1275
+ sortJoin = `LEFT JOIN (
1276
+ SELECT "guid", "string", "number"
1277
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype)}
1278
+ WHERE "name"=@${name}
1279
+ ORDER BY "number"${order}, "string"${order}
1280
+ ) ${sTable} ON ${eTable}."guid"=${sTable}."guid"`;
1281
+ sortJoinInner = `LEFT JOIN (
1282
+ SELECT "guid", "string", "number"
1283
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype)}
1284
+ WHERE "name"=@${name}
1285
+ ORDER BY "number"${order}, "string"${order}
1286
+ ) ${sTable} ON ${ieTable}."guid"=${sTable}."guid"`;
1287
+ sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
1288
+ sortByInner = sortBy;
1289
+ params[name] = sort;
1265
1290
  break;
1266
1291
  }
1267
- if (options.reverse) {
1268
- sortBy += ' DESC';
1269
- }
1270
1292
  let query;
1271
1293
  if (queryParts.length) {
1272
1294
  if (subquery) {
@@ -1302,8 +1324,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1302
1324
  : `${ieTable}."guid"`;
1303
1325
  query = `SELECT ${guidColumn} AS "guid"
1304
1326
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1327
+ ${sortJoinInner}
1305
1328
  WHERE (${whereClause})
1306
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1329
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1307
1330
  }
1308
1331
  else {
1309
1332
  query = `SELECT
@@ -1318,13 +1341,15 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1318
1341
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1319
1342
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1320
1343
  INNER JOIN ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1344
+ ${sortJoin}
1321
1345
  INNER JOIN (
1322
1346
  SELECT ${ieTable}."guid"
1323
1347
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1348
+ ${sortJoinInner}
1324
1349
  WHERE (${whereClause})
1325
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1350
+ ORDER BY ${sortByInner}${limit}${offset}
1326
1351
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1327
- ORDER BY ${eTable}.${sortBy}`;
1352
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1328
1353
  }
1329
1354
  }
1330
1355
  }
@@ -1359,7 +1384,8 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1359
1384
  : `${ieTable}."guid"`;
1360
1385
  query = `SELECT ${guidColumn} AS "guid"
1361
1386
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1362
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1387
+ ${sortJoinInner}
1388
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1363
1389
  }
1364
1390
  else {
1365
1391
  if (limit || offset) {
@@ -1375,12 +1401,14 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1375
1401
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1376
1402
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1377
1403
  INNER JOIN ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1404
+ ${sortJoin}
1378
1405
  INNER JOIN (
1379
1406
  SELECT ${ieTable}."guid"
1380
1407
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1381
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1408
+ ${sortJoinInner}
1409
+ ORDER BY ${sortByInner}${limit}${offset}
1382
1410
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1383
- ORDER BY ${eTable}.${sortBy}`;
1411
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1384
1412
  }
1385
1413
  else {
1386
1414
  query = `SELECT
@@ -1395,7 +1423,8 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1395
1423
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1396
1424
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1397
1425
  INNER JOIN ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1398
- ORDER BY ${eTable}.${sortBy}`;
1426
+ ${sortJoin}
1427
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1399
1428
  }
1400
1429
  }
1401
1430
  }
@@ -1416,15 +1445,10 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1416
1445
  result,
1417
1446
  };
1418
1447
  }
1419
- performQuerySync(options, formattedSelectors, etype) {
1420
- const { query, params, etypes } = this.makeEntityQuery(options, formattedSelectors, etype);
1421
- const result = (this.queryIterSync(query, { etypes, params }) || [])[Symbol.iterator]();
1422
- return {
1423
- result,
1424
- };
1425
- }
1426
1448
  async getEntities(options = {}, ...selectors) {
1427
- const { result: resultPromise, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuery(options, formattedSelectors, etype), () => {
1449
+ const { result: resultPromise, process } = this.getEntitiesRowLike(
1450
+ // @ts-ignore: options is correct here.
1451
+ options, selectors, ({ options, selectors, etype }) => this.performQuery(options, selectors, etype), () => {
1428
1452
  const next = result.next();
1429
1453
  return next.done ? null : next.value;
1430
1454
  }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
@@ -1446,28 +1470,6 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1446
1470
  }
1447
1471
  return value;
1448
1472
  }
1449
- getEntitiesSync(options = {}, ...selectors) {
1450
- const { result, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuerySync(options, formattedSelectors, etype), () => {
1451
- const next = result.next();
1452
- return next.done ? null : next.value;
1453
- }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
1454
- tags: row.tags,
1455
- cdate: isNaN(Number(row.cdate)) ? null : Number(row.cdate),
1456
- mdate: isNaN(Number(row.mdate)) ? null : Number(row.mdate),
1457
- }), (row) => ({
1458
- name: row.name,
1459
- svalue: row.value === 'N'
1460
- ? JSON.stringify(Number(row.number))
1461
- : row.value === 'S'
1462
- ? JSON.stringify(row.string)
1463
- : row.value,
1464
- }));
1465
- const value = process();
1466
- if (value instanceof Error) {
1467
- throw value;
1468
- }
1469
- return value;
1470
- }
1471
1473
  async getUID(name) {
1472
1474
  if (name == null) {
1473
1475
  throw new nymph_1.InvalidParametersError('Name not given for UID.');
@@ -1479,114 +1481,160 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1479
1481
  });
1480
1482
  return result?.cur_uid == null ? null : Number(result.cur_uid);
1481
1483
  }
1482
- async import(filename) {
1484
+ async import(filename, transaction) {
1483
1485
  try {
1484
1486
  const result = await this.importFromFile(filename, async (guid, tags, sdata, etype) => {
1485
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1486
- etypes: [etype],
1487
- params: {
1488
- guid,
1489
- },
1490
- });
1491
- await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`, {
1492
- etypes: [etype],
1493
- params: {
1494
- guid,
1495
- tags,
1496
- cdate: isNaN(Number(JSON.parse(sdata.cdate)))
1497
- ? null
1498
- : Number(JSON.parse(sdata.cdate)),
1499
- mdate: isNaN(Number(JSON.parse(sdata.mdate)))
1500
- ? null
1501
- : Number(JSON.parse(sdata.mdate)),
1502
- },
1503
- });
1504
- const promises = [];
1505
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1506
- etypes: [etype],
1507
- params: {
1508
- guid,
1509
- },
1510
- }));
1511
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1512
- etypes: [etype],
1513
- params: {
1514
- guid,
1515
- },
1516
- }));
1517
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1518
- etypes: [etype],
1519
- params: {
1520
- guid,
1521
- },
1522
- }));
1523
- await Promise.all(promises);
1524
- delete sdata.cdate;
1525
- delete sdata.mdate;
1526
- for (const name in sdata) {
1527
- const value = sdata[name];
1528
- const uvalue = JSON.parse(value);
1529
- if (value === undefined) {
1530
- continue;
1531
- }
1532
- const storageValue = typeof uvalue === 'number'
1533
- ? 'N'
1534
- : typeof uvalue === 'string'
1535
- ? 'S'
1536
- : value;
1487
+ try {
1488
+ await this.internalTransaction(`nymph-import-entity-${guid}`);
1489
+ const cdate = Number(JSON.parse(sdata.cdate));
1490
+ delete sdata.cdate;
1491
+ const mdate = Number(JSON.parse(sdata.mdate));
1492
+ delete sdata.mdate;
1493
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1494
+ etypes: [etype],
1495
+ params: {
1496
+ guid,
1497
+ },
1498
+ });
1499
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`, {
1500
+ etypes: [etype],
1501
+ params: {
1502
+ guid,
1503
+ tags,
1504
+ cdate: isNaN(cdate) ? null : cdate,
1505
+ mdate: isNaN(mdate) ? null : mdate,
1506
+ },
1507
+ });
1537
1508
  const promises = [];
1538
- promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`, {
1509
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1539
1510
  etypes: [etype],
1540
1511
  params: {
1541
1512
  guid,
1542
- name,
1543
- storageValue,
1544
1513
  },
1545
1514
  }));
1546
- promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ("guid", "name", "truthy", "string", "number") VALUES (decode(@guid, 'hex'), @name, @truthy, @string, @number);`, {
1515
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1547
1516
  etypes: [etype],
1548
1517
  params: {
1549
1518
  guid,
1550
- name,
1551
- truthy: !!uvalue,
1552
- string: `${uvalue}`,
1553
- number: isNaN(Number(uvalue)) ? null : Number(uvalue),
1554
1519
  },
1555
1520
  }));
1556
- const references = this.findReferences(value);
1557
- for (const reference of references) {
1558
- promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`, {
1521
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1522
+ etypes: [etype],
1523
+ params: {
1524
+ guid,
1525
+ },
1526
+ }));
1527
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1528
+ etypes: [etype],
1529
+ params: {
1530
+ guid,
1531
+ },
1532
+ }));
1533
+ await Promise.all(promises);
1534
+ for (const name in sdata) {
1535
+ const value = sdata[name];
1536
+ const uvalue = JSON.parse(value);
1537
+ if (value === undefined) {
1538
+ continue;
1539
+ }
1540
+ const storageValue = typeof uvalue === 'number'
1541
+ ? 'N'
1542
+ : typeof uvalue === 'string'
1543
+ ? 'S'
1544
+ : value;
1545
+ const promises = [];
1546
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`, {
1559
1547
  etypes: [etype],
1560
1548
  params: {
1561
1549
  guid,
1562
1550
  name,
1563
- reference,
1551
+ storageValue,
1564
1552
  },
1565
1553
  }));
1554
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ("guid", "name", "truthy", "string", "number") VALUES (decode(@guid, 'hex'), @name, @truthy, @string, @number);`, {
1555
+ etypes: [etype],
1556
+ params: {
1557
+ guid,
1558
+ name,
1559
+ truthy: !!uvalue,
1560
+ string: `${uvalue}`,
1561
+ number: isNaN(Number(uvalue)) ? null : Number(uvalue),
1562
+ },
1563
+ }));
1564
+ const references = this.findReferences(value);
1565
+ for (const reference of references) {
1566
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`, {
1567
+ etypes: [etype],
1568
+ params: {
1569
+ guid,
1570
+ name,
1571
+ reference,
1572
+ },
1573
+ }));
1574
+ }
1566
1575
  }
1576
+ const uniques = await this.nymph
1577
+ .getEntityClassByEtype(etype)
1578
+ .getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
1579
+ for (const unique of uniques) {
1580
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`, {
1581
+ etypes: [etype],
1582
+ params: {
1583
+ guid,
1584
+ unique,
1585
+ },
1586
+ }).catch((e) => {
1587
+ if (e instanceof nymph_1.EntityUniqueConstraintError) {
1588
+ this.nymph.config.debugError('postgresql', `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
1589
+ }
1590
+ return e;
1591
+ }));
1592
+ }
1593
+ await Promise.all(promises);
1594
+ await this.commit(`nymph-import-entity-${guid}`);
1595
+ }
1596
+ catch (e) {
1597
+ this.nymph.config.debugError('postgresql', `Import entity error: "${e}"`);
1598
+ await this.rollback(`nymph-import-entity-${guid}`);
1599
+ throw e;
1567
1600
  }
1568
- await Promise.all(promises);
1569
1601
  }, async (name, curUid) => {
1570
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1571
- params: {
1572
- name,
1573
- },
1574
- });
1575
- await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
1576
- params: {
1577
- name,
1578
- curUid,
1579
- },
1580
- });
1602
+ try {
1603
+ await this.internalTransaction(`nymph-import-uid-${name}`);
1604
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1605
+ params: {
1606
+ name,
1607
+ },
1608
+ });
1609
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
1610
+ params: {
1611
+ name,
1612
+ curUid,
1613
+ },
1614
+ });
1615
+ await this.commit(`nymph-import-uid-${name}`);
1616
+ }
1617
+ catch (e) {
1618
+ this.nymph.config.debugError('postgresql', `Import UID error: "${e}"`);
1619
+ await this.rollback(`nymph-import-uid-${name}`);
1620
+ throw e;
1621
+ }
1581
1622
  }, async () => {
1582
- await this.internalTransaction('nymph-import');
1623
+ if (transaction) {
1624
+ await this.internalTransaction('nymph-import');
1625
+ }
1583
1626
  }, async () => {
1584
- await this.commit('nymph-import');
1627
+ if (transaction) {
1628
+ await this.commit('nymph-import');
1629
+ }
1585
1630
  });
1586
1631
  return result;
1587
1632
  }
1588
1633
  catch (e) {
1589
- await this.rollback('nymph-import');
1634
+ this.nymph.config.debugError('postgresql', `Import error: "${e}"`);
1635
+ if (transaction) {
1636
+ await this.rollback('nymph-import');
1637
+ }
1590
1638
  return false;
1591
1639
  }
1592
1640
  }
@@ -1595,13 +1643,14 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1595
1643
  throw new nymph_1.InvalidParametersError('Name not given for UID.');
1596
1644
  }
1597
1645
  await this.internalTransaction('nymph-newuid');
1646
+ let curUid = undefined;
1598
1647
  try {
1599
1648
  const lock = await this.queryGet(`SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name FOR UPDATE;`, {
1600
1649
  params: {
1601
1650
  name,
1602
1651
  },
1603
1652
  });
1604
- let curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
1653
+ curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
1605
1654
  if (curUid == null) {
1606
1655
  curUid = 1;
1607
1656
  await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
@@ -1620,13 +1669,14 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1620
1669
  },
1621
1670
  });
1622
1671
  }
1623
- await this.commit('nymph-newuid');
1624
- return curUid;
1625
1672
  }
1626
1673
  catch (e) {
1674
+ this.nymph.config.debugError('postgresql', `New UID error: "${e}"`);
1627
1675
  await this.rollback('nymph-newuid');
1628
1676
  throw e;
1629
1677
  }
1678
+ await this.commit('nymph-newuid');
1679
+ return curUid;
1630
1680
  }
1631
1681
  async renameUID(oldName, newName) {
1632
1682
  if (oldName == null || newName == null) {
@@ -1659,7 +1709,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1659
1709
  return true;
1660
1710
  }
1661
1711
  async saveEntity(entity) {
1662
- const insertData = async (guid, data, sdata, etype) => {
1712
+ const insertData = async (guid, data, sdata, uniques, etype) => {
1663
1713
  const runInsertQuery = async (name, value, svalue) => {
1664
1714
  if (value === undefined) {
1665
1715
  return;
@@ -1701,6 +1751,23 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1701
1751
  }
1702
1752
  await Promise.all(promises);
1703
1753
  };
1754
+ for (const unique of uniques) {
1755
+ try {
1756
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`, {
1757
+ etypes: [etype],
1758
+ params: {
1759
+ guid,
1760
+ unique,
1761
+ },
1762
+ });
1763
+ }
1764
+ catch (e) {
1765
+ if (e instanceof nymph_1.EntityUniqueConstraintError) {
1766
+ this.nymph.config.debugError('postgresql', `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
1767
+ }
1768
+ throw e;
1769
+ }
1770
+ }
1704
1771
  for (const name in data) {
1705
1772
  await runInsertQuery(name, data[name], JSON.stringify(data[name]));
1706
1773
  }
@@ -1708,8 +1775,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1708
1775
  await runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
1709
1776
  }
1710
1777
  };
1778
+ let inTransaction = false;
1711
1779
  try {
1712
- const result = await this.saveEntityRowLike(entity, async (_entity, guid, tags, data, sdata, cdate, etype) => {
1780
+ const result = await this.saveEntityRowLike(entity, async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
1781
+ if (Object.keys(data).length === 0 &&
1782
+ Object.keys(sdata).length === 0) {
1783
+ return false;
1784
+ }
1713
1785
  await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @cdate);`, {
1714
1786
  etypes: [etype],
1715
1787
  params: {
@@ -1718,9 +1790,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1718
1790
  cdate,
1719
1791
  },
1720
1792
  });
1721
- await insertData(guid, data, sdata, etype);
1793
+ await insertData(guid, data, sdata, uniques, etype);
1722
1794
  return true;
1723
- }, async (entity, guid, tags, data, sdata, mdate, etype) => {
1795
+ }, async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
1796
+ if (Object.keys(data).length === 0 &&
1797
+ Object.keys(sdata).length === 0) {
1798
+ return false;
1799
+ }
1724
1800
  const promises = [];
1725
1801
  promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1726
1802
  etypes: [etype],
@@ -1746,6 +1822,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1746
1822
  guid,
1747
1823
  },
1748
1824
  }));
1825
+ promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1826
+ etypes: [etype],
1827
+ params: {
1828
+ guid,
1829
+ },
1830
+ }));
1749
1831
  await Promise.all(promises);
1750
1832
  const info = await this.queryRun(`UPDATE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=decode(@guid, 'hex') AND "mdate" <= @emdate;`, {
1751
1833
  etypes: [etype],
@@ -1777,26 +1859,39 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1777
1859
  guid,
1778
1860
  },
1779
1861
  }));
1862
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1863
+ etypes: [etype],
1864
+ params: {
1865
+ guid,
1866
+ },
1867
+ }));
1780
1868
  await Promise.all(promises);
1781
- await insertData(guid, data, sdata, etype);
1869
+ await insertData(guid, data, sdata, uniques, etype);
1782
1870
  success = true;
1783
1871
  }
1784
1872
  return success;
1785
1873
  }, async () => {
1786
1874
  await this.internalTransaction('nymph-save');
1875
+ inTransaction = true;
1787
1876
  }, async (success) => {
1788
- if (success) {
1789
- await this.commit('nymph-save');
1790
- }
1791
- else {
1792
- await this.rollback('nymph-save');
1877
+ if (inTransaction) {
1878
+ inTransaction = false;
1879
+ if (success) {
1880
+ await this.commit('nymph-save');
1881
+ }
1882
+ else {
1883
+ await this.rollback('nymph-save');
1884
+ }
1793
1885
  }
1794
1886
  return success;
1795
1887
  });
1796
1888
  return result;
1797
1889
  }
1798
1890
  catch (e) {
1799
- await this.rollback('nymph-save');
1891
+ this.nymph.config.debugError('postgresql', `Save entity error: "${e}"`);
1892
+ if (inTransaction) {
1893
+ await this.rollback('nymph-save');
1894
+ }
1800
1895
  throw e;
1801
1896
  }
1802
1897
  }
@@ -1818,23 +1913,25 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1818
1913
  curUid,
1819
1914
  },
1820
1915
  });
1821
- await this.commit('nymph-setuid');
1822
- return true;
1823
1916
  }
1824
1917
  catch (e) {
1825
1918
  await this.rollback('nymph-setuid');
1826
1919
  throw e;
1827
1920
  }
1921
+ await this.commit('nymph-setuid');
1922
+ return true;
1828
1923
  }
1829
1924
  async internalTransaction(name) {
1830
1925
  if (name == null || typeof name !== 'string' || name.length === 0) {
1831
1926
  throw new nymph_1.InvalidParametersError('Transaction start attempted without a name.');
1832
1927
  }
1833
1928
  if (!this.transaction || this.transaction.count === 0) {
1929
+ // Lock to one connection.
1834
1930
  this.transaction = {
1835
1931
  count: 0,
1836
1932
  connection: await this.getConnection(),
1837
1933
  };
1934
+ // We're not in a transaction yet, so start one.
1838
1935
  await this.queryRun('BEGIN;');
1839
1936
  }
1840
1937
  await this.queryRun(`SAVEPOINT ${PostgreSQLDriver.escape(name)};`);
@@ -1842,14 +1939,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1842
1939
  return this.transaction;
1843
1940
  }
1844
1941
  async startTransaction(name) {
1845
- const inTransaction = this.inTransaction();
1942
+ const inTransaction = await this.inTransaction();
1846
1943
  const transaction = await this.internalTransaction(name);
1847
1944
  if (!inTransaction) {
1848
1945
  this.transaction = null;
1849
1946
  }
1850
1947
  const nymph = this.nymph.clone();
1851
- nymph.driver = new PostgreSQLDriver(this.config, this.link, transaction);
1852
- nymph.driver.init(nymph);
1948
+ nymph.driver.transaction = transaction;
1853
1949
  return nymph;
1854
1950
  }
1855
1951
  }