@nymphjs/driver-postgresql 1.0.0-beta.9 → 1.0.0-beta.91

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,26 +1,57 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const child_process_1 = __importDefault(require("child_process"));
7
- const pg_1 = require("pg");
8
- const pg_format_1 = __importDefault(require("pg-format"));
9
- const nymph_1 = require("@nymphjs/nymph");
10
- const guid_1 = require("@nymphjs/guid");
11
- const conf_1 = require("./conf");
12
- class PostgreSQLDriver extends nymph_1.NymphDriver {
1
+ import pg from 'pg';
2
+ import format from 'pg-format';
3
+ import Cursor from 'pg-cursor';
4
+ import { NymphDriver, EntityUniqueConstraintError, InvalidParametersError, NotConfiguredError, QueryFailedError, UnableToConnectError, xor, } from '@nymphjs/nymph';
5
+ import { makeTableSuffix } from '@nymphjs/guid';
6
+ import { PostgreSQLDriverConfigDefaults as defaults, } from './conf/index.js';
7
+ /**
8
+ * The PostgreSQL Nymph database driver.
9
+ */
10
+ export default class PostgreSQLDriver extends NymphDriver {
11
+ config;
12
+ postgresqlConfig;
13
+ prefix;
14
+ connected = false;
15
+ // @ts-ignore: this is assigned in connect(), which is called by the constructor.
16
+ link;
17
+ transaction = null;
13
18
  static escape(input) {
14
- return pg_format_1.default.ident(input);
19
+ return format.ident(input);
15
20
  }
16
21
  static escapeValue(input) {
17
- return pg_format_1.default.literal(input);
22
+ return format.literal(input);
23
+ }
24
+ static escapeNullSequences(input) {
25
+ // Postgres doesn't support null bytes in `text`, and it converts strings
26
+ // in JSON to `text`, so we need to escape the escape sequences for null
27
+ // bytes.
28
+ return (input
29
+ .replace(/\uFFFD/g, () => '\uFFFD\uFFFD')
30
+ // n so that if there's already an escape, it turns into \n
31
+ // - so that it won't match a \uFFFD that got turned into \uFFFD\uFFFD
32
+ .replace(/\\u0000/g, () => 'nu\uFFFD-')
33
+ .replace(/\\x00/g, () => 'nx\uFFFD-'));
34
+ }
35
+ static unescapeNullSequences(input) {
36
+ return input
37
+ .replace(/nu\uFFFD-/g, () => '\\u0000')
38
+ .replace(/nx\uFFFD-/g, () => '\\x00')
39
+ .replace(/\uFFFD\uFFFD/g, () => '\uFFFD');
40
+ }
41
+ static escapeNulls(input) {
42
+ // Postgres doesn't support null bytes in `text`.
43
+ return input
44
+ .replace(/\uFFFD/g, () => '\uFFFD\uFFFD')
45
+ .replace(/\x00/g, () => '-\uFFFD-');
46
+ }
47
+ static unescapeNulls(input) {
48
+ return input
49
+ .replace(/-\uFFFD-/g, () => '\x00')
50
+ .replace(/\uFFFD\uFFFD/g, () => '\uFFFD');
18
51
  }
19
52
  constructor(config, link, transaction) {
20
53
  super();
21
- this.connected = false;
22
- this.transaction = null;
23
- this.config = { ...conf_1.PostgreSQLDriverConfigDefaults, ...config };
54
+ this.config = { ...defaults, ...config };
24
55
  const { host, user, password, database, port, customPoolConfig } = this.config;
25
56
  this.postgresqlConfig = customPoolConfig ?? {
26
57
  host,
@@ -41,19 +72,40 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
41
72
  this.connect();
42
73
  }
43
74
  }
75
+ /**
76
+ * This is used internally by Nymph. Don't call it yourself.
77
+ *
78
+ * @returns A clone of this instance.
79
+ */
44
80
  clone() {
45
81
  return new PostgreSQLDriver(this.config, this.link, this.transaction ?? undefined);
46
82
  }
47
- getConnection() {
48
- if (this.transaction != null && this.transaction.connection != null) {
83
+ getConnection(outsideTransaction = false) {
84
+ if (this.transaction != null &&
85
+ this.transaction.connection != null &&
86
+ !outsideTransaction) {
49
87
  return Promise.resolve(this.transaction.connection);
50
88
  }
51
- return new Promise((resolve, reject) => this.link.connect((err, client, done) => err ? reject(err) : resolve({ client, done })));
89
+ return new Promise((resolve, reject) => this.link.connect((err, client, done) => err
90
+ ? reject(err)
91
+ : client
92
+ ? resolve({ client, done })
93
+ : reject('No client returned from connect.')));
52
94
  }
95
+ /**
96
+ * Connect to the PostgreSQL database.
97
+ *
98
+ * @returns Whether this instance is connected to a PostgreSQL database.
99
+ */
53
100
  async connect() {
101
+ // If we think we're connected, try pinging the server.
54
102
  try {
55
103
  if (this.connected) {
56
- const connection = await new Promise((resolve, reject) => this.link.connect((err, client, done) => err ? reject(err) : resolve({ client, done })));
104
+ const connection = await new Promise((resolve, reject) => this.link.connect((err, client, done) => err
105
+ ? reject(err)
106
+ : client
107
+ ? resolve({ client, done })
108
+ : reject('No client returned from connect.')));
57
109
  await new Promise((resolve, reject) => connection.client.query('SELECT 1;', [], (err, res) => {
58
110
  if (err) {
59
111
  reject(err);
@@ -66,9 +118,10 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
66
118
  catch (e) {
67
119
  this.connected = false;
68
120
  }
121
+ // Connecting, selecting database
69
122
  if (!this.connected) {
70
123
  try {
71
- this.link = new pg_1.Pool(this.postgresqlConfig);
124
+ this.link = new pg.Pool(this.postgresqlConfig);
72
125
  this.connected = true;
73
126
  }
74
127
  catch (e) {
@@ -76,15 +129,20 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
76
129
  this.postgresqlConfig.user === 'nymph' &&
77
130
  this.postgresqlConfig.password === 'password' &&
78
131
  this.postgresqlConfig.database === 'nymph') {
79
- throw new nymph_1.NotConfiguredError("It seems the config hasn't been set up correctly.");
132
+ throw new NotConfiguredError("It seems the config hasn't been set up correctly.");
80
133
  }
81
134
  else {
82
- throw new nymph_1.UnableToConnectError('Could not connect: ' + e?.message);
135
+ throw new UnableToConnectError('Could not connect: ' + e?.message);
83
136
  }
84
137
  }
85
138
  }
86
139
  return this.connected;
87
140
  }
141
+ /**
142
+ * Disconnect from the PostgreSQL database.
143
+ *
144
+ * @returns Whether this instance is connected to a PostgreSQL database.
145
+ */
88
146
  async disconnect() {
89
147
  if (this.connected) {
90
148
  await new Promise((resolve) => this.link.end(() => resolve(0)));
@@ -93,87 +151,121 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
93
151
  return this.connected;
94
152
  }
95
153
  async inTransaction() {
154
+ if (this.transaction && this.transaction.count === 0) {
155
+ this.transaction = null;
156
+ }
96
157
  return !!this.transaction;
97
158
  }
159
+ /**
160
+ * Check connection status.
161
+ *
162
+ * @returns Whether this instance is connected to a PostgreSQL database.
163
+ */
98
164
  isConnected() {
99
165
  return this.connected;
100
166
  }
101
- createTables(etype = null) {
167
+ /**
168
+ * Create entity tables in the database.
169
+ *
170
+ * @param etype The entity type to create a table for. If this is blank, the default tables are created.
171
+ * @returns True on success, false on failure.
172
+ */
173
+ async createTables(etype = null) {
174
+ const connection = await this.getConnection(true);
102
175
  if (etype != null) {
103
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} (
176
+ // Create the entity table.
177
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} (
104
178
  "guid" BYTEA NOT NULL,
105
179
  "tags" TEXT[],
106
180
  "cdate" DOUBLE PRECISION NOT NULL,
107
181
  "mdate" DOUBLE PRECISION NOT NULL,
108
182
  PRIMARY KEY ("guid")
109
- ) WITH ( OIDS=FALSE );`);
110
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
111
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)};`);
112
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("cdate");`);
113
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)};`);
114
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("mdate");`);
115
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)};`);
116
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING gin ("tags");`);
117
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} (
118
- "guid" BYTEA NOT NULL,
119
- "name" TEXT NOT NULL,
120
- "value" TEXT NOT NULL,
121
- PRIMARY KEY ("guid", "name"),
122
- FOREIGN KEY ("guid")
123
- REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
124
- ) WITH ( OIDS=FALSE );`);
125
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
126
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)};`);
127
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid");`);
128
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)};`);
129
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name");`);
130
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__user`)};`);
131
- 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;`);
132
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__group`)};`);
133
- 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;`);
134
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} (
183
+ ) WITH ( OIDS=FALSE );`, { connection });
184
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
185
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)};`, { connection });
186
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("cdate");`, { connection });
187
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)};`, { connection });
188
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("mdate");`, { connection });
189
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)};`, { connection });
190
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING gin ("tags");`, { connection });
191
+ // Create the data table.
192
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} (
135
193
  "guid" BYTEA NOT NULL,
136
194
  "name" TEXT NOT NULL,
137
- "truthy" BOOLEAN,
195
+ "value" CHARACTER(1) NOT NULL,
196
+ "json" JSONB,
138
197
  "string" TEXT,
139
198
  "number" DOUBLE PRECISION,
199
+ "truthy" BOOLEAN,
140
200
  PRIMARY KEY ("guid", "name"),
141
201
  FOREIGN KEY ("guid")
142
202
  REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
143
- ) WITH ( OIDS=FALSE );`);
144
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
145
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid`)};`);
146
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("guid");`);
147
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_name`)};`);
148
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} USING btree ("name");`);
149
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_truthy`)};`);
150
- 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;`);
151
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}_id_guid_name_falsy`)};`);
152
- 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;`);
153
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} (
203
+ ) WITH ( OIDS=FALSE );`, { connection });
204
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
205
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)};`, { connection });
206
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid");`, { connection });
207
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name`)};`, { connection });
208
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid", "name");`, { connection });
209
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__user`)};`, { connection });
210
+ 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;`, { connection });
211
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__group`)};`, { connection });
212
+ 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;`, { connection });
213
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)};`, { connection });
214
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name");`, { connection });
215
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_string`)};`, { connection });
216
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_string`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name", LEFT("string", 512));`, { connection });
217
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_number`)};`, { connection });
218
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_number`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name", "number");`, { connection });
219
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_truthy`)};`, { connection });
220
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_truthy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name") WHERE "truthy" = TRUE;`, { connection });
221
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_falsy`)};`, { connection });
222
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_falsy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name") WHERE "truthy" <> TRUE;`, { connection });
223
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_string`)};`, { connection });
224
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_string`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING gin ("string" gin_trgm_ops);`, { connection });
225
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_json`)};`, { connection });
226
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_json`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING gin ("json");`, { connection });
227
+ // Create the references table.
228
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} (
154
229
  "guid" BYTEA NOT NULL,
155
230
  "name" TEXT NOT NULL,
156
231
  "reference" BYTEA NOT NULL,
157
232
  PRIMARY KEY ("guid", "name", "reference"),
158
233
  FOREIGN KEY ("guid")
159
234
  REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
160
- ) WITH ( OIDS=FALSE );`);
161
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
162
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)};`);
163
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("guid");`);
164
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)};`);
165
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("name");`);
166
- this.queryRunSync(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)};`);
167
- this.queryRunSync(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference");`);
235
+ ) WITH ( OIDS=FALSE );`, { connection });
236
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
237
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)};`, { connection });
238
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("guid");`, { connection });
239
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)};`, { connection });
240
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("name");`, { connection });
241
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name_reference`)};`, { connection });
242
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name_reference`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("name", "reference");`, { connection });
243
+ // Create the unique strings table.
244
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} (
245
+ "guid" BYTEA NOT NULL,
246
+ "unique" TEXT NOT NULL UNIQUE,
247
+ PRIMARY KEY ("guid", "unique"),
248
+ FOREIGN KEY ("guid")
249
+ REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
250
+ ) WITH ( OIDS=FALSE );`, { connection });
168
251
  }
169
252
  else {
170
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uids`)} (
253
+ // Add trigram extensions.
254
+ try {
255
+ await this.queryRun(`CREATE EXTENSION pg_trgm;`, { connection });
256
+ }
257
+ catch (e) {
258
+ // Ignore errors.
259
+ }
260
+ // Create the UID table.
261
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uids`)} (
171
262
  "name" TEXT NOT NULL,
172
263
  "cur_uid" BIGINT NOT NULL,
173
264
  PRIMARY KEY ("name")
174
- ) WITH ( OIDS = FALSE );`);
175
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
265
+ ) WITH ( OIDS = FALSE );`, { connection });
266
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
176
267
  }
268
+ connection.done();
177
269
  return true;
178
270
  }
179
271
  translateQuery(origQuery, origParams) {
@@ -191,49 +283,32 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
191
283
  }
192
284
  async query(runQuery, query, etypes = []) {
193
285
  try {
286
+ this.nymph.config.debugInfo('postgresql:query', query);
194
287
  return await runQuery();
195
288
  }
196
289
  catch (e) {
197
290
  const errorCode = e?.code;
198
- if (errorCode === '42P01' && this.createTables()) {
291
+ if (errorCode === '42P01' && (await this.createTables())) {
292
+ // If the tables don't exist yet, create them.
199
293
  for (let etype of etypes) {
200
- this.createTables(etype);
294
+ await this.createTables(etype);
201
295
  }
202
296
  try {
203
297
  return await runQuery();
204
298
  }
205
299
  catch (e2) {
206
- throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
300
+ throw new QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
207
301
  }
208
302
  }
209
- else {
210
- throw e;
211
- }
212
- }
213
- }
214
- querySync(runQuery, query, etypes = []) {
215
- try {
216
- return runQuery();
217
- }
218
- catch (e) {
219
- const errorCode = e?.code;
220
- if (errorCode === '42P01' && this.createTables()) {
221
- for (let etype of etypes) {
222
- this.createTables(etype);
223
- }
224
- try {
225
- return runQuery();
226
- }
227
- catch (e2) {
228
- throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
229
- }
303
+ else if (errorCode === '23505') {
304
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
230
305
  }
231
306
  else {
232
- throw e;
307
+ throw new QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
233
308
  }
234
309
  }
235
310
  }
236
- queryIter(query, { etypes = [], params = {}, } = {}) {
311
+ queryArray(query, { etypes = [], params = {}, } = {}) {
237
312
  const { query: newQuery, params: newParams } = this.translateQuery(query, params);
238
313
  return this.query(async () => {
239
314
  const results = await new Promise((resolve, reject) => {
@@ -249,32 +324,30 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
249
324
  return results.rows;
250
325
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
251
326
  }
252
- queryIterSync(query, { etypes = [], params = {}, } = {}) {
327
+ async queryIter(query, { etypes = [], params = {}, } = {}) {
253
328
  const { query: newQuery, params: newParams } = this.translateQuery(query, params);
254
- return this.querySync(() => {
255
- const output = child_process_1.default.spawnSync(process.argv0, [__dirname + '/runPostgresqlSync.js'], {
256
- input: JSON.stringify({
257
- postgresqlConfig: this.postgresqlConfig,
258
- query: newQuery,
259
- params: newParams,
260
- }),
261
- timeout: 30000,
262
- maxBuffer: 100 * 1024 * 1024,
263
- encoding: 'utf8',
264
- windowsHide: true,
265
- });
266
- try {
267
- return JSON.parse(output.stdout).rows;
268
- }
269
- catch (e) {
270
- }
271
- if (output.status === 0) {
272
- throw new Error('Unknown parse error.');
273
- }
274
- const err = JSON.parse(output.stderr);
275
- const e = new Error(err.name);
276
- for (const name in err) {
277
- e[name] = err[name];
329
+ const that = this;
330
+ return this.query(async function* () {
331
+ const transaction = !!that.transaction?.connection;
332
+ const connection = await that.getConnection();
333
+ const cursor = new Cursor(newQuery, newParams);
334
+ const iter = connection.client.query(cursor);
335
+ while (true) {
336
+ const rows = await iter.read(100);
337
+ if (!rows.length) {
338
+ await new Promise((resolve) => {
339
+ iter.close(() => {
340
+ if (!transaction) {
341
+ connection.done();
342
+ }
343
+ resolve();
344
+ });
345
+ });
346
+ return;
347
+ }
348
+ for (let row of rows) {
349
+ yield row;
350
+ }
278
351
  }
279
352
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
280
353
  }
@@ -294,12 +367,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
294
367
  return results.rows[0];
295
368
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
296
369
  }
297
- queryRun(query, { etypes = [], params = {}, } = {}) {
370
+ queryRun(query, { etypes = [], params = {}, connection, } = {}) {
298
371
  const { query: newQuery, params: newParams } = this.translateQuery(query, params);
299
372
  return this.query(async () => {
300
373
  const results = await new Promise((resolve, reject) => {
301
374
  try {
302
- (this.transaction?.connection?.client ?? this.link)
375
+ ((connection ?? this.transaction?.connection)?.client ??
376
+ this.link)
303
377
  .query(newQuery, newParams)
304
378
  .then((results) => resolve(results), (error) => reject(error));
305
379
  }
@@ -310,39 +384,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
310
384
  return { rowCount: results.rowCount ?? 0 };
311
385
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
312
386
  }
313
- queryRunSync(query, { etypes = [], params = {}, } = {}) {
314
- const { query: newQuery, params: newParams } = this.translateQuery(query, params);
315
- return this.querySync(() => {
316
- const output = child_process_1.default.spawnSync(process.argv0, [__dirname + '/runPostgresqlSync.js'], {
317
- input: JSON.stringify({
318
- postgresqlConfig: this.postgresqlConfig,
319
- query: newQuery,
320
- params: newParams,
321
- }),
322
- timeout: 30000,
323
- maxBuffer: 100 * 1024 * 1024,
324
- encoding: 'utf8',
325
- windowsHide: true,
326
- });
327
- try {
328
- const results = JSON.parse(output.stdout);
329
- return { rowCount: results.rowCount ?? 0 };
330
- }
331
- catch (e) {
332
- }
333
- if (output.status === 0) {
334
- throw new Error('Unknown parse error.');
335
- }
336
- const err = JSON.parse(output.stderr);
337
- const e = new Error(err.name);
338
- for (const name in err) {
339
- e[name] = err[name];
340
- }
341
- }, `${query} -- ${JSON.stringify(params)}`, etypes);
342
- }
343
387
  async commit(name) {
344
388
  if (name == null || typeof name !== 'string' || name.length === 0) {
345
- throw new nymph_1.InvalidParametersError('Transaction commit attempted without a name.');
389
+ throw new InvalidParametersError('Transaction commit attempted without a name.');
346
390
  }
347
391
  if (!this.transaction || this.transaction.count === 0) {
348
392
  this.transaction = null;
@@ -382,32 +426,34 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
382
426
  guid,
383
427
  },
384
428
  });
385
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
429
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
386
430
  etypes: [etype],
387
431
  params: {
388
432
  guid,
389
433
  },
390
434
  });
391
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
435
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
392
436
  etypes: [etype],
393
437
  params: {
394
438
  guid,
395
439
  },
396
440
  });
397
- await this.commit('nymph-delete');
398
- if (this.nymph.config.cache) {
399
- this.cleanCache(guid);
400
- }
401
- return true;
402
441
  }
403
442
  catch (e) {
443
+ this.nymph.config.debugError('postgresql', `Delete entity error: "${e}"`);
404
444
  await this.rollback('nymph-delete');
405
445
  throw e;
406
446
  }
447
+ await this.commit('nymph-delete');
448
+ // Remove any cached versions of this entity.
449
+ if (this.nymph.config.cache) {
450
+ this.cleanCache(guid);
451
+ }
452
+ return true;
407
453
  }
408
454
  async deleteUID(name) {
409
455
  if (!name) {
410
- throw new nymph_1.InvalidParametersError('Name not given for UID');
456
+ throw new InvalidParametersError('Name not given for UID');
411
457
  }
412
458
  await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
413
459
  params: {
@@ -416,79 +462,124 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
416
462
  });
417
463
  return true;
418
464
  }
419
- async exportEntities(writeLine) {
420
- writeLine('#nex2');
421
- writeLine('# Nymph Entity Exchange v2');
422
- writeLine('# http://nymph.io');
423
- writeLine('#');
424
- writeLine('# Generation Time: ' + new Date().toLocaleString());
425
- writeLine('');
426
- writeLine('#');
427
- writeLine('# UIDs');
428
- writeLine('#');
429
- writeLine('');
465
+ async *exportDataIterator() {
466
+ if (yield {
467
+ type: 'comment',
468
+ content: `#nex2
469
+ # Nymph Entity Exchange v2
470
+ # http://nymph.io
471
+ #
472
+ # Generation Time: ${new Date().toLocaleString()}
473
+ `,
474
+ }) {
475
+ return;
476
+ }
477
+ if (yield {
478
+ type: 'comment',
479
+ content: `
480
+
481
+ #
482
+ # UIDs
483
+ #
484
+
485
+ `,
486
+ }) {
487
+ return;
488
+ }
489
+ // Export UIDs.
430
490
  let uids = await this.queryIter(`SELECT * FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
431
- for (const uid of uids) {
432
- writeLine(`<${uid.name}>[${uid.cur_uid}]`);
491
+ for await (const uid of uids) {
492
+ if (yield { type: 'uid', content: `<${uid.name}>[${uid.cur_uid}]\n` }) {
493
+ return;
494
+ }
495
+ }
496
+ if (yield {
497
+ type: 'comment',
498
+ content: `
499
+
500
+ #
501
+ # Entities
502
+ #
503
+
504
+ `,
505
+ }) {
506
+ return;
433
507
  }
434
- writeLine('');
435
- writeLine('#');
436
- writeLine('# Entities');
437
- writeLine('#');
438
- writeLine('');
439
- const tables = await this.queryIter('SELECT relname FROM pg_stat_user_tables ORDER BY relname;');
508
+ // Get the etypes.
509
+ const tables = await this.queryArray('SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @prefix;', {
510
+ params: {
511
+ db: this.config.database,
512
+ prefix: this.prefix + 'entities_' + '%',
513
+ },
514
+ });
440
515
  const etypes = [];
441
- for (const tableRow of tables) {
442
- const table = tableRow.relname;
443
- if (table.startsWith(this.prefix + 'entities_')) {
444
- etypes.push(table.substr((this.prefix + 'entities_').length));
445
- }
516
+ for (const table of tables) {
517
+ etypes.push(table.table_name.substr((this.prefix + 'entities_').length));
446
518
  }
447
519
  for (const etype of etypes) {
448
- 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"
520
+ // Export entities.
521
+ const dataIterator = await this.queryIter(`SELECT encode(e."guid", 'hex') AS "guid", e."tags", e."cdate", e."mdate", d."name", d."value", d."json", d."string", d."number"
449
522
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} e
450
523
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} d ON e."guid"=d."guid"
451
- INNER JOIN ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} c ON d."guid"=c."guid" AND d."name"=c."name"
452
- ORDER BY e."guid";`))[Symbol.iterator]();
453
- let datum = dataIterator.next();
524
+ ORDER BY e."guid";`);
525
+ let datum = await dataIterator.next();
454
526
  while (!datum.done) {
455
527
  const guid = datum.value.guid;
456
- const tags = datum.value.tags.join(',');
528
+ const tags = datum.value.tags.filter((tag) => tag).join(',');
457
529
  const cdate = datum.value.cdate;
458
530
  const mdate = datum.value.mdate;
459
- writeLine(`{${guid}}<${etype}>[${tags}]`);
460
- writeLine(`\tcdate=${JSON.stringify(cdate)}`);
461
- writeLine(`\tmdate=${JSON.stringify(mdate)}`);
462
- if (datum.value.dname != null) {
531
+ let currentEntityExport = [];
532
+ currentEntityExport.push(`{${guid}}<${etype}>[${tags}]`);
533
+ currentEntityExport.push(`\tcdate=${JSON.stringify(cdate)}`);
534
+ currentEntityExport.push(`\tmdate=${JSON.stringify(mdate)}`);
535
+ if (datum.value.name != null) {
536
+ // This do will keep going and adding the data until the
537
+ // next entity is reached. $row will end on the next entity.
463
538
  do {
464
- const value = datum.value.dvalue === 'N'
539
+ const value = datum.value.value === 'N'
465
540
  ? JSON.stringify(Number(datum.value.number))
466
- : datum.value.dvalue === 'S'
467
- ? JSON.stringify(datum.value.string)
468
- : datum.value.dvalue;
469
- writeLine(`\t${datum.value.dname}=${value}`);
470
- datum = dataIterator.next();
541
+ : datum.value.value === 'S'
542
+ ? JSON.stringify(PostgreSQLDriver.unescapeNulls(datum.value.string))
543
+ : datum.value.value === 'J'
544
+ ? PostgreSQLDriver.unescapeNullSequences(JSON.stringify(datum.value.json))
545
+ : datum.value.value;
546
+ currentEntityExport.push(`\t${datum.value.name}=${value}`);
547
+ datum = await dataIterator.next();
471
548
  } while (!datum.done && datum.value.guid === guid);
472
549
  }
473
550
  else {
474
- datum = dataIterator.next();
551
+ // Make sure that datum is incremented :)
552
+ datum = await dataIterator.next();
553
+ }
554
+ currentEntityExport.push('');
555
+ if (yield { type: 'entity', content: currentEntityExport.join('\n') }) {
556
+ return;
475
557
  }
476
558
  }
477
559
  }
478
- return;
479
560
  }
480
- makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = []) {
561
+ /**
562
+ * Generate the PostgreSQL query.
563
+ * @param options The options array.
564
+ * @param formattedSelectors The formatted selector array.
565
+ * @param etype
566
+ * @param count Used to track internal params.
567
+ * @param params Used to store internal params.
568
+ * @param subquery Whether only a subquery should be returned.
569
+ * @returns The SQL query.
570
+ */
571
+ makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = [], guidSelector = undefined) {
481
572
  if (typeof options.class?.alterOptions === 'function') {
482
573
  options = options.class.alterOptions(options);
483
574
  }
484
575
  const eTable = `e${tableSuffix}`;
485
576
  const dTable = `d${tableSuffix}`;
486
- const cTable = `c${tableSuffix}`;
487
577
  const fTable = `f${tableSuffix}`;
488
578
  const ieTable = `ie${tableSuffix}`;
489
579
  const countTable = `count${tableSuffix}`;
580
+ const sTable = `s${tableSuffix}`;
490
581
  const sort = options.sort ?? 'cdate';
491
- const queryParts = this.iterateSelectorsForQuery(formattedSelectors, (key, value, typeIsOr, typeIsNot) => {
582
+ const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => {
492
583
  const clauseNot = key.startsWith('!');
493
584
  let curQuery = '';
494
585
  for (const curValue of value) {
@@ -501,7 +592,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
501
592
  }
502
593
  const guid = `param${++count.i}`;
503
594
  curQuery +=
504
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
595
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
505
596
  ieTable +
506
597
  '."guid"=decode(@' +
507
598
  guid +
@@ -517,7 +608,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
517
608
  }
518
609
  const tag = `param${++count.i}`;
519
610
  curQuery +=
520
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
611
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
521
612
  '@' +
522
613
  tag +
523
614
  ' <@ ie."tags"';
@@ -532,12 +623,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
532
623
  }
533
624
  const name = `param${++count.i}`;
534
625
  curQuery +=
535
- ieTable +
536
- '."guid" ' +
537
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
538
- 'IN (SELECT "guid" FROM ' +
626
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
627
+ 'EXISTS (SELECT "guid" FROM ' +
539
628
  PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
540
- ' WHERE "name"=@' +
629
+ ' WHERE "guid"=' +
630
+ ieTable +
631
+ '."guid" AND "name"=@' +
541
632
  name +
542
633
  ')';
543
634
  params[name] = curVar;
@@ -551,7 +642,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
551
642
  }
552
643
  if (curVar === 'cdate') {
553
644
  curQuery +=
554
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
645
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
555
646
  '(' +
556
647
  ieTable +
557
648
  '."cdate" NOT NULL)';
@@ -559,7 +650,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
559
650
  }
560
651
  else if (curVar === 'mdate') {
561
652
  curQuery +=
562
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
653
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
563
654
  '(' +
564
655
  ieTable +
565
656
  '."mdate" NOT NULL)';
@@ -568,11 +659,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
568
659
  else {
569
660
  const name = `param${++count.i}`;
570
661
  curQuery +=
571
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
662
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
663
+ 'EXISTS (SELECT "guid" FROM ' +
664
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
665
+ ' WHERE "guid"=' +
572
666
  ieTable +
573
- '."guid" IN (SELECT "guid" FROM ' +
574
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
575
- ' WHERE "name"=@' +
667
+ '."guid" AND "name"=@' +
576
668
  name +
577
669
  ' AND "truthy"=TRUE)';
578
670
  params[name] = curVar;
@@ -587,7 +679,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
587
679
  }
588
680
  const cdate = `param${++count.i}`;
589
681
  curQuery +=
590
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
682
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
591
683
  ieTable +
592
684
  '."cdate"=@' +
593
685
  cdate;
@@ -602,7 +694,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
602
694
  }
603
695
  const mdate = `param${++count.i}`;
604
696
  curQuery +=
605
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
697
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
606
698
  ieTable +
607
699
  '."mdate"=@' +
608
700
  mdate;
@@ -618,11 +710,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
618
710
  const name = `param${++count.i}`;
619
711
  const value = `param${++count.i}`;
620
712
  curQuery +=
621
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
713
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
714
+ 'EXISTS (SELECT "guid" FROM ' +
715
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
716
+ ' WHERE "guid"=' +
622
717
  ieTable +
623
- '."guid" IN (SELECT "guid" FROM ' +
624
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
625
- ' WHERE "name"=@' +
718
+ '."guid" AND "name"=@' +
626
719
  name +
627
720
  ' AND "number"=@' +
628
721
  value +
@@ -637,17 +730,20 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
637
730
  const name = `param${++count.i}`;
638
731
  const value = `param${++count.i}`;
639
732
  curQuery +=
640
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
733
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
734
+ 'EXISTS (SELECT "guid" FROM ' +
735
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
736
+ ' WHERE "guid"=' +
641
737
  ieTable +
642
- '."guid" IN (SELECT "guid" FROM ' +
643
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
644
- ' WHERE "name"=@' +
738
+ '."guid" AND "name"=@' +
645
739
  name +
646
- ' AND "string"=@' +
647
- value +
740
+ ' AND "string"=' +
741
+ (curValue[1].length < 512
742
+ ? 'LEFT(@' + value + ', 512)'
743
+ : '@' + value) +
648
744
  ')';
649
745
  params[name] = curValue[0];
650
- params[value] = curValue[1];
746
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
651
747
  }
652
748
  else {
653
749
  if (curQuery) {
@@ -664,17 +760,18 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
664
760
  const name = `param${++count.i}`;
665
761
  const value = `param${++count.i}`;
666
762
  curQuery +=
667
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
668
- ieTable +
669
- '."guid" IN (SELECT "guid" FROM ' +
763
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
764
+ 'EXISTS (SELECT "guid" FROM ' +
670
765
  PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
671
- ' WHERE "name"=@' +
766
+ ' WHERE "guid"=' +
767
+ ieTable +
768
+ '."guid" AND "name"=@' +
672
769
  name +
673
- ' AND "value"=@' +
770
+ ' AND "json"=@' +
674
771
  value +
675
772
  ')';
676
773
  params[name] = curValue[0];
677
- params[value] = svalue;
774
+ params[value] = PostgreSQLDriver.escapeNullSequences(svalue);
678
775
  }
679
776
  break;
680
777
  case 'contain':
@@ -685,9 +782,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
685
782
  }
686
783
  const cdate = `param${++count.i}`;
687
784
  curQuery +=
688
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
785
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
689
786
  ieTable +
690
- '."cdate"=' +
787
+ '."cdate"=@' +
691
788
  cdate;
692
789
  params[cdate] = isNaN(Number(curValue[1]))
693
790
  ? null
@@ -700,9 +797,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
700
797
  }
701
798
  const mdate = `param${++count.i}`;
702
799
  curQuery +=
703
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
800
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
704
801
  ieTable +
705
- '."mdate"=' +
802
+ '."mdate"=@' +
706
803
  mdate;
707
804
  params[mdate] = isNaN(Number(curValue[1]))
708
805
  ? null
@@ -714,55 +811,32 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
714
811
  curQuery += typeIsOr ? ' OR ' : ' AND ';
715
812
  }
716
813
  let svalue;
717
- let stringValue;
718
814
  if (curValue[1] instanceof Object &&
719
815
  typeof curValue[1].toReference === 'function') {
720
816
  svalue = JSON.stringify(curValue[1].toReference());
721
- stringValue = `${curValue[1].toReference()}`;
722
817
  }
723
- else {
818
+ else if (typeof curValue[1] === 'string' ||
819
+ typeof curValue[1] === 'number') {
724
820
  svalue = JSON.stringify(curValue[1]);
725
- stringValue = `${curValue[1]}`;
726
- }
727
- const name = `param${++count.i}`;
728
- const value = `param${++count.i}`;
729
- if (typeof curValue[1] === 'string') {
730
- const stringParam = `param${++count.i}`;
731
- curQuery +=
732
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
733
- '(' +
734
- ieTable +
735
- '."guid" IN (SELECT "guid" FROM ' +
736
- PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
737
- ' WHERE "name"=@' +
738
- name +
739
- ' AND position(@' +
740
- value +
741
- ' IN "value")>0) OR ' +
742
- ieTable +
743
- '."guid" IN (SELECT "guid" FROM ' +
744
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
745
- ' WHERE "name"=@' +
746
- name +
747
- ' AND "string"=@' +
748
- stringParam +
749
- '))';
750
- params[stringParam] = stringValue;
751
821
  }
752
822
  else {
753
- curQuery +=
754
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
755
- ieTable +
756
- '."guid" IN (SELECT "guid" FROM ' +
757
- PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
758
- ' WHERE "name"=@' +
759
- name +
760
- ' AND position(@' +
761
- value +
762
- ' IN "value")>0)';
823
+ svalue = JSON.stringify([curValue[1]]);
763
824
  }
825
+ const name = `param${++count.i}`;
826
+ const value = `param${++count.i}`;
827
+ curQuery +=
828
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
829
+ 'EXISTS (SELECT "guid" FROM ' +
830
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
831
+ ' WHERE "guid"=' +
832
+ ieTable +
833
+ '."guid" AND "name"=@' +
834
+ name +
835
+ ' AND "json" @> @' +
836
+ value +
837
+ ')';
764
838
  params[name] = curValue[0];
765
- params[value] = svalue;
839
+ params[value] = PostgreSQLDriver.escapeNullSequences(svalue);
766
840
  }
767
841
  break;
768
842
  case 'match':
@@ -773,7 +847,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
773
847
  }
774
848
  const cdate = `param${++count.i}`;
775
849
  curQuery +=
776
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
850
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
777
851
  '(' +
778
852
  ieTable +
779
853
  '."cdate" ~ @' +
@@ -788,7 +862,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
788
862
  }
789
863
  const mdate = `param${++count.i}`;
790
864
  curQuery +=
791
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
865
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
792
866
  '(' +
793
867
  ieTable +
794
868
  '."mdate" ~ @' +
@@ -804,17 +878,18 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
804
878
  const name = `param${++count.i}`;
805
879
  const value = `param${++count.i}`;
806
880
  curQuery +=
807
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
881
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
882
+ 'EXISTS (SELECT "guid" FROM ' +
883
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
884
+ ' WHERE "guid"=' +
808
885
  ieTable +
809
- '."guid" IN (SELECT "guid" FROM ' +
810
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
811
- ' WHERE "name"=@' +
886
+ '."guid" AND "name"=@' +
812
887
  name +
813
888
  ' AND "string" ~ @' +
814
889
  value +
815
890
  ')';
816
891
  params[name] = curValue[0];
817
- params[value] = curValue[1];
892
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
818
893
  }
819
894
  break;
820
895
  case 'imatch':
@@ -825,7 +900,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
825
900
  }
826
901
  const cdate = `param${++count.i}`;
827
902
  curQuery +=
828
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
903
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
829
904
  '(' +
830
905
  ieTable +
831
906
  '."cdate" ~* @' +
@@ -840,7 +915,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
840
915
  }
841
916
  const mdate = `param${++count.i}`;
842
917
  curQuery +=
843
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
918
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
844
919
  '(' +
845
920
  ieTable +
846
921
  '."mdate" ~* @' +
@@ -856,17 +931,18 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
856
931
  const name = `param${++count.i}`;
857
932
  const value = `param${++count.i}`;
858
933
  curQuery +=
859
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
934
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
935
+ 'EXISTS (SELECT "guid" FROM ' +
936
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
937
+ ' WHERE "guid"=' +
860
938
  ieTable +
861
- '."guid" IN (SELECT "guid" FROM ' +
862
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
863
- ' WHERE "name"=@' +
939
+ '."guid" AND "name"=@' +
864
940
  name +
865
941
  ' AND "string" ~* @' +
866
942
  value +
867
943
  ')';
868
944
  params[name] = curValue[0];
869
- params[value] = curValue[1];
945
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
870
946
  }
871
947
  break;
872
948
  case 'like':
@@ -877,7 +953,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
877
953
  }
878
954
  const cdate = `param${++count.i}`;
879
955
  curQuery +=
880
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
956
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
881
957
  '(' +
882
958
  ieTable +
883
959
  '."cdate" LIKE @' +
@@ -892,7 +968,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
892
968
  }
893
969
  const mdate = `param${++count.i}`;
894
970
  curQuery +=
895
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
971
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
896
972
  '(' +
897
973
  ieTable +
898
974
  '."mdate" LIKE @' +
@@ -908,17 +984,18 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
908
984
  const name = `param${++count.i}`;
909
985
  const value = `param${++count.i}`;
910
986
  curQuery +=
911
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
987
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
988
+ 'EXISTS (SELECT "guid" FROM ' +
989
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
990
+ ' WHERE "guid"=' +
912
991
  ieTable +
913
- '."guid" IN (SELECT "guid" FROM ' +
914
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
915
- ' WHERE "name"=@' +
992
+ '."guid" AND "name"=@' +
916
993
  name +
917
994
  ' AND "string" LIKE @' +
918
995
  value +
919
996
  ')';
920
997
  params[name] = curValue[0];
921
- params[value] = curValue[1];
998
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
922
999
  }
923
1000
  break;
924
1001
  case 'ilike':
@@ -929,7 +1006,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
929
1006
  }
930
1007
  const cdate = `param${++count.i}`;
931
1008
  curQuery +=
932
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1009
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
933
1010
  '(' +
934
1011
  ieTable +
935
1012
  '."cdate" ILIKE @' +
@@ -944,7 +1021,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
944
1021
  }
945
1022
  const mdate = `param${++count.i}`;
946
1023
  curQuery +=
947
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1024
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
948
1025
  '(' +
949
1026
  ieTable +
950
1027
  '."mdate" ILIKE @' +
@@ -960,17 +1037,18 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
960
1037
  const name = `param${++count.i}`;
961
1038
  const value = `param${++count.i}`;
962
1039
  curQuery +=
963
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1040
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1041
+ 'EXISTS (SELECT "guid" FROM ' +
1042
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1043
+ ' WHERE "guid"=' +
964
1044
  ieTable +
965
- '."guid" IN (SELECT "guid" FROM ' +
966
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
967
- ' WHERE "name"=@' +
1045
+ '."guid" AND "name"=@' +
968
1046
  name +
969
1047
  ' AND "string" ILIKE @' +
970
1048
  value +
971
1049
  ')';
972
1050
  params[name] = curValue[0];
973
- params[value] = curValue[1];
1051
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
974
1052
  }
975
1053
  break;
976
1054
  case 'gt':
@@ -981,7 +1059,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
981
1059
  }
982
1060
  const cdate = `param${++count.i}`;
983
1061
  curQuery +=
984
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1062
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
985
1063
  ieTable +
986
1064
  '."cdate">@' +
987
1065
  cdate;
@@ -996,7 +1074,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
996
1074
  }
997
1075
  const mdate = `param${++count.i}`;
998
1076
  curQuery +=
999
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1077
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1000
1078
  ieTable +
1001
1079
  '."mdate">@' +
1002
1080
  mdate;
@@ -1012,11 +1090,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1012
1090
  const name = `param${++count.i}`;
1013
1091
  const value = `param${++count.i}`;
1014
1092
  curQuery +=
1015
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1093
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1094
+ 'EXISTS (SELECT "guid" FROM ' +
1095
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1096
+ ' WHERE "guid"=' +
1016
1097
  ieTable +
1017
- '."guid" IN (SELECT "guid" FROM ' +
1018
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1019
- ' WHERE "name"=@' +
1098
+ '."guid" AND "name"=@' +
1020
1099
  name +
1021
1100
  ' AND "number">@' +
1022
1101
  value +
@@ -1035,7 +1114,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1035
1114
  }
1036
1115
  const cdate = `param${++count.i}`;
1037
1116
  curQuery +=
1038
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1117
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1039
1118
  ieTable +
1040
1119
  '."cdate">=@' +
1041
1120
  cdate;
@@ -1050,7 +1129,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1050
1129
  }
1051
1130
  const mdate = `param${++count.i}`;
1052
1131
  curQuery +=
1053
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1132
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1054
1133
  ieTable +
1055
1134
  '."mdate">=@' +
1056
1135
  mdate;
@@ -1066,11 +1145,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1066
1145
  const name = `param${++count.i}`;
1067
1146
  const value = `param${++count.i}`;
1068
1147
  curQuery +=
1069
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1148
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1149
+ 'EXISTS (SELECT "guid" FROM ' +
1150
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1151
+ ' WHERE "guid"=' +
1070
1152
  ieTable +
1071
- '."guid" IN (SELECT "guid" FROM ' +
1072
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1073
- ' WHERE "name"=@' +
1153
+ '."guid" AND "name"=@' +
1074
1154
  name +
1075
1155
  ' AND "number">=@' +
1076
1156
  value +
@@ -1089,7 +1169,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1089
1169
  }
1090
1170
  const cdate = `param${++count.i}`;
1091
1171
  curQuery +=
1092
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1172
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1093
1173
  ieTable +
1094
1174
  '."cdate"<@' +
1095
1175
  cdate;
@@ -1104,7 +1184,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1104
1184
  }
1105
1185
  const mdate = `param${++count.i}`;
1106
1186
  curQuery +=
1107
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1187
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1108
1188
  ieTable +
1109
1189
  '."mdate"<@' +
1110
1190
  mdate;
@@ -1120,11 +1200,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1120
1200
  const name = `param${++count.i}`;
1121
1201
  const value = `param${++count.i}`;
1122
1202
  curQuery +=
1123
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1203
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1204
+ 'EXISTS (SELECT "guid" FROM ' +
1205
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1206
+ ' WHERE "guid"=' +
1124
1207
  ieTable +
1125
- '."guid" IN (SELECT "guid" FROM ' +
1126
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1127
- ' WHERE "name"=@' +
1208
+ '."guid" AND "name"=@' +
1128
1209
  name +
1129
1210
  ' AND "number"<@' +
1130
1211
  value +
@@ -1143,7 +1224,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1143
1224
  }
1144
1225
  const cdate = `param${++count.i}`;
1145
1226
  curQuery +=
1146
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1227
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1147
1228
  ieTable +
1148
1229
  '."cdate"<=@' +
1149
1230
  cdate;
@@ -1158,7 +1239,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1158
1239
  }
1159
1240
  const mdate = `param${++count.i}`;
1160
1241
  curQuery +=
1161
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1242
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1162
1243
  ieTable +
1163
1244
  '."mdate"<=@' +
1164
1245
  mdate;
@@ -1174,11 +1255,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1174
1255
  const name = `param${++count.i}`;
1175
1256
  const value = `param${++count.i}`;
1176
1257
  curQuery +=
1177
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1258
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1259
+ 'EXISTS (SELECT "guid" FROM ' +
1260
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1261
+ ' WHERE "guid"=' +
1178
1262
  ieTable +
1179
- '."guid" IN (SELECT "guid" FROM ' +
1180
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1181
- ' WHERE "name"=@' +
1263
+ '."guid" AND "name"=@' +
1182
1264
  name +
1183
1265
  ' AND "number"<=@' +
1184
1266
  value +
@@ -1207,11 +1289,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1207
1289
  const name = `param${++count.i}`;
1208
1290
  const guid = `param${++count.i}`;
1209
1291
  curQuery +=
1210
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1211
- ieTable +
1212
- '."guid" IN (SELECT "guid" FROM ' +
1292
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1293
+ 'EXISTS (SELECT "guid" FROM ' +
1213
1294
  PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1214
- ' WHERE "name"=@' +
1295
+ ' WHERE "guid"=' +
1296
+ ieTable +
1297
+ '."guid" AND "name"=@' +
1215
1298
  name +
1216
1299
  ' AND "reference"=decode(@' +
1217
1300
  guid +
@@ -1226,29 +1309,37 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1226
1309
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1227
1310
  }
1228
1311
  curQuery +=
1229
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1312
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1230
1313
  '(' +
1231
1314
  subquery.query +
1232
1315
  ')';
1233
1316
  break;
1234
1317
  case 'qref':
1235
1318
  case '!qref':
1319
+ const referenceTableSuffix = makeTableSuffix();
1236
1320
  const [qrefOptions, ...qrefSelectors] = curValue[1];
1237
1321
  const QrefEntityClass = qrefOptions.class;
1238
1322
  etypes.push(QrefEntityClass.ETYPE);
1239
- const qrefQuery = this.makeEntityQuery({ ...qrefOptions, return: 'guid', class: QrefEntityClass }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, (0, guid_1.makeTableSuffix)(), etypes);
1323
+ const qrefQuery = this.makeEntityQuery({ ...qrefOptions, return: 'guid', class: QrefEntityClass }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' + referenceTableSuffix + '."reference"');
1240
1324
  if (curQuery) {
1241
1325
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1242
1326
  }
1243
1327
  const qrefName = `param${++count.i}`;
1244
1328
  curQuery +=
1245
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1246
- ieTable +
1247
- '."guid" IN (SELECT "guid" FROM ' +
1329
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1330
+ 'EXISTS (SELECT "guid" FROM ' +
1248
1331
  PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1249
- ' WHERE "name"=@' +
1332
+ ' r' +
1333
+ referenceTableSuffix +
1334
+ ' WHERE r' +
1335
+ referenceTableSuffix +
1336
+ '."guid"=' +
1337
+ ieTable +
1338
+ '."guid" AND r' +
1339
+ referenceTableSuffix +
1340
+ '."name"=@' +
1250
1341
  qrefName +
1251
- ' AND "reference" IN (' +
1342
+ ' AND EXISTS (' +
1252
1343
  qrefQuery.query +
1253
1344
  '))';
1254
1345
  params[qrefName] = curValue[0];
@@ -1258,18 +1349,38 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1258
1349
  return curQuery;
1259
1350
  });
1260
1351
  let sortBy;
1352
+ let sortByInner;
1353
+ let sortJoin = '';
1354
+ let sortJoinInner = '';
1355
+ const order = options.reverse ? ' DESC' : '';
1261
1356
  switch (sort) {
1262
1357
  case 'mdate':
1263
- sortBy = '"mdate"';
1358
+ sortBy = `${eTable}."mdate"${order}`;
1359
+ sortByInner = `${ieTable}."mdate"${order}`;
1264
1360
  break;
1265
1361
  case 'cdate':
1362
+ sortBy = `${eTable}."cdate"${order}`;
1363
+ sortByInner = `${ieTable}."cdate"${order}`;
1364
+ break;
1266
1365
  default:
1267
- sortBy = '"cdate"';
1366
+ const name = `param${++count.i}`;
1367
+ sortJoin = `LEFT JOIN (
1368
+ SELECT "guid", "string", "number"
1369
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'data_' + etype)}
1370
+ WHERE "name"=@${name}
1371
+ ORDER BY "number"${order}, "string"${order}
1372
+ ) ${sTable} ON ${eTable}."guid"=${sTable}."guid"`;
1373
+ sortJoinInner = `LEFT JOIN (
1374
+ SELECT "guid", "string", "number"
1375
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'data_' + etype)}
1376
+ WHERE "name"=@${name}
1377
+ ORDER BY "number"${order}, "string"${order}
1378
+ ) ${sTable} ON ${ieTable}."guid"=${sTable}."guid"`;
1379
+ sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
1380
+ sortByInner = sortBy;
1381
+ params[name] = sort;
1268
1382
  break;
1269
1383
  }
1270
- if (options.reverse) {
1271
- sortBy += ' DESC';
1272
- }
1273
1384
  let query;
1274
1385
  if (queryParts.length) {
1275
1386
  if (subquery) {
@@ -1285,18 +1396,21 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1285
1396
  offset = ` OFFSET ${Math.floor(isNaN(Number(options.offset)) ? 0 : Number(options.offset))}`;
1286
1397
  }
1287
1398
  const whereClause = queryParts.join(') AND (');
1399
+ const guidClause = guidSelector
1400
+ ? `${ieTable}."guid"=${guidSelector} AND `
1401
+ : '';
1288
1402
  if (options.return === 'count') {
1289
1403
  if (limit || offset) {
1290
1404
  query = `SELECT COUNT(${countTable}."guid") AS "count" FROM (
1291
- SELECT COUNT(${ieTable}."guid") AS "guid"
1405
+ SELECT ${ieTable}."guid" AS "guid"
1292
1406
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1293
- WHERE (${whereClause})${limit}${offset}
1407
+ WHERE ${guidClause}(${whereClause})${limit}${offset}
1294
1408
  ) ${countTable}`;
1295
1409
  }
1296
1410
  else {
1297
1411
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1298
1412
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1299
- WHERE (${whereClause})`;
1413
+ WHERE ${guidClause}(${whereClause})`;
1300
1414
  }
1301
1415
  }
1302
1416
  else if (options.return === 'guid') {
@@ -1305,8 +1419,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1305
1419
  : `${ieTable}."guid"`;
1306
1420
  query = `SELECT ${guidColumn} AS "guid"
1307
1421
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1308
- WHERE (${whereClause})
1309
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1422
+ ${sortJoinInner}
1423
+ WHERE ${guidClause}(${whereClause})
1424
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1310
1425
  }
1311
1426
  else {
1312
1427
  query = `SELECT
@@ -1316,18 +1431,20 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1316
1431
  ${eTable}."mdate",
1317
1432
  ${dTable}."name",
1318
1433
  ${dTable}."value",
1319
- ${cTable}."string",
1320
- ${cTable}."number"
1434
+ ${dTable}."json",
1435
+ ${dTable}."string",
1436
+ ${dTable}."number"
1321
1437
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1322
1438
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1323
- INNER JOIN ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1439
+ ${sortJoin}
1324
1440
  INNER JOIN (
1325
1441
  SELECT ${ieTable}."guid"
1326
1442
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1327
- WHERE (${whereClause})
1328
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1443
+ ${sortJoinInner}
1444
+ WHERE ${guidClause}(${whereClause})
1445
+ ORDER BY ${sortByInner}${limit}${offset}
1329
1446
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1330
- ORDER BY ${eTable}.${sortBy}`;
1447
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1331
1448
  }
1332
1449
  }
1333
1450
  }
@@ -1344,16 +1461,19 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1344
1461
  if ('offset' in options) {
1345
1462
  offset = ` OFFSET ${Math.floor(isNaN(Number(options.offset)) ? 0 : Number(options.offset))}`;
1346
1463
  }
1464
+ const guidClause = guidSelector
1465
+ ? ` WHERE ${ieTable}."guid"=${guidSelector}`
1466
+ : '';
1347
1467
  if (options.return === 'count') {
1348
1468
  if (limit || offset) {
1349
1469
  query = `SELECT COUNT(${countTable}."guid") AS "count" FROM (
1350
- SELECT COUNT(${ieTable}."guid") AS "guid"
1351
- FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}${limit}${offset}
1470
+ SELECT ${ieTable}."guid" AS "guid"
1471
+ FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}${guidClause}${limit}${offset}
1352
1472
  ) ${countTable}`;
1353
1473
  }
1354
1474
  else {
1355
1475
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1356
- FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}`;
1476
+ FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}${guidClause}`;
1357
1477
  }
1358
1478
  }
1359
1479
  else if (options.return === 'guid') {
@@ -1362,7 +1482,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1362
1482
  : `${ieTable}."guid"`;
1363
1483
  query = `SELECT ${guidColumn} AS "guid"
1364
1484
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1365
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1485
+ ${sortJoinInner}
1486
+ ${guidClause}
1487
+ ORDER BY ${sortByInner}, ${ieTable}."guid"${limit}${offset}`;
1366
1488
  }
1367
1489
  else {
1368
1490
  if (limit || offset) {
@@ -1373,17 +1495,20 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1373
1495
  ${eTable}."mdate",
1374
1496
  ${dTable}."name",
1375
1497
  ${dTable}."value",
1376
- ${cTable}."string",
1377
- ${cTable}."number"
1498
+ ${dTable}."json",
1499
+ ${dTable}."string",
1500
+ ${dTable}."number"
1378
1501
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1379
1502
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1380
- INNER JOIN ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1503
+ ${sortJoin}
1381
1504
  INNER JOIN (
1382
1505
  SELECT ${ieTable}."guid"
1383
1506
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1384
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1507
+ ${sortJoinInner}
1508
+ ${guidClause}
1509
+ ORDER BY ${sortByInner}${limit}${offset}
1385
1510
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1386
- ORDER BY ${eTable}.${sortBy}`;
1511
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1387
1512
  }
1388
1513
  else {
1389
1514
  query = `SELECT
@@ -1393,12 +1518,14 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1393
1518
  ${eTable}."mdate",
1394
1519
  ${dTable}."name",
1395
1520
  ${dTable}."value",
1396
- ${cTable}."string",
1397
- ${cTable}."number"
1521
+ ${dTable}."json",
1522
+ ${dTable}."string",
1523
+ ${dTable}."number"
1398
1524
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1399
1525
  LEFT JOIN ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ${dTable} ON ${eTable}."guid"=${dTable}."guid"
1400
- INNER JOIN ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} ${cTable} ON ${dTable}."guid"=${cTable}."guid" AND ${dTable}."name"=${cTable}."name"
1401
- ORDER BY ${eTable}.${sortBy}`;
1526
+ ${sortJoin}
1527
+ ${guidSelector ? `WHERE ${eTable}."guid"=${guidSelector}` : ''}
1528
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1402
1529
  }
1403
1530
  }
1404
1531
  }
@@ -1414,24 +1541,19 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1414
1541
  }
1415
1542
  performQuery(options, formattedSelectors, etype) {
1416
1543
  const { query, params, etypes } = this.makeEntityQuery(options, formattedSelectors, etype);
1417
- const result = this.queryIter(query, { etypes, params }).then((val) => val[Symbol.iterator]());
1418
- return {
1419
- result,
1420
- };
1421
- }
1422
- performQuerySync(options, formattedSelectors, etype) {
1423
- const { query, params, etypes } = this.makeEntityQuery(options, formattedSelectors, etype);
1424
- const result = (this.queryIterSync(query, { etypes, params }) || [])[Symbol.iterator]();
1544
+ const result = this.queryArray(query, { etypes, params }).then((val) => val[Symbol.iterator]());
1425
1545
  return {
1426
1546
  result,
1427
1547
  };
1428
1548
  }
1429
1549
  async getEntities(options = {}, ...selectors) {
1430
- const { result: resultPromise, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuery(options, formattedSelectors, etype), () => {
1550
+ const { result: resultPromise, process } = this.getEntitiesRowLike(
1551
+ // @ts-ignore: options is correct here.
1552
+ options, selectors, ({ options, selectors, etype }) => this.performQuery(options, selectors, etype), () => {
1431
1553
  const next = result.next();
1432
1554
  return next.done ? null : next.value;
1433
1555
  }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
1434
- tags: row.tags,
1556
+ tags: row.tags.filter((tag) => tag),
1435
1557
  cdate: isNaN(Number(row.cdate)) ? null : Number(row.cdate),
1436
1558
  mdate: isNaN(Number(row.mdate)) ? null : Number(row.mdate),
1437
1559
  }), (row) => ({
@@ -1439,8 +1561,10 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1439
1561
  svalue: row.value === 'N'
1440
1562
  ? JSON.stringify(Number(row.number))
1441
1563
  : row.value === 'S'
1442
- ? JSON.stringify(row.string)
1443
- : row.value,
1564
+ ? JSON.stringify(PostgreSQLDriver.unescapeNulls(row.string))
1565
+ : row.value === 'J'
1566
+ ? PostgreSQLDriver.unescapeNullSequences(JSON.stringify(row.json))
1567
+ : row.value,
1444
1568
  }));
1445
1569
  const result = await resultPromise;
1446
1570
  const value = process();
@@ -1449,31 +1573,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1449
1573
  }
1450
1574
  return value;
1451
1575
  }
1452
- getEntitiesSync(options = {}, ...selectors) {
1453
- const { result, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuerySync(options, formattedSelectors, etype), () => {
1454
- const next = result.next();
1455
- return next.done ? null : next.value;
1456
- }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
1457
- tags: row.tags,
1458
- cdate: isNaN(Number(row.cdate)) ? null : Number(row.cdate),
1459
- mdate: isNaN(Number(row.mdate)) ? null : Number(row.mdate),
1460
- }), (row) => ({
1461
- name: row.name,
1462
- svalue: row.value === 'N'
1463
- ? JSON.stringify(Number(row.number))
1464
- : row.value === 'S'
1465
- ? JSON.stringify(row.string)
1466
- : row.value,
1467
- }));
1468
- const value = process();
1469
- if (value instanceof Error) {
1470
- throw value;
1471
- }
1472
- return value;
1473
- }
1474
1576
  async getUID(name) {
1475
1577
  if (name == null) {
1476
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1578
+ throw new InvalidParametersError('Name not given for UID.');
1477
1579
  }
1478
1580
  const result = await this.queryGet(`SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1479
1581
  params: {
@@ -1482,129 +1584,143 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1482
1584
  });
1483
1585
  return result?.cur_uid == null ? null : Number(result.cur_uid);
1484
1586
  }
1485
- async import(filename) {
1587
+ async importEntity({ guid, cdate, mdate, tags, sdata, etype, }) {
1486
1588
  try {
1487
- const result = await this.importFromFile(filename, async (guid, tags, sdata, etype) => {
1488
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1489
- etypes: [etype],
1490
- params: {
1491
- guid,
1492
- },
1493
- });
1494
- await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`, {
1589
+ let promises = [];
1590
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1591
+ etypes: [etype],
1592
+ params: {
1593
+ guid,
1594
+ },
1595
+ }));
1596
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1597
+ etypes: [etype],
1598
+ params: {
1599
+ guid,
1600
+ },
1601
+ }));
1602
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1603
+ etypes: [etype],
1604
+ params: {
1605
+ guid,
1606
+ },
1607
+ }));
1608
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1609
+ etypes: [etype],
1610
+ params: {
1611
+ guid,
1612
+ },
1613
+ }));
1614
+ await Promise.all(promises);
1615
+ promises = [];
1616
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`, {
1617
+ etypes: [etype],
1618
+ params: {
1619
+ guid,
1620
+ tags,
1621
+ cdate: isNaN(cdate) ? null : cdate,
1622
+ mdate: isNaN(mdate) ? null : mdate,
1623
+ },
1624
+ });
1625
+ for (const name in sdata) {
1626
+ const value = sdata[name];
1627
+ const uvalue = JSON.parse(value);
1628
+ if (value === undefined) {
1629
+ continue;
1630
+ }
1631
+ const storageValue = typeof uvalue === 'number'
1632
+ ? 'N'
1633
+ : typeof uvalue === 'string'
1634
+ ? 'S'
1635
+ : 'J';
1636
+ const jsonValue = storageValue === 'J'
1637
+ ? PostgreSQLDriver.escapeNullSequences(value)
1638
+ : null;
1639
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (decode(@guid, 'hex'), @name, @storageValue, @jsonValue, @string, @number, @truthy);`, {
1495
1640
  etypes: [etype],
1496
1641
  params: {
1497
1642
  guid,
1498
- tags,
1499
- cdate: isNaN(Number(JSON.parse(sdata.cdate)))
1500
- ? null
1501
- : Number(JSON.parse(sdata.cdate)),
1502
- mdate: isNaN(Number(JSON.parse(sdata.mdate)))
1643
+ name,
1644
+ storageValue,
1645
+ jsonValue,
1646
+ string: storageValue === 'J'
1503
1647
  ? null
1504
- : Number(JSON.parse(sdata.mdate)),
1505
- },
1506
- });
1507
- const promises = [];
1508
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1509
- etypes: [etype],
1510
- params: {
1511
- guid,
1512
- },
1513
- }));
1514
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1515
- etypes: [etype],
1516
- params: {
1517
- guid,
1518
- },
1519
- }));
1520
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1521
- etypes: [etype],
1522
- params: {
1523
- guid,
1648
+ : PostgreSQLDriver.escapeNulls(`${uvalue}`),
1649
+ number: isNaN(Number(uvalue)) ? null : Number(uvalue),
1650
+ truthy: !!uvalue,
1524
1651
  },
1525
1652
  }));
1526
- await Promise.all(promises);
1527
- delete sdata.cdate;
1528
- delete sdata.mdate;
1529
- for (const name in sdata) {
1530
- const value = sdata[name];
1531
- const uvalue = JSON.parse(value);
1532
- if (value === undefined) {
1533
- continue;
1534
- }
1535
- const storageValue = typeof uvalue === 'number'
1536
- ? 'N'
1537
- : typeof uvalue === 'string'
1538
- ? 'S'
1539
- : value;
1540
- const promises = [];
1541
- promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`, {
1542
- etypes: [etype],
1543
- params: {
1544
- guid,
1545
- name,
1546
- storageValue,
1547
- },
1548
- }));
1549
- 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);`, {
1653
+ const references = this.findReferences(value);
1654
+ for (const reference of references) {
1655
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`, {
1550
1656
  etypes: [etype],
1551
1657
  params: {
1552
1658
  guid,
1553
1659
  name,
1554
- truthy: !!uvalue,
1555
- string: `${uvalue}`,
1556
- number: isNaN(Number(uvalue)) ? null : Number(uvalue),
1660
+ reference,
1557
1661
  },
1558
1662
  }));
1559
- const references = this.findReferences(value);
1560
- for (const reference of references) {
1561
- promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`, {
1562
- etypes: [etype],
1563
- params: {
1564
- guid,
1565
- name,
1566
- reference,
1567
- },
1568
- }));
1569
- }
1570
1663
  }
1571
- await Promise.all(promises);
1572
- }, async (name, curUid) => {
1573
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1574
- params: {
1575
- name,
1576
- },
1577
- });
1578
- await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
1664
+ }
1665
+ const uniques = await this.nymph
1666
+ .getEntityClassByEtype(etype)
1667
+ .getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
1668
+ for (const unique of uniques) {
1669
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`, {
1670
+ etypes: [etype],
1579
1671
  params: {
1580
- name,
1581
- curUid,
1672
+ guid,
1673
+ unique,
1582
1674
  },
1583
- });
1584
- }, async () => {
1585
- await this.internalTransaction('nymph-import');
1586
- }, async () => {
1587
- await this.commit('nymph-import');
1675
+ }).catch((e) => {
1676
+ if (e instanceof EntityUniqueConstraintError) {
1677
+ this.nymph.config.debugError('postgresql', `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
1678
+ }
1679
+ return e;
1680
+ }));
1681
+ }
1682
+ await Promise.all(promises);
1683
+ }
1684
+ catch (e) {
1685
+ this.nymph.config.debugError('postgresql', `Import entity error: "${e}"`);
1686
+ throw e;
1687
+ }
1688
+ }
1689
+ async importUID({ name, value }) {
1690
+ try {
1691
+ await this.internalTransaction(`nymph-import-uid-${name}`);
1692
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1693
+ params: {
1694
+ name,
1695
+ },
1588
1696
  });
1589
- return result;
1697
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @value);`, {
1698
+ params: {
1699
+ name,
1700
+ value,
1701
+ },
1702
+ });
1703
+ await this.commit(`nymph-import-uid-${name}`);
1590
1704
  }
1591
1705
  catch (e) {
1592
- await this.rollback('nymph-import');
1593
- return false;
1706
+ this.nymph.config.debugError('postgresql', `Import UID error: "${e}"`);
1707
+ await this.rollback(`nymph-import-uid-${name}`);
1708
+ throw e;
1594
1709
  }
1595
1710
  }
1596
1711
  async newUID(name) {
1597
1712
  if (name == null) {
1598
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1713
+ throw new InvalidParametersError('Name not given for UID.');
1599
1714
  }
1600
1715
  await this.internalTransaction('nymph-newuid');
1716
+ let curUid = undefined;
1601
1717
  try {
1602
1718
  const lock = await this.queryGet(`SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name FOR UPDATE;`, {
1603
1719
  params: {
1604
1720
  name,
1605
1721
  },
1606
1722
  });
1607
- let curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
1723
+ curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
1608
1724
  if (curUid == null) {
1609
1725
  curUid = 1;
1610
1726
  await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
@@ -1623,17 +1739,18 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1623
1739
  },
1624
1740
  });
1625
1741
  }
1626
- await this.commit('nymph-newuid');
1627
- return curUid;
1628
1742
  }
1629
1743
  catch (e) {
1744
+ this.nymph.config.debugError('postgresql', `New UID error: "${e}"`);
1630
1745
  await this.rollback('nymph-newuid');
1631
1746
  throw e;
1632
1747
  }
1748
+ await this.commit('nymph-newuid');
1749
+ return curUid;
1633
1750
  }
1634
1751
  async renameUID(oldName, newName) {
1635
1752
  if (oldName == null || newName == null) {
1636
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1753
+ throw new InvalidParametersError('Name not given for UID.');
1637
1754
  }
1638
1755
  await this.queryRun(`UPDATE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} SET "name"=@newName WHERE "name"=@oldName;`, {
1639
1756
  params: {
@@ -1645,7 +1762,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1645
1762
  }
1646
1763
  async rollback(name) {
1647
1764
  if (name == null || typeof name !== 'string' || name.length === 0) {
1648
- throw new nymph_1.InvalidParametersError('Transaction rollback attempted without a name.');
1765
+ throw new InvalidParametersError('Transaction rollback attempted without a name.');
1649
1766
  }
1650
1767
  if (!this.transaction || this.transaction.count === 0) {
1651
1768
  this.transaction = null;
@@ -1662,7 +1779,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1662
1779
  return true;
1663
1780
  }
1664
1781
  async saveEntity(entity) {
1665
- const insertData = async (guid, data, sdata, etype) => {
1782
+ const insertData = async (guid, data, sdata, uniques, etype) => {
1666
1783
  const runInsertQuery = async (name, value, svalue) => {
1667
1784
  if (value === undefined) {
1668
1785
  return;
@@ -1671,24 +1788,23 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1671
1788
  ? 'N'
1672
1789
  : typeof value === 'string'
1673
1790
  ? 'S'
1674
- : svalue;
1791
+ : 'J';
1792
+ const jsonValue = storageValue === 'J'
1793
+ ? PostgreSQLDriver.escapeNullSequences(svalue)
1794
+ : null;
1675
1795
  const promises = [];
1676
- promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`, {
1796
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (decode(@guid, 'hex'), @name, @storageValue, @jsonValue, @string, @number, @truthy);`, {
1677
1797
  etypes: [etype],
1678
1798
  params: {
1679
1799
  guid,
1680
1800
  name,
1681
1801
  storageValue,
1682
- },
1683
- }));
1684
- 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);`, {
1685
- etypes: [etype],
1686
- params: {
1687
- guid,
1688
- name,
1689
- truthy: !!value,
1690
- string: `${value}`,
1802
+ jsonValue,
1803
+ string: storageValue === 'J'
1804
+ ? null
1805
+ : PostgreSQLDriver.escapeNulls(`${value}`),
1691
1806
  number: isNaN(Number(value)) ? null : Number(value),
1807
+ truthy: !!value,
1692
1808
  },
1693
1809
  }));
1694
1810
  const references = this.findReferences(svalue);
@@ -1704,6 +1820,23 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1704
1820
  }
1705
1821
  await Promise.all(promises);
1706
1822
  };
1823
+ for (const unique of uniques) {
1824
+ try {
1825
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`, {
1826
+ etypes: [etype],
1827
+ params: {
1828
+ guid,
1829
+ unique,
1830
+ },
1831
+ });
1832
+ }
1833
+ catch (e) {
1834
+ if (e instanceof EntityUniqueConstraintError) {
1835
+ this.nymph.config.debugError('postgresql', `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
1836
+ }
1837
+ throw e;
1838
+ }
1839
+ }
1707
1840
  for (const name in data) {
1708
1841
  await runInsertQuery(name, data[name], JSON.stringify(data[name]));
1709
1842
  }
@@ -1711,8 +1844,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1711
1844
  await runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
1712
1845
  }
1713
1846
  };
1847
+ let inTransaction = false;
1714
1848
  try {
1715
- const result = await this.saveEntityRowLike(entity, async (_entity, guid, tags, data, sdata, cdate, etype) => {
1849
+ const result = await this.saveEntityRowLike(entity, async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
1850
+ if (Object.keys(data).length === 0 &&
1851
+ Object.keys(sdata).length === 0) {
1852
+ return false;
1853
+ }
1716
1854
  await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @cdate);`, {
1717
1855
  etypes: [etype],
1718
1856
  params: {
@@ -1721,9 +1859,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1721
1859
  cdate,
1722
1860
  },
1723
1861
  });
1724
- await insertData(guid, data, sdata, etype);
1862
+ await insertData(guid, data, sdata, uniques, etype);
1725
1863
  return true;
1726
- }, async (entity, guid, tags, data, sdata, mdate, etype) => {
1864
+ }, async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
1865
+ if (Object.keys(data).length === 0 &&
1866
+ Object.keys(sdata).length === 0) {
1867
+ return false;
1868
+ }
1727
1869
  const promises = [];
1728
1870
  promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1729
1871
  etypes: [etype],
@@ -1737,13 +1879,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1737
1879
  guid,
1738
1880
  },
1739
1881
  }));
1740
- promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1882
+ promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1741
1883
  etypes: [etype],
1742
1884
  params: {
1743
1885
  guid,
1744
1886
  },
1745
1887
  }));
1746
- promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1888
+ promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1747
1889
  etypes: [etype],
1748
1890
  params: {
1749
1891
  guid,
@@ -1768,44 +1910,51 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1768
1910
  guid,
1769
1911
  },
1770
1912
  }));
1771
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1913
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1772
1914
  etypes: [etype],
1773
1915
  params: {
1774
1916
  guid,
1775
1917
  },
1776
1918
  }));
1777
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1919
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1778
1920
  etypes: [etype],
1779
1921
  params: {
1780
1922
  guid,
1781
1923
  },
1782
1924
  }));
1783
1925
  await Promise.all(promises);
1784
- await insertData(guid, data, sdata, etype);
1926
+ await insertData(guid, data, sdata, uniques, etype);
1785
1927
  success = true;
1786
1928
  }
1787
1929
  return success;
1788
1930
  }, async () => {
1789
1931
  await this.internalTransaction('nymph-save');
1932
+ inTransaction = true;
1790
1933
  }, async (success) => {
1791
- if (success) {
1792
- await this.commit('nymph-save');
1793
- }
1794
- else {
1795
- await this.rollback('nymph-save');
1934
+ if (inTransaction) {
1935
+ inTransaction = false;
1936
+ if (success) {
1937
+ await this.commit('nymph-save');
1938
+ }
1939
+ else {
1940
+ await this.rollback('nymph-save');
1941
+ }
1796
1942
  }
1797
1943
  return success;
1798
1944
  });
1799
1945
  return result;
1800
1946
  }
1801
1947
  catch (e) {
1802
- await this.rollback('nymph-save');
1948
+ this.nymph.config.debugError('postgresql', `Save entity error: "${e}"`);
1949
+ if (inTransaction) {
1950
+ await this.rollback('nymph-save');
1951
+ }
1803
1952
  throw e;
1804
1953
  }
1805
1954
  }
1806
1955
  async setUID(name, curUid) {
1807
1956
  if (name == null) {
1808
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1957
+ throw new InvalidParametersError('Name not given for UID.');
1809
1958
  }
1810
1959
  await this.internalTransaction('nymph-setuid');
1811
1960
  try {
@@ -1821,23 +1970,25 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1821
1970
  curUid,
1822
1971
  },
1823
1972
  });
1824
- await this.commit('nymph-setuid');
1825
- return true;
1826
1973
  }
1827
1974
  catch (e) {
1828
1975
  await this.rollback('nymph-setuid');
1829
1976
  throw e;
1830
1977
  }
1978
+ await this.commit('nymph-setuid');
1979
+ return true;
1831
1980
  }
1832
1981
  async internalTransaction(name) {
1833
1982
  if (name == null || typeof name !== 'string' || name.length === 0) {
1834
- throw new nymph_1.InvalidParametersError('Transaction start attempted without a name.');
1983
+ throw new InvalidParametersError('Transaction start attempted without a name.');
1835
1984
  }
1836
1985
  if (!this.transaction || this.transaction.count === 0) {
1986
+ // Lock to one connection.
1837
1987
  this.transaction = {
1838
1988
  count: 0,
1839
1989
  connection: await this.getConnection(),
1840
1990
  };
1991
+ // We're not in a transaction yet, so start one.
1841
1992
  await this.queryRun('BEGIN;');
1842
1993
  }
1843
1994
  await this.queryRun(`SAVEPOINT ${PostgreSQLDriver.escape(name)};`);
@@ -1845,7 +1996,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1845
1996
  return this.transaction;
1846
1997
  }
1847
1998
  async startTransaction(name) {
1848
- const inTransaction = this.inTransaction();
1999
+ const inTransaction = await this.inTransaction();
1849
2000
  const transaction = await this.internalTransaction(name);
1850
2001
  if (!inTransaction) {
1851
2002
  this.transaction = null;
@@ -1854,6 +2005,23 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1854
2005
  nymph.driver.transaction = transaction;
1855
2006
  return nymph;
1856
2007
  }
2008
+ async needsMigration() {
2009
+ const table = await this.queryGet('SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @prefix LIMIT 1;', {
2010
+ params: {
2011
+ db: this.config.database,
2012
+ prefix: this.prefix + 'data_' + '%',
2013
+ },
2014
+ });
2015
+ if (table?.table_name) {
2016
+ const result = await this.queryGet('SELECT 1 AS "exists" FROM "information_schema"."columns" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name"=@table AND "column_name"=\'json\';', {
2017
+ params: {
2018
+ db: this.config.database,
2019
+ table: table.table_name,
2020
+ },
2021
+ });
2022
+ return !result?.exists;
2023
+ }
2024
+ return false;
2025
+ }
1857
2026
  }
1858
- exports.default = PostgreSQLDriver;
1859
2027
  //# sourceMappingURL=PostgreSQLDriver.js.map