@nymphjs/driver-sqlite3 1.0.0-beta.13 → 1.0.0-beta.131

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,29 +1,36 @@
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 better_sqlite3_1 = __importDefault(require("better-sqlite3"));
7
- const nymph_1 = require("@nymphjs/nymph");
8
- const guid_1 = require("@nymphjs/guid");
9
- const conf_1 = require("./conf");
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { NymphDriver, EntityConflictError, EntityInvalidDataError, EntityUniqueConstraintError, InvalidParametersError, NotConfiguredError, QueryFailedError, UnableToConnectError, xor, } from '@nymphjs/nymph';
3
+ import { makeTableSuffix } from '@nymphjs/guid';
4
+ import { SQLite3DriverConfigDefaults as defaults, } from './conf/index.js';
10
5
  class InternalStore {
6
+ link;
7
+ linkWrite;
8
+ connected = false;
9
+ transactionsStarted = 0;
11
10
  constructor(link) {
12
- this.connected = false;
13
- this.transactionsStarted = 0;
14
11
  this.link = link;
15
12
  }
16
13
  }
17
- class SQLite3Driver extends nymph_1.NymphDriver {
14
+ /**
15
+ * The SQLite3 Nymph database driver.
16
+ */
17
+ export default class SQLite3Driver extends NymphDriver {
18
+ config;
19
+ prefix;
20
+ // @ts-ignore: this is assigned in connect(), which is called by the constructor.
21
+ store;
18
22
  static escape(input) {
19
23
  if (input.indexOf('\x00') !== -1) {
20
- throw new nymph_1.InvalidParametersError('SQLite3 identifiers (like entity ETYPE) cannot contain null characters.');
24
+ throw new InvalidParametersError('SQLite3 identifiers (like entity ETYPE) cannot contain null characters.');
21
25
  }
22
26
  return '"' + input.replace(/"/g, () => '""') + '"';
23
27
  }
28
+ static escapeValue(input) {
29
+ return "'" + input.replace(/'/g, () => "''") + "'";
30
+ }
24
31
  constructor(config, store) {
25
32
  super();
26
- this.config = { ...conf_1.SQLite3DriverConfigDefaults, ...config };
33
+ this.config = { ...defaults, ...config };
27
34
  if (this.config.filename === ':memory:') {
28
35
  this.config.explicitWrite = true;
29
36
  }
@@ -35,45 +42,69 @@ class SQLite3Driver extends nymph_1.NymphDriver {
35
42
  this.connect();
36
43
  }
37
44
  }
45
+ /**
46
+ * This is used internally by Nymph. Don't call it yourself.
47
+ *
48
+ * @returns A clone of this instance.
49
+ */
38
50
  clone() {
39
51
  return new SQLite3Driver(this.config, this.store);
40
52
  }
53
+ /**
54
+ * Connect to the SQLite3 database.
55
+ *
56
+ * @returns Whether this instance is connected to a SQLite3 database.
57
+ */
41
58
  connect() {
42
59
  if (this.store && this.store.connected) {
43
60
  return Promise.resolve(true);
44
61
  }
62
+ // Connecting
45
63
  this._connect(false);
46
64
  return Promise.resolve(this.store.connected);
47
65
  }
48
66
  _connect(write) {
49
- const { filename, fileMustExist, timeout, explicitWrite, wal, verbose } = this.config;
67
+ const { filename, fileMustExist, timeout, explicitWrite, wal } = this.config;
50
68
  try {
69
+ const setOptions = (link) => {
70
+ // Set database and connection options.
71
+ if (wal) {
72
+ link.exec('PRAGMA journal_mode = WAL;');
73
+ }
74
+ link.exec('PRAGMA encoding = "UTF-8";');
75
+ link.exec('PRAGMA foreign_keys = 1;');
76
+ link.exec('PRAGMA case_sensitive_like = 1;');
77
+ for (let pragma of this.config.pragmas) {
78
+ link.exec(`PRAGMA ${pragma}`);
79
+ }
80
+ // Create the preg_match and regexp functions.
81
+ link.function('regexp', { deterministic: true }, ((pattern, subject) => (this.posixRegexMatch(pattern, subject) ? 1 : 0)));
82
+ };
51
83
  let link;
52
84
  try {
53
- link = new better_sqlite3_1.default(filename, {
54
- readonly: !explicitWrite && !write,
55
- fileMustExist,
85
+ link = new DatabaseSync(filename, {
86
+ readOnly: !explicitWrite && !write,
56
87
  timeout,
57
- verbose,
58
88
  });
59
89
  }
60
90
  catch (e) {
61
- if (e.code === 'SQLITE_CANTOPEN' &&
91
+ if (e.code === 'ERR_SQLITE_CANTOPEN' &&
62
92
  !explicitWrite &&
63
93
  !write &&
64
- !this.config.fileMustExist) {
65
- const writeLink = new better_sqlite3_1.default(filename, {
66
- readonly: false,
67
- fileMustExist,
94
+ !fileMustExist) {
95
+ // This happens when the file doesn't exist and we attempt to open it
96
+ // readonly.
97
+ // First open it in write mode.
98
+ const writeLink = new DatabaseSync(filename, {
99
+ readOnly: false,
68
100
  timeout,
69
- verbose,
70
101
  });
102
+ setOptions(writeLink);
71
103
  writeLink.close();
72
- link = new better_sqlite3_1.default(filename, {
73
- readonly: true,
74
- fileMustExist,
104
+ // Now open in readonly.
105
+ link = new DatabaseSync(filename, {
106
+ readOnly: true,
75
107
  timeout,
76
- verbose,
77
108
  });
78
109
  }
79
110
  else {
@@ -93,27 +124,26 @@ class SQLite3Driver extends nymph_1.NymphDriver {
93
124
  this.store.link = link;
94
125
  }
95
126
  this.store.connected = true;
96
- if (wal) {
97
- link.pragma('journal_mode = WAL;');
98
- }
99
- link.pragma('encoding = "UTF-8";');
100
- link.pragma('foreign_keys = 1;');
101
- link.pragma('case_sensitive_like = 1;');
102
- link.function('regexp', { deterministic: true }, (pattern, subject) => this.posixRegexMatch(pattern, subject) ? 1 : 0);
127
+ setOptions(link);
103
128
  }
104
129
  catch (e) {
105
130
  if (this.store) {
106
131
  this.store.connected = false;
107
132
  }
108
133
  if (filename === ':memory:') {
109
- throw new nymph_1.NotConfiguredError("It seems the config hasn't been set up correctly. Could not connect: " +
134
+ throw new NotConfiguredError("It seems the config hasn't been set up correctly. Could not connect: " +
110
135
  e?.message);
111
136
  }
112
137
  else {
113
- throw new nymph_1.UnableToConnectError('Could not connect: ' + e?.message);
138
+ throw new UnableToConnectError('Could not connect: ' + e?.message);
114
139
  }
115
140
  }
116
141
  }
142
+ /**
143
+ * Disconnect from the SQLite3 database.
144
+ *
145
+ * @returns Whether this instance is connected to a SQLite3 database.
146
+ */
117
147
  async disconnect() {
118
148
  if (this.store.connected) {
119
149
  if (this.store.linkWrite && !this.config.explicitWrite) {
@@ -133,51 +163,102 @@ class SQLite3Driver extends nymph_1.NymphDriver {
133
163
  async inTransaction() {
134
164
  return this.store.transactionsStarted > 0;
135
165
  }
166
+ /**
167
+ * Check connection status.
168
+ *
169
+ * @returns Whether this instance is connected to a SQLite3 database.
170
+ */
136
171
  isConnected() {
137
172
  return this.store.connected;
138
173
  }
174
+ createEntitiesTable(etype) {
175
+ // Create the entity table.
176
+ this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL, "user" CHARACTER(24), "group" CHARACTER(24), "acUser" INT(1), "acGroup" INT(1), "acOther" INT(1), "acRead" TEXT, "acWrite" TEXT, "acFull" TEXT);`);
177
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("cdate");`);
178
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("mdate");`);
179
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("tags");`);
180
+ this.createEntitiesTilmeldIndexes(etype);
181
+ }
182
+ addTilmeldColumnsAndIndexes(etype) {
183
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "user" CHARACTER(24);`);
184
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "group" CHARACTER(24);`);
185
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "acUser" INT(1);`);
186
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "acGroup" INT(1);`);
187
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "acOther" INT(1);`);
188
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "acRead" TEXT;`);
189
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "acWrite" TEXT;`);
190
+ this.queryRun(`ALTER TABLE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ADD COLUMN "acFull" TEXT;`);
191
+ this.createEntitiesTilmeldIndexes(etype);
192
+ }
193
+ createEntitiesTilmeldIndexes(etype) {
194
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_user_acUser`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("user", "acUser");`);
195
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_group_acGroup`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("group", "acGroup");`);
196
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_acUser`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("acUser");`);
197
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_acGroup`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("acGroup");`);
198
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_acOther`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("acOther");`);
199
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_acRead`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("acRead");`);
200
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_acWrite`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("acWrite");`);
201
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_acFull`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("acFull");`);
202
+ }
203
+ createDataTable(etype) {
204
+ // Create the data table.
205
+ this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "value" CHARACTER(1) NOT NULL, "json" BLOB, "string" TEXT, "number" REAL, "truthy" INTEGER, PRIMARY KEY("guid", "name"));`);
206
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name", "guid");`);
207
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name_string_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name", "string", "guid");`);
208
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name_number_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name", "number", "guid");`);
209
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name_truthy_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name", "truthy", "guid");`);
210
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name_json_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name", "json", "guid")`);
211
+ }
212
+ createReferencesTable(etype) {
213
+ // Create the references table.
214
+ this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`);
215
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_name_reference_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("name", "reference", "guid");`);
216
+ }
217
+ createTokensTable(etype) {
218
+ // Create the tokens table.
219
+ this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "token" INTEGER NOT NULL, "position" INTEGER NOT NULL, "stem" INTEGER NOT NULL, PRIMARY KEY("guid", "name", "token", "position"));`);
220
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}_id_name_token_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} ("name", "token", "guid");`);
221
+ }
222
+ createUniquesTable(etype) {
223
+ // Create the unique strings table.
224
+ this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "unique" TEXT NOT NULL UNIQUE, PRIMARY KEY("guid", "unique"));`);
225
+ }
226
+ /**
227
+ * Create entity tables in the database.
228
+ *
229
+ * @param etype The entity type to create a table for. If this is blank, the default tables are created.
230
+ */
139
231
  createTables(etype = null) {
140
232
  this.startTransaction('nymph-tablecreation');
141
233
  try {
142
234
  if (etype != null) {
143
- this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL);`);
144
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("cdate");`);
145
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("mdate");`);
146
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("tags");`);
147
- this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "value" TEXT NOT NULL, PRIMARY KEY("guid", "name"));`);
148
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid");`);
149
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name");`);
150
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_value`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("value");`);
151
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid__name_user`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid") WHERE "name" = \'user\';`);
152
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid__name_group`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid") WHERE "name" = \'group\';`);
153
- this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "truthy" INTEGER, "string" TEXT, "number" REAL, PRIMARY KEY("guid", "name"));`);
154
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid");`);
155
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("name");`);
156
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_name__truthy`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("name") WHERE "truthy" = 1;`);
157
- this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`);
158
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid");`);
159
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("name");`);
160
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_reference`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("reference");`);
235
+ this.createEntitiesTable(etype);
236
+ this.createDataTable(etype);
237
+ this.createReferencesTable(etype);
238
+ this.createTokensTable(etype);
239
+ this.createUniquesTable(etype);
161
240
  }
162
241
  else {
242
+ // Create the UID table.
163
243
  this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name" TEXT PRIMARY KEY NOT NULL, "cur_uid" INTEGER NOT NULL);`);
164
244
  }
165
- this.commit('nymph-tablecreation');
166
- return true;
167
245
  }
168
246
  catch (e) {
169
247
  this.rollback('nymph-tablecreation');
170
248
  throw e;
171
249
  }
250
+ this.commit('nymph-tablecreation');
251
+ return true;
172
252
  }
173
253
  query(runQuery, query, etypes = []) {
174
254
  try {
255
+ this.nymph.config.debugInfo('sqlite3:query', query);
175
256
  return runQuery();
176
257
  }
177
258
  catch (e) {
178
259
  const errorCode = e?.code;
179
260
  const errorMsg = e?.message;
180
- if (errorCode === 'SQLITE_ERROR' &&
261
+ if (errorCode === 'ERR_SQLITE_ERROR' &&
181
262
  errorMsg.match(/^no such table: /) &&
182
263
  this.createTables()) {
183
264
  for (let etype of etypes) {
@@ -187,15 +268,19 @@ class SQLite3Driver extends nymph_1.NymphDriver {
187
268
  return runQuery();
188
269
  }
189
270
  catch (e2) {
190
- throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
271
+ throw new QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query, e2?.code);
191
272
  }
192
273
  }
274
+ else if (errorCode === 'ERR_SQLITE_ERROR' &&
275
+ errorMsg.match(/^UNIQUE constraint failed: /)) {
276
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
277
+ }
193
278
  else {
194
- throw new nymph_1.QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
279
+ throw new QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query, e?.code);
195
280
  }
196
281
  }
197
282
  }
198
- queryIter(query, { etypes = [], params = {}, } = {}) {
283
+ queryArray(query, { etypes = [], params = {}, } = {}) {
199
284
  return this.query(() => (this.store.linkWrite || this.store.link)
200
285
  .prepare(query)
201
286
  .iterate(params), `${query} -- ${JSON.stringify(params)}`, etypes);
@@ -208,7 +293,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
208
293
  }
209
294
  async commit(name) {
210
295
  if (name == null || typeof name !== 'string' || name.length === 0) {
211
- throw new nymph_1.InvalidParametersError('Transaction commit attempted without a name.');
296
+ throw new InvalidParametersError('Transaction commit attempted without a name.');
212
297
  }
213
298
  if (this.store.transactionsStarted === 0) {
214
299
  return true;
@@ -248,32 +333,40 @@ class SQLite3Driver extends nymph_1.NymphDriver {
248
333
  guid,
249
334
  },
250
335
  });
251
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
336
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
252
337
  etypes: [etype],
253
338
  params: {
254
339
  guid,
255
340
  },
256
341
  });
257
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
342
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=@guid;`, {
343
+ etypes: [etype],
344
+ params: {
345
+ guid,
346
+ },
347
+ });
348
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
258
349
  etypes: [etype],
259
350
  params: {
260
351
  guid,
261
352
  },
262
353
  });
263
- await this.commit('nymph-delete');
264
- if (this.nymph.config.cache) {
265
- this.cleanCache(guid);
266
- }
267
- return true;
268
354
  }
269
355
  catch (e) {
356
+ this.nymph.config.debugError('sqlite3', `Delete entity error: "${e}"`);
270
357
  await this.rollback('nymph-delete');
271
358
  throw e;
272
359
  }
360
+ await this.commit('nymph-delete');
361
+ // Remove any cached versions of this entity.
362
+ if (this.nymph.config.cache) {
363
+ this.cleanCache(guid);
364
+ }
365
+ return true;
273
366
  }
274
367
  async deleteUID(name) {
275
368
  if (!name) {
276
- throw new nymph_1.InvalidParametersError('Name not given for UID');
369
+ throw new InvalidParametersError('Name not given for UID');
277
370
  }
278
371
  await this.startTransaction('nymph-delete-uid');
279
372
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
@@ -284,73 +377,227 @@ class SQLite3Driver extends nymph_1.NymphDriver {
284
377
  await this.commit('nymph-delete-uid');
285
378
  return true;
286
379
  }
287
- async exportEntities(writeLine) {
288
- writeLine('#nex2');
289
- writeLine('# Nymph Entity Exchange v2');
290
- writeLine('# http://nymph.io');
291
- writeLine('#');
292
- writeLine('# Generation Time: ' + new Date().toLocaleString());
293
- writeLine('');
294
- writeLine('#');
295
- writeLine('# UIDs');
296
- writeLine('#');
297
- writeLine('');
298
- let uids = this.queryIter(`SELECT * FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
299
- for (const uid of uids) {
300
- writeLine(`<${uid.name}>[${uid.cur_uid}]`);
301
- }
302
- writeLine('');
303
- writeLine('#');
304
- writeLine('# Entities');
305
- writeLine('#');
306
- writeLine('');
307
- const tables = this.queryIter("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;");
380
+ async getIndexes(etype) {
381
+ const indexes = [];
382
+ for (let [scope, suffix] of [
383
+ ['data', '_string_guid'],
384
+ ['references', '_reference_guid'],
385
+ ['tokens', '_token_guid'],
386
+ ]) {
387
+ const indexDefinitions = this.queryArray(`SELECT "name", "sql" FROM "sqlite_master" WHERE "type"='index' AND "name" LIKE @pattern;`, {
388
+ params: {
389
+ pattern: `${this.prefix}${scope}_${etype}_id_custom_%${suffix}`,
390
+ },
391
+ });
392
+ for (const indexDefinition of indexDefinitions) {
393
+ indexes.push({
394
+ scope,
395
+ name: indexDefinition.name.substring(`${this.prefix}${scope}_${etype}_id_custom_`.length, indexDefinition.name.length - suffix.length),
396
+ property: (indexDefinition.sql.match(/WHERE\s+"name"\s*=\s*'(.*)'/) ?? [])[1] ?? '',
397
+ });
398
+ }
399
+ }
400
+ return indexes;
401
+ }
402
+ async addIndex(etype, definition) {
403
+ this.checkIndexName(definition.name);
404
+ await this.deleteIndex(etype, definition.scope, definition.name);
405
+ if (definition.scope === 'data') {
406
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_string_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("string", "guid") WHERE "name"=${SQLite3Driver.escapeValue(definition.property)};`);
407
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_number_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("number", "guid") WHERE "name"=${SQLite3Driver.escapeValue(definition.property)};`);
408
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_truthy_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("truthy", "guid") WHERE "name"=${SQLite3Driver.escapeValue(definition.property)};`);
409
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${definition.name}_json_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("json", "guid") WHERE "name"=${SQLite3Driver.escapeValue(definition.property)};`);
410
+ }
411
+ else if (definition.scope === 'references') {
412
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_custom_${definition.name}_reference_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("reference", "guid") WHERE "name"=${SQLite3Driver.escapeValue(definition.property)};`);
413
+ }
414
+ else if (definition.scope === 'tokens') {
415
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}_id_custom_${definition.name}_token_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} ("token", "guid") WHERE "name"=${SQLite3Driver.escapeValue(definition.property)};`);
416
+ }
417
+ return true;
418
+ }
419
+ async deleteIndex(etype, scope, name) {
420
+ this.checkIndexName(name);
421
+ if (scope === 'data') {
422
+ this.queryRun(`DROP INDEX IF EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${name}_string_guid`)};`);
423
+ this.queryRun(`DROP INDEX IF EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${name}_number_guid`)};`);
424
+ this.queryRun(`DROP INDEX IF EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${name}_truthy_guid`)};`);
425
+ this.queryRun(`DROP INDEX IF EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_custom_${name}_json_guid`)};`);
426
+ }
427
+ else if (scope === 'references') {
428
+ this.queryRun(`DROP INDEX IF EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_custom_${name}_reference_guid`)};`);
429
+ }
430
+ else if (scope === 'tokens') {
431
+ this.queryRun(`DROP INDEX IF EXISTS ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}_id_custom_${name}_token_guid`)};`);
432
+ }
433
+ return true;
434
+ }
435
+ async getEtypes() {
436
+ const tables = this.queryArray(`SELECT "name" FROM "sqlite_master" WHERE "type"='table' AND "name" LIKE @prefix;`, {
437
+ params: {
438
+ prefix: this.prefix + 'entities_' + '%',
439
+ },
440
+ });
308
441
  const etypes = [];
309
442
  for (const table of tables) {
310
- if (table.name.startsWith(this.prefix + 'entities_')) {
311
- etypes.push(table.name.substr((this.prefix + 'entities_').length));
443
+ etypes.push(table.name.substr((this.prefix + 'entities_').length));
444
+ }
445
+ return etypes;
446
+ }
447
+ async *exportDataIterator() {
448
+ if (yield {
449
+ type: 'comment',
450
+ content: `#nex2
451
+ # Nymph Entity Exchange v2
452
+ # http://nymph.io
453
+ #
454
+ # Generation Time: ${new Date().toLocaleString()}
455
+ `,
456
+ }) {
457
+ return;
458
+ }
459
+ if (yield {
460
+ type: 'comment',
461
+ content: `
462
+
463
+ #
464
+ # UIDs
465
+ #
466
+
467
+ `,
468
+ }) {
469
+ return;
470
+ }
471
+ // Export UIDs.
472
+ let uids = this.queryArray(`SELECT * FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
473
+ for (const uid of uids) {
474
+ if (yield { type: 'uid', content: `<${uid.name}>[${uid.cur_uid}]\n` }) {
475
+ return;
312
476
  }
313
477
  }
478
+ if (yield {
479
+ type: 'comment',
480
+ content: `
481
+
482
+ #
483
+ # Entities
484
+ #
485
+
486
+ `,
487
+ }) {
488
+ return;
489
+ }
490
+ // Get the etypes.
491
+ const etypes = await this.getEtypes();
314
492
  for (const etype of etypes) {
315
- const dataIterator = this.queryIter(`SELECT e.*, d."name" AS "dname", d."value" AS "dvalue", c."string", c."number" FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} e LEFT JOIN ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} d USING ("guid") INNER JOIN ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} c USING ("guid", "name") ORDER BY e."guid";`)[Symbol.iterator]();
316
- let datum = dataIterator.next();
317
- while (!datum.done) {
318
- const guid = datum.value.guid;
319
- const tags = datum.value.tags.slice(1, -1);
320
- const cdate = datum.value.cdate;
321
- const mdate = datum.value.mdate;
322
- writeLine(`{${guid}}<${etype}>[${tags}]`);
323
- writeLine(`\tcdate=${JSON.stringify(cdate)}`);
324
- writeLine(`\tmdate=${JSON.stringify(mdate)}`);
325
- if (datum.value.dname != null) {
326
- do {
327
- const value = datum.value.dvalue === 'N'
328
- ? JSON.stringify(datum.value.number)
329
- : datum.value.dvalue === 'S'
330
- ? JSON.stringify(datum.value.string)
331
- : datum.value.dvalue;
332
- writeLine(`\t${datum.value.dname}=${value}`);
333
- datum = dataIterator.next();
334
- } while (!datum.done && datum.value.guid === guid);
493
+ // Export entities.
494
+ const entityIterator = this.exportEntityInternal(etype);
495
+ for await (let content of entityIterator) {
496
+ yield { type: 'entity', content };
497
+ }
498
+ }
499
+ }
500
+ async exportEntity(etype, guid) {
501
+ const entityIterator = this.exportEntityInternal(etype, guid);
502
+ let exportContent = '';
503
+ for await (let content of entityIterator) {
504
+ exportContent += content;
505
+ }
506
+ return exportContent;
507
+ }
508
+ async *exportEntityInternal(etype, guid) {
509
+ // Export entities.
510
+ const dataIterator = this.queryArray(`SELECT e."guid", e."tags", e."cdate", e."mdate", e."user", e."group", e."acUser", e."acGroup", e."acOther", e."acRead", e."acWrite", e."acFull", d."name", d."value", json(d."json") as "json", d."string", d."number" FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} e LEFT JOIN ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} d USING ("guid")${guid == null ? '' : ` WHERE e."guid"=@guid`} ORDER BY e."guid", d."name";`, guid == null ? undefined : { params: { guid } })[Symbol.iterator]();
511
+ let datum = dataIterator.next();
512
+ while (!datum.done) {
513
+ const guid = datum.value.guid;
514
+ const tags = datum.value.tags.slice(1, -1);
515
+ const cdate = datum.value.cdate;
516
+ const mdate = datum.value.mdate;
517
+ const user = datum.value.user;
518
+ const group = datum.value.group;
519
+ const acUser = datum.value.acUser;
520
+ const acGroup = datum.value.acGroup;
521
+ const acOther = datum.value.acOther;
522
+ const acRead = datum.value.acRead?.slice(1, -1).split(',');
523
+ const acWrite = datum.value.acWrite?.slice(1, -1).split(',');
524
+ const acFull = datum.value.acFull?.slice(1, -1).split(',');
525
+ let currentEntityExport = [];
526
+ currentEntityExport.push(`{${guid}}<${etype}>[${tags}]`);
527
+ currentEntityExport.push(`\tcdate=${JSON.stringify(cdate)}`);
528
+ currentEntityExport.push(`\tmdate=${JSON.stringify(mdate)}`);
529
+ if (this.nymph.tilmeld != null) {
530
+ if (user != null) {
531
+ currentEntityExport.push(`\tuser=${JSON.stringify(['nymph_entity_reference', user, 'User'])}`);
335
532
  }
336
- else {
337
- datum = dataIterator.next();
533
+ if (group != null) {
534
+ currentEntityExport.push(`\tgroup=${JSON.stringify(['nymph_entity_reference', group, 'Group'])}`);
535
+ }
536
+ if (acUser != null) {
537
+ currentEntityExport.push(`\tacUser=${JSON.stringify(acUser)}`);
538
+ }
539
+ if (acGroup != null) {
540
+ currentEntityExport.push(`\tacGroup=${JSON.stringify(acGroup)}`);
541
+ }
542
+ if (acOther != null) {
543
+ currentEntityExport.push(`\tacOther=${JSON.stringify(acOther)}`);
544
+ }
545
+ if (acRead != null) {
546
+ currentEntityExport.push(`\tacRead=${JSON.stringify(acRead)}`);
547
+ }
548
+ if (acWrite != null) {
549
+ currentEntityExport.push(`\tacWrite=${JSON.stringify(acWrite)}`);
550
+ }
551
+ if (acFull != null) {
552
+ currentEntityExport.push(`\tacFull=${JSON.stringify(acFull)}`);
338
553
  }
339
554
  }
555
+ if (datum.value.name != null) {
556
+ // This do will keep going and adding the data until the
557
+ // next entity is reached. datum will end on the next entity.
558
+ do {
559
+ const value = datum.value.value === 'N'
560
+ ? JSON.stringify(datum.value.number)
561
+ : datum.value.value === 'S'
562
+ ? JSON.stringify(datum.value.string)
563
+ : datum.value.value === 'J'
564
+ ? datum.value.json
565
+ : datum.value.value;
566
+ currentEntityExport.push(`\t${datum.value.name}=${value}`);
567
+ datum = dataIterator.next();
568
+ } while (!datum.done && datum.value.guid === guid);
569
+ }
570
+ else {
571
+ // Make sure that datum is incremented :)
572
+ datum = dataIterator.next();
573
+ }
574
+ currentEntityExport.push('');
575
+ if (yield currentEntityExport.join('\n')) {
576
+ return;
577
+ }
340
578
  }
341
- return;
342
579
  }
343
- makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = []) {
580
+ /**
581
+ * Generate the SQLite3 query.
582
+ * @param options The options array.
583
+ * @param formattedSelectors The formatted selector array.
584
+ * @param etype
585
+ * @param count Used to track internal params.
586
+ * @param params Used to store internal params.
587
+ * @param subquery Whether only a subquery should be returned.
588
+ * @returns The SQL query.
589
+ */
590
+ makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = [], guidSelector = undefined) {
344
591
  if (typeof options.class?.alterOptions === 'function') {
345
592
  options = options.class.alterOptions(options);
346
593
  }
347
594
  const eTable = `e${tableSuffix}`;
348
595
  const dTable = `d${tableSuffix}`;
349
- const cTable = `c${tableSuffix}`;
350
596
  const fTable = `f${tableSuffix}`;
351
597
  const ieTable = `ie${tableSuffix}`;
352
- const sort = options.sort ?? 'cdate';
353
- const queryParts = this.iterateSelectorsForQuery(formattedSelectors, (key, value, typeIsOr, typeIsNot) => {
598
+ const sTable = `s${tableSuffix}`;
599
+ const sort = options.sort === undefined ? 'cdate' : options.sort;
600
+ const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => {
354
601
  const clauseNot = key.startsWith('!');
355
602
  let curQuery = '';
356
603
  for (const curValue of value) {
@@ -363,7 +610,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
363
610
  }
364
611
  const guid = `param${++count.i}`;
365
612
  curQuery +=
366
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
613
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
367
614
  ieTable +
368
615
  '."guid"=@' +
369
616
  guid;
@@ -378,7 +625,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
378
625
  }
379
626
  const tag = `param${++count.i}`;
380
627
  curQuery +=
381
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
628
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
382
629
  ieTable +
383
630
  '."tags" LIKE @' +
384
631
  tag +
@@ -398,17 +645,38 @@ class SQLite3Driver extends nymph_1.NymphDriver {
398
645
  if (curQuery) {
399
646
  curQuery += typeIsOr ? ' OR ' : ' AND ';
400
647
  }
401
- const name = `param${++count.i}`;
402
- curQuery +=
403
- ieTable +
404
- '."guid" ' +
405
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
406
- 'IN (SELECT "guid" FROM ' +
407
- SQLite3Driver.escape(this.prefix + 'data_' + etype) +
408
- ' WHERE "name"=@' +
409
- name +
410
- ')';
411
- params[name] = curVar;
648
+ if (curVar === 'cdate' ||
649
+ curVar === 'mdate' ||
650
+ (this.nymph.tilmeld != null &&
651
+ (curVar === 'user' ||
652
+ curVar === 'group' ||
653
+ curVar === 'acUser' ||
654
+ curVar === 'acGroup' ||
655
+ curVar === 'acOther' ||
656
+ curVar === 'acRead' ||
657
+ curVar === 'acWrite' ||
658
+ curVar === 'acFull'))) {
659
+ curQuery +=
660
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
661
+ '(' +
662
+ ieTable +
663
+ '.' +
664
+ SQLite3Driver.escape(curVar) +
665
+ ' IS NOT NULL)';
666
+ }
667
+ else {
668
+ const name = `param${++count.i}`;
669
+ curQuery +=
670
+ ieTable +
671
+ '."guid" ' +
672
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
673
+ 'IN (SELECT "guid" FROM ' +
674
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
675
+ ' WHERE "name"=@' +
676
+ name +
677
+ ')';
678
+ params[name] = curVar;
679
+ }
412
680
  }
413
681
  break;
414
682
  case 'truthy':
@@ -417,30 +685,34 @@ class SQLite3Driver extends nymph_1.NymphDriver {
417
685
  if (curQuery) {
418
686
  curQuery += typeIsOr ? ' OR ' : ' AND ';
419
687
  }
420
- if (curVar === 'cdate') {
421
- curQuery +=
422
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
423
- '(' +
424
- ieTable +
425
- '."cdate" NOT NULL)';
426
- break;
427
- }
428
- else if (curVar === 'mdate') {
688
+ if (curVar === 'cdate' ||
689
+ curVar === 'mdate' ||
690
+ (this.nymph.tilmeld != null &&
691
+ (curVar === 'user' ||
692
+ curVar === 'group' ||
693
+ curVar === 'acUser' ||
694
+ curVar === 'acGroup' ||
695
+ curVar === 'acOther' ||
696
+ curVar === 'acRead' ||
697
+ curVar === 'acWrite' ||
698
+ curVar === 'acFull'))) {
429
699
  curQuery +=
430
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
700
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
431
701
  '(' +
432
702
  ieTable +
433
- '."mdate" NOT NULL)';
434
- break;
703
+ '.' +
704
+ SQLite3Driver.escape(curVar) +
705
+ ' IS NOT NULL)';
435
706
  }
436
707
  else {
437
708
  const name = `param${++count.i}`;
438
709
  curQuery +=
439
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
710
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
711
+ 'EXISTS (SELECT "guid" FROM ' +
712
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
713
+ ' WHERE "guid"=' +
440
714
  ieTable +
441
- '."guid" IN (SELECT "guid" FROM ' +
442
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
443
- ' WHERE "name"=@' +
715
+ '."guid" AND "name"=@' +
444
716
  name +
445
717
  ' AND "truthy"=1)';
446
718
  params[name] = curVar;
@@ -449,31 +721,58 @@ class SQLite3Driver extends nymph_1.NymphDriver {
449
721
  break;
450
722
  case 'equal':
451
723
  case '!equal':
452
- if (curValue[0] === 'cdate') {
724
+ if (curValue[0] === 'cdate' ||
725
+ curValue[0] === 'mdate' ||
726
+ (this.nymph.tilmeld != null &&
727
+ (curValue[0] === 'acUser' ||
728
+ curValue[0] === 'acGroup' ||
729
+ curValue[0] === 'acOther'))) {
453
730
  if (curQuery) {
454
731
  curQuery += typeIsOr ? ' OR ' : ' AND ';
455
732
  }
456
- const cdate = `param${++count.i}`;
733
+ const value = `param${++count.i}`;
457
734
  curQuery +=
458
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
735
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
459
736
  ieTable +
460
- '."cdate"=@' +
461
- cdate;
462
- params[cdate] = Number(curValue[1]);
463
- break;
737
+ '.' +
738
+ SQLite3Driver.escape(curValue[0]) +
739
+ '=@' +
740
+ value;
741
+ params[value] = Number(curValue[1]);
464
742
  }
465
- else if (curValue[0] === 'mdate') {
743
+ else if (this.nymph.tilmeld != null &&
744
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
466
745
  if (curQuery) {
467
746
  curQuery += typeIsOr ? ' OR ' : ' AND ';
468
747
  }
469
- const mdate = `param${++count.i}`;
748
+ const value = `param${++count.i}`;
470
749
  curQuery +=
471
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
750
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
472
751
  ieTable +
473
- '."mdate"=@' +
474
- mdate;
475
- params[mdate] = Number(curValue[1]);
476
- break;
752
+ '.' +
753
+ SQLite3Driver.escape(curValue[0]) +
754
+ '=@' +
755
+ value;
756
+ params[value] = `${curValue[1]}`;
757
+ }
758
+ else if (this.nymph.tilmeld != null &&
759
+ (curValue[0] === 'acRead' ||
760
+ curValue[0] === 'acWrite' ||
761
+ curValue[0] === 'acFull')) {
762
+ if (curQuery) {
763
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
764
+ }
765
+ const value = `param${++count.i}`;
766
+ curQuery +=
767
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
768
+ ieTable +
769
+ '.' +
770
+ SQLite3Driver.escape(curValue[0]) +
771
+ '=@' +
772
+ value;
773
+ params[value] = Array.isArray(curValue[1])
774
+ ? ',' + curValue[1].join(',') + ','
775
+ : '';
477
776
  }
478
777
  else if (typeof curValue[1] === 'number') {
479
778
  if (curQuery) {
@@ -482,11 +781,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
482
781
  const name = `param${++count.i}`;
483
782
  const value = `param${++count.i}`;
484
783
  curQuery +=
485
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
784
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
785
+ 'EXISTS (SELECT "guid" FROM ' +
786
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
787
+ ' WHERE "guid"=' +
486
788
  ieTable +
487
- '."guid" IN (SELECT "guid" FROM ' +
488
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
489
- ' WHERE "name"=@' +
789
+ '."guid" AND "name"=@' +
490
790
  name +
491
791
  ' AND "number"=@' +
492
792
  value +
@@ -501,11 +801,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
501
801
  const name = `param${++count.i}`;
502
802
  const value = `param${++count.i}`;
503
803
  curQuery +=
504
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
804
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
805
+ 'EXISTS (SELECT "guid" FROM ' +
806
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
807
+ ' WHERE "guid"=' +
505
808
  ieTable +
506
- '."guid" IN (SELECT "guid" FROM ' +
507
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
508
- ' WHERE "name"=@' +
809
+ '."guid" AND "name"=@' +
509
810
  name +
510
811
  ' AND "string"=@' +
511
812
  value +
@@ -528,134 +829,273 @@ class SQLite3Driver extends nymph_1.NymphDriver {
528
829
  const name = `param${++count.i}`;
529
830
  const value = `param${++count.i}`;
530
831
  curQuery +=
531
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
532
- ieTable +
533
- '."guid" IN (SELECT "guid" FROM ' +
832
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
833
+ 'EXISTS (SELECT "guid" FROM ' +
534
834
  SQLite3Driver.escape(this.prefix + 'data_' + etype) +
535
- ' WHERE "name"=@' +
835
+ ' WHERE "guid"=' +
836
+ ieTable +
837
+ '."guid" AND "name"=@' +
536
838
  name +
537
- ' AND "value"=@' +
839
+ ' AND "json"=jsonb(@' +
538
840
  value +
539
- ')';
841
+ '))';
540
842
  params[name] = curValue[0];
541
843
  params[value] = svalue;
542
844
  }
543
845
  break;
544
846
  case 'contain':
545
847
  case '!contain':
546
- if (curValue[0] === 'cdate') {
848
+ if (curValue[0] === 'cdate' ||
849
+ curValue[0] === 'mdate' ||
850
+ (this.nymph.tilmeld != null &&
851
+ (curValue[0] === 'acUser' ||
852
+ curValue[0] === 'acGroup' ||
853
+ curValue[0] === 'acOther'))) {
547
854
  if (curQuery) {
548
855
  curQuery += typeIsOr ? ' OR ' : ' AND ';
549
856
  }
550
- const cdate = `param${++count.i}`;
857
+ const value = `param${++count.i}`;
551
858
  curQuery +=
552
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
859
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
553
860
  ieTable +
554
- '."cdate"=' +
555
- cdate;
556
- params[cdate] = Number(curValue[1]);
557
- break;
861
+ '.' +
862
+ SQLite3Driver.escape(curValue[0]) +
863
+ '=@' +
864
+ value;
865
+ params[value] = Number(curValue[1]);
558
866
  }
559
- else if (curValue[0] === 'mdate') {
867
+ else if (this.nymph.tilmeld != null &&
868
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
560
869
  if (curQuery) {
561
870
  curQuery += typeIsOr ? ' OR ' : ' AND ';
562
871
  }
563
- const mdate = `param${++count.i}`;
872
+ const value = `param${++count.i}`;
564
873
  curQuery +=
565
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
874
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
566
875
  ieTable +
567
- '."mdate"=' +
568
- mdate;
569
- params[mdate] = Number(curValue[1]);
570
- break;
876
+ '.' +
877
+ SQLite3Driver.escape(curValue[0]) +
878
+ '=@' +
879
+ value;
880
+ params[value] = `${curValue[1]}`;
881
+ }
882
+ else if (this.nymph.tilmeld != null &&
883
+ (curValue[0] === 'acRead' ||
884
+ curValue[0] === 'acWrite' ||
885
+ curValue[0] === 'acFull')) {
886
+ if (curQuery) {
887
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
888
+ }
889
+ const id = `param${++count.i}`;
890
+ curQuery +=
891
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
892
+ ieTable +
893
+ '.' +
894
+ SQLite3Driver.escape(curValue[0]) +
895
+ ' LIKE @' +
896
+ id +
897
+ " ESCAPE '\\'";
898
+ params[id] =
899
+ '%,' +
900
+ curValue[1]
901
+ .replace('\\', '\\\\')
902
+ .replace('%', '\\%')
903
+ .replace('_', '\\_') +
904
+ ',%';
571
905
  }
572
906
  else {
907
+ const containTableSuffix = makeTableSuffix();
573
908
  if (curQuery) {
574
909
  curQuery += typeIsOr ? ' OR ' : ' AND ';
575
910
  }
576
911
  let svalue;
577
- let stringValue;
578
912
  if (curValue[1] instanceof Object &&
579
913
  typeof curValue[1].toReference === 'function') {
580
914
  svalue = JSON.stringify(curValue[1].toReference());
581
- stringValue = `${curValue[1].toReference()}`;
582
915
  }
583
916
  else {
584
917
  svalue = JSON.stringify(curValue[1]);
585
- stringValue = `${curValue[1]}`;
586
918
  }
587
919
  const name = `param${++count.i}`;
588
920
  const value = `param${++count.i}`;
589
- if (typeof curValue[1] === 'string') {
590
- const stringParam = `param${++count.i}`;
591
- curQuery +=
592
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
593
- '(' +
921
+ curQuery +=
922
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
923
+ 'EXISTS (SELECT "guid" FROM ' +
924
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
925
+ ' d' +
926
+ containTableSuffix +
927
+ ' WHERE "guid"=' +
928
+ ieTable +
929
+ '."guid" AND "name"=@' +
930
+ name +
931
+ ' AND json(@' +
932
+ value +
933
+ ') IN (SELECT json_quote("value") FROM json_each(d' +
934
+ containTableSuffix +
935
+ '."json")))';
936
+ params[name] = curValue[0];
937
+ params[value] = svalue;
938
+ }
939
+ break;
940
+ case 'search':
941
+ case '!search':
942
+ if (curValue[0] === 'cdate' ||
943
+ curValue[0] === 'mdate' ||
944
+ (this.nymph.tilmeld != null &&
945
+ (curValue[0] === 'user' ||
946
+ curValue[0] === 'group' ||
947
+ curValue[0] === 'acUser' ||
948
+ curValue[0] === 'acGroup' ||
949
+ curValue[0] === 'acOther' ||
950
+ curValue[0] === 'acRead' ||
951
+ curValue[0] === 'acWrite' ||
952
+ curValue[0] === 'acFull'))) {
953
+ if (curQuery) {
954
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
955
+ }
956
+ curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(0)';
957
+ }
958
+ else {
959
+ if (curQuery) {
960
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
961
+ }
962
+ const parsedFTSQuery = this.tokenizer.parseSearchQuery(curValue[1]);
963
+ if (!parsedFTSQuery.length) {
964
+ curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(0)';
965
+ }
966
+ else {
967
+ const name = `param${++count.i}`;
968
+ const queryPartToken = (term) => {
969
+ const value = `param${++count.i}`;
970
+ params[value] = term.token;
971
+ return ('EXISTS (SELECT "guid" FROM ' +
972
+ SQLite3Driver.escape(this.prefix + 'tokens_' + etype) +
973
+ ' WHERE "guid"=' +
594
974
  ieTable +
595
- '."guid" IN (SELECT "guid" FROM ' +
596
- SQLite3Driver.escape(this.prefix + 'data_' + etype) +
597
- ' WHERE "name"=@' +
975
+ '."guid" AND "name"=@' +
598
976
  name +
599
- ' AND instr("value", @' +
977
+ ' AND "token"=@' +
600
978
  value +
601
- ')) OR ' +
979
+ (term.nostemmed ? ' AND "stem"=0' : '') +
980
+ ')');
981
+ };
982
+ const queryPartSeries = (series) => {
983
+ const tokenTableSuffix = makeTableSuffix();
984
+ const tokenParts = series.tokens.map((token, i) => {
985
+ const value = `param${++count.i}`;
986
+ params[value] = token.token;
987
+ return {
988
+ fromClause: i === 0
989
+ ? 'FROM ' +
990
+ SQLite3Driver.escape(this.prefix + 'tokens_' + etype) +
991
+ ' t' +
992
+ tokenTableSuffix +
993
+ '0'
994
+ : 'JOIN ' +
995
+ SQLite3Driver.escape(this.prefix + 'tokens_' + etype) +
996
+ ' t' +
997
+ tokenTableSuffix +
998
+ i +
999
+ ' ON t' +
1000
+ tokenTableSuffix +
1001
+ i +
1002
+ '."guid" = t' +
1003
+ tokenTableSuffix +
1004
+ '0."guid" AND t' +
1005
+ tokenTableSuffix +
1006
+ i +
1007
+ '."name" = t' +
1008
+ tokenTableSuffix +
1009
+ '0."name" AND t' +
1010
+ tokenTableSuffix +
1011
+ i +
1012
+ '."position" = t' +
1013
+ tokenTableSuffix +
1014
+ '0."position" + ' +
1015
+ i,
1016
+ whereClause: 't' +
1017
+ tokenTableSuffix +
1018
+ i +
1019
+ '."token"=@' +
1020
+ value +
1021
+ (token.nostemmed
1022
+ ? ' AND t' + tokenTableSuffix + i + '."stem"=0'
1023
+ : ''),
1024
+ };
1025
+ });
1026
+ return ('EXISTS (SELECT t' +
1027
+ tokenTableSuffix +
1028
+ '0."guid" ' +
1029
+ tokenParts.map((part) => part.fromClause).join(' ') +
1030
+ ' WHERE t' +
1031
+ tokenTableSuffix +
1032
+ '0."guid"=' +
602
1033
  ieTable +
603
- '."guid" IN (SELECT "guid" FROM ' +
604
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
605
- ' WHERE "name"=@' +
1034
+ '."guid" AND t' +
1035
+ tokenTableSuffix +
1036
+ '0."name"=@' +
606
1037
  name +
607
- ' AND "string"=@' +
608
- stringParam +
609
- '))';
610
- params[stringParam] = stringValue;
611
- }
612
- else {
1038
+ ' AND ' +
1039
+ tokenParts.map((part) => part.whereClause).join(' AND ') +
1040
+ ')');
1041
+ };
1042
+ const queryPartTerm = (term) => {
1043
+ if (term.type === 'series') {
1044
+ return queryPartSeries(term);
1045
+ }
1046
+ else if (term.type === 'not') {
1047
+ return 'NOT ' + queryPartTerm(term.operand);
1048
+ }
1049
+ else if (term.type === 'or') {
1050
+ let queryParts = [];
1051
+ for (let operand of term.operands) {
1052
+ queryParts.push(queryPartTerm(operand));
1053
+ }
1054
+ return '(' + queryParts.join(' OR ') + ')';
1055
+ }
1056
+ return queryPartToken(term);
1057
+ };
1058
+ // Run through the query and add terms.
1059
+ let termStrings = [];
1060
+ for (let term of parsedFTSQuery) {
1061
+ termStrings.push(queryPartTerm(term));
1062
+ }
613
1063
  curQuery +=
614
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
615
- ieTable +
616
- '."guid" IN (SELECT "guid" FROM ' +
617
- SQLite3Driver.escape(this.prefix + 'data_' + etype) +
618
- ' WHERE "name"=@' +
619
- name +
620
- ' AND instr("value", @' +
621
- value +
622
- '))';
1064
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1065
+ '(' +
1066
+ termStrings.join(' AND ') +
1067
+ ')';
1068
+ params[name] = curValue[0];
623
1069
  }
624
- params[name] = curValue[0];
625
- params[value] = svalue;
626
1070
  }
627
1071
  break;
628
1072
  case 'match':
629
1073
  case '!match':
630
- if (curValue[0] === 'cdate') {
1074
+ if (curValue[0] === 'cdate' ||
1075
+ curValue[0] === 'mdate' ||
1076
+ (this.nymph.tilmeld != null &&
1077
+ (curValue[0] === 'user' ||
1078
+ curValue[0] === 'group' ||
1079
+ curValue[0] === 'acUser' ||
1080
+ curValue[0] === 'acGroup' ||
1081
+ curValue[0] === 'acOther' ||
1082
+ curValue[0] === 'acRead' ||
1083
+ curValue[0] === 'acWrite' ||
1084
+ curValue[0] === 'acFull'))) {
631
1085
  if (curQuery) {
632
1086
  curQuery += typeIsOr ? ' OR ' : ' AND ';
633
1087
  }
634
- const cdate = `param${++count.i}`;
635
- curQuery +=
636
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
637
- '(' +
638
- ieTable +
639
- '."cdate" REGEXP @' +
640
- cdate +
641
- ')';
642
- params[cdate] = curValue[1];
643
- break;
644
- }
645
- else if (curValue[0] === 'mdate') {
646
- if (curQuery) {
647
- curQuery += typeIsOr ? ' OR ' : ' AND ';
648
- }
649
- const mdate = `param${++count.i}`;
1088
+ const value = `param${++count.i}`;
650
1089
  curQuery +=
651
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1090
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
652
1091
  '(' +
653
1092
  ieTable +
654
- '."mdate" REGEXP @' +
655
- mdate +
1093
+ '.' +
1094
+ SQLite3Driver.escape(curValue[0]) +
1095
+ ' REGEXP @' +
1096
+ value +
656
1097
  ')';
657
- params[mdate] = curValue[1];
658
- break;
1098
+ params[value] = curValue[1];
659
1099
  }
660
1100
  else {
661
1101
  if (curQuery) {
@@ -664,11 +1104,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
664
1104
  const name = `param${++count.i}`;
665
1105
  const value = `param${++count.i}`;
666
1106
  curQuery +=
667
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1107
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1108
+ 'EXISTS (SELECT "guid" FROM ' +
1109
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1110
+ ' WHERE "guid"=' +
668
1111
  ieTable +
669
- '."guid" IN (SELECT "guid" FROM ' +
670
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
671
- ' WHERE "name"=@' +
1112
+ '."guid" AND "name"=@' +
672
1113
  name +
673
1114
  ' AND "string" REGEXP @' +
674
1115
  value +
@@ -679,35 +1120,31 @@ class SQLite3Driver extends nymph_1.NymphDriver {
679
1120
  break;
680
1121
  case 'imatch':
681
1122
  case '!imatch':
682
- if (curValue[0] === 'cdate') {
683
- if (curQuery) {
684
- curQuery += typeIsOr ? ' OR ' : ' AND ';
685
- }
686
- const cdate = `param${++count.i}`;
687
- curQuery +=
688
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
689
- '(' +
690
- ieTable +
691
- '."cdate" REGEXP @' +
692
- cdate +
693
- ')';
694
- params[cdate] = curValue[1];
695
- break;
696
- }
697
- else if (curValue[0] === 'mdate') {
1123
+ if (curValue[0] === 'cdate' ||
1124
+ curValue[0] === 'mdate' ||
1125
+ (this.nymph.tilmeld != null &&
1126
+ (curValue[0] === 'user' ||
1127
+ curValue[0] === 'group' ||
1128
+ curValue[0] === 'acUser' ||
1129
+ curValue[0] === 'acGroup' ||
1130
+ curValue[0] === 'acOther' ||
1131
+ curValue[0] === 'acRead' ||
1132
+ curValue[0] === 'acWrite' ||
1133
+ curValue[0] === 'acFull'))) {
698
1134
  if (curQuery) {
699
1135
  curQuery += typeIsOr ? ' OR ' : ' AND ';
700
1136
  }
701
- const mdate = `param${++count.i}`;
1137
+ const value = `param${++count.i}`;
702
1138
  curQuery +=
703
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
704
- '(' +
1139
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1140
+ '(lower(' +
705
1141
  ieTable +
706
- '."mdate" REGEXP @' +
707
- mdate +
708
- ')';
709
- params[mdate] = curValue[1];
710
- break;
1142
+ '.' +
1143
+ SQLite3Driver.escape(curValue[0]) +
1144
+ ') REGEXP lower(@' +
1145
+ value +
1146
+ '))';
1147
+ params[value] = curValue[1];
711
1148
  }
712
1149
  else {
713
1150
  if (curQuery) {
@@ -716,11 +1153,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
716
1153
  const name = `param${++count.i}`;
717
1154
  const value = `param${++count.i}`;
718
1155
  curQuery +=
719
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1156
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1157
+ 'EXISTS (SELECT "guid" FROM ' +
1158
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1159
+ ' WHERE "guid"=' +
720
1160
  ieTable +
721
- '."guid" IN (SELECT "guid" FROM ' +
722
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
723
- ' WHERE "name"=@' +
1161
+ '."guid" AND "name"=@' +
724
1162
  name +
725
1163
  ' AND lower("string") REGEXP lower(@' +
726
1164
  value +
@@ -731,35 +1169,31 @@ class SQLite3Driver extends nymph_1.NymphDriver {
731
1169
  break;
732
1170
  case 'like':
733
1171
  case '!like':
734
- if (curValue[0] === 'cdate') {
1172
+ if (curValue[0] === 'cdate' ||
1173
+ curValue[0] === 'mdate' ||
1174
+ (this.nymph.tilmeld != null &&
1175
+ (curValue[0] === 'user' ||
1176
+ curValue[0] === 'group' ||
1177
+ curValue[0] === 'acUser' ||
1178
+ curValue[0] === 'acGroup' ||
1179
+ curValue[0] === 'acOther' ||
1180
+ curValue[0] === 'acRead' ||
1181
+ curValue[0] === 'acWrite' ||
1182
+ curValue[0] === 'acFull'))) {
735
1183
  if (curQuery) {
736
1184
  curQuery += typeIsOr ? ' OR ' : ' AND ';
737
1185
  }
738
- const cdate = `param${++count.i}`;
739
- curQuery +=
740
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
741
- '(' +
742
- ieTable +
743
- '."cdate" LIKE @' +
744
- cdate +
745
- " ESCAPE '\\')";
746
- params[cdate] = curValue[1];
747
- break;
748
- }
749
- else if (curValue[0] === 'mdate') {
750
- if (curQuery) {
751
- curQuery += typeIsOr ? ' OR ' : ' AND ';
752
- }
753
- const mdate = `param${++count.i}`;
1186
+ const value = `param${++count.i}`;
754
1187
  curQuery +=
755
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1188
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
756
1189
  '(' +
757
1190
  ieTable +
758
- '."mdate" LIKE @' +
759
- mdate +
1191
+ '.' +
1192
+ SQLite3Driver.escape(curValue[0]) +
1193
+ ' LIKE @' +
1194
+ value +
760
1195
  " ESCAPE '\\')";
761
- params[mdate] = curValue[1];
762
- break;
1196
+ params[value] = curValue[1];
763
1197
  }
764
1198
  else {
765
1199
  if (curQuery) {
@@ -768,11 +1202,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
768
1202
  const name = `param${++count.i}`;
769
1203
  const value = `param${++count.i}`;
770
1204
  curQuery +=
771
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1205
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1206
+ 'EXISTS (SELECT "guid" FROM ' +
1207
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1208
+ ' WHERE "guid"=' +
772
1209
  ieTable +
773
- '."guid" IN (SELECT "guid" FROM ' +
774
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
775
- ' WHERE "name"=@' +
1210
+ '."guid" AND "name"=@' +
776
1211
  name +
777
1212
  ' AND "string" LIKE @' +
778
1213
  value +
@@ -783,35 +1218,31 @@ class SQLite3Driver extends nymph_1.NymphDriver {
783
1218
  break;
784
1219
  case 'ilike':
785
1220
  case '!ilike':
786
- if (curValue[0] === 'cdate') {
787
- if (curQuery) {
788
- curQuery += typeIsOr ? ' OR ' : ' AND ';
789
- }
790
- const cdate = `param${++count.i}`;
791
- curQuery +=
792
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
793
- '(' +
794
- ieTable +
795
- '."cdate" LIKE @' +
796
- cdate +
797
- " ESCAPE '\\')";
798
- params[cdate] = curValue[1];
799
- break;
800
- }
801
- else if (curValue[0] === 'mdate') {
1221
+ if (curValue[0] === 'cdate' ||
1222
+ curValue[0] === 'mdate' ||
1223
+ (this.nymph.tilmeld != null &&
1224
+ (curValue[0] === 'user' ||
1225
+ curValue[0] === 'group' ||
1226
+ curValue[0] === 'acUser' ||
1227
+ curValue[0] === 'acGroup' ||
1228
+ curValue[0] === 'acOther' ||
1229
+ curValue[0] === 'acRead' ||
1230
+ curValue[0] === 'acWrite' ||
1231
+ curValue[0] === 'acFull'))) {
802
1232
  if (curQuery) {
803
1233
  curQuery += typeIsOr ? ' OR ' : ' AND ';
804
1234
  }
805
- const mdate = `param${++count.i}`;
1235
+ const value = `param${++count.i}`;
806
1236
  curQuery +=
807
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
808
- '(' +
1237
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1238
+ '(lower(' +
809
1239
  ieTable +
810
- '."mdate" LIKE @' +
811
- mdate +
812
- " ESCAPE '\\')";
813
- params[mdate] = curValue[1];
814
- break;
1240
+ '.' +
1241
+ SQLite3Driver.escape(curValue[0]) +
1242
+ ') LIKE lower(@' +
1243
+ value +
1244
+ ") ESCAPE '\\')";
1245
+ params[value] = curValue[1];
815
1246
  }
816
1247
  else {
817
1248
  if (curQuery) {
@@ -820,11 +1251,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
820
1251
  const name = `param${++count.i}`;
821
1252
  const value = `param${++count.i}`;
822
1253
  curQuery +=
823
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1254
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1255
+ 'EXISTS (SELECT "guid" FROM ' +
1256
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1257
+ ' WHERE "guid"=' +
824
1258
  ieTable +
825
- '."guid" IN (SELECT "guid" FROM ' +
826
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
827
- ' WHERE "name"=@' +
1259
+ '."guid" AND "name"=@' +
828
1260
  name +
829
1261
  ' AND lower("string") LIKE lower(@' +
830
1262
  value +
@@ -835,31 +1267,29 @@ class SQLite3Driver extends nymph_1.NymphDriver {
835
1267
  break;
836
1268
  case 'gt':
837
1269
  case '!gt':
838
- if (curValue[0] === 'cdate') {
1270
+ if (curValue[0] === 'cdate' ||
1271
+ curValue[0] === 'mdate' ||
1272
+ (this.nymph.tilmeld != null &&
1273
+ (curValue[0] === 'user' ||
1274
+ curValue[0] === 'group' ||
1275
+ curValue[0] === 'acUser' ||
1276
+ curValue[0] === 'acGroup' ||
1277
+ curValue[0] === 'acOther' ||
1278
+ curValue[0] === 'acRead' ||
1279
+ curValue[0] === 'acWrite' ||
1280
+ curValue[0] === 'acFull'))) {
839
1281
  if (curQuery) {
840
1282
  curQuery += typeIsOr ? ' OR ' : ' AND ';
841
1283
  }
842
- const cdate = `param${++count.i}`;
843
- curQuery +=
844
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
845
- ieTable +
846
- '."cdate">@' +
847
- cdate;
848
- params[cdate] = Number(curValue[1]);
849
- break;
850
- }
851
- else if (curValue[0] === 'mdate') {
852
- if (curQuery) {
853
- curQuery += typeIsOr ? ' OR ' : ' AND ';
854
- }
855
- const mdate = `param${++count.i}`;
1284
+ const value = `param${++count.i}`;
856
1285
  curQuery +=
857
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1286
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
858
1287
  ieTable +
859
- '."mdate">@' +
860
- mdate;
861
- params[mdate] = Number(curValue[1]);
862
- break;
1288
+ '.' +
1289
+ SQLite3Driver.escape(curValue[0]) +
1290
+ '>@' +
1291
+ value;
1292
+ params[value] = Number(curValue[1]);
863
1293
  }
864
1294
  else {
865
1295
  if (curQuery) {
@@ -868,11 +1298,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
868
1298
  const name = `param${++count.i}`;
869
1299
  const value = `param${++count.i}`;
870
1300
  curQuery +=
871
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1301
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1302
+ 'EXISTS (SELECT "guid" FROM ' +
1303
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1304
+ ' WHERE "guid"=' +
872
1305
  ieTable +
873
- '."guid" IN (SELECT "guid" FROM ' +
874
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
875
- ' WHERE "name"=@' +
1306
+ '."guid" AND "name"=@' +
876
1307
  name +
877
1308
  ' AND "number">@' +
878
1309
  value +
@@ -883,31 +1314,29 @@ class SQLite3Driver extends nymph_1.NymphDriver {
883
1314
  break;
884
1315
  case 'gte':
885
1316
  case '!gte':
886
- if (curValue[0] === 'cdate') {
887
- if (curQuery) {
888
- curQuery += typeIsOr ? ' OR ' : ' AND ';
889
- }
890
- const cdate = `param${++count.i}`;
891
- curQuery +=
892
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
893
- ieTable +
894
- '."cdate">=@' +
895
- cdate;
896
- params[cdate] = Number(curValue[1]);
897
- break;
898
- }
899
- else if (curValue[0] === 'mdate') {
1317
+ if (curValue[0] === 'cdate' ||
1318
+ curValue[0] === 'mdate' ||
1319
+ (this.nymph.tilmeld != null &&
1320
+ (curValue[0] === 'user' ||
1321
+ curValue[0] === 'group' ||
1322
+ curValue[0] === 'acUser' ||
1323
+ curValue[0] === 'acGroup' ||
1324
+ curValue[0] === 'acOther' ||
1325
+ curValue[0] === 'acRead' ||
1326
+ curValue[0] === 'acWrite' ||
1327
+ curValue[0] === 'acFull'))) {
900
1328
  if (curQuery) {
901
1329
  curQuery += typeIsOr ? ' OR ' : ' AND ';
902
1330
  }
903
- const mdate = `param${++count.i}`;
1331
+ const value = `param${++count.i}`;
904
1332
  curQuery +=
905
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1333
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
906
1334
  ieTable +
907
- '."mdate">=@' +
908
- mdate;
909
- params[mdate] = Number(curValue[1]);
910
- break;
1335
+ '.' +
1336
+ SQLite3Driver.escape(curValue[0]) +
1337
+ '>=@' +
1338
+ value;
1339
+ params[value] = Number(curValue[1]);
911
1340
  }
912
1341
  else {
913
1342
  if (curQuery) {
@@ -916,11 +1345,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
916
1345
  const name = `param${++count.i}`;
917
1346
  const value = `param${++count.i}`;
918
1347
  curQuery +=
919
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1348
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1349
+ 'EXISTS (SELECT "guid" FROM ' +
1350
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1351
+ ' WHERE "guid"=' +
920
1352
  ieTable +
921
- '."guid" IN (SELECT "guid" FROM ' +
922
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
923
- ' WHERE "name"=@' +
1353
+ '."guid" AND "name"=@' +
924
1354
  name +
925
1355
  ' AND "number">=@' +
926
1356
  value +
@@ -931,31 +1361,29 @@ class SQLite3Driver extends nymph_1.NymphDriver {
931
1361
  break;
932
1362
  case 'lt':
933
1363
  case '!lt':
934
- if (curValue[0] === 'cdate') {
1364
+ if (curValue[0] === 'cdate' ||
1365
+ curValue[0] === 'mdate' ||
1366
+ (this.nymph.tilmeld != null &&
1367
+ (curValue[0] === 'user' ||
1368
+ curValue[0] === 'group' ||
1369
+ curValue[0] === 'acUser' ||
1370
+ curValue[0] === 'acGroup' ||
1371
+ curValue[0] === 'acOther' ||
1372
+ curValue[0] === 'acRead' ||
1373
+ curValue[0] === 'acWrite' ||
1374
+ curValue[0] === 'acFull'))) {
935
1375
  if (curQuery) {
936
1376
  curQuery += typeIsOr ? ' OR ' : ' AND ';
937
1377
  }
938
- const cdate = `param${++count.i}`;
939
- curQuery +=
940
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
941
- ieTable +
942
- '."cdate"<@' +
943
- cdate;
944
- params[cdate] = Number(curValue[1]);
945
- break;
946
- }
947
- else if (curValue[0] === 'mdate') {
948
- if (curQuery) {
949
- curQuery += typeIsOr ? ' OR ' : ' AND ';
950
- }
951
- const mdate = `param${++count.i}`;
1378
+ const value = `param${++count.i}`;
952
1379
  curQuery +=
953
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1380
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
954
1381
  ieTable +
955
- '."mdate"<@' +
956
- mdate;
957
- params[mdate] = Number(curValue[1]);
958
- break;
1382
+ '.' +
1383
+ SQLite3Driver.escape(curValue[0]) +
1384
+ '<@' +
1385
+ value;
1386
+ params[value] = Number(curValue[1]);
959
1387
  }
960
1388
  else {
961
1389
  if (curQuery) {
@@ -964,11 +1392,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
964
1392
  const name = `param${++count.i}`;
965
1393
  const value = `param${++count.i}`;
966
1394
  curQuery +=
967
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1395
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1396
+ 'EXISTS (SELECT "guid" FROM ' +
1397
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1398
+ ' WHERE "guid"=' +
968
1399
  ieTable +
969
- '."guid" IN (SELECT "guid" FROM ' +
970
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
971
- ' WHERE "name"=@' +
1400
+ '."guid" AND "name"=@' +
972
1401
  name +
973
1402
  ' AND "number"<@' +
974
1403
  value +
@@ -979,31 +1408,29 @@ class SQLite3Driver extends nymph_1.NymphDriver {
979
1408
  break;
980
1409
  case 'lte':
981
1410
  case '!lte':
982
- if (curValue[0] === 'cdate') {
983
- if (curQuery) {
984
- curQuery += typeIsOr ? ' OR ' : ' AND ';
985
- }
986
- const cdate = `param${++count.i}`;
987
- curQuery +=
988
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
989
- ieTable +
990
- '."cdate"<=@' +
991
- cdate;
992
- params[cdate] = Number(curValue[1]);
993
- break;
994
- }
995
- else if (curValue[0] === 'mdate') {
1411
+ if (curValue[0] === 'cdate' ||
1412
+ curValue[0] === 'mdate' ||
1413
+ (this.nymph.tilmeld != null &&
1414
+ (curValue[0] === 'user' ||
1415
+ curValue[0] === 'group' ||
1416
+ curValue[0] === 'acUser' ||
1417
+ curValue[0] === 'acGroup' ||
1418
+ curValue[0] === 'acOther' ||
1419
+ curValue[0] === 'acRead' ||
1420
+ curValue[0] === 'acWrite' ||
1421
+ curValue[0] === 'acFull'))) {
996
1422
  if (curQuery) {
997
1423
  curQuery += typeIsOr ? ' OR ' : ' AND ';
998
1424
  }
999
- const mdate = `param${++count.i}`;
1425
+ const value = `param${++count.i}`;
1000
1426
  curQuery +=
1001
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1427
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1002
1428
  ieTable +
1003
- '."mdate"<=@' +
1004
- mdate;
1005
- params[mdate] = Number(curValue[1]);
1006
- break;
1429
+ '.' +
1430
+ SQLite3Driver.escape(curValue[0]) +
1431
+ '<=@' +
1432
+ value;
1433
+ params[value] = Number(curValue[1]);
1007
1434
  }
1008
1435
  else {
1009
1436
  if (curQuery) {
@@ -1012,11 +1439,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1012
1439
  const name = `param${++count.i}`;
1013
1440
  const value = `param${++count.i}`;
1014
1441
  curQuery +=
1015
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1442
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1443
+ 'EXISTS (SELECT "guid" FROM ' +
1444
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1445
+ ' WHERE "guid"=' +
1016
1446
  ieTable +
1017
- '."guid" IN (SELECT "guid" FROM ' +
1018
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
1019
- ' WHERE "name"=@' +
1447
+ '."guid" AND "name"=@' +
1020
1448
  name +
1021
1449
  ' AND "number"<=@' +
1022
1450
  value +
@@ -1040,71 +1468,205 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1040
1468
  if (curQuery) {
1041
1469
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1042
1470
  }
1043
- const name = `param${++count.i}`;
1044
- const guid = `param${++count.i}`;
1045
- curQuery +=
1046
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1047
- ieTable +
1048
- '."guid" IN (SELECT "guid" FROM ' +
1049
- SQLite3Driver.escape(this.prefix + 'references_' + etype) +
1050
- ' WHERE "name"=@' +
1051
- name +
1052
- ' AND "reference"=@' +
1053
- guid +
1054
- ')';
1055
- params[name] = curValue[0];
1056
- params[guid] = curQguid;
1471
+ if (this.nymph.tilmeld != null &&
1472
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
1473
+ const guid = `param${++count.i}`;
1474
+ curQuery +=
1475
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1476
+ ieTable +
1477
+ '.' +
1478
+ SQLite3Driver.escape(curValue[0]) +
1479
+ '=@' +
1480
+ guid;
1481
+ params[guid] = curQguid;
1482
+ }
1483
+ else if (this.nymph.tilmeld != null &&
1484
+ (curValue[0] === 'acRead' ||
1485
+ curValue[0] === 'acWrite' ||
1486
+ curValue[0] === 'acFull')) {
1487
+ const guid = `param${++count.i}`;
1488
+ curQuery +=
1489
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1490
+ ieTable +
1491
+ '.' +
1492
+ SQLite3Driver.escape(curValue[0]) +
1493
+ ' LIKE @' +
1494
+ guid +
1495
+ " ESCAPE '\\'";
1496
+ params[guid] =
1497
+ '%,' +
1498
+ curQguid
1499
+ .replace('\\', '\\\\')
1500
+ .replace('%', '\\%')
1501
+ .replace('_', '\\_') +
1502
+ ',%';
1503
+ }
1504
+ else {
1505
+ const name = `param${++count.i}`;
1506
+ const guid = `param${++count.i}`;
1507
+ curQuery +=
1508
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1509
+ 'EXISTS (SELECT "guid" FROM ' +
1510
+ SQLite3Driver.escape(this.prefix + 'references_' + etype) +
1511
+ ' WHERE "guid"=' +
1512
+ ieTable +
1513
+ '."guid" AND "name"=@' +
1514
+ name +
1515
+ ' AND "reference"=@' +
1516
+ guid +
1517
+ ')';
1518
+ params[name] = curValue[0];
1519
+ params[guid] = curQguid;
1520
+ }
1057
1521
  break;
1058
1522
  case 'selector':
1059
1523
  case '!selector':
1060
- const subquery = this.makeEntityQuery(options, [curValue], etype, count, params, true, tableSuffix, etypes);
1524
+ const innerquery = this.makeEntityQuery({ ...options, sort: null, limit: undefined }, [curValue], etype, count, params, true, tableSuffix, etypes);
1061
1525
  if (curQuery) {
1062
1526
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1063
1527
  }
1064
1528
  curQuery +=
1065
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1529
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1066
1530
  '(' +
1067
- subquery.query +
1531
+ innerquery.query +
1068
1532
  ')';
1069
1533
  break;
1070
1534
  case 'qref':
1071
1535
  case '!qref':
1536
+ const referenceTableSuffix = makeTableSuffix();
1072
1537
  const [qrefOptions, ...qrefSelectors] = curValue[1];
1073
1538
  const QrefEntityClass = qrefOptions.class;
1074
1539
  etypes.push(QrefEntityClass.ETYPE);
1075
- const qrefQuery = this.makeEntityQuery({ ...qrefOptions, return: 'guid', class: QrefEntityClass }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, (0, guid_1.makeTableSuffix)(), etypes);
1076
1540
  if (curQuery) {
1077
1541
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1078
1542
  }
1079
- const qrefName = `param${++count.i}`;
1080
- curQuery +=
1081
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1082
- ieTable +
1083
- '."guid" IN (SELECT "guid" FROM ' +
1084
- SQLite3Driver.escape(this.prefix + 'references_' + etype) +
1085
- ' WHERE "name"=@' +
1086
- qrefName +
1087
- ' AND "reference" IN (' +
1088
- qrefQuery.query +
1089
- '))';
1090
- params[qrefName] = curValue[0];
1543
+ if (this.nymph.tilmeld != null &&
1544
+ (curValue[0] === 'user' || curValue[0] === 'group')) {
1545
+ const qrefQuery = this.makeEntityQuery({
1546
+ ...qrefOptions,
1547
+ sort: qrefOptions.sort ?? null,
1548
+ return: 'guid',
1549
+ class: QrefEntityClass,
1550
+ }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' +
1551
+ referenceTableSuffix +
1552
+ '.' +
1553
+ SQLite3Driver.escape(curValue[0]));
1554
+ curQuery +=
1555
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1556
+ 'EXISTS (SELECT "guid" FROM ' +
1557
+ SQLite3Driver.escape(this.prefix + 'entities_' + etype) +
1558
+ ' r' +
1559
+ referenceTableSuffix +
1560
+ ' WHERE r' +
1561
+ referenceTableSuffix +
1562
+ '."guid"=' +
1563
+ ieTable +
1564
+ '."guid" AND EXISTS (' +
1565
+ qrefQuery.query +
1566
+ '))';
1567
+ }
1568
+ else if (this.nymph.tilmeld != null &&
1569
+ (curValue[0] === 'acRead' ||
1570
+ curValue[0] === 'acWrite' ||
1571
+ curValue[0] === 'acFull')) {
1572
+ const qrefQuery = this.makeEntityQuery({
1573
+ ...qrefOptions,
1574
+ sort: qrefOptions.sort ?? null,
1575
+ return: 'guid',
1576
+ class: QrefEntityClass,
1577
+ }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' + referenceTableSuffix + '."ref"');
1578
+ const splitTableSuffix = makeTableSuffix();
1579
+ curQuery +=
1580
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1581
+ `EXISTS (SELECT "guid", "ref" FROM (
1582
+ WITH RECURSIVE "spl${splitTableSuffix}" AS (
1583
+ SELECT
1584
+ "guid",
1585
+ SUBSTR(${SQLite3Driver.escape(curValue[0])}, 1, INSTR(${SQLite3Driver.escape(curValue[0])}, ',') - 1) AS "ref",
1586
+ SUBSTR(${SQLite3Driver.escape(curValue[0])}, INSTR(${SQLite3Driver.escape(curValue[0])}, ',') + 1) AS "remainder"
1587
+ FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)}
1588
+ UNION ALL
1589
+ SELECT
1590
+ "guid",
1591
+ SUBSTR("remainder", 1, INSTR("remainder", ',') - 1) AS "ref",
1592
+ SUBSTR("remainder", INSTR("remainder", ',') + 1) AS "remainder"
1593
+ FROM "spl${splitTableSuffix}" WHERE "remainder" != ''
1594
+ )
1595
+ SELECT "guid", "ref" FROM "spl${splitTableSuffix}" WHERE "ref" != ''
1596
+ ) ` +
1597
+ ' r' +
1598
+ referenceTableSuffix +
1599
+ ' WHERE r' +
1600
+ referenceTableSuffix +
1601
+ '."guid"=' +
1602
+ ieTable +
1603
+ '."guid" AND EXISTS (' +
1604
+ qrefQuery.query +
1605
+ '))';
1606
+ }
1607
+ else {
1608
+ const qrefQuery = this.makeEntityQuery({
1609
+ ...qrefOptions,
1610
+ sort: qrefOptions.sort ?? null,
1611
+ return: 'guid',
1612
+ class: QrefEntityClass,
1613
+ }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' + referenceTableSuffix + '."reference"');
1614
+ const qrefName = `param${++count.i}`;
1615
+ curQuery +=
1616
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1617
+ 'EXISTS (SELECT "guid" FROM ' +
1618
+ SQLite3Driver.escape(this.prefix + 'references_' + etype) +
1619
+ ' r' +
1620
+ referenceTableSuffix +
1621
+ ' WHERE r' +
1622
+ referenceTableSuffix +
1623
+ '."guid"=' +
1624
+ ieTable +
1625
+ '."guid" AND r' +
1626
+ referenceTableSuffix +
1627
+ '."name"=@' +
1628
+ qrefName +
1629
+ ' AND EXISTS (' +
1630
+ qrefQuery.query +
1631
+ '))';
1632
+ params[qrefName] = curValue[0];
1633
+ }
1091
1634
  break;
1092
1635
  }
1093
1636
  }
1094
1637
  return curQuery;
1095
1638
  });
1096
1639
  let sortBy;
1097
- switch (sort) {
1098
- case 'mdate':
1099
- sortBy = '"mdate"';
1100
- break;
1101
- case 'cdate':
1102
- default:
1103
- sortBy = '"cdate"';
1104
- break;
1105
- }
1106
- if (options.reverse) {
1107
- sortBy += ' DESC';
1640
+ let sortByInner;
1641
+ let sortJoin = '';
1642
+ const order = options.reverse ? ' DESC' : '';
1643
+ if (sort == null) {
1644
+ sortBy = '';
1645
+ sortByInner = '';
1646
+ }
1647
+ else {
1648
+ switch (sort) {
1649
+ case 'mdate':
1650
+ sortBy = `ORDER BY ${eTable}."mdate"${order}`;
1651
+ sortByInner = `ORDER BY ${ieTable}."mdate"${order}`;
1652
+ break;
1653
+ case 'cdate':
1654
+ sortBy = `ORDER BY ${eTable}."cdate"${order}`;
1655
+ sortByInner = `ORDER BY ${ieTable}."cdate"${order}`;
1656
+ break;
1657
+ default:
1658
+ const name = `param${++count.i}`;
1659
+ sortJoin = `LEFT JOIN (
1660
+ SELECT "guid", "string", "number"
1661
+ FROM ${SQLite3Driver.escape(this.prefix + 'data_' + etype)}
1662
+ WHERE "name"=@${name}
1663
+ ORDER BY "number"${order}, "string"${order}
1664
+ ) ${sTable} USING ("guid")`;
1665
+ sortBy = `ORDER BY ${sTable}."number"${order}, ${sTable}."string"${order}`;
1666
+ sortByInner = sortBy;
1667
+ params[name] = sort;
1668
+ break;
1669
+ }
1108
1670
  }
1109
1671
  let query;
1110
1672
  if (queryParts.length) {
@@ -1121,25 +1683,29 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1121
1683
  offset = ` OFFSET ${Math.floor(Number(options.offset))}`;
1122
1684
  }
1123
1685
  const whereClause = queryParts.join(') AND (');
1686
+ const guidClause = guidSelector
1687
+ ? `${ieTable}."guid"=${guidSelector} AND `
1688
+ : '';
1124
1689
  if (options.return === 'count') {
1125
1690
  if (limit || offset) {
1126
1691
  query = `SELECT COUNT("guid") AS "count" FROM (
1127
1692
  SELECT "guid"
1128
1693
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1129
- WHERE (${whereClause})${limit}${offset}
1694
+ WHERE ${guidClause}(${whereClause})${limit}${offset}
1130
1695
  )`;
1131
1696
  }
1132
1697
  else {
1133
1698
  query = `SELECT COUNT("guid") AS "count"
1134
1699
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1135
- WHERE (${whereClause})`;
1700
+ WHERE ${guidClause}(${whereClause})`;
1136
1701
  }
1137
1702
  }
1138
1703
  else if (options.return === 'guid') {
1139
1704
  query = `SELECT "guid"
1140
1705
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1141
- WHERE (${whereClause})
1142
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1706
+ ${sortJoin}
1707
+ WHERE ${guidClause}(${whereClause})
1708
+ ${sortByInner ? sortByInner + ', ' : 'ORDER BY '}"guid"${limit}${offset}`;
1143
1709
  }
1144
1710
  else {
1145
1711
  query = `SELECT
@@ -1147,20 +1713,30 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1147
1713
  ${eTable}."tags",
1148
1714
  ${eTable}."cdate",
1149
1715
  ${eTable}."mdate",
1716
+ ${eTable}."user",
1717
+ ${eTable}."group",
1718
+ ${eTable}."acUser",
1719
+ ${eTable}."acGroup",
1720
+ ${eTable}."acOther",
1721
+ ${eTable}."acRead",
1722
+ ${eTable}."acWrite",
1723
+ ${eTable}."acFull",
1150
1724
  ${dTable}."name",
1151
1725
  ${dTable}."value",
1152
- ${cTable}."string",
1153
- ${cTable}."number"
1726
+ json(${dTable}."json") as "json",
1727
+ ${dTable}."string",
1728
+ ${dTable}."number"
1154
1729
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1155
1730
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1156
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} ${cTable} USING ("guid", "name")
1731
+ ${sortJoin}
1157
1732
  INNER JOIN (
1158
1733
  SELECT "guid"
1159
1734
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1160
- WHERE (${whereClause})
1161
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1735
+ ${sortJoin}
1736
+ WHERE ${guidClause}(${whereClause})
1737
+ ${sortByInner}${limit}${offset}
1162
1738
  ) ${fTable} USING ("guid")
1163
- ORDER BY ${eTable}.${sortBy}`;
1739
+ ${sortBy ? sortBy + ', ' : 'ORDER BY '}${eTable}."guid"`;
1164
1740
  }
1165
1741
  }
1166
1742
  }
@@ -1177,22 +1753,27 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1177
1753
  if ('offset' in options) {
1178
1754
  offset = ` OFFSET ${Math.floor(Number(options.offset))}`;
1179
1755
  }
1756
+ const guidClause = guidSelector
1757
+ ? ` WHERE ${ieTable}."guid"=${guidSelector}`
1758
+ : '';
1180
1759
  if (options.return === 'count') {
1181
1760
  if (limit || offset) {
1182
1761
  query = `SELECT COUNT("guid") AS "count" FROM (
1183
1762
  SELECT "guid"
1184
- FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${limit}${offset}
1763
+ FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${guidClause}${limit}${offset}
1185
1764
  )`;
1186
1765
  }
1187
1766
  else {
1188
1767
  query = `SELECT COUNT("guid") AS "count"
1189
- FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}`;
1768
+ FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${guidClause}`;
1190
1769
  }
1191
1770
  }
1192
1771
  else if (options.return === 'guid') {
1193
1772
  query = `SELECT "guid"
1194
1773
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1195
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1774
+ ${sortJoin}
1775
+ ${guidClause}
1776
+ ${sortByInner ? sortByInner + ', ' : 'ORDER BY '}"guid"${limit}${offset}`;
1196
1777
  }
1197
1778
  else {
1198
1779
  if (limit || offset) {
@@ -1201,19 +1782,30 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1201
1782
  ${eTable}."tags",
1202
1783
  ${eTable}."cdate",
1203
1784
  ${eTable}."mdate",
1785
+ ${eTable}."user",
1786
+ ${eTable}."group",
1787
+ ${eTable}."acUser",
1788
+ ${eTable}."acGroup",
1789
+ ${eTable}."acOther",
1790
+ ${eTable}."acRead",
1791
+ ${eTable}."acWrite",
1792
+ ${eTable}."acFull",
1204
1793
  ${dTable}."name",
1205
1794
  ${dTable}."value",
1206
- ${cTable}."string",
1207
- ${cTable}."number"
1795
+ json(${dTable}."json") as "json",
1796
+ ${dTable}."string",
1797
+ ${dTable}."number"
1208
1798
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1209
1799
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1210
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} c USING ("guid", "name")
1800
+ ${sortJoin}
1211
1801
  INNER JOIN (
1212
1802
  SELECT "guid"
1213
1803
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1214
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1804
+ ${sortJoin}
1805
+ ${guidClause}
1806
+ ${sortByInner}${limit}${offset}
1215
1807
  ) ${fTable} USING ("guid")
1216
- ORDER BY ${eTable}.${sortBy}`;
1808
+ ${sortBy ? sortBy + ', ' : 'ORDER BY '}${eTable}."guid"`;
1217
1809
  }
1218
1810
  else {
1219
1811
  query = `SELECT
@@ -1221,14 +1813,24 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1221
1813
  ${eTable}."tags",
1222
1814
  ${eTable}."cdate",
1223
1815
  ${eTable}."mdate",
1816
+ ${eTable}."user",
1817
+ ${eTable}."group",
1818
+ ${eTable}."acUser",
1819
+ ${eTable}."acGroup",
1820
+ ${eTable}."acOther",
1821
+ ${eTable}."acRead",
1822
+ ${eTable}."acWrite",
1823
+ ${eTable}."acFull",
1224
1824
  ${dTable}."name",
1225
1825
  ${dTable}."value",
1226
- ${cTable}."string",
1227
- ${cTable}."number"
1826
+ json(${dTable}."json") as "json",
1827
+ ${dTable}."string",
1828
+ ${dTable}."number"
1228
1829
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1229
1830
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1230
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} ${cTable} USING ("guid", "name")
1231
- ORDER BY ${eTable}.${sortBy}`;
1831
+ ${sortJoin}
1832
+ ${guidSelector ? `WHERE ${eTable}."guid"=${guidSelector}` : ''}
1833
+ ${sortBy ? sortBy + ', ' : 'ORDER BY '}${eTable}."guid"`;
1232
1834
  }
1233
1835
  }
1234
1836
  }
@@ -1244,29 +1846,41 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1244
1846
  }
1245
1847
  performQuery(options, formattedSelectors, etype) {
1246
1848
  const { query, params, etypes } = this.makeEntityQuery(options, formattedSelectors, etype);
1247
- const result = this.queryIter(query, { etypes, params })[Symbol.iterator]();
1849
+ const result = this.queryArray(query, { etypes, params })[Symbol.iterator]();
1248
1850
  return {
1249
1851
  result,
1250
1852
  };
1251
1853
  }
1252
1854
  async getEntities(options = {}, ...selectors) {
1253
- return this.getEntitiesSync(options, ...selectors);
1254
- }
1255
- getEntitiesSync(options = {}, ...selectors) {
1256
- const { result, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuery(options, formattedSelectors, etype), () => {
1855
+ const { result, process } = this.getEntitiesRowLike(options, selectors, ({ options, selectors, etype }) => this.performQuery(options, selectors, etype), () => {
1257
1856
  const next = result.next();
1258
1857
  return next.done ? null : next.value;
1259
1858
  }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
1260
- tags: row.tags.length > 2 ? row.tags.slice(1, -1).split(',') : [],
1859
+ tags: row.tags.length > 2
1860
+ ? row.tags
1861
+ .slice(1, -1)
1862
+ .split(',')
1863
+ .filter((tag) => tag)
1864
+ : [],
1261
1865
  cdate: Number(row.cdate),
1262
1866
  mdate: Number(row.mdate),
1867
+ user: row.user,
1868
+ group: row.group,
1869
+ acUser: row.acUser,
1870
+ acGroup: row.acGroup,
1871
+ acOther: row.acOther,
1872
+ acRead: row.acRead?.slice(1, -1).split(',') ?? [],
1873
+ acWrite: row.acWrite?.slice(1, -1).split(',') ?? [],
1874
+ acFull: row.acFull?.slice(1, -1).split(',') ?? [],
1263
1875
  }), (row) => ({
1264
1876
  name: row.name,
1265
1877
  svalue: row.value === 'N'
1266
1878
  ? JSON.stringify(row.number)
1267
1879
  : row.value === 'S'
1268
1880
  ? JSON.stringify(row.string)
1269
- : row.value,
1881
+ : row.value === 'J'
1882
+ ? row.json
1883
+ : row.value,
1270
1884
  }));
1271
1885
  const value = process();
1272
1886
  if (value instanceof Error) {
@@ -1276,7 +1890,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1276
1890
  }
1277
1891
  async getUID(name) {
1278
1892
  if (name == null) {
1279
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1893
+ throw new InvalidParametersError('Name not given for UID.');
1280
1894
  }
1281
1895
  const result = this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1282
1896
  params: {
@@ -1285,9 +1899,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1285
1899
  });
1286
1900
  return result?.cur_uid ?? null;
1287
1901
  }
1288
- async import(filename) {
1902
+ async importEntity(entity) {
1903
+ return await this.importEntityInternal(entity);
1904
+ }
1905
+ async importEntityTokens(entity) {
1906
+ return await this.importEntityInternal(entity, { only: 'tokens' });
1907
+ }
1908
+ async importEntityTilmeldAC(entity) {
1909
+ return await this.importEntityInternal(entity, { only: 'tilmeldAC' });
1910
+ }
1911
+ async importEntityInternal({ guid, cdate, mdate, tags, sdata, etype, }, { only = undefined } = {}) {
1289
1912
  try {
1290
- return this.importFromFile(filename, async (guid, tags, sdata, etype) => {
1913
+ if (only == null) {
1291
1914
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, {
1292
1915
  etypes: [etype],
1293
1916
  params: {
@@ -1300,29 +1923,48 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1300
1923
  guid,
1301
1924
  },
1302
1925
  });
1303
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
1926
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
1304
1927
  etypes: [etype],
1305
1928
  params: {
1306
1929
  guid,
1307
1930
  },
1308
1931
  });
1309
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
1932
+ }
1933
+ if (only == null || only === 'tokens') {
1934
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=@guid;`, {
1310
1935
  etypes: [etype],
1311
1936
  params: {
1312
1937
  guid,
1313
1938
  },
1314
1939
  });
1315
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`, {
1940
+ }
1941
+ if (only == null) {
1942
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
1943
+ etypes: [etype],
1944
+ params: {
1945
+ guid,
1946
+ },
1947
+ });
1948
+ }
1949
+ if (only == null) {
1950
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull } = this.removeAndReturnACValues(etype, {}, sdata);
1951
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate", "user", "group", "acUser", "acGroup", "acOther", "acRead", "acWrite", "acFull") VALUES (@guid, @tags, @cdate, @mdate, @user, @group, @acUser, @acGroup, @acOther, @acRead, @acWrite, @acFull);`, {
1316
1952
  etypes: [etype],
1317
1953
  params: {
1318
1954
  guid,
1319
1955
  tags: ',' + tags.join(',') + ',',
1320
- cdate: Number(JSON.parse(sdata.cdate)),
1321
- mdate: Number(JSON.parse(sdata.mdate)),
1956
+ cdate,
1957
+ mdate,
1958
+ user,
1959
+ group,
1960
+ acUser,
1961
+ acGroup,
1962
+ acOther,
1963
+ acRead: acRead && ',' + acRead.join(',') + ',',
1964
+ acWrite: acWrite && ',' + acWrite.join(',') + ',',
1965
+ acFull: acFull && ',' + acFull.join(',') + ',',
1322
1966
  },
1323
1967
  });
1324
- delete sdata.cdate;
1325
- delete sdata.mdate;
1326
1968
  for (const name in sdata) {
1327
1969
  const value = sdata[name];
1328
1970
  const uvalue = JSON.parse(value);
@@ -1333,23 +1975,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1333
1975
  ? 'N'
1334
1976
  : typeof uvalue === 'string'
1335
1977
  ? 'S'
1336
- : value;
1337
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`, {
1978
+ : 'J';
1979
+ const jsonValue = storageValue === 'J' ? value : null;
1980
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (@guid, @name, @storageValue, jsonb(@jsonValue), @string, @number, @truthy);`, {
1338
1981
  etypes: [etype],
1339
1982
  params: {
1340
1983
  guid,
1341
1984
  name,
1342
1985
  storageValue,
1343
- },
1344
- });
1345
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`, {
1346
- etypes: [etype],
1347
- params: {
1348
- guid,
1349
- name,
1350
- truthy: uvalue ? 1 : 0,
1351
- string: `${uvalue}`,
1986
+ jsonValue,
1987
+ string: storageValue === 'J' ? null : `${uvalue}`,
1352
1988
  number: Number(uvalue),
1989
+ truthy: uvalue ? 1 : 0,
1353
1990
  },
1354
1991
  });
1355
1992
  const references = this.findReferences(value);
@@ -1364,40 +2001,132 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1364
2001
  });
1365
2002
  }
1366
2003
  }
1367
- }, async (name, curUid) => {
1368
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
2004
+ }
2005
+ if (only === 'tilmeldAC') {
2006
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull } = this.removeAndReturnACValues(etype, {}, sdata);
2007
+ this.queryRun(`UPDATE OR IGNORE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} SET "user"=@user, "group"=@group, "acUser"=@acUser, "acGroup"=@acGroup, "acOther"=@acOther, "acRead"=@acRead, "acWrite"=@acWrite, "acFull"=@acFull WHERE "guid"=@guid;`, {
2008
+ etypes: [etype],
1369
2009
  params: {
1370
- name,
2010
+ user,
2011
+ group,
2012
+ acUser,
2013
+ acGroup,
2014
+ acOther,
2015
+ acRead: acRead && ',' + acRead.join(',') + ',',
2016
+ acWrite: acWrite && ',' + acWrite.join(',') + ',',
2017
+ acFull: acFull && ',' + acFull.join(',') + ',',
2018
+ guid,
1371
2019
  },
1372
2020
  });
1373
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
1374
- params: {
1375
- name,
1376
- curUid,
1377
- },
2021
+ }
2022
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
2023
+ if (only == null || only === 'tokens') {
2024
+ for (let name in sdata) {
2025
+ let tokenString = null;
2026
+ try {
2027
+ tokenString = EntityClass.getFTSText(name, JSON.parse(sdata[name]));
2028
+ }
2029
+ catch (e) {
2030
+ // Ignore error.
2031
+ }
2032
+ if (tokenString != null) {
2033
+ const tokens = this.tokenizer.tokenize(tokenString);
2034
+ while (tokens.length) {
2035
+ const currentTokens = tokens.splice(0, 100);
2036
+ const params = {
2037
+ guid,
2038
+ name,
2039
+ };
2040
+ const values = [];
2041
+ for (let i = 0; i < currentTokens.length; i++) {
2042
+ const token = currentTokens[i];
2043
+ params['token' + i] = token.token;
2044
+ params['position' + i] = token.position;
2045
+ params['stem' + i] = token.stem ? 1 : 0;
2046
+ values.push('(@guid, @name, @token' +
2047
+ i +
2048
+ ', @position' +
2049
+ i +
2050
+ ', @stem' +
2051
+ i +
2052
+ ')');
2053
+ }
2054
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`, {
2055
+ etypes: [etype],
2056
+ params,
2057
+ });
2058
+ }
2059
+ }
2060
+ }
2061
+ }
2062
+ if (only == null) {
2063
+ const uniques = await EntityClass.getUniques({
2064
+ guid,
2065
+ cdate,
2066
+ mdate,
2067
+ tags,
2068
+ data: {},
2069
+ sdata,
1378
2070
  });
1379
- }, async () => {
1380
- await this.startTransaction('nymph-import');
1381
- }, async () => {
1382
- await this.commit('nymph-import');
2071
+ for (const unique of uniques) {
2072
+ try {
2073
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (@guid, @unique);`, {
2074
+ etypes: [etype],
2075
+ params: {
2076
+ guid,
2077
+ unique,
2078
+ },
2079
+ });
2080
+ }
2081
+ catch (e) {
2082
+ if (e instanceof EntityUniqueConstraintError) {
2083
+ this.nymph.config.debugError('sqlite3', `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
2084
+ }
2085
+ throw e;
2086
+ }
2087
+ }
2088
+ }
2089
+ }
2090
+ catch (e) {
2091
+ this.nymph.config.debugError('sqlite3', `Import entity error: "${e}"`);
2092
+ throw e;
2093
+ }
2094
+ }
2095
+ async importUID({ name, value }) {
2096
+ try {
2097
+ await this.startTransaction(`nymph-import-uid-${name}`);
2098
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
2099
+ params: {
2100
+ name,
2101
+ },
1383
2102
  });
2103
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @value);`, {
2104
+ params: {
2105
+ name,
2106
+ value,
2107
+ },
2108
+ });
2109
+ await this.commit(`nymph-import-uid-${name}`);
1384
2110
  }
1385
2111
  catch (e) {
1386
- await this.rollback('nymph-import');
2112
+ this.nymph.config.debugError('sqlite3', `Import UID error: "${e}"`);
2113
+ await this.rollback(`nymph-import-uid-${name}`);
1387
2114
  throw e;
1388
2115
  }
1389
2116
  }
1390
2117
  async newUID(name) {
1391
2118
  if (name == null) {
1392
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2119
+ throw new InvalidParametersError('Name not given for UID.');
1393
2120
  }
1394
2121
  await this.startTransaction('nymph-newuid');
2122
+ let curUid = undefined;
1395
2123
  try {
1396
- let curUid = this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1397
- params: {
1398
- name,
1399
- },
1400
- })?.cur_uid ?? null;
2124
+ curUid =
2125
+ this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
2126
+ params: {
2127
+ name,
2128
+ },
2129
+ })?.cur_uid ?? null;
1401
2130
  if (curUid == null) {
1402
2131
  curUid = 1;
1403
2132
  this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
@@ -1416,17 +2145,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1416
2145
  },
1417
2146
  });
1418
2147
  }
1419
- await this.commit('nymph-newuid');
1420
- return curUid;
1421
2148
  }
1422
2149
  catch (e) {
2150
+ this.nymph.config.debugError('sqlite3', `New UID error: "${e}"`);
1423
2151
  await this.rollback('nymph-newuid');
1424
2152
  throw e;
1425
2153
  }
2154
+ await this.commit('nymph-newuid');
2155
+ return curUid;
1426
2156
  }
1427
2157
  async renameUID(oldName, newName) {
1428
2158
  if (oldName == null || newName == null) {
1429
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2159
+ throw new InvalidParametersError('Name not given for UID.');
1430
2160
  }
1431
2161
  await this.startTransaction('nymph-rename-uid');
1432
2162
  this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}uids`)} SET "name"=@newName WHERE "name"=@oldName;`, {
@@ -1440,7 +2170,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1440
2170
  }
1441
2171
  async rollback(name) {
1442
2172
  if (name == null || typeof name !== 'string' || name.length === 0) {
1443
- throw new nymph_1.InvalidParametersError('Transaction rollback attempted without a name.');
2173
+ throw new InvalidParametersError('Transaction rollback attempted without a name.');
1444
2174
  }
1445
2175
  if (this.store.transactionsStarted === 0) {
1446
2176
  return true;
@@ -1457,7 +2187,8 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1457
2187
  return true;
1458
2188
  }
1459
2189
  async saveEntity(entity) {
1460
- const insertData = (guid, data, sdata, etype) => {
2190
+ const insertData = (guid, data, sdata, uniques, etype) => {
2191
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
1461
2192
  const runInsertQuery = (name, value, svalue) => {
1462
2193
  if (value === undefined) {
1463
2194
  return;
@@ -1466,23 +2197,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1466
2197
  ? 'N'
1467
2198
  : typeof value === 'string'
1468
2199
  ? 'S'
1469
- : svalue;
1470
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`, {
2200
+ : 'J';
2201
+ const jsonValue = storageValue === 'J' ? svalue : null;
2202
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (@guid, @name, @storageValue, jsonb(@jsonValue), @string, @number, @truthy);`, {
1471
2203
  etypes: [etype],
1472
2204
  params: {
1473
2205
  guid,
1474
2206
  name,
1475
2207
  storageValue,
1476
- },
1477
- });
1478
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`, {
1479
- etypes: [etype],
1480
- params: {
1481
- guid,
1482
- name,
1483
- truthy: value ? 1 : 0,
1484
- string: `${value}`,
2208
+ jsonValue,
2209
+ string: storageValue === 'J' ? null : `${value}`,
1485
2210
  number: Number(value),
2211
+ truthy: value ? 1 : 0,
1486
2212
  },
1487
2213
  });
1488
2214
  const references = this.findReferences(svalue);
@@ -1496,7 +2222,59 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1496
2222
  },
1497
2223
  });
1498
2224
  }
2225
+ let tokenString = null;
2226
+ try {
2227
+ tokenString = EntityClass.getFTSText(name, value);
2228
+ }
2229
+ catch (e) {
2230
+ // Ignore error.
2231
+ }
2232
+ if (tokenString != null) {
2233
+ const tokens = this.tokenizer.tokenize(tokenString);
2234
+ while (tokens.length) {
2235
+ const currentTokens = tokens.splice(0, 100);
2236
+ const params = {
2237
+ guid,
2238
+ name,
2239
+ };
2240
+ const values = [];
2241
+ for (let i = 0; i < currentTokens.length; i++) {
2242
+ const token = currentTokens[i];
2243
+ params['token' + i] = token.token;
2244
+ params['position' + i] = token.position;
2245
+ params['stem' + i] = token.stem ? 1 : 0;
2246
+ values.push('(@guid, @name, @token' +
2247
+ i +
2248
+ ', @position' +
2249
+ i +
2250
+ ', @stem' +
2251
+ i +
2252
+ ')');
2253
+ }
2254
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`, {
2255
+ etypes: [etype],
2256
+ params,
2257
+ });
2258
+ }
2259
+ }
1499
2260
  };
2261
+ for (const unique of uniques) {
2262
+ try {
2263
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (@guid, @unique);`, {
2264
+ etypes: [etype],
2265
+ params: {
2266
+ guid,
2267
+ unique,
2268
+ },
2269
+ });
2270
+ }
2271
+ catch (e) {
2272
+ if (e instanceof EntityUniqueConstraintError) {
2273
+ this.nymph.config.debugError('sqlite3', `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
2274
+ }
2275
+ throw e;
2276
+ }
2277
+ }
1500
2278
  for (const name in data) {
1501
2279
  runInsertQuery(name, data[name], JSON.stringify(data[name]));
1502
2280
  }
@@ -1504,24 +2282,55 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1504
2282
  runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
1505
2283
  }
1506
2284
  };
2285
+ let inTransaction = false;
1507
2286
  try {
1508
- return this.saveEntityRowLike(entity, async (_entity, guid, tags, data, sdata, cdate, etype) => {
1509
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @cdate);`, {
2287
+ return this.saveEntityRowLike(entity, async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
2288
+ if (Object.keys(data).length === 0 &&
2289
+ Object.keys(sdata).length === 0) {
2290
+ throw new EntityInvalidDataError('Entity contains no data.');
2291
+ }
2292
+ let processedData = this.removeAndReturnACValues(etype, { ...data }, { ...sdata });
2293
+ ({ data, sdata } = processedData);
2294
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull, } = processedData;
2295
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate", "user", "group", "acUser", "acGroup", "acOther", "acRead", "acWrite", "acFull") VALUES (@guid, @tags, @cdate, @cdate, @user, @group, @acUser, @acGroup, @acOther, @acRead, @acWrite, @acFull);`, {
1510
2296
  etypes: [etype],
1511
2297
  params: {
1512
2298
  guid,
1513
2299
  tags: ',' + tags.join(',') + ',',
1514
2300
  cdate,
2301
+ user,
2302
+ group,
2303
+ acUser,
2304
+ acGroup,
2305
+ acOther,
2306
+ acRead: acRead && ',' + acRead.join(',') + ',',
2307
+ acWrite: acWrite && ',' + acWrite.join(',') + ',',
2308
+ acFull: acFull && ',' + acFull.join(',') + ',',
1515
2309
  },
1516
2310
  });
1517
- insertData(guid, data, sdata, etype);
2311
+ insertData(guid, data, sdata, uniques, etype);
1518
2312
  return true;
1519
- }, async (entity, guid, tags, data, sdata, mdate, etype) => {
1520
- const info = this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=@guid AND "mdate" <= @emdate;`, {
2313
+ }, async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
2314
+ if (Object.keys(data).length === 0 &&
2315
+ Object.keys(sdata).length === 0) {
2316
+ throw new EntityInvalidDataError('Entity contains no data.');
2317
+ }
2318
+ let processedData = this.removeAndReturnACValues(etype, { ...data }, { ...sdata });
2319
+ ({ data, sdata } = processedData);
2320
+ let { user, group, acUser, acGroup, acOther, acRead, acWrite, acFull, } = processedData;
2321
+ const info = this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate, "user"=@user, "group"=@group, "acUser"=@acUser, "acGroup"=@acGroup, "acOther"=@acOther, "acRead"=@acRead, "acWrite"=@acWrite, "acFull"=@acFull WHERE "guid"=@guid AND "mdate" <= @emdate;`, {
1521
2322
  etypes: [etype],
1522
2323
  params: {
1523
2324
  tags: ',' + tags.join(',') + ',',
1524
2325
  mdate,
2326
+ user,
2327
+ group,
2328
+ acUser,
2329
+ acGroup,
2330
+ acOther,
2331
+ acRead: acRead && ',' + acRead.join(',') + ',',
2332
+ acWrite: acWrite && ',' + acWrite.join(',') + ',',
2333
+ acFull: acFull && ',' + acFull.join(',') + ',',
1525
2334
  guid,
1526
2335
  emdate: Number(entity.mdate),
1527
2336
  },
@@ -1534,42 +2343,58 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1534
2343
  guid,
1535
2344
  },
1536
2345
  });
1537
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
2346
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
2347
+ etypes: [etype],
2348
+ params: {
2349
+ guid,
2350
+ },
2351
+ });
2352
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=@guid;`, {
1538
2353
  etypes: [etype],
1539
2354
  params: {
1540
2355
  guid,
1541
2356
  },
1542
2357
  });
1543
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
2358
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
1544
2359
  etypes: [etype],
1545
2360
  params: {
1546
2361
  guid,
1547
2362
  },
1548
2363
  });
1549
- insertData(guid, data, sdata, etype);
2364
+ insertData(guid, data, sdata, uniques, etype);
1550
2365
  success = true;
1551
2366
  }
2367
+ if (!success && info.changes === 0) {
2368
+ throw new EntityConflictError("Entity either doesn't exist or is newer in the DB.");
2369
+ }
1552
2370
  return success;
1553
2371
  }, async () => {
1554
2372
  await this.startTransaction('nymph-save');
2373
+ inTransaction = true;
1555
2374
  }, async (success) => {
1556
- if (success) {
1557
- await this.commit('nymph-save');
1558
- }
1559
- else {
1560
- await this.rollback('nymph-save');
2375
+ if (inTransaction) {
2376
+ inTransaction = false;
2377
+ if (success) {
2378
+ success = await this.commit('nymph-save');
2379
+ }
2380
+ else {
2381
+ await this.rollback('nymph-save');
2382
+ }
1561
2383
  }
1562
2384
  return success;
1563
2385
  });
1564
2386
  }
1565
2387
  catch (e) {
1566
- await this.rollback('nymph-save');
2388
+ this.nymph.config.debugError('sqlite3', `Save entity error: "${e}"`);
2389
+ if (inTransaction) {
2390
+ await this.rollback('nymph-save');
2391
+ }
1567
2392
  throw e;
1568
2393
  }
1569
2394
  }
1570
2395
  async setUID(name, curUid) {
1571
2396
  if (name == null) {
1572
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2397
+ throw new InvalidParametersError('Name not given for UID.');
1573
2398
  }
1574
2399
  await this.startTransaction('nymph-set-uid');
1575
2400
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
@@ -1586,9 +2411,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1586
2411
  await this.commit('nymph-set-uid');
1587
2412
  return true;
1588
2413
  }
2414
+ async internalTransaction(name) {
2415
+ await this.startTransaction(name);
2416
+ }
1589
2417
  async startTransaction(name) {
1590
2418
  if (name == null || typeof name !== 'string' || name.length === 0) {
1591
- throw new nymph_1.InvalidParametersError('Transaction start attempted without a name.');
2419
+ throw new InvalidParametersError('Transaction start attempted without a name.');
1592
2420
  }
1593
2421
  if (!this.config.explicitWrite && !this.store.linkWrite) {
1594
2422
  this._connect(true);
@@ -1597,6 +2425,107 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1597
2425
  this.store.transactionsStarted++;
1598
2426
  return this.nymph;
1599
2427
  }
2428
+ async removeTilmeldOldRows(etype) {
2429
+ await this.startTransaction('nymph-remove-tilmeld-rows');
2430
+ try {
2431
+ for (let name of [
2432
+ 'user',
2433
+ 'group',
2434
+ 'acUser',
2435
+ 'acGroup',
2436
+ 'acOther',
2437
+ 'acRead',
2438
+ 'acWrite',
2439
+ 'acFull',
2440
+ ]) {
2441
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} WHERE "name"=@name;`, {
2442
+ etypes: [etype],
2443
+ params: {
2444
+ name,
2445
+ },
2446
+ });
2447
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "name"=@name;`, {
2448
+ etypes: [etype],
2449
+ params: {
2450
+ name,
2451
+ },
2452
+ });
2453
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} WHERE "name"=@name;`, {
2454
+ etypes: [etype],
2455
+ params: {
2456
+ name,
2457
+ },
2458
+ });
2459
+ }
2460
+ }
2461
+ catch (e) {
2462
+ this.nymph.config.debugError('sqlite3', `Remove tilmeld rows error: "${e}"`);
2463
+ await this.rollback('nymph-remove-tilmeld-rows');
2464
+ throw e;
2465
+ }
2466
+ await this.commit('nymph-remove-tilmeld-rows');
2467
+ return true;
2468
+ }
2469
+ async needsMigration() {
2470
+ const table = this.queryGet(`SELECT "name" FROM "sqlite_master" WHERE "type"='table' AND "name" LIKE @prefix LIMIT 1;`, {
2471
+ params: {
2472
+ prefix: this.prefix + 'data_' + '%',
2473
+ },
2474
+ });
2475
+ if (table?.name) {
2476
+ const result = this.queryGet(`SELECT 1 AS "exists" FROM pragma_table_info(@table) WHERE "name"='json';`, {
2477
+ params: {
2478
+ table: table.name,
2479
+ },
2480
+ });
2481
+ if (!result?.exists) {
2482
+ return 'json';
2483
+ }
2484
+ }
2485
+ const table2 = this.queryGet(`SELECT "name" FROM "sqlite_master" WHERE "type"='table' AND "name" LIKE @tokenTable LIMIT 1;`, {
2486
+ params: {
2487
+ tokenTable: this.prefix + 'tokens_' + '%',
2488
+ },
2489
+ });
2490
+ if (!table2 || !table2.name) {
2491
+ return 'tokens';
2492
+ }
2493
+ const table3 = this.queryGet(`SELECT "name" FROM "sqlite_master" WHERE "type"='table' AND "name" LIKE @prefix LIMIT 1;`, {
2494
+ params: {
2495
+ prefix: this.prefix + 'entities_' + '%',
2496
+ },
2497
+ });
2498
+ if (table3?.name) {
2499
+ const result = this.queryGet(`SELECT 1 AS "exists" FROM pragma_table_info(@table) WHERE "name"='user';`, {
2500
+ params: {
2501
+ table: table3.name,
2502
+ },
2503
+ });
2504
+ if (!result?.exists) {
2505
+ return 'tilmeldColumns';
2506
+ }
2507
+ }
2508
+ return false;
2509
+ }
2510
+ async liveMigration(migrationType) {
2511
+ if (migrationType === 'tokenTables') {
2512
+ const etypes = await this.getEtypes();
2513
+ for (let etype of etypes) {
2514
+ this.createTokensTable(etype);
2515
+ }
2516
+ }
2517
+ else if (migrationType === 'tilmeldColumns') {
2518
+ const etypes = await this.getEtypes();
2519
+ for (let etype of etypes) {
2520
+ this.addTilmeldColumnsAndIndexes(etype);
2521
+ }
2522
+ }
2523
+ else if (migrationType === 'tilmeldRemoveOldRows') {
2524
+ const etypes = await this.getEtypes();
2525
+ for (let etype of etypes) {
2526
+ await this.removeTilmeldOldRows(etype);
2527
+ }
2528
+ }
2529
+ }
1600
2530
  }
1601
- exports.default = SQLite3Driver;
1602
2531
  //# sourceMappingURL=SQLite3Driver.js.map