@nymphjs/driver-mysql 1.0.0-beta.12 → 1.0.0-beta.121

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