@nymphjs/driver-postgresql 1.0.0-beta.11 → 1.0.0-beta.110

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,217 @@ 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) {
102
- if (etype != null) {
103
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} (
167
+ async createEntitiesTable(etype, connection) {
168
+ // Create the entity table.
169
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} (
104
170
  "guid" BYTEA NOT NULL,
105
171
  "tags" TEXT[],
106
172
  "cdate" DOUBLE PRECISION NOT NULL,
107
173
  "mdate" DOUBLE PRECISION NOT NULL,
174
+ "user" BYTEA,
175
+ "group" BYTEA,
176
+ "acUser" SMALLINT,
177
+ "acGroup" SMALLINT,
178
+ "acOther" SMALLINT,
179
+ "acRead" BYTEA[],
180
+ "acWrite" BYTEA[],
181
+ "acFull" BYTEA[],
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
+ await this.createEntitiesTilmeldIndexes(etype, connection);
192
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`, { connection });
193
+ }
194
+ async addTilmeldColumnsAndIndexes(etype, connection) {
195
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN IF NOT EXISTS "user" BYTEA,
196
+ ADD COLUMN IF NOT EXISTS "group" BYTEA,
197
+ ADD COLUMN IF NOT EXISTS "acUser" SMALLINT,
198
+ ADD COLUMN IF NOT EXISTS "acGroup" SMALLINT,
199
+ ADD COLUMN IF NOT EXISTS "acOther" SMALLINT,
200
+ ADD COLUMN IF NOT EXISTS "acRead" BYTEA[],
201
+ ADD COLUMN IF NOT EXISTS "acWrite" BYTEA[],
202
+ ADD COLUMN IF NOT EXISTS "acFull" BYTEA[];`);
203
+ await this.createEntitiesTilmeldIndexes(etype, connection);
204
+ }
205
+ async createEntitiesTilmeldIndexes(etype, connection) {
206
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_user_acUser`)};`, { connection });
207
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_user_acUser`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("user", "acUser");`, { connection });
208
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_group_acGroup`)};`, { connection });
209
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_group_acGroup`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("group", "acGroup");`, { connection });
210
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acUser`)};`, { connection });
211
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acUser`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("acUser");`, { connection });
212
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acGroup`)};`, { connection });
213
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acGroup`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("acGroup");`, { connection });
214
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acOther`)};`, { connection });
215
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acOther`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING btree ("acOther");`, { connection });
216
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acRead`)};`, { connection });
217
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acRead`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING gin ("acRead");`, { connection });
218
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acWrite`)};`, { connection });
219
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acWrite`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING gin ("acWrite");`, { connection });
220
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acFull`)};`, { connection });
221
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}_id_acFull`)} ON ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} USING gin ("acFull");`, { connection });
222
+ }
223
+ async createDataTable(etype, connection) {
224
+ // Create the data table.
225
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} (
135
226
  "guid" BYTEA NOT NULL,
136
227
  "name" TEXT NOT NULL,
137
- "truthy" BOOLEAN,
228
+ "value" CHARACTER(1) NOT NULL,
229
+ "json" JSONB,
138
230
  "string" TEXT,
139
231
  "number" DOUBLE PRECISION,
232
+ "truthy" BOOLEAN,
140
233
  PRIMARY KEY ("guid", "name"),
141
234
  FOREIGN KEY ("guid")
142
235
  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}`)} (
236
+ ) WITH ( OIDS=FALSE );`, { connection });
237
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
238
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)};`, { connection });
239
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid");`, { connection });
240
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name`)};`, { connection });
241
+ 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 });
242
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__user`)};`, { connection });
243
+ 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 });
244
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name__group`)};`, { connection });
245
+ 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 });
246
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)};`, { connection });
247
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name");`, { connection });
248
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_string`)};`, { connection });
249
+ 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 });
250
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_number`)};`, { connection });
251
+ 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 });
252
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name_number`)};`, { connection });
253
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name_number`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid", "name", "number");`, { connection });
254
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_truthy`)};`, { connection });
255
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_name_truthy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("name", "truthy");`, { connection });
256
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name_truthy`)};`, { connection });
257
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_guid_name_truthy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid", "name", "truthy");`, { connection });
258
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_string`)};`, { connection });
259
+ 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 });
260
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_json`)};`, { connection });
261
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_json`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING gin ("json");`, { connection });
262
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acuserread`)};`, { connection });
263
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acuserread`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name"='acUser' AND "number" >= 1;`, { connection });
264
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acgroupread`)};`, { connection });
265
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acgroupread`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name"='acGroup' AND "number" >= 1;`, { connection });
266
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acotherread`)};`, { connection });
267
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acotherread`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name"='acOther' AND "number" >= 1;`, { connection });
268
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acuser`)};`, { connection });
269
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acuser`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name"='user';`, { connection });
270
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acgroup`)};`, { connection });
271
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_acgroup`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("guid") WHERE "name"='group';`, { connection });
272
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`, { connection });
273
+ }
274
+ async createReferencesTable(etype, connection) {
275
+ // Create the references table.
276
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} (
154
277
  "guid" BYTEA NOT NULL,
155
278
  "name" TEXT NOT NULL,
156
279
  "reference" BYTEA NOT NULL,
157
280
  PRIMARY KEY ("guid", "name", "reference"),
158
281
  FOREIGN KEY ("guid")
159
282
  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");`);
283
+ ) WITH ( OIDS=FALSE );`, { connection });
284
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
285
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)};`, { connection });
286
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("guid");`, { connection });
287
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)};`, { connection });
288
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("name");`, { connection });
289
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_name_reference`)};`, { connection });
290
+ 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 });
291
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)};`, { connection });
292
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference");`, { connection });
293
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid_name_reference`)};`, { connection });
294
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_guid_name_reference`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("guid", "name", "reference");`, { connection });
295
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_name_guid`)};`, { connection });
296
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_name_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference", "name", "guid");`, { connection });
297
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_guid_name`)};`, { connection });
298
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_guid_name`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference", "guid", "name");`, { connection });
299
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_guid_nameuser`)};`, { connection });
300
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_guid_nameuser`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference", "guid") WHERE "name"='user';`, { connection });
301
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_guid_namegroup`)};`, { connection });
302
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_reference_guid_namegroup`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference", "guid") WHERE "name"='group';`, { connection });
303
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`, { connection });
304
+ }
305
+ async createTokensTable(etype, connection) {
306
+ // Create the tokens table.
307
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} (
308
+ "guid" BYTEA NOT NULL,
309
+ "name" TEXT NOT NULL,
310
+ "token" INTEGER NOT NULL,
311
+ "position" INTEGER NOT NULL,
312
+ "stem" BOOLEAN NOT NULL,
313
+ PRIMARY KEY ("guid", "name", "token", "position"),
314
+ FOREIGN KEY ("guid")
315
+ REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
316
+ ) WITH ( OIDS=FALSE );`, { connection });
317
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
318
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}_id_name_token`)};`, { connection });
319
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}_id_name_token`)} ON ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} USING btree ("name", "token");`, { connection });
320
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`, { connection });
321
+ }
322
+ async createUniquesTable(etype, connection) {
323
+ // Create the unique strings table.
324
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} (
325
+ "guid" BYTEA NOT NULL,
326
+ "unique" TEXT NOT NULL UNIQUE,
327
+ PRIMARY KEY ("guid", "unique"),
328
+ FOREIGN KEY ("guid")
329
+ REFERENCES ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
330
+ ) WITH ( OIDS=FALSE );`, { connection });
331
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
332
+ }
333
+ /**
334
+ * Create entity tables in the database.
335
+ *
336
+ * @param etype The entity type to create a table for. If this is blank, the default tables are created.
337
+ * @returns True on success, false on failure.
338
+ */
339
+ async createTables(etype = null) {
340
+ const connection = await this.getConnection(true);
341
+ if (etype != null) {
342
+ await this.createEntitiesTable(etype, connection);
343
+ await this.createDataTable(etype, connection);
344
+ await this.createReferencesTable(etype, connection);
345
+ await this.createTokensTable(etype, connection);
346
+ await this.createUniquesTable(etype, connection);
168
347
  }
169
348
  else {
170
- this.queryRunSync(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uids`)} (
349
+ // Add trigram extensions.
350
+ try {
351
+ await this.queryRun(`CREATE EXTENSION pg_trgm;`, { connection });
352
+ }
353
+ catch (e) {
354
+ // Ignore errors.
355
+ }
356
+ // Create the UID table.
357
+ await this.queryRun(`CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(`${this.prefix}uids`)} (
171
358
  "name" TEXT NOT NULL,
172
359
  "cur_uid" BIGINT NOT NULL,
173
360
  PRIMARY KEY ("name")
174
- ) WITH ( OIDS = FALSE );`);
175
- this.queryRunSync(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`);
361
+ ) WITH ( OIDS = FALSE );`, { connection });
362
+ await this.queryRun(`ALTER TABLE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`, { connection });
176
363
  }
364
+ connection.done();
177
365
  return true;
178
366
  }
179
367
  translateQuery(origQuery, origParams) {
@@ -191,49 +379,32 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
191
379
  }
192
380
  async query(runQuery, query, etypes = []) {
193
381
  try {
382
+ this.nymph.config.debugInfo('postgresql:query', query);
194
383
  return await runQuery();
195
384
  }
196
385
  catch (e) {
197
386
  const errorCode = e?.code;
198
- if (errorCode === '42P01' && this.createTables()) {
387
+ if (errorCode === '42P01' && (await this.createTables())) {
388
+ // If the tables don't exist yet, create them.
199
389
  for (let etype of etypes) {
200
- this.createTables(etype);
390
+ await this.createTables(etype);
201
391
  }
202
392
  try {
203
393
  return await runQuery();
204
394
  }
205
395
  catch (e2) {
206
- throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
396
+ throw new QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query, e2?.code);
207
397
  }
208
398
  }
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
- }
399
+ else if (errorCode === '23505') {
400
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
230
401
  }
231
402
  else {
232
- throw e;
403
+ throw new QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query, e?.code);
233
404
  }
234
405
  }
235
406
  }
236
- queryIter(query, { etypes = [], params = {}, } = {}) {
407
+ queryArray(query, { etypes = [], params = {}, } = {}) {
237
408
  const { query: newQuery, params: newParams } = this.translateQuery(query, params);
238
409
  return this.query(async () => {
239
410
  const results = await new Promise((resolve, reject) => {
@@ -249,32 +420,30 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
249
420
  return results.rows;
250
421
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
251
422
  }
252
- queryIterSync(query, { etypes = [], params = {}, } = {}) {
423
+ async queryIter(query, { etypes = [], params = {}, } = {}) {
253
424
  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];
425
+ const that = this;
426
+ return this.query(async function* () {
427
+ const transaction = !!that.transaction?.connection;
428
+ const connection = await that.getConnection();
429
+ const cursor = new Cursor(newQuery, newParams);
430
+ const iter = connection.client.query(cursor);
431
+ while (true) {
432
+ const rows = await iter.read(100);
433
+ if (!rows.length) {
434
+ await new Promise((resolve) => {
435
+ iter.close(() => {
436
+ if (!transaction) {
437
+ connection.done();
438
+ }
439
+ resolve();
440
+ });
441
+ });
442
+ return;
443
+ }
444
+ for (let row of rows) {
445
+ yield row;
446
+ }
278
447
  }
279
448
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
280
449
  }
@@ -294,12 +463,13 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
294
463
  return results.rows[0];
295
464
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
296
465
  }
297
- queryRun(query, { etypes = [], params = {}, } = {}) {
466
+ queryRun(query, { etypes = [], params = {}, connection, } = {}) {
298
467
  const { query: newQuery, params: newParams } = this.translateQuery(query, params);
299
468
  return this.query(async () => {
300
469
  const results = await new Promise((resolve, reject) => {
301
470
  try {
302
- (this.transaction?.connection?.client ?? this.link)
471
+ ((connection ?? this.transaction?.connection)?.client ??
472
+ this.link)
303
473
  .query(newQuery, newParams)
304
474
  .then((results) => resolve(results), (error) => reject(error));
305
475
  }
@@ -310,39 +480,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
310
480
  return { rowCount: results.rowCount ?? 0 };
311
481
  }, `${query} -- ${JSON.stringify(params)}`, etypes);
312
482
  }
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
483
  async commit(name) {
344
484
  if (name == null || typeof name !== 'string' || name.length === 0) {
345
- throw new nymph_1.InvalidParametersError('Transaction commit attempted without a name.');
485
+ throw new InvalidParametersError('Transaction commit attempted without a name.');
346
486
  }
347
487
  if (!this.transaction || this.transaction.count === 0) {
348
488
  this.transaction = null;
@@ -382,32 +522,40 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
382
522
  guid,
383
523
  },
384
524
  });
385
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
525
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
386
526
  etypes: [etype],
387
527
  params: {
388
528
  guid,
389
529
  },
390
530
  });
391
- await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
531
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
532
+ etypes: [etype],
533
+ params: {
534
+ guid,
535
+ },
536
+ });
537
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
392
538
  etypes: [etype],
393
539
  params: {
394
540
  guid,
395
541
  },
396
542
  });
397
- await this.commit('nymph-delete');
398
- if (this.nymph.config.cache) {
399
- this.cleanCache(guid);
400
- }
401
- return true;
402
543
  }
403
544
  catch (e) {
545
+ this.nymph.config.debugError('postgresql', `Delete entity error: "${e}"`);
404
546
  await this.rollback('nymph-delete');
405
547
  throw e;
406
548
  }
549
+ await this.commit('nymph-delete');
550
+ // Remove any cached versions of this entity.
551
+ if (this.nymph.config.cache) {
552
+ this.cleanCache(guid);
553
+ }
554
+ return true;
407
555
  }
408
556
  async deleteUID(name) {
409
557
  if (!name) {
410
- throw new nymph_1.InvalidParametersError('Name not given for UID');
558
+ throw new InvalidParametersError('Name not given for UID');
411
559
  }
412
560
  await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
413
561
  params: {
@@ -416,79 +564,223 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
416
564
  });
417
565
  return true;
418
566
  }
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('');
430
- 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}]`);
433
- }
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;');
567
+ async getIndexes(etype) {
568
+ const indexes = [];
569
+ for (let [scope, suffix] of [
570
+ ['data', '_json'],
571
+ ['references', '_reference_guid'],
572
+ ['tokens', '_token_position_stem'],
573
+ ]) {
574
+ const indexDefinitions = await this.queryArray(`SELECT * FROM "pg_indexes" WHERE "indexname" LIKE @pattern;`, {
575
+ params: {
576
+ pattern: `${this.prefix}${scope}_${etype}_id_custom_%${suffix}`,
577
+ },
578
+ });
579
+ for (const indexDefinition of indexDefinitions) {
580
+ indexes.push({
581
+ scope,
582
+ name: indexDefinition.indexname.substring(`${this.prefix}${scope}_${etype}_id_custom_`.length, indexDefinition.indexname.length - suffix.length),
583
+ property: (indexDefinition.indexdef.match(/WHERE\s+\(?\s*name\s*=\s*'(.*)'/) ?? [])[1] ?? '',
584
+ });
585
+ }
586
+ }
587
+ return indexes;
588
+ }
589
+ async addIndex(etype, definition) {
590
+ this.checkIndexName(definition.name);
591
+ await this.deleteIndex(etype, definition.scope, definition.name);
592
+ const connection = await this.getConnection(true);
593
+ if (definition.scope === 'data') {
594
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_json`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING gin ("json") WHERE "name"=${PostgreSQLDriver.escapeValue(definition.property)};`, { connection });
595
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_string_gin`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING gin ("string" gin_trgm_ops) WHERE "name"=${PostgreSQLDriver.escapeValue(definition.property)};`, { connection });
596
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_string_btree`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree (LEFT("string", 1024)) WHERE "name"=${PostgreSQLDriver.escapeValue(definition.property)};`, { connection });
597
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_number`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("number") WHERE "name"=${PostgreSQLDriver.escapeValue(definition.property)};`, { connection });
598
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_truthy`)} ON ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} USING btree ("truthy") WHERE "name"=${PostgreSQLDriver.escapeValue(definition.property)};`, { connection });
599
+ }
600
+ else if (definition.scope === 'references') {
601
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_custom_${definition.name}_reference_guid`)} ON ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} USING btree ("reference", "guid") WHERE "name"=${PostgreSQLDriver.escapeValue(definition.property)};`, { connection });
602
+ }
603
+ else if (definition.scope === 'tokens') {
604
+ await this.queryRun(`CREATE INDEX ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}_id_custom_${definition.name}_token_position_stem`)} ON ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} USING btree ("token", "position", "stem") WHERE "name"=${PostgreSQLDriver.escapeValue(definition.property)};`, { connection });
605
+ }
606
+ connection.done();
607
+ return true;
608
+ }
609
+ async deleteIndex(etype, scope, name) {
610
+ this.checkIndexName(name);
611
+ const connection = await this.getConnection(true);
612
+ if (scope === 'data') {
613
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${name}_json`)};`, { connection });
614
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${name}_string_gin`)};`, { connection });
615
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${name}_string_btree`)};`, { connection });
616
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${name}_number`)};`, { connection });
617
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}_id_custom_${name}_truthy`)};`, { connection });
618
+ }
619
+ else if (scope === 'references') {
620
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}_id_custom_${name}_reference_guid`)};`, { connection });
621
+ }
622
+ else if (scope === 'tokens') {
623
+ await this.queryRun(`DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}_id_custom_${name}_token_position_stem`)};`, { connection });
624
+ }
625
+ connection.done();
626
+ return true;
627
+ }
628
+ async getEtypes() {
629
+ 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;', {
630
+ params: {
631
+ db: this.config.database,
632
+ prefix: this.prefix + 'entities_' + '%',
633
+ },
634
+ });
440
635
  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));
636
+ for (const table of tables) {
637
+ etypes.push(table.table_name.substr((this.prefix + 'entities_').length));
638
+ }
639
+ return etypes;
640
+ }
641
+ async *exportDataIterator() {
642
+ if (yield {
643
+ type: 'comment',
644
+ content: `#nex2
645
+ # Nymph Entity Exchange v2
646
+ # http://nymph.io
647
+ #
648
+ # Generation Time: ${new Date().toLocaleString()}
649
+ `,
650
+ }) {
651
+ return;
652
+ }
653
+ if (yield {
654
+ type: 'comment',
655
+ content: `
656
+
657
+ #
658
+ # UIDs
659
+ #
660
+
661
+ `,
662
+ }) {
663
+ return;
664
+ }
665
+ // Export UIDs.
666
+ let uids = await this.queryIter(`SELECT * FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
667
+ for await (const uid of uids) {
668
+ if (yield { type: 'uid', content: `<${uid.name}>[${uid.cur_uid}]\n` }) {
669
+ return;
445
670
  }
446
671
  }
672
+ if (yield {
673
+ type: 'comment',
674
+ content: `
675
+
676
+ #
677
+ # Entities
678
+ #
679
+
680
+ `,
681
+ }) {
682
+ return;
683
+ }
684
+ // Get the etypes.
685
+ const etypes = await this.getEtypes();
447
686
  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"
687
+ // Export entities.
688
+ const dataIterator = await this.queryIter(`SELECT encode(e."guid", 'hex') AS "guid", e."tags", e."cdate", e."mdate", encode(e."user", 'hex') AS "user", encode(e."group", 'hex') AS "group", e."acUser", e."acGroup", e."acOther", array(SELECT encode(n, 'hex') FROM unnest(e."acRead") AS n) as "acRead", array(SELECT encode(n, 'hex') FROM unnest(e."acWrite") AS n) as "acWrite", array(SELECT encode(n, 'hex') FROM unnest(e."acFull") AS n) as "acFull", d."name", d."value", d."json", d."string", d."number"
449
689
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} e
450
690
  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();
691
+ ORDER BY e."guid";`);
692
+ let datum = await dataIterator.next();
454
693
  while (!datum.done) {
455
694
  const guid = datum.value.guid;
456
- const tags = datum.value.tags.join(',');
695
+ const tags = datum.value.tags.filter((tag) => tag).join(',');
457
696
  const cdate = datum.value.cdate;
458
697
  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) {
698
+ const user = datum.value.user;
699
+ const group = datum.value.group;
700
+ const acUser = datum.value.acUser;
701
+ const acGroup = datum.value.acGroup;
702
+ const acOther = datum.value.acOther;
703
+ const acRead = datum.value.acRead?.filter((guid) => guid);
704
+ const acWrite = datum.value.acWrite?.filter((guid) => guid);
705
+ const acFull = datum.value.acFull?.filter((guid) => guid);
706
+ let currentEntityExport = [];
707
+ currentEntityExport.push(`{${guid}}<${etype}>[${tags}]`);
708
+ currentEntityExport.push(`\tcdate=${JSON.stringify(cdate)}`);
709
+ currentEntityExport.push(`\tmdate=${JSON.stringify(mdate)}`);
710
+ if (this.nymph.tilmeld != null) {
711
+ if (user != null) {
712
+ currentEntityExport.push(`\tuser=${JSON.stringify(['nymph_entity_reference', user, 'User'])}`);
713
+ }
714
+ if (group != null) {
715
+ currentEntityExport.push(`\tgroup=${JSON.stringify(['nymph_entity_reference', group, 'Group'])}`);
716
+ }
717
+ if (acUser != null) {
718
+ currentEntityExport.push(`\tacUser=${JSON.stringify(acUser)}`);
719
+ }
720
+ if (acGroup != null) {
721
+ currentEntityExport.push(`\tacGroup=${JSON.stringify(acGroup)}`);
722
+ }
723
+ if (acOther != null) {
724
+ currentEntityExport.push(`\tacOther=${JSON.stringify(acOther)}`);
725
+ }
726
+ if (acRead != null) {
727
+ currentEntityExport.push(`\tacRead=${JSON.stringify(acRead)}`);
728
+ }
729
+ if (acWrite != null) {
730
+ currentEntityExport.push(`\tacWrite=${JSON.stringify(acWrite)}`);
731
+ }
732
+ if (acFull != null) {
733
+ currentEntityExport.push(`\tacFull=${JSON.stringify(acFull)}`);
734
+ }
735
+ }
736
+ if (datum.value.name != null) {
737
+ // This do will keep going and adding the data until the
738
+ // next entity is reached. $row will end on the next entity.
463
739
  do {
464
- const value = datum.value.dvalue === 'N'
740
+ const value = datum.value.value === 'N'
465
741
  ? 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();
742
+ : datum.value.value === 'S'
743
+ ? JSON.stringify(PostgreSQLDriver.unescapeNulls(datum.value.string))
744
+ : datum.value.value === 'J'
745
+ ? PostgreSQLDriver.unescapeNullSequences(JSON.stringify(datum.value.json))
746
+ : datum.value.value;
747
+ currentEntityExport.push(`\t${datum.value.name}=${value}`);
748
+ datum = await dataIterator.next();
471
749
  } while (!datum.done && datum.value.guid === guid);
472
750
  }
473
751
  else {
474
- datum = dataIterator.next();
752
+ // Make sure that datum is incremented :)
753
+ datum = await dataIterator.next();
754
+ }
755
+ currentEntityExport.push('');
756
+ if (yield { type: 'entity', content: currentEntityExport.join('\n') }) {
757
+ return;
475
758
  }
476
759
  }
477
760
  }
478
- return;
479
761
  }
480
- makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = []) {
762
+ /**
763
+ * Generate the PostgreSQL query.
764
+ * @param options The options array.
765
+ * @param formattedSelectors The formatted selector array.
766
+ * @param etype
767
+ * @param count Used to track internal params.
768
+ * @param params Used to store internal params.
769
+ * @param subquery Whether only a subquery should be returned.
770
+ * @returns The SQL query.
771
+ */
772
+ makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = [], guidSelector = undefined, guidExplicitSelector = undefined) {
481
773
  if (typeof options.class?.alterOptions === 'function') {
482
774
  options = options.class.alterOptions(options);
483
775
  }
484
776
  const eTable = `e${tableSuffix}`;
485
777
  const dTable = `d${tableSuffix}`;
486
- const cTable = `c${tableSuffix}`;
487
778
  const fTable = `f${tableSuffix}`;
488
779
  const ieTable = `ie${tableSuffix}`;
489
780
  const countTable = `count${tableSuffix}`;
490
- const sort = options.sort ?? 'cdate';
491
- const queryParts = this.iterateSelectorsForQuery(formattedSelectors, (key, value, typeIsOr, typeIsNot) => {
781
+ const sTable = `s${tableSuffix}`;
782
+ const sort = options.sort === undefined ? 'cdate' : options.sort;
783
+ const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => {
492
784
  const clauseNot = key.startsWith('!');
493
785
  let curQuery = '';
494
786
  for (const curValue of value) {
@@ -501,7 +793,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
501
793
  }
502
794
  const guid = `param${++count.i}`;
503
795
  curQuery +=
504
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
796
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
505
797
  ieTable +
506
798
  '."guid"=decode(@' +
507
799
  guid +
@@ -517,7 +809,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
517
809
  }
518
810
  const tag = `param${++count.i}`;
519
811
  curQuery +=
520
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
812
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
521
813
  '@' +
522
814
  tag +
523
815
  ' <@ ie."tags"';
@@ -530,17 +822,38 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
530
822
  if (curQuery) {
531
823
  curQuery += typeIsOr ? ' OR ' : ' AND ';
532
824
  }
533
- const name = `param${++count.i}`;
534
- curQuery +=
535
- ieTable +
536
- '."guid" ' +
537
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
538
- 'IN (SELECT "guid" FROM ' +
539
- PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
540
- ' WHERE "name"=@' +
541
- name +
542
- ')';
543
- params[name] = curVar;
825
+ if (curVar === 'cdate' ||
826
+ curVar === 'mdate' ||
827
+ (this.nymph.tilmeld != null &&
828
+ (curVar === 'user' ||
829
+ curVar === 'group' ||
830
+ curVar === 'acUser' ||
831
+ curVar === 'acGroup' ||
832
+ curVar === 'acOther' ||
833
+ curVar === 'acRead' ||
834
+ curVar === 'acWrite' ||
835
+ curVar === 'acFull'))) {
836
+ curQuery +=
837
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
838
+ '(' +
839
+ ieTable +
840
+ '.' +
841
+ PostgreSQLDriver.escape(curVar) +
842
+ ' IS NOT NULL)';
843
+ }
844
+ else {
845
+ const name = `param${++count.i}`;
846
+ curQuery +=
847
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
848
+ 'EXISTS (SELECT "guid" FROM ' +
849
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
850
+ ' WHERE "guid"=' +
851
+ ieTable +
852
+ '."guid" AND "name"=@' +
853
+ name +
854
+ ')';
855
+ params[name] = curVar;
856
+ }
544
857
  }
545
858
  break;
546
859
  case 'truthy':
@@ -549,30 +862,34 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
549
862
  if (curQuery) {
550
863
  curQuery += typeIsOr ? ' OR ' : ' AND ';
551
864
  }
552
- if (curVar === 'cdate') {
553
- curQuery +=
554
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
555
- '(' +
556
- ieTable +
557
- '."cdate" NOT NULL)';
558
- break;
559
- }
560
- else if (curVar === 'mdate') {
865
+ if (curVar === 'cdate' ||
866
+ curVar === 'mdate' ||
867
+ (this.nymph.tilmeld != null &&
868
+ (curVar === 'user' ||
869
+ curVar === 'group' ||
870
+ curVar === 'acUser' ||
871
+ curVar === 'acGroup' ||
872
+ curVar === 'acOther' ||
873
+ curVar === 'acRead' ||
874
+ curVar === 'acWrite' ||
875
+ curVar === 'acFull'))) {
561
876
  curQuery +=
562
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
877
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
563
878
  '(' +
564
879
  ieTable +
565
- '."mdate" NOT NULL)';
566
- break;
880
+ '.' +
881
+ PostgreSQLDriver.escape(curVar) +
882
+ ' IS NOT NULL)';
567
883
  }
568
884
  else {
569
885
  const name = `param${++count.i}`;
570
886
  curQuery +=
571
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
887
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
888
+ 'EXISTS (SELECT "guid" FROM ' +
889
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
890
+ ' WHERE "guid"=' +
572
891
  ieTable +
573
- '."guid" IN (SELECT "guid" FROM ' +
574
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
575
- ' WHERE "name"=@' +
892
+ '."guid" AND "name"=@' +
576
893
  name +
577
894
  ' AND "truthy"=TRUE)';
578
895
  params[name] = curVar;
@@ -581,35 +898,63 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
581
898
  break;
582
899
  case 'equal':
583
900
  case '!equal':
584
- if (curValue[0] === 'cdate') {
901
+ if (curValue[0] === 'cdate' ||
902
+ curValue[0] === 'mdate' ||
903
+ (this.nymph.tilmeld != null &&
904
+ (curValue[0] === 'acUser' ||
905
+ curValue[0] === 'acGroup' ||
906
+ curValue[0] === 'acOther'))) {
585
907
  if (curQuery) {
586
908
  curQuery += typeIsOr ? ' OR ' : ' AND ';
587
909
  }
588
- const cdate = `param${++count.i}`;
910
+ const value = `param${++count.i}`;
589
911
  curQuery +=
590
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
912
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
591
913
  ieTable +
592
- '."cdate"=@' +
593
- cdate;
594
- params[cdate] = isNaN(Number(curValue[1]))
914
+ '.' +
915
+ PostgreSQLDriver.escape(curValue[0]) +
916
+ '=@' +
917
+ value;
918
+ params[value] = isNaN(Number(curValue[1]))
595
919
  ? null
596
920
  : Number(curValue[1]);
597
- break;
598
921
  }
599
- else if (curValue[0] === 'mdate') {
922
+ else if (this.nymph.tilmeld != null &&
923
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
600
924
  if (curQuery) {
601
925
  curQuery += typeIsOr ? ' OR ' : ' AND ';
602
926
  }
603
- const mdate = `param${++count.i}`;
927
+ const value = `param${++count.i}`;
604
928
  curQuery +=
605
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
929
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
606
930
  ieTable +
607
- '."mdate"=@' +
608
- mdate;
609
- params[mdate] = isNaN(Number(curValue[1]))
610
- ? null
611
- : Number(curValue[1]);
612
- break;
931
+ '.' +
932
+ PostgreSQLDriver.escape(curValue[0]) +
933
+ '=decode(@' +
934
+ value +
935
+ ", 'hex')";
936
+ params[value] = `${curValue[1]}`;
937
+ }
938
+ else if (this.nymph.tilmeld != null &&
939
+ (curValue[0] === 'acRead' ||
940
+ curValue[0] === 'acWrite' ||
941
+ curValue[0] === 'acFull')) {
942
+ if (curQuery) {
943
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
944
+ }
945
+ const value = `param${++count.i}`;
946
+ curQuery +=
947
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
948
+ ieTable +
949
+ '.' +
950
+ PostgreSQLDriver.escape(curValue[0]) +
951
+ '=' +
952
+ !curValue[1]?.length
953
+ ? '@' + value
954
+ : "array(SELECT decode(n, 'hex') FROM unnest(@" +
955
+ value +
956
+ '::text[]) AS n)';
957
+ params[value] = Array.isArray(curValue[1]) ? curValue[1] : '';
613
958
  }
614
959
  else if (typeof curValue[1] === 'number') {
615
960
  if (curQuery) {
@@ -618,11 +963,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
618
963
  const name = `param${++count.i}`;
619
964
  const value = `param${++count.i}`;
620
965
  curQuery +=
621
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
966
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
967
+ 'EXISTS (SELECT "guid" FROM ' +
968
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
969
+ ' WHERE "guid"=' +
622
970
  ieTable +
623
- '."guid" IN (SELECT "guid" FROM ' +
624
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
625
- ' WHERE "name"=@' +
971
+ '."guid" AND "name"=@' +
626
972
  name +
627
973
  ' AND "number"=@' +
628
974
  value +
@@ -637,17 +983,20 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
637
983
  const name = `param${++count.i}`;
638
984
  const value = `param${++count.i}`;
639
985
  curQuery +=
640
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
986
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
987
+ 'EXISTS (SELECT "guid" FROM ' +
988
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
989
+ ' WHERE "guid"=' +
641
990
  ieTable +
642
- '."guid" IN (SELECT "guid" FROM ' +
643
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
644
- ' WHERE "name"=@' +
991
+ '."guid" AND "name"=@' +
645
992
  name +
646
- ' AND "string"=@' +
647
- value +
993
+ ' AND "string"=' +
994
+ (curValue[1].length < 512
995
+ ? 'LEFT(@' + value + ', 512)'
996
+ : '@' + value) +
648
997
  ')';
649
998
  params[name] = curValue[0];
650
- params[value] = curValue[1];
999
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
651
1000
  }
652
1001
  else {
653
1002
  if (curQuery) {
@@ -664,138 +1013,272 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
664
1013
  const name = `param${++count.i}`;
665
1014
  const value = `param${++count.i}`;
666
1015
  curQuery +=
667
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
668
- ieTable +
669
- '."guid" IN (SELECT "guid" FROM ' +
1016
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1017
+ 'EXISTS (SELECT "guid" FROM ' +
670
1018
  PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
671
- ' WHERE "name"=@' +
1019
+ ' WHERE "guid"=' +
1020
+ ieTable +
1021
+ '."guid" AND "name"=@' +
672
1022
  name +
673
- ' AND "value"=@' +
1023
+ ' AND "json"=@' +
674
1024
  value +
675
1025
  ')';
676
1026
  params[name] = curValue[0];
677
- params[value] = svalue;
1027
+ params[value] = PostgreSQLDriver.escapeNullSequences(svalue);
678
1028
  }
679
1029
  break;
680
1030
  case 'contain':
681
1031
  case '!contain':
682
- if (curValue[0] === 'cdate') {
1032
+ if (curValue[0] === 'cdate' ||
1033
+ curValue[0] === 'mdate' ||
1034
+ (this.nymph.tilmeld != null &&
1035
+ (curValue[0] === 'acUser' ||
1036
+ curValue[0] === 'acGroup' ||
1037
+ curValue[0] === 'acOther'))) {
683
1038
  if (curQuery) {
684
1039
  curQuery += typeIsOr ? ' OR ' : ' AND ';
685
1040
  }
686
- const cdate = `param${++count.i}`;
1041
+ const value = `param${++count.i}`;
687
1042
  curQuery +=
688
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1043
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
689
1044
  ieTable +
690
- '."cdate"=' +
691
- cdate;
692
- params[cdate] = isNaN(Number(curValue[1]))
1045
+ '.' +
1046
+ PostgreSQLDriver.escape(curValue[0]) +
1047
+ '=@' +
1048
+ value;
1049
+ params[value] = isNaN(Number(curValue[1]))
693
1050
  ? null
694
1051
  : Number(curValue[1]);
695
- break;
696
1052
  }
697
- else if (curValue[0] === 'mdate') {
1053
+ else if (this.nymph.tilmeld != null &&
1054
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
698
1055
  if (curQuery) {
699
1056
  curQuery += typeIsOr ? ' OR ' : ' AND ';
700
1057
  }
701
- const mdate = `param${++count.i}`;
1058
+ const value = `param${++count.i}`;
702
1059
  curQuery +=
703
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1060
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
704
1061
  ieTable +
705
- '."mdate"=' +
706
- mdate;
707
- params[mdate] = isNaN(Number(curValue[1]))
708
- ? null
709
- : Number(curValue[1]);
710
- break;
1062
+ '.' +
1063
+ PostgreSQLDriver.escape(curValue[0]) +
1064
+ '=decode(@' +
1065
+ value +
1066
+ ", 'hex')";
1067
+ params[value] = `${curValue[1]}`;
1068
+ }
1069
+ else if (this.nymph.tilmeld != null &&
1070
+ (curValue[0] === 'acRead' ||
1071
+ curValue[0] === 'acWrite' ||
1072
+ curValue[0] === 'acFull')) {
1073
+ if (curQuery) {
1074
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
1075
+ }
1076
+ const value = `param${++count.i}`;
1077
+ curQuery +=
1078
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1079
+ 'decode(@' +
1080
+ value +
1081
+ ", 'hex')=ANY(" +
1082
+ ieTable +
1083
+ '.' +
1084
+ PostgreSQLDriver.escape(curValue[0]) +
1085
+ ')';
1086
+ params[value] = `${curValue[1]}`;
711
1087
  }
712
1088
  else {
713
1089
  if (curQuery) {
714
1090
  curQuery += typeIsOr ? ' OR ' : ' AND ';
715
1091
  }
716
1092
  let svalue;
717
- let stringValue;
718
1093
  if (curValue[1] instanceof Object &&
719
1094
  typeof curValue[1].toReference === 'function') {
720
1095
  svalue = JSON.stringify(curValue[1].toReference());
721
- stringValue = `${curValue[1].toReference()}`;
722
1096
  }
723
- else {
1097
+ else if (typeof curValue[1] === 'string' ||
1098
+ typeof curValue[1] === 'number') {
724
1099
  svalue = JSON.stringify(curValue[1]);
725
- stringValue = `${curValue[1]}`;
1100
+ }
1101
+ else {
1102
+ svalue = JSON.stringify([curValue[1]]);
726
1103
  }
727
1104
  const name = `param${++count.i}`;
728
1105
  const value = `param${++count.i}`;
729
- if (typeof curValue[1] === 'string') {
730
- const stringParam = `param${++count.i}`;
1106
+ curQuery +=
1107
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1108
+ 'EXISTS (SELECT "guid" FROM ' +
1109
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1110
+ ' WHERE "guid"=' +
1111
+ ieTable +
1112
+ '."guid" AND "name"=@' +
1113
+ name +
1114
+ ' AND "json" @> @' +
1115
+ value +
1116
+ ')';
1117
+ params[name] = curValue[0];
1118
+ params[value] = PostgreSQLDriver.escapeNullSequences(svalue);
1119
+ }
1120
+ break;
1121
+ case 'search':
1122
+ case '!search':
1123
+ if (curValue[0] === 'cdate' ||
1124
+ curValue[0] === 'mdate' ||
1125
+ (this.nymph.tilmeld != null &&
1126
+ (curValue[0] === 'user' ||
1127
+ curValue[0] === 'group' ||
1128
+ curValue[0] === 'acUser' ||
1129
+ curValue[0] === 'acGroup' ||
1130
+ curValue[0] === 'acOther' ||
1131
+ curValue[0] === 'acRead' ||
1132
+ curValue[0] === 'acWrite' ||
1133
+ curValue[0] === 'acFull'))) {
1134
+ if (curQuery) {
1135
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
1136
+ }
1137
+ curQuery +=
1138
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(FALSE)';
1139
+ }
1140
+ else {
1141
+ if (curQuery) {
1142
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
1143
+ }
1144
+ const parsedFTSQuery = this.tokenizer.parseSearchQuery(curValue[1]);
1145
+ if (!parsedFTSQuery.length) {
731
1146
  curQuery +=
732
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
733
- '(' +
1147
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(FALSE)';
1148
+ }
1149
+ else {
1150
+ const name = `param${++count.i}`;
1151
+ const queryPartToken = (term) => {
1152
+ const value = `param${++count.i}`;
1153
+ params[value] = term.token;
1154
+ return ('EXISTS (SELECT "guid" FROM ' +
1155
+ PostgreSQLDriver.escape(this.prefix + 'tokens_' + etype) +
1156
+ ' WHERE "guid"=' +
734
1157
  ieTable +
735
- '."guid" IN (SELECT "guid" FROM ' +
736
- PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
737
- ' WHERE "name"=@' +
1158
+ '."guid" AND "name"=@' +
738
1159
  name +
739
- ' AND position(@' +
1160
+ ' AND "token"=@' +
740
1161
  value +
741
- ' IN "value")>0) OR ' +
1162
+ (term.nostemmed ? ' AND "stem"=FALSE' : '') +
1163
+ ')');
1164
+ };
1165
+ const queryPartSeries = (series) => {
1166
+ const tokenTableSuffix = makeTableSuffix();
1167
+ const tokenParts = series.tokens.map((token, i) => {
1168
+ const value = `param${++count.i}`;
1169
+ params[value] = token.token;
1170
+ return {
1171
+ fromClause: i === 0
1172
+ ? 'FROM ' +
1173
+ PostgreSQLDriver.escape(this.prefix + 'tokens_' + etype) +
1174
+ ' t' +
1175
+ tokenTableSuffix +
1176
+ '0'
1177
+ : 'JOIN ' +
1178
+ PostgreSQLDriver.escape(this.prefix + 'tokens_' + etype) +
1179
+ ' t' +
1180
+ tokenTableSuffix +
1181
+ i +
1182
+ ' ON t' +
1183
+ tokenTableSuffix +
1184
+ i +
1185
+ '."guid" = t' +
1186
+ tokenTableSuffix +
1187
+ '0."guid" AND t' +
1188
+ tokenTableSuffix +
1189
+ i +
1190
+ '."name" = t' +
1191
+ tokenTableSuffix +
1192
+ '0."name" AND t' +
1193
+ tokenTableSuffix +
1194
+ i +
1195
+ '."position" = t' +
1196
+ tokenTableSuffix +
1197
+ '0."position" + ' +
1198
+ i,
1199
+ whereClause: 't' +
1200
+ tokenTableSuffix +
1201
+ i +
1202
+ '."token"=@' +
1203
+ value +
1204
+ (token.nostemmed
1205
+ ? ' AND t' + tokenTableSuffix + i + '."stem"=FALSE'
1206
+ : ''),
1207
+ };
1208
+ });
1209
+ return ('EXISTS (SELECT t' +
1210
+ tokenTableSuffix +
1211
+ '0."guid" ' +
1212
+ tokenParts.map((part) => part.fromClause).join(' ') +
1213
+ ' WHERE t' +
1214
+ tokenTableSuffix +
1215
+ '0."guid"=' +
742
1216
  ieTable +
743
- '."guid" IN (SELECT "guid" FROM ' +
744
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
745
- ' WHERE "name"=@' +
1217
+ '."guid" AND t' +
1218
+ tokenTableSuffix +
1219
+ '0."name"=@' +
746
1220
  name +
747
- ' AND "string"=@' +
748
- stringParam +
749
- '))';
750
- params[stringParam] = stringValue;
751
- }
752
- else {
1221
+ ' AND ' +
1222
+ tokenParts.map((part) => part.whereClause).join(' AND ') +
1223
+ ')');
1224
+ };
1225
+ const queryPartTerm = (term) => {
1226
+ if (term.type === 'series') {
1227
+ return queryPartSeries(term);
1228
+ }
1229
+ else if (term.type === 'not') {
1230
+ return 'NOT ' + queryPartTerm(term.operand);
1231
+ }
1232
+ else if (term.type === 'or') {
1233
+ let queryParts = [];
1234
+ for (let operand of term.operands) {
1235
+ queryParts.push(queryPartTerm(operand));
1236
+ }
1237
+ return '(' + queryParts.join(' OR ') + ')';
1238
+ }
1239
+ return queryPartToken(term);
1240
+ };
1241
+ // Run through the query and add terms.
1242
+ let termStrings = [];
1243
+ for (let term of parsedFTSQuery) {
1244
+ termStrings.push(queryPartTerm(term));
1245
+ }
753
1246
  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)';
1247
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1248
+ '(' +
1249
+ termStrings.join(' AND ') +
1250
+ ')';
1251
+ params[name] = curValue[0];
763
1252
  }
764
- params[name] = curValue[0];
765
- params[value] = svalue;
766
1253
  }
767
1254
  break;
768
1255
  case 'match':
769
1256
  case '!match':
770
- if (curValue[0] === 'cdate') {
771
- if (curQuery) {
772
- curQuery += typeIsOr ? ' OR ' : ' AND ';
773
- }
774
- const cdate = `param${++count.i}`;
775
- curQuery +=
776
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
777
- '(' +
778
- ieTable +
779
- '."cdate" ~ @' +
780
- cdate +
781
- ')';
782
- params[cdate] = curValue[1];
783
- break;
784
- }
785
- else if (curValue[0] === 'mdate') {
1257
+ if (curValue[0] === 'cdate' ||
1258
+ curValue[0] === 'mdate' ||
1259
+ (this.nymph.tilmeld != null &&
1260
+ (curValue[0] === 'user' ||
1261
+ curValue[0] === 'group' ||
1262
+ curValue[0] === 'acUser' ||
1263
+ curValue[0] === 'acGroup' ||
1264
+ curValue[0] === 'acOther' ||
1265
+ curValue[0] === 'acRead' ||
1266
+ curValue[0] === 'acWrite' ||
1267
+ curValue[0] === 'acFull'))) {
786
1268
  if (curQuery) {
787
1269
  curQuery += typeIsOr ? ' OR ' : ' AND ';
788
1270
  }
789
- const mdate = `param${++count.i}`;
1271
+ const value = `param${++count.i}`;
790
1272
  curQuery +=
791
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1273
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
792
1274
  '(' +
793
1275
  ieTable +
794
- '."mdate" ~ @' +
795
- mdate +
1276
+ '.' +
1277
+ PostgreSQLDriver.escape(curValue[0]) +
1278
+ ' ~ @' +
1279
+ value +
796
1280
  ')';
797
- params[mdate] = curValue[1];
798
- break;
1281
+ params[value] = curValue[1];
799
1282
  }
800
1283
  else {
801
1284
  if (curQuery) {
@@ -804,50 +1287,47 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
804
1287
  const name = `param${++count.i}`;
805
1288
  const value = `param${++count.i}`;
806
1289
  curQuery +=
807
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1290
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1291
+ 'EXISTS (SELECT "guid" FROM ' +
1292
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1293
+ ' WHERE "guid"=' +
808
1294
  ieTable +
809
- '."guid" IN (SELECT "guid" FROM ' +
810
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
811
- ' WHERE "name"=@' +
1295
+ '."guid" AND "name"=@' +
812
1296
  name +
813
1297
  ' AND "string" ~ @' +
814
1298
  value +
815
1299
  ')';
816
1300
  params[name] = curValue[0];
817
- params[value] = curValue[1];
1301
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
818
1302
  }
819
1303
  break;
820
1304
  case 'imatch':
821
1305
  case '!imatch':
822
- if (curValue[0] === 'cdate') {
1306
+ if (curValue[0] === 'cdate' ||
1307
+ curValue[0] === 'mdate' ||
1308
+ (this.nymph.tilmeld != null &&
1309
+ (curValue[0] === 'user' ||
1310
+ curValue[0] === 'group' ||
1311
+ curValue[0] === 'acUser' ||
1312
+ curValue[0] === 'acGroup' ||
1313
+ curValue[0] === 'acOther' ||
1314
+ curValue[0] === 'acRead' ||
1315
+ curValue[0] === 'acWrite' ||
1316
+ curValue[0] === 'acFull'))) {
823
1317
  if (curQuery) {
824
1318
  curQuery += typeIsOr ? ' OR ' : ' AND ';
825
1319
  }
826
- const cdate = `param${++count.i}`;
827
- curQuery +=
828
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
829
- '(' +
830
- ieTable +
831
- '."cdate" ~* @' +
832
- cdate +
833
- ')';
834
- params[cdate] = curValue[1];
835
- break;
836
- }
837
- else if (curValue[0] === 'mdate') {
838
- if (curQuery) {
839
- curQuery += typeIsOr ? ' OR ' : ' AND ';
840
- }
841
- const mdate = `param${++count.i}`;
1320
+ const value = `param${++count.i}`;
842
1321
  curQuery +=
843
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1322
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
844
1323
  '(' +
845
1324
  ieTable +
846
- '."mdate" ~* @' +
847
- mdate +
1325
+ '.' +
1326
+ PostgreSQLDriver.escape(curValue[0]) +
1327
+ ' ~* @' +
1328
+ value +
848
1329
  ')';
849
- params[mdate] = curValue[1];
850
- break;
1330
+ params[value] = curValue[1];
851
1331
  }
852
1332
  else {
853
1333
  if (curQuery) {
@@ -856,50 +1336,47 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
856
1336
  const name = `param${++count.i}`;
857
1337
  const value = `param${++count.i}`;
858
1338
  curQuery +=
859
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1339
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1340
+ 'EXISTS (SELECT "guid" FROM ' +
1341
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1342
+ ' WHERE "guid"=' +
860
1343
  ieTable +
861
- '."guid" IN (SELECT "guid" FROM ' +
862
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
863
- ' WHERE "name"=@' +
1344
+ '."guid" AND "name"=@' +
864
1345
  name +
865
1346
  ' AND "string" ~* @' +
866
1347
  value +
867
1348
  ')';
868
1349
  params[name] = curValue[0];
869
- params[value] = curValue[1];
1350
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
870
1351
  }
871
1352
  break;
872
1353
  case 'like':
873
1354
  case '!like':
874
- if (curValue[0] === 'cdate') {
875
- if (curQuery) {
876
- curQuery += typeIsOr ? ' OR ' : ' AND ';
877
- }
878
- const cdate = `param${++count.i}`;
879
- curQuery +=
880
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
881
- '(' +
882
- ieTable +
883
- '."cdate" LIKE @' +
884
- cdate +
885
- ')';
886
- params[cdate] = curValue[1];
887
- break;
888
- }
889
- else if (curValue[0] === 'mdate') {
1355
+ if (curValue[0] === 'cdate' ||
1356
+ curValue[0] === 'mdate' ||
1357
+ (this.nymph.tilmeld != null &&
1358
+ (curValue[0] === 'user' ||
1359
+ curValue[0] === 'group' ||
1360
+ curValue[0] === 'acUser' ||
1361
+ curValue[0] === 'acGroup' ||
1362
+ curValue[0] === 'acOther' ||
1363
+ curValue[0] === 'acRead' ||
1364
+ curValue[0] === 'acWrite' ||
1365
+ curValue[0] === 'acFull'))) {
890
1366
  if (curQuery) {
891
1367
  curQuery += typeIsOr ? ' OR ' : ' AND ';
892
1368
  }
893
- const mdate = `param${++count.i}`;
1369
+ const value = `param${++count.i}`;
894
1370
  curQuery +=
895
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1371
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
896
1372
  '(' +
897
1373
  ieTable +
898
- '."mdate" LIKE @' +
899
- mdate +
1374
+ '.' +
1375
+ PostgreSQLDriver.escape(curValue[0]) +
1376
+ ' LIKE @' +
1377
+ value +
900
1378
  ')';
901
- params[mdate] = curValue[1];
902
- break;
1379
+ params[value] = curValue[1];
903
1380
  }
904
1381
  else {
905
1382
  if (curQuery) {
@@ -908,102 +1385,96 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
908
1385
  const name = `param${++count.i}`;
909
1386
  const value = `param${++count.i}`;
910
1387
  curQuery +=
911
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1388
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1389
+ 'EXISTS (SELECT "guid" FROM ' +
1390
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1391
+ ' WHERE "guid"=' +
912
1392
  ieTable +
913
- '."guid" IN (SELECT "guid" FROM ' +
914
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
915
- ' WHERE "name"=@' +
1393
+ '."guid" AND "name"=@' +
916
1394
  name +
917
1395
  ' AND "string" LIKE @' +
918
1396
  value +
919
1397
  ')';
920
1398
  params[name] = curValue[0];
921
- params[value] = curValue[1];
1399
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
922
1400
  }
923
1401
  break;
924
1402
  case 'ilike':
925
1403
  case '!ilike':
926
- if (curValue[0] === 'cdate') {
1404
+ if (curValue[0] === 'cdate' ||
1405
+ curValue[0] === 'mdate' ||
1406
+ (this.nymph.tilmeld != null &&
1407
+ (curValue[0] === 'user' ||
1408
+ curValue[0] === 'group' ||
1409
+ curValue[0] === 'acUser' ||
1410
+ curValue[0] === 'acGroup' ||
1411
+ curValue[0] === 'acOther' ||
1412
+ curValue[0] === 'acRead' ||
1413
+ curValue[0] === 'acWrite' ||
1414
+ curValue[0] === 'acFull'))) {
927
1415
  if (curQuery) {
928
1416
  curQuery += typeIsOr ? ' OR ' : ' AND ';
929
1417
  }
930
- const cdate = `param${++count.i}`;
1418
+ const value = `param${++count.i}`;
931
1419
  curQuery +=
932
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1420
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
933
1421
  '(' +
934
1422
  ieTable +
935
- '."cdate" ILIKE @' +
936
- cdate +
1423
+ '.' +
1424
+ PostgreSQLDriver.escape(curValue[0]) +
1425
+ ' ILIKE @' +
1426
+ value +
937
1427
  ')';
938
- params[cdate] = curValue[1];
939
- break;
1428
+ params[value] = curValue[1];
940
1429
  }
941
- else if (curValue[0] === 'mdate') {
1430
+ else {
942
1431
  if (curQuery) {
943
1432
  curQuery += typeIsOr ? ' OR ' : ' AND ';
944
1433
  }
945
- const mdate = `param${++count.i}`;
1434
+ const name = `param${++count.i}`;
1435
+ const value = `param${++count.i}`;
946
1436
  curQuery +=
947
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
948
- '(' +
949
- ieTable +
950
- '."mdate" ILIKE @' +
951
- mdate +
952
- ')';
953
- params[mdate] = curValue[1];
954
- break;
955
- }
956
- else {
957
- if (curQuery) {
958
- curQuery += typeIsOr ? ' OR ' : ' AND ';
959
- }
960
- const name = `param${++count.i}`;
961
- const value = `param${++count.i}`;
962
- curQuery +=
963
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1437
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1438
+ 'EXISTS (SELECT "guid" FROM ' +
1439
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1440
+ ' WHERE "guid"=' +
964
1441
  ieTable +
965
- '."guid" IN (SELECT "guid" FROM ' +
966
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
967
- ' WHERE "name"=@' +
1442
+ '."guid" AND "name"=@' +
968
1443
  name +
969
1444
  ' AND "string" ILIKE @' +
970
1445
  value +
971
1446
  ')';
972
1447
  params[name] = curValue[0];
973
- params[value] = curValue[1];
1448
+ params[value] = PostgreSQLDriver.escapeNulls(curValue[1]);
974
1449
  }
975
1450
  break;
976
1451
  case 'gt':
977
1452
  case '!gt':
978
- if (curValue[0] === 'cdate') {
979
- if (curQuery) {
980
- curQuery += typeIsOr ? ' OR ' : ' AND ';
981
- }
982
- const cdate = `param${++count.i}`;
983
- curQuery +=
984
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
985
- ieTable +
986
- '."cdate">@' +
987
- cdate;
988
- params[cdate] = isNaN(Number(curValue[1]))
989
- ? null
990
- : Number(curValue[1]);
991
- break;
992
- }
993
- else if (curValue[0] === 'mdate') {
1453
+ if (curValue[0] === 'cdate' ||
1454
+ curValue[0] === 'mdate' ||
1455
+ (this.nymph.tilmeld != null &&
1456
+ (curValue[0] === 'user' ||
1457
+ curValue[0] === 'group' ||
1458
+ curValue[0] === 'acUser' ||
1459
+ curValue[0] === 'acGroup' ||
1460
+ curValue[0] === 'acOther' ||
1461
+ curValue[0] === 'acRead' ||
1462
+ curValue[0] === 'acWrite' ||
1463
+ curValue[0] === 'acFull'))) {
994
1464
  if (curQuery) {
995
1465
  curQuery += typeIsOr ? ' OR ' : ' AND ';
996
1466
  }
997
- const mdate = `param${++count.i}`;
1467
+ const value = `param${++count.i}`;
998
1468
  curQuery +=
999
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1469
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1000
1470
  ieTable +
1001
- '."mdate">@' +
1002
- mdate;
1003
- params[mdate] = isNaN(Number(curValue[1]))
1471
+ '.' +
1472
+ PostgreSQLDriver.escape(curValue[0]) +
1473
+ '>@' +
1474
+ value;
1475
+ params[value] = isNaN(Number(curValue[1]))
1004
1476
  ? null
1005
1477
  : Number(curValue[1]);
1006
- break;
1007
1478
  }
1008
1479
  else {
1009
1480
  if (curQuery) {
@@ -1012,11 +1483,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1012
1483
  const name = `param${++count.i}`;
1013
1484
  const value = `param${++count.i}`;
1014
1485
  curQuery +=
1015
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1486
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1487
+ 'EXISTS (SELECT "guid" FROM ' +
1488
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1489
+ ' WHERE "guid"=' +
1016
1490
  ieTable +
1017
- '."guid" IN (SELECT "guid" FROM ' +
1018
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1019
- ' WHERE "name"=@' +
1491
+ '."guid" AND "name"=@' +
1020
1492
  name +
1021
1493
  ' AND "number">@' +
1022
1494
  value +
@@ -1029,35 +1501,31 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1029
1501
  break;
1030
1502
  case 'gte':
1031
1503
  case '!gte':
1032
- if (curValue[0] === 'cdate') {
1504
+ if (curValue[0] === 'cdate' ||
1505
+ curValue[0] === 'mdate' ||
1506
+ (this.nymph.tilmeld != null &&
1507
+ (curValue[0] === 'user' ||
1508
+ curValue[0] === 'group' ||
1509
+ curValue[0] === 'acUser' ||
1510
+ curValue[0] === 'acGroup' ||
1511
+ curValue[0] === 'acOther' ||
1512
+ curValue[0] === 'acRead' ||
1513
+ curValue[0] === 'acWrite' ||
1514
+ curValue[0] === 'acFull'))) {
1033
1515
  if (curQuery) {
1034
1516
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1035
1517
  }
1036
- const cdate = `param${++count.i}`;
1037
- curQuery +=
1038
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1039
- ieTable +
1040
- '."cdate">=@' +
1041
- cdate;
1042
- params[cdate] = isNaN(Number(curValue[1]))
1043
- ? null
1044
- : Number(curValue[1]);
1045
- break;
1046
- }
1047
- else if (curValue[0] === 'mdate') {
1048
- if (curQuery) {
1049
- curQuery += typeIsOr ? ' OR ' : ' AND ';
1050
- }
1051
- const mdate = `param${++count.i}`;
1518
+ const value = `param${++count.i}`;
1052
1519
  curQuery +=
1053
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1520
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1054
1521
  ieTable +
1055
- '."mdate">=@' +
1056
- mdate;
1057
- params[mdate] = isNaN(Number(curValue[1]))
1522
+ '.' +
1523
+ PostgreSQLDriver.escape(curValue[0]) +
1524
+ '>=@' +
1525
+ value;
1526
+ params[value] = isNaN(Number(curValue[1]))
1058
1527
  ? null
1059
1528
  : Number(curValue[1]);
1060
- break;
1061
1529
  }
1062
1530
  else {
1063
1531
  if (curQuery) {
@@ -1066,11 +1534,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1066
1534
  const name = `param${++count.i}`;
1067
1535
  const value = `param${++count.i}`;
1068
1536
  curQuery +=
1069
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1537
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1538
+ 'EXISTS (SELECT "guid" FROM ' +
1539
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1540
+ ' WHERE "guid"=' +
1070
1541
  ieTable +
1071
- '."guid" IN (SELECT "guid" FROM ' +
1072
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1073
- ' WHERE "name"=@' +
1542
+ '."guid" AND "name"=@' +
1074
1543
  name +
1075
1544
  ' AND "number">=@' +
1076
1545
  value +
@@ -1083,35 +1552,31 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1083
1552
  break;
1084
1553
  case 'lt':
1085
1554
  case '!lt':
1086
- if (curValue[0] === 'cdate') {
1087
- if (curQuery) {
1088
- curQuery += typeIsOr ? ' OR ' : ' AND ';
1089
- }
1090
- const cdate = `param${++count.i}`;
1091
- curQuery +=
1092
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1093
- ieTable +
1094
- '."cdate"<@' +
1095
- cdate;
1096
- params[cdate] = isNaN(Number(curValue[1]))
1097
- ? null
1098
- : Number(curValue[1]);
1099
- break;
1100
- }
1101
- else if (curValue[0] === 'mdate') {
1555
+ if (curValue[0] === 'cdate' ||
1556
+ curValue[0] === 'mdate' ||
1557
+ (this.nymph.tilmeld != null &&
1558
+ (curValue[0] === 'user' ||
1559
+ curValue[0] === 'group' ||
1560
+ curValue[0] === 'acUser' ||
1561
+ curValue[0] === 'acGroup' ||
1562
+ curValue[0] === 'acOther' ||
1563
+ curValue[0] === 'acRead' ||
1564
+ curValue[0] === 'acWrite' ||
1565
+ curValue[0] === 'acFull'))) {
1102
1566
  if (curQuery) {
1103
1567
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1104
1568
  }
1105
- const mdate = `param${++count.i}`;
1569
+ const value = `param${++count.i}`;
1106
1570
  curQuery +=
1107
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1571
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1108
1572
  ieTable +
1109
- '."mdate"<@' +
1110
- mdate;
1111
- params[mdate] = isNaN(Number(curValue[1]))
1573
+ '.' +
1574
+ PostgreSQLDriver.escape(curValue[0]) +
1575
+ '<@' +
1576
+ value;
1577
+ params[value] = isNaN(Number(curValue[1]))
1112
1578
  ? null
1113
1579
  : Number(curValue[1]);
1114
- break;
1115
1580
  }
1116
1581
  else {
1117
1582
  if (curQuery) {
@@ -1120,11 +1585,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1120
1585
  const name = `param${++count.i}`;
1121
1586
  const value = `param${++count.i}`;
1122
1587
  curQuery +=
1123
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1588
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1589
+ 'EXISTS (SELECT "guid" FROM ' +
1590
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1591
+ ' WHERE "guid"=' +
1124
1592
  ieTable +
1125
- '."guid" IN (SELECT "guid" FROM ' +
1126
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1127
- ' WHERE "name"=@' +
1593
+ '."guid" AND "name"=@' +
1128
1594
  name +
1129
1595
  ' AND "number"<@' +
1130
1596
  value +
@@ -1137,35 +1603,31 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1137
1603
  break;
1138
1604
  case 'lte':
1139
1605
  case '!lte':
1140
- if (curValue[0] === 'cdate') {
1606
+ if (curValue[0] === 'cdate' ||
1607
+ curValue[0] === 'mdate' ||
1608
+ (this.nymph.tilmeld != null &&
1609
+ (curValue[0] === 'user' ||
1610
+ curValue[0] === 'group' ||
1611
+ curValue[0] === 'acUser' ||
1612
+ curValue[0] === 'acGroup' ||
1613
+ curValue[0] === 'acOther' ||
1614
+ curValue[0] === 'acRead' ||
1615
+ curValue[0] === 'acWrite' ||
1616
+ curValue[0] === 'acFull'))) {
1141
1617
  if (curQuery) {
1142
1618
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1143
1619
  }
1144
- const cdate = `param${++count.i}`;
1145
- curQuery +=
1146
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1147
- ieTable +
1148
- '."cdate"<=@' +
1149
- cdate;
1150
- params[cdate] = isNaN(Number(curValue[1]))
1151
- ? null
1152
- : Number(curValue[1]);
1153
- break;
1154
- }
1155
- else if (curValue[0] === 'mdate') {
1156
- if (curQuery) {
1157
- curQuery += typeIsOr ? ' OR ' : ' AND ';
1158
- }
1159
- const mdate = `param${++count.i}`;
1620
+ const value = `param${++count.i}`;
1160
1621
  curQuery +=
1161
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1622
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1162
1623
  ieTable +
1163
- '."mdate"<=@' +
1164
- mdate;
1165
- params[mdate] = isNaN(Number(curValue[1]))
1624
+ '.' +
1625
+ PostgreSQLDriver.escape(curValue[0]) +
1626
+ '<=@' +
1627
+ value;
1628
+ params[value] = isNaN(Number(curValue[1]))
1166
1629
  ? null
1167
1630
  : Number(curValue[1]);
1168
- break;
1169
1631
  }
1170
1632
  else {
1171
1633
  if (curQuery) {
@@ -1174,11 +1636,12 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1174
1636
  const name = `param${++count.i}`;
1175
1637
  const value = `param${++count.i}`;
1176
1638
  curQuery +=
1177
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1639
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1640
+ 'EXISTS (SELECT "guid" FROM ' +
1641
+ PostgreSQLDriver.escape(this.prefix + 'data_' + etype) +
1642
+ ' WHERE "guid"=' +
1178
1643
  ieTable +
1179
- '."guid" IN (SELECT "guid" FROM ' +
1180
- PostgreSQLDriver.escape(this.prefix + 'comparisons_' + etype) +
1181
- ' WHERE "name"=@' +
1644
+ '."guid" AND "name"=@' +
1182
1645
  name +
1183
1646
  ' AND "number"<=@' +
1184
1647
  value +
@@ -1204,71 +1667,190 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1204
1667
  if (curQuery) {
1205
1668
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1206
1669
  }
1207
- const name = `param${++count.i}`;
1208
- const guid = `param${++count.i}`;
1209
- curQuery +=
1210
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1211
- ieTable +
1212
- '."guid" IN (SELECT "guid" FROM ' +
1213
- PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1214
- ' WHERE "name"=@' +
1215
- name +
1216
- ' AND "reference"=decode(@' +
1217
- guid +
1218
- ", 'hex'))";
1219
- params[name] = curValue[0];
1220
- params[guid] = curQguid;
1670
+ if (this.nymph.tilmeld != null &&
1671
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
1672
+ const guid = `param${++count.i}`;
1673
+ curQuery +=
1674
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1675
+ ieTable +
1676
+ '.' +
1677
+ PostgreSQLDriver.escape(curValue[0]) +
1678
+ '=decode(@' +
1679
+ guid +
1680
+ ", 'hex')";
1681
+ params[guid] = curQguid;
1682
+ }
1683
+ else if (this.nymph.tilmeld != null &&
1684
+ (curValue[0] === 'acRead' ||
1685
+ curValue[0] === 'acWrite' ||
1686
+ curValue[0] === 'acFull')) {
1687
+ const guid = `param${++count.i}`;
1688
+ curQuery +=
1689
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1690
+ 'decode(@' +
1691
+ guid +
1692
+ ", 'hex')=ANY(" +
1693
+ ieTable +
1694
+ '.' +
1695
+ PostgreSQLDriver.escape(curValue[0]) +
1696
+ ')';
1697
+ params[guid] = curQguid;
1698
+ }
1699
+ else {
1700
+ const name = `param${++count.i}`;
1701
+ const guid = `param${++count.i}`;
1702
+ curQuery +=
1703
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1704
+ 'EXISTS (SELECT "guid" FROM ' +
1705
+ PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1706
+ ' WHERE "guid"=' +
1707
+ ieTable +
1708
+ '."guid" AND "name"=@' +
1709
+ name +
1710
+ ' AND "reference"=decode(@' +
1711
+ guid +
1712
+ ", 'hex'))";
1713
+ params[name] = curValue[0];
1714
+ params[guid] = curQguid;
1715
+ }
1221
1716
  break;
1222
1717
  case 'selector':
1223
1718
  case '!selector':
1224
- const subquery = this.makeEntityQuery(options, [curValue], etype, count, params, true, tableSuffix, etypes);
1719
+ const innerquery = this.makeEntityQuery({ ...options, sort: null, limit: undefined }, [curValue], etype, count, params, true, tableSuffix, etypes);
1225
1720
  if (curQuery) {
1226
1721
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1227
1722
  }
1228
1723
  curQuery +=
1229
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1724
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1230
1725
  '(' +
1231
- subquery.query +
1726
+ innerquery.query +
1232
1727
  ')';
1233
1728
  break;
1234
1729
  case 'qref':
1235
1730
  case '!qref':
1731
+ const referenceTableSuffix = makeTableSuffix();
1236
1732
  const [qrefOptions, ...qrefSelectors] = curValue[1];
1237
1733
  const QrefEntityClass = qrefOptions.class;
1238
1734
  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);
1240
1735
  if (curQuery) {
1241
1736
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1242
1737
  }
1243
- const qrefName = `param${++count.i}`;
1244
- curQuery +=
1245
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1738
+ if (this.nymph.tilmeld != null &&
1739
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
1740
+ const qrefQuery = this.makeEntityQuery({
1741
+ ...qrefOptions,
1742
+ sort: qrefOptions.sort ?? null,
1743
+ return: 'guid',
1744
+ class: QrefEntityClass,
1745
+ }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' +
1746
+ referenceTableSuffix +
1747
+ '.' +
1748
+ PostgreSQLDriver.escape(curValue[0]));
1749
+ curQuery +=
1750
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1751
+ 'EXISTS (SELECT "guid" FROM ' +
1752
+ PostgreSQLDriver.escape(this.prefix + 'entities_' + etype) +
1753
+ ' r' +
1754
+ referenceTableSuffix +
1755
+ ' WHERE r' +
1756
+ referenceTableSuffix +
1757
+ '."guid"=' +
1758
+ ieTable +
1759
+ '."guid" AND EXISTS (' +
1760
+ qrefQuery.query +
1761
+ '))';
1762
+ }
1763
+ else if (this.nymph.tilmeld != null &&
1764
+ (curValue[0] === 'acRead' ||
1765
+ curValue[0] === 'acWrite' ||
1766
+ curValue[0] === 'acFull')) {
1767
+ const qrefQuery = this.makeEntityQuery({
1768
+ ...qrefOptions,
1769
+ sort: qrefOptions.sort ?? null,
1770
+ return: 'guid',
1771
+ class: QrefEntityClass,
1772
+ }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, undefined, (guid) => guid +
1773
+ '=ANY(' +
1246
1774
  ieTable +
1247
- '."guid" IN (SELECT "guid" FROM ' +
1248
- PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1249
- ' WHERE "name"=@' +
1250
- qrefName +
1251
- ' AND "reference" IN (' +
1252
- qrefQuery.query +
1253
- '))';
1254
- params[qrefName] = curValue[0];
1775
+ '.' +
1776
+ PostgreSQLDriver.escape(curValue[0]) +
1777
+ ')');
1778
+ curQuery +=
1779
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1780
+ 'EXISTS (' +
1781
+ qrefQuery.query +
1782
+ ')';
1783
+ }
1784
+ else {
1785
+ const qrefQuery = this.makeEntityQuery({
1786
+ ...qrefOptions,
1787
+ sort: qrefOptions.sort ?? null,
1788
+ return: 'guid',
1789
+ class: QrefEntityClass,
1790
+ }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' + referenceTableSuffix + '."reference"');
1791
+ const qrefName = `param${++count.i}`;
1792
+ curQuery +=
1793
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1794
+ 'EXISTS (SELECT "guid" FROM ' +
1795
+ PostgreSQLDriver.escape(this.prefix + 'references_' + etype) +
1796
+ ' r' +
1797
+ referenceTableSuffix +
1798
+ ' WHERE r' +
1799
+ referenceTableSuffix +
1800
+ '."guid"=' +
1801
+ ieTable +
1802
+ '."guid" AND r' +
1803
+ referenceTableSuffix +
1804
+ '."name"=@' +
1805
+ qrefName +
1806
+ ' AND EXISTS (' +
1807
+ qrefQuery.query +
1808
+ '))';
1809
+ params[qrefName] = curValue[0];
1810
+ }
1255
1811
  break;
1256
1812
  }
1257
1813
  }
1258
1814
  return curQuery;
1259
1815
  });
1260
1816
  let sortBy;
1261
- switch (sort) {
1262
- case 'mdate':
1263
- sortBy = '"mdate"';
1264
- break;
1265
- case 'cdate':
1266
- default:
1267
- sortBy = '"cdate"';
1268
- break;
1269
- }
1270
- if (options.reverse) {
1271
- sortBy += ' DESC';
1817
+ let sortByInner;
1818
+ let sortJoin = '';
1819
+ let sortJoinInner = '';
1820
+ const order = options.reverse ? ' DESC' : '';
1821
+ if (sort == null) {
1822
+ sortBy = '';
1823
+ sortByInner = '';
1824
+ }
1825
+ else {
1826
+ switch (sort) {
1827
+ case 'mdate':
1828
+ sortBy = `ORDER BY ${eTable}."mdate"${order}`;
1829
+ sortByInner = `ORDER BY ${ieTable}."mdate"${order}`;
1830
+ break;
1831
+ case 'cdate':
1832
+ sortBy = `ORDER BY ${eTable}."cdate"${order}`;
1833
+ sortByInner = `ORDER BY ${ieTable}."cdate"${order}`;
1834
+ break;
1835
+ default:
1836
+ const name = `param${++count.i}`;
1837
+ sortJoin = `LEFT JOIN (
1838
+ SELECT "guid", "string", "number"
1839
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'data_' + etype)}
1840
+ WHERE "name"=@${name}
1841
+ ORDER BY "number"${order}, "string"${order}
1842
+ ) ${sTable} ON ${eTable}."guid"=${sTable}."guid"`;
1843
+ sortJoinInner = `LEFT JOIN (
1844
+ SELECT "guid", "string", "number"
1845
+ FROM ${PostgreSQLDriver.escape(this.prefix + 'data_' + etype)}
1846
+ WHERE "name"=@${name}
1847
+ ORDER BY "number"${order}, "string"${order}
1848
+ ) ${sTable} ON ${ieTable}."guid"=${sTable}."guid"`;
1849
+ sortBy = `ORDER BY ${sTable}."number"${order}, ${sTable}."string"${order}`;
1850
+ sortByInner = sortBy;
1851
+ params[name] = sort;
1852
+ break;
1853
+ }
1272
1854
  }
1273
1855
  let query;
1274
1856
  if (queryParts.length) {
@@ -1285,18 +1867,23 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1285
1867
  offset = ` OFFSET ${Math.floor(isNaN(Number(options.offset)) ? 0 : Number(options.offset))}`;
1286
1868
  }
1287
1869
  const whereClause = queryParts.join(') AND (');
1870
+ const guidClause = guidExplicitSelector
1871
+ ? `${guidExplicitSelector(`${ieTable}."guid"`)} AND `
1872
+ : guidSelector
1873
+ ? `${ieTable}."guid"=${guidSelector} AND `
1874
+ : '';
1288
1875
  if (options.return === 'count') {
1289
1876
  if (limit || offset) {
1290
1877
  query = `SELECT COUNT(${countTable}."guid") AS "count" FROM (
1291
- SELECT COUNT(${ieTable}."guid") AS "guid"
1878
+ SELECT ${ieTable}."guid" AS "guid"
1292
1879
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1293
- WHERE (${whereClause})${limit}${offset}
1880
+ WHERE ${guidClause}(${whereClause})${limit}${offset}
1294
1881
  ) ${countTable}`;
1295
1882
  }
1296
1883
  else {
1297
1884
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1298
1885
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1299
- WHERE (${whereClause})`;
1886
+ WHERE ${guidClause}(${whereClause})`;
1300
1887
  }
1301
1888
  }
1302
1889
  else if (options.return === 'guid') {
@@ -1305,8 +1892,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1305
1892
  : `${ieTable}."guid"`;
1306
1893
  query = `SELECT ${guidColumn} AS "guid"
1307
1894
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1308
- WHERE (${whereClause})
1309
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1895
+ ${sortJoinInner}
1896
+ WHERE ${guidClause}(${whereClause})
1897
+ ${sortByInner ? sortByInner + ', ' : 'ORDER BY '}${ieTable}."guid"${limit}${offset}`;
1310
1898
  }
1311
1899
  else {
1312
1900
  query = `SELECT
@@ -1314,20 +1902,30 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1314
1902
  ${eTable}."tags",
1315
1903
  ${eTable}."cdate",
1316
1904
  ${eTable}."mdate",
1905
+ encode(${eTable}."user", 'hex') AS "user",
1906
+ encode(${eTable}."group", 'hex') AS "group",
1907
+ ${eTable}."acUser",
1908
+ ${eTable}."acGroup",
1909
+ ${eTable}."acOther",
1910
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acRead") AS n) as "acRead",
1911
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acWrite") AS n) as "acWrite",
1912
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acFull") AS n) as "acFull",
1317
1913
  ${dTable}."name",
1318
1914
  ${dTable}."value",
1319
- ${cTable}."string",
1320
- ${cTable}."number"
1915
+ ${dTable}."json",
1916
+ ${dTable}."string",
1917
+ ${dTable}."number"
1321
1918
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1322
1919
  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"
1920
+ ${sortJoin}
1324
1921
  INNER JOIN (
1325
1922
  SELECT ${ieTable}."guid"
1326
1923
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1327
- WHERE (${whereClause})
1328
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1924
+ ${sortJoinInner}
1925
+ WHERE ${guidClause}(${whereClause})
1926
+ ${sortByInner}${limit}${offset}
1329
1927
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1330
- ORDER BY ${eTable}.${sortBy}`;
1928
+ ${sortBy ? sortBy + ', ' : 'ORDER BY '}${eTable}."guid"`;
1331
1929
  }
1332
1930
  }
1333
1931
  }
@@ -1344,16 +1942,21 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1344
1942
  if ('offset' in options) {
1345
1943
  offset = ` OFFSET ${Math.floor(isNaN(Number(options.offset)) ? 0 : Number(options.offset))}`;
1346
1944
  }
1945
+ const guidClause = guidExplicitSelector
1946
+ ? ` WHERE ${guidExplicitSelector(`${ieTable}."guid"`)}`
1947
+ : guidSelector
1948
+ ? ` WHERE ${ieTable}."guid"=${guidSelector}`
1949
+ : '';
1347
1950
  if (options.return === 'count') {
1348
1951
  if (limit || offset) {
1349
1952
  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}
1953
+ SELECT ${ieTable}."guid" AS "guid"
1954
+ FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}${guidClause}${limit}${offset}
1352
1955
  ) ${countTable}`;
1353
1956
  }
1354
1957
  else {
1355
1958
  query = `SELECT COUNT(${ieTable}."guid") AS "count"
1356
- FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}`;
1959
+ FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}${guidClause}`;
1357
1960
  }
1358
1961
  }
1359
1962
  else if (options.return === 'guid') {
@@ -1362,7 +1965,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1362
1965
  : `${ieTable}."guid"`;
1363
1966
  query = `SELECT ${guidColumn} AS "guid"
1364
1967
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1365
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1968
+ ${sortJoinInner}
1969
+ ${guidClause}
1970
+ ${sortByInner ? sortByInner + ', ' : 'ORDER BY '}${ieTable}."guid"${limit}${offset}`;
1366
1971
  }
1367
1972
  else {
1368
1973
  if (limit || offset) {
@@ -1371,19 +1976,30 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1371
1976
  ${eTable}."tags",
1372
1977
  ${eTable}."cdate",
1373
1978
  ${eTable}."mdate",
1979
+ encode(${eTable}."user", 'hex') AS "user",
1980
+ encode(${eTable}."group", 'hex') AS "group",
1981
+ ${eTable}."acUser",
1982
+ ${eTable}."acGroup",
1983
+ ${eTable}."acOther",
1984
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acRead") AS n) as "acRead",
1985
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acWrite") AS n) as "acWrite",
1986
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acFull") AS n) as "acFull",
1374
1987
  ${dTable}."name",
1375
1988
  ${dTable}."value",
1376
- ${cTable}."string",
1377
- ${cTable}."number"
1989
+ ${dTable}."json",
1990
+ ${dTable}."string",
1991
+ ${dTable}."number"
1378
1992
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1379
1993
  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"
1994
+ ${sortJoin}
1381
1995
  INNER JOIN (
1382
1996
  SELECT ${ieTable}."guid"
1383
1997
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${ieTable}
1384
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1998
+ ${sortJoinInner}
1999
+ ${guidClause}
2000
+ ${sortByInner}${limit}${offset}
1385
2001
  ) ${fTable} ON ${eTable}."guid"=${fTable}."guid"
1386
- ORDER BY ${eTable}.${sortBy}`;
2002
+ ${sortBy ? sortBy + ', ' : 'ORDER BY '}${eTable}."guid"`;
1387
2003
  }
1388
2004
  else {
1389
2005
  query = `SELECT
@@ -1391,14 +2007,24 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1391
2007
  ${eTable}."tags",
1392
2008
  ${eTable}."cdate",
1393
2009
  ${eTable}."mdate",
2010
+ encode(${eTable}."user", 'hex') AS "user",
2011
+ encode(${eTable}."group", 'hex') AS "group",
2012
+ ${eTable}."acUser",
2013
+ ${eTable}."acGroup",
2014
+ ${eTable}."acOther",
2015
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acRead") AS n) as "acRead",
2016
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acWrite") AS n) as "acWrite",
2017
+ array(SELECT encode(n, 'hex') FROM unnest(${eTable}."acFull") AS n) as "acFull",
1394
2018
  ${dTable}."name",
1395
2019
  ${dTable}."value",
1396
- ${cTable}."string",
1397
- ${cTable}."number"
2020
+ ${dTable}."json",
2021
+ ${dTable}."string",
2022
+ ${dTable}."number"
1398
2023
  FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ${eTable}
1399
2024
  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}`;
2025
+ ${sortJoin}
2026
+ ${guidExplicitSelector ? `WHERE ${guidExplicitSelector(`${eTable}."guid"`)}` : guidSelector ? `WHERE ${eTable}."guid"=${guidSelector}` : ''}
2027
+ ${sortBy ? sortBy + ', ' : 'ORDER BY '}${eTable}."guid"`;
1402
2028
  }
1403
2029
  }
1404
2030
  }
@@ -1414,33 +2040,38 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1414
2040
  }
1415
2041
  performQuery(options, formattedSelectors, etype) {
1416
2042
  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]();
2043
+ const result = this.queryArray(query, { etypes, params }).then((val) => val[Symbol.iterator]());
1425
2044
  return {
1426
2045
  result,
1427
2046
  };
1428
2047
  }
1429
2048
  async getEntities(options = {}, ...selectors) {
1430
- const { result: resultPromise, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuery(options, formattedSelectors, etype), () => {
2049
+ const { result: resultPromise, process } = this.getEntitiesRowLike(
2050
+ // @ts-ignore: options is correct here.
2051
+ options, selectors, ({ options, selectors, etype }) => this.performQuery(options, selectors, etype), () => {
1431
2052
  const next = result.next();
1432
2053
  return next.done ? null : next.value;
1433
2054
  }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
1434
- tags: row.tags,
1435
- cdate: isNaN(Number(row.cdate)) ? null : Number(row.cdate),
1436
- mdate: isNaN(Number(row.mdate)) ? null : Number(row.mdate),
2055
+ tags: row.tags.filter((tag) => tag),
2056
+ cdate: isNaN(Number(row.cdate)) ? Date.now() : Number(row.cdate),
2057
+ mdate: isNaN(Number(row.mdate)) ? Date.now() : Number(row.mdate),
2058
+ user: row.user,
2059
+ group: row.group,
2060
+ acUser: row.acUser,
2061
+ acGroup: row.acGroup,
2062
+ acOther: row.acOther,
2063
+ acRead: row.acRead?.filter((guid) => guid) ?? [],
2064
+ acWrite: row.acWrite?.filter((guid) => guid) ?? [],
2065
+ acFull: row.acFull?.filter((guid) => guid) ?? [],
1437
2066
  }), (row) => ({
1438
2067
  name: row.name,
1439
2068
  svalue: row.value === 'N'
1440
2069
  ? JSON.stringify(Number(row.number))
1441
2070
  : row.value === 'S'
1442
- ? JSON.stringify(row.string)
1443
- : row.value,
2071
+ ? JSON.stringify(PostgreSQLDriver.unescapeNulls(row.string))
2072
+ : row.value === 'J'
2073
+ ? PostgreSQLDriver.unescapeNullSequences(JSON.stringify(row.json))
2074
+ : row.value,
1444
2075
  }));
1445
2076
  const result = await resultPromise;
1446
2077
  const value = process();
@@ -1449,31 +2080,9 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1449
2080
  }
1450
2081
  return value;
1451
2082
  }
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
2083
  async getUID(name) {
1475
2084
  if (name == null) {
1476
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2085
+ throw new InvalidParametersError('Name not given for UID.');
1477
2086
  }
1478
2087
  const result = await this.queryGet(`SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1479
2088
  params: {
@@ -1482,50 +2091,75 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1482
2091
  });
1483
2092
  return result?.cur_uid == null ? null : Number(result.cur_uid);
1484
2093
  }
1485
- async import(filename) {
2094
+ async importEntity(entity) {
2095
+ return await this.importEntityInternal(entity);
2096
+ }
2097
+ async importEntityTokens(entity) {
2098
+ return await this.importEntityInternal(entity, { only: 'tokens' });
2099
+ }
2100
+ async importEntityTilmeldAC(entity) {
2101
+ return await this.importEntityInternal(entity, { only: 'tilmeldAC' });
2102
+ }
2103
+ async importEntityInternal({ guid, cdate, mdate, tags, sdata, etype, }, { only = undefined } = {}) {
1486
2104
  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');`, {
2105
+ let promises = [];
2106
+ if (only == null) {
2107
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1489
2108
  etypes: [etype],
1490
2109
  params: {
1491
2110
  guid,
1492
2111
  },
1493
- });
1494
- await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`, {
2112
+ }));
2113
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1495
2114
  etypes: [etype],
1496
2115
  params: {
1497
2116
  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)))
1503
- ? null
1504
- : Number(JSON.parse(sdata.mdate)),
1505
2117
  },
1506
- });
1507
- const promises = [];
1508
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
2118
+ }));
2119
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1509
2120
  etypes: [etype],
1510
2121
  params: {
1511
2122
  guid,
1512
2123
  },
1513
2124
  }));
1514
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
2125
+ }
2126
+ if (only == null || only === 'tokens') {
2127
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1515
2128
  etypes: [etype],
1516
2129
  params: {
1517
2130
  guid,
1518
2131
  },
1519
2132
  }));
1520
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
2133
+ }
2134
+ if (only == null) {
2135
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1521
2136
  etypes: [etype],
1522
2137
  params: {
1523
2138
  guid,
1524
2139
  },
1525
2140
  }));
1526
- await Promise.all(promises);
1527
- delete sdata.cdate;
1528
- delete sdata.mdate;
2141
+ }
2142
+ await Promise.all(promises);
2143
+ promises = [];
2144
+ if (only == null) {
2145
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull } = this.removeAndReturnACValues(etype, {}, sdata);
2146
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate", "user", "group", "acUser", "acGroup", "acOther", "acRead", "acWrite", "acFull") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate, ${user == null ? '@user' : "decode(@user, 'hex')"}, ${group == null ? '@group' : "decode(@group, 'hex')"}, @acUser, @acGroup, @acOther, ${!acRead?.length ? '@acRead' : "array(SELECT decode(n, 'hex') FROM unnest(@acRead::text[]) AS n)"}, ${!acWrite?.length ? '@acWrite' : "array(SELECT decode(n, 'hex') FROM unnest(@acWrite::text[]) AS n)"}, ${!acFull?.length ? '@acFull' : "array(SELECT decode(n, 'hex') FROM unnest(@acFull::text[]) AS n)"});`, {
2147
+ etypes: [etype],
2148
+ params: {
2149
+ guid,
2150
+ tags,
2151
+ cdate: isNaN(cdate) ? null : cdate,
2152
+ mdate: isNaN(mdate) ? null : mdate,
2153
+ user,
2154
+ group,
2155
+ acUser,
2156
+ acGroup,
2157
+ acOther,
2158
+ acRead,
2159
+ acWrite,
2160
+ acFull,
2161
+ },
2162
+ });
1529
2163
  for (const name in sdata) {
1530
2164
  const value = sdata[name];
1531
2165
  const uvalue = JSON.parse(value);
@@ -1536,24 +2170,22 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1536
2170
  ? 'N'
1537
2171
  : typeof uvalue === 'string'
1538
2172
  ? '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);`, {
2173
+ : 'J';
2174
+ const jsonValue = storageValue === 'J'
2175
+ ? PostgreSQLDriver.escapeNullSequences(value)
2176
+ : null;
2177
+ 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);`, {
1542
2178
  etypes: [etype],
1543
2179
  params: {
1544
2180
  guid,
1545
2181
  name,
1546
2182
  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);`, {
1550
- etypes: [etype],
1551
- params: {
1552
- guid,
1553
- name,
1554
- truthy: !!uvalue,
1555
- string: `${uvalue}`,
2183
+ jsonValue,
2184
+ string: storageValue === 'J'
2185
+ ? null
2186
+ : PostgreSQLDriver.escapeNulls(`${uvalue}`),
1556
2187
  number: isNaN(Number(uvalue)) ? null : Number(uvalue),
2188
+ truthy: !!uvalue,
1557
2189
  },
1558
2190
  }));
1559
2191
  const references = this.findReferences(value);
@@ -1568,43 +2200,130 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1568
2200
  }));
1569
2201
  }
1570
2202
  }
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);`, {
2203
+ }
2204
+ if (only === 'tilmeldAC') {
2205
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull } = this.removeAndReturnACValues(etype, {}, sdata);
2206
+ promises.push(this.queryRun(`UPDATE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} SET "user"=${user == null ? '@user' : "decode(@user, 'hex')"}, "group"=${group == null ? '@group' : "decode(@group, 'hex')"}, "acUser"=@acUser, "acGroup"=@acGroup, "acOther"=@acOther, "acRead"=${!acRead?.length ? '@acRead' : "array(SELECT decode(n, 'hex') FROM unnest(@acRead::text[]) AS n)"}, "acWrite"=${!acWrite?.length ? '@acWrite' : "array(SELECT decode(n, 'hex') FROM unnest(@acWrite::text[]) AS n)"}, "acFull"=${!acFull?.length ? '@acFull' : "array(SELECT decode(n, 'hex') FROM unnest(@acFull::text[]) AS n)"} WHERE "guid"=decode(@guid, 'hex');`, {
2207
+ etypes: [etype],
1579
2208
  params: {
1580
- name,
1581
- curUid,
2209
+ user,
2210
+ group,
2211
+ acUser,
2212
+ acGroup,
2213
+ acOther,
2214
+ acRead,
2215
+ acWrite,
2216
+ acFull,
2217
+ guid,
1582
2218
  },
2219
+ }));
2220
+ }
2221
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
2222
+ if (only == null || only === 'tokens') {
2223
+ for (let name in sdata) {
2224
+ let tokenString = null;
2225
+ try {
2226
+ tokenString = EntityClass.getFTSText(name, JSON.parse(sdata[name]));
2227
+ }
2228
+ catch (e) {
2229
+ // Ignore error.
2230
+ }
2231
+ if (tokenString != null) {
2232
+ const tokens = this.tokenizer.tokenize(tokenString);
2233
+ while (tokens.length) {
2234
+ const currentTokens = tokens.splice(0, 100);
2235
+ const params = {
2236
+ guid,
2237
+ name,
2238
+ };
2239
+ const values = [];
2240
+ for (let i = 0; i < currentTokens.length; i++) {
2241
+ const token = currentTokens[i];
2242
+ params['token' + i] = token.token;
2243
+ params['position' + i] = token.position;
2244
+ params['stem' + i] = token.stem;
2245
+ values.push("(decode(@guid, 'hex'), @name, @token" +
2246
+ i +
2247
+ ', @position' +
2248
+ i +
2249
+ ', @stem' +
2250
+ i +
2251
+ ')');
2252
+ }
2253
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`, {
2254
+ etypes: [etype],
2255
+ params,
2256
+ }));
2257
+ }
2258
+ }
2259
+ }
2260
+ }
2261
+ if (only == null) {
2262
+ const uniques = await EntityClass.getUniques({
2263
+ guid,
2264
+ cdate,
2265
+ mdate,
2266
+ tags,
2267
+ data: {},
2268
+ sdata,
1583
2269
  });
1584
- }, async () => {
1585
- await this.internalTransaction('nymph-import');
1586
- }, async () => {
1587
- await this.commit('nymph-import');
2270
+ for (const unique of uniques) {
2271
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`, {
2272
+ etypes: [etype],
2273
+ params: {
2274
+ guid,
2275
+ unique,
2276
+ },
2277
+ }).catch((e) => {
2278
+ if (e instanceof EntityUniqueConstraintError) {
2279
+ this.nymph.config.debugError('postgresql', `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
2280
+ }
2281
+ return e;
2282
+ }));
2283
+ }
2284
+ }
2285
+ await Promise.all(promises);
2286
+ }
2287
+ catch (e) {
2288
+ this.nymph.config.debugError('postgresql', `Import entity error: "${e}"`);
2289
+ throw e;
2290
+ }
2291
+ }
2292
+ async importUID({ name, value }) {
2293
+ try {
2294
+ await this.internalTransaction(`nymph-import-uid-${name}`);
2295
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
2296
+ params: {
2297
+ name,
2298
+ },
1588
2299
  });
1589
- return result;
2300
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @value);`, {
2301
+ params: {
2302
+ name,
2303
+ value,
2304
+ },
2305
+ });
2306
+ await this.commit(`nymph-import-uid-${name}`);
1590
2307
  }
1591
2308
  catch (e) {
1592
- await this.rollback('nymph-import');
1593
- return false;
2309
+ this.nymph.config.debugError('postgresql', `Import UID error: "${e}"`);
2310
+ await this.rollback(`nymph-import-uid-${name}`);
2311
+ throw e;
1594
2312
  }
1595
2313
  }
1596
2314
  async newUID(name) {
1597
2315
  if (name == null) {
1598
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2316
+ throw new InvalidParametersError('Name not given for UID.');
1599
2317
  }
1600
2318
  await this.internalTransaction('nymph-newuid');
2319
+ let curUid = undefined;
1601
2320
  try {
1602
2321
  const lock = await this.queryGet(`SELECT "cur_uid" FROM ${PostgreSQLDriver.escape(`${this.prefix}uids`)} WHERE "name"=@name FOR UPDATE;`, {
1603
2322
  params: {
1604
2323
  name,
1605
2324
  },
1606
2325
  });
1607
- let curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
2326
+ curUid = lock?.cur_uid == null ? undefined : Number(lock.cur_uid);
1608
2327
  if (curUid == null) {
1609
2328
  curUid = 1;
1610
2329
  await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
@@ -1623,17 +2342,18 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1623
2342
  },
1624
2343
  });
1625
2344
  }
1626
- await this.commit('nymph-newuid');
1627
- return curUid;
1628
2345
  }
1629
2346
  catch (e) {
2347
+ this.nymph.config.debugError('postgresql', `New UID error: "${e}"`);
1630
2348
  await this.rollback('nymph-newuid');
1631
2349
  throw e;
1632
2350
  }
2351
+ await this.commit('nymph-newuid');
2352
+ return curUid;
1633
2353
  }
1634
2354
  async renameUID(oldName, newName) {
1635
2355
  if (oldName == null || newName == null) {
1636
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2356
+ throw new InvalidParametersError('Name not given for UID.');
1637
2357
  }
1638
2358
  await this.queryRun(`UPDATE ${PostgreSQLDriver.escape(`${this.prefix}uids`)} SET "name"=@newName WHERE "name"=@oldName;`, {
1639
2359
  params: {
@@ -1645,7 +2365,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1645
2365
  }
1646
2366
  async rollback(name) {
1647
2367
  if (name == null || typeof name !== 'string' || name.length === 0) {
1648
- throw new nymph_1.InvalidParametersError('Transaction rollback attempted without a name.');
2368
+ throw new InvalidParametersError('Transaction rollback attempted without a name.');
1649
2369
  }
1650
2370
  if (!this.transaction || this.transaction.count === 0) {
1651
2371
  this.transaction = null;
@@ -1662,7 +2382,8 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1662
2382
  return true;
1663
2383
  }
1664
2384
  async saveEntity(entity) {
1665
- const insertData = async (guid, data, sdata, etype) => {
2385
+ const insertData = async (guid, data, sdata, uniques, etype) => {
2386
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
1666
2387
  const runInsertQuery = async (name, value, svalue) => {
1667
2388
  if (value === undefined) {
1668
2389
  return;
@@ -1671,24 +2392,23 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1671
2392
  ? 'N'
1672
2393
  : typeof value === 'string'
1673
2394
  ? 'S'
1674
- : svalue;
2395
+ : 'J';
2396
+ const jsonValue = storageValue === 'J'
2397
+ ? PostgreSQLDriver.escapeNullSequences(svalue)
2398
+ : null;
1675
2399
  const promises = [];
1676
- promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (decode(@guid, 'hex'), @name, @storageValue);`, {
2400
+ 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
2401
  etypes: [etype],
1678
2402
  params: {
1679
2403
  guid,
1680
2404
  name,
1681
2405
  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}`,
2406
+ jsonValue,
2407
+ string: storageValue === 'J'
2408
+ ? null
2409
+ : PostgreSQLDriver.escapeNulls(`${value}`),
1691
2410
  number: isNaN(Number(value)) ? null : Number(value),
2411
+ truthy: !!value,
1692
2412
  },
1693
2413
  }));
1694
2414
  const references = this.findReferences(svalue);
@@ -1702,8 +2422,60 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1702
2422
  },
1703
2423
  }));
1704
2424
  }
2425
+ let tokenString = null;
2426
+ try {
2427
+ tokenString = EntityClass.getFTSText(name, value);
2428
+ }
2429
+ catch (e) {
2430
+ // Ignore error.
2431
+ }
2432
+ if (tokenString != null) {
2433
+ const tokens = this.tokenizer.tokenize(tokenString);
2434
+ while (tokens.length) {
2435
+ const currentTokens = tokens.splice(0, 100);
2436
+ const params = {
2437
+ guid,
2438
+ name,
2439
+ };
2440
+ const values = [];
2441
+ for (let i = 0; i < currentTokens.length; i++) {
2442
+ const token = currentTokens[i];
2443
+ params['token' + i] = token.token;
2444
+ params['position' + i] = token.position;
2445
+ params['stem' + i] = token.stem;
2446
+ values.push("(decode(@guid, 'hex'), @name, @token" +
2447
+ i +
2448
+ ', @position' +
2449
+ i +
2450
+ ', @stem' +
2451
+ i +
2452
+ ')');
2453
+ }
2454
+ promises.push(this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`, {
2455
+ etypes: [etype],
2456
+ params,
2457
+ }));
2458
+ }
2459
+ }
1705
2460
  await Promise.all(promises);
1706
2461
  };
2462
+ for (const unique of uniques) {
2463
+ try {
2464
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`, {
2465
+ etypes: [etype],
2466
+ params: {
2467
+ guid,
2468
+ unique,
2469
+ },
2470
+ });
2471
+ }
2472
+ catch (e) {
2473
+ if (e instanceof EntityUniqueConstraintError) {
2474
+ this.nymph.config.debugError('postgresql', `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
2475
+ }
2476
+ throw e;
2477
+ }
2478
+ }
1707
2479
  for (const name in data) {
1708
2480
  await runInsertQuery(name, data[name], JSON.stringify(data[name]));
1709
2481
  }
@@ -1711,19 +2483,38 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1711
2483
  await runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
1712
2484
  }
1713
2485
  };
2486
+ let inTransaction = false;
1714
2487
  try {
1715
- const result = await this.saveEntityRowLike(entity, async (_entity, guid, tags, data, sdata, cdate, etype) => {
1716
- await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @cdate);`, {
2488
+ const result = await this.saveEntityRowLike(entity, async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
2489
+ if (Object.keys(data).length === 0 &&
2490
+ Object.keys(sdata).length === 0) {
2491
+ return false;
2492
+ }
2493
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull, } = this.removeAndReturnACValues(etype, data, sdata);
2494
+ await this.queryRun(`INSERT INTO ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate", "user", "group", "acUser", "acGroup", "acOther", "acRead", "acWrite", "acFull") VALUES (decode(@guid, 'hex'), @tags, @cdate, @cdate, ${user == null ? '@user' : "decode(@user, 'hex')"}, ${group == null ? '@group' : "decode(@group, 'hex')"}, @acUser, @acGroup, @acOther, ${!acRead?.length ? '@acRead' : "array(SELECT decode(n, 'hex') FROM unnest(@acRead::text[]) AS n)"}, ${!acWrite?.length ? '@acWrite' : "array(SELECT decode(n, 'hex') FROM unnest(@acWrite::text[]) AS n)"}, ${!acFull?.length ? '@acFull' : "array(SELECT decode(n, 'hex') FROM unnest(@acFull::text[]) AS n)"});`, {
1717
2495
  etypes: [etype],
1718
2496
  params: {
1719
2497
  guid,
1720
2498
  tags,
1721
2499
  cdate,
2500
+ user,
2501
+ group,
2502
+ acUser,
2503
+ acGroup,
2504
+ acOther,
2505
+ acRead,
2506
+ acWrite,
2507
+ acFull,
1722
2508
  },
1723
2509
  });
1724
- await insertData(guid, data, sdata, etype);
2510
+ await insertData(guid, data, sdata, uniques, etype);
1725
2511
  return true;
1726
- }, async (entity, guid, tags, data, sdata, mdate, etype) => {
2512
+ }, async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
2513
+ if (Object.keys(data).length === 0 &&
2514
+ Object.keys(sdata).length === 0) {
2515
+ return false;
2516
+ }
2517
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull, } = this.removeAndReturnACValues(etype, data, sdata);
1727
2518
  const promises = [];
1728
2519
  promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1729
2520
  etypes: [etype],
@@ -1737,24 +2528,38 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1737
2528
  guid,
1738
2529
  },
1739
2530
  }));
1740
- promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
2531
+ promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1741
2532
  etypes: [etype],
1742
2533
  params: {
1743
2534
  guid,
1744
2535
  },
1745
2536
  }));
1746
- promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
2537
+ promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
2538
+ etypes: [etype],
2539
+ params: {
2540
+ guid,
2541
+ },
2542
+ }));
2543
+ promises.push(this.queryRun(`SELECT 1 FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`, {
1747
2544
  etypes: [etype],
1748
2545
  params: {
1749
2546
  guid,
1750
2547
  },
1751
2548
  }));
1752
2549
  await Promise.all(promises);
1753
- const info = await this.queryRun(`UPDATE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=decode(@guid, 'hex') AND "mdate" <= @emdate;`, {
2550
+ const info = await this.queryRun(`UPDATE ${PostgreSQLDriver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate, "user"=${user == null ? '@user' : "decode(@user, 'hex')"}, "group"=${group == null ? '@group' : "decode(@group, 'hex')"}, "acUser"=@acUser, "acGroup"=@acGroup, "acOther"=@acOther, "acRead"=${!acRead?.length ? '@acRead' : "array(SELECT decode(n, 'hex') FROM unnest(@acRead::text[]) AS n)"}, "acWrite"=${!acWrite?.length ? '@acWrite' : "array(SELECT decode(n, 'hex') FROM unnest(@acWrite::text[]) AS n)"}, "acFull"=${!acFull?.length ? '@acFull' : "array(SELECT decode(n, 'hex') FROM unnest(@acFull::text[]) AS n)"} WHERE "guid"=decode(@guid, 'hex') AND "mdate" <= @emdate;`, {
1754
2551
  etypes: [etype],
1755
2552
  params: {
1756
2553
  tags,
1757
2554
  mdate,
2555
+ user,
2556
+ group,
2557
+ acUser,
2558
+ acGroup,
2559
+ acOther,
2560
+ acRead,
2561
+ acWrite,
2562
+ acFull,
1758
2563
  guid,
1759
2564
  emdate: isNaN(Number(entity.mdate)) ? 0 : Number(entity.mdate),
1760
2565
  },
@@ -1768,44 +2573,57 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1768
2573
  guid,
1769
2574
  },
1770
2575
  }));
1771
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
2576
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1772
2577
  etypes: [etype],
1773
2578
  params: {
1774
2579
  guid,
1775
2580
  },
1776
2581
  }));
1777
- promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
2582
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
2583
+ etypes: [etype],
2584
+ params: {
2585
+ guid,
2586
+ },
2587
+ }));
2588
+ promises.push(this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=decode(@guid, 'hex');`, {
1778
2589
  etypes: [etype],
1779
2590
  params: {
1780
2591
  guid,
1781
2592
  },
1782
2593
  }));
1783
2594
  await Promise.all(promises);
1784
- await insertData(guid, data, sdata, etype);
2595
+ await insertData(guid, data, sdata, uniques, etype);
1785
2596
  success = true;
1786
2597
  }
1787
2598
  return success;
1788
2599
  }, async () => {
1789
2600
  await this.internalTransaction('nymph-save');
2601
+ inTransaction = true;
1790
2602
  }, async (success) => {
1791
- if (success) {
1792
- await this.commit('nymph-save');
1793
- }
1794
- else {
1795
- await this.rollback('nymph-save');
2603
+ if (inTransaction) {
2604
+ inTransaction = false;
2605
+ if (success) {
2606
+ await this.commit('nymph-save');
2607
+ }
2608
+ else {
2609
+ await this.rollback('nymph-save');
2610
+ }
1796
2611
  }
1797
2612
  return success;
1798
2613
  });
1799
2614
  return result;
1800
2615
  }
1801
2616
  catch (e) {
1802
- await this.rollback('nymph-save');
2617
+ this.nymph.config.debugError('postgresql', `Save entity error: "${e}"`);
2618
+ if (inTransaction) {
2619
+ await this.rollback('nymph-save');
2620
+ }
1803
2621
  throw e;
1804
2622
  }
1805
2623
  }
1806
2624
  async setUID(name, curUid) {
1807
2625
  if (name == null) {
1808
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2626
+ throw new InvalidParametersError('Name not given for UID.');
1809
2627
  }
1810
2628
  await this.internalTransaction('nymph-setuid');
1811
2629
  try {
@@ -1821,23 +2639,25 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1821
2639
  curUid,
1822
2640
  },
1823
2641
  });
1824
- await this.commit('nymph-setuid');
1825
- return true;
1826
2642
  }
1827
2643
  catch (e) {
1828
2644
  await this.rollback('nymph-setuid');
1829
2645
  throw e;
1830
2646
  }
2647
+ await this.commit('nymph-setuid');
2648
+ return true;
1831
2649
  }
1832
2650
  async internalTransaction(name) {
1833
2651
  if (name == null || typeof name !== 'string' || name.length === 0) {
1834
- throw new nymph_1.InvalidParametersError('Transaction start attempted without a name.');
2652
+ throw new InvalidParametersError('Transaction start attempted without a name.');
1835
2653
  }
1836
2654
  if (!this.transaction || this.transaction.count === 0) {
2655
+ // Lock to one connection.
1837
2656
  this.transaction = {
1838
2657
  count: 0,
1839
2658
  connection: await this.getConnection(),
1840
2659
  };
2660
+ // We're not in a transaction yet, so start one.
1841
2661
  await this.queryRun('BEGIN;');
1842
2662
  }
1843
2663
  await this.queryRun(`SAVEPOINT ${PostgreSQLDriver.escape(name)};`);
@@ -1845,7 +2665,7 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1845
2665
  return this.transaction;
1846
2666
  }
1847
2667
  async startTransaction(name) {
1848
- const inTransaction = this.inTransaction();
2668
+ const inTransaction = await this.inTransaction();
1849
2669
  const transaction = await this.internalTransaction(name);
1850
2670
  if (!inTransaction) {
1851
2671
  this.transaction = null;
@@ -1854,6 +2674,121 @@ class PostgreSQLDriver extends nymph_1.NymphDriver {
1854
2674
  nymph.driver.transaction = transaction;
1855
2675
  return nymph;
1856
2676
  }
2677
+ async removeTilmeldOldRows(etype, connection) {
2678
+ await this.internalTransaction('nymph-remove-tilmeld-rows');
2679
+ try {
2680
+ for (let name of [
2681
+ 'user',
2682
+ 'group',
2683
+ 'acUser',
2684
+ 'acGroup',
2685
+ 'acOther',
2686
+ 'acRead',
2687
+ 'acWrite',
2688
+ 'acFull',
2689
+ ]) {
2690
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}data_${etype}`)} WHERE "name"=@name;`, {
2691
+ etypes: [etype],
2692
+ params: {
2693
+ name,
2694
+ },
2695
+ connection,
2696
+ });
2697
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}references_${etype}`)} WHERE "name"=@name;`, {
2698
+ etypes: [etype],
2699
+ params: {
2700
+ name,
2701
+ },
2702
+ connection,
2703
+ });
2704
+ await this.queryRun(`DELETE FROM ${PostgreSQLDriver.escape(`${this.prefix}tokens_${etype}`)} WHERE "name"=@name;`, {
2705
+ etypes: [etype],
2706
+ params: {
2707
+ name,
2708
+ },
2709
+ connection,
2710
+ });
2711
+ }
2712
+ }
2713
+ catch (e) {
2714
+ this.nymph.config.debugError('postgresql', `Remove tilmeld rows error: "${e}"`);
2715
+ await this.rollback('nymph-remove-tilmeld-rows');
2716
+ throw e;
2717
+ }
2718
+ await this.commit('nymph-remove-tilmeld-rows');
2719
+ return true;
2720
+ }
2721
+ async needsMigration() {
2722
+ 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;', {
2723
+ params: {
2724
+ db: this.config.database,
2725
+ prefix: this.prefix + 'data_' + '%',
2726
+ },
2727
+ });
2728
+ if (table?.table_name) {
2729
+ 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\';', {
2730
+ params: {
2731
+ db: this.config.database,
2732
+ table: table.table_name,
2733
+ },
2734
+ });
2735
+ if (!result?.exists) {
2736
+ return 'json';
2737
+ }
2738
+ }
2739
+ const table2 = 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 @tokenTable LIMIT 1;', {
2740
+ params: {
2741
+ db: this.config.database,
2742
+ tokenTable: this.prefix + 'tokens_' + '%',
2743
+ },
2744
+ });
2745
+ if (!table2 || !table2.table_name) {
2746
+ return 'tokens';
2747
+ }
2748
+ const table3 = 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;', {
2749
+ params: {
2750
+ db: this.config.database,
2751
+ prefix: this.prefix + 'entities_' + '%',
2752
+ },
2753
+ });
2754
+ if (table3?.table_name) {
2755
+ 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"=\'user\';', {
2756
+ params: {
2757
+ db: this.config.database,
2758
+ table: table3.table_name,
2759
+ },
2760
+ });
2761
+ if (!result?.exists) {
2762
+ return 'tilmeldColumns';
2763
+ }
2764
+ }
2765
+ return false;
2766
+ }
2767
+ async liveMigration(migrationType) {
2768
+ if (migrationType === 'tokenTables') {
2769
+ const etypes = await this.getEtypes();
2770
+ const connection = await this.getConnection(true);
2771
+ for (let etype of etypes) {
2772
+ await this.createTokensTable(etype, connection);
2773
+ }
2774
+ connection.done();
2775
+ }
2776
+ else if (migrationType === 'tilmeldColumns') {
2777
+ const etypes = await this.getEtypes();
2778
+ const connection = await this.getConnection(true);
2779
+ for (let etype of etypes) {
2780
+ await this.addTilmeldColumnsAndIndexes(etype, connection);
2781
+ }
2782
+ connection.done();
2783
+ }
2784
+ else if (migrationType === 'tilmeldRemoveOldRows') {
2785
+ const etypes = await this.getEtypes();
2786
+ const connection = await this.getConnection(true);
2787
+ for (let etype of etypes) {
2788
+ await this.removeTilmeldOldRows(etype, connection);
2789
+ }
2790
+ connection.done();
2791
+ }
2792
+ }
1857
2793
  }
1858
- exports.default = PostgreSQLDriver;
1859
2794
  //# sourceMappingURL=PostgreSQLDriver.js.map