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

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