@nymphjs/driver-mysql 1.0.0-beta.13 → 1.0.0-beta.130

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