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

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