@nymphjs/driver-sqlite3 1.0.0-beta.1 → 1.0.0-beta.100

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,111 +1,256 @@
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");
10
- class SQLite3Driver extends nymph_1.NymphDriver {
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';
5
+ class InternalStore {
6
+ link;
7
+ linkWrite;
8
+ connected = false;
9
+ transactionsStarted = 0;
10
+ constructor(link) {
11
+ this.link = link;
12
+ }
13
+ }
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;
11
22
  static escape(input) {
12
23
  if (input.indexOf('\x00') !== -1) {
13
- 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.');
14
25
  }
15
26
  return '"' + input.replace(/"/g, () => '""') + '"';
16
27
  }
17
- constructor(config) {
28
+ constructor(config, store) {
18
29
  super();
19
- this.connected = false;
20
- this.transactionsStarted = 0;
21
- this.config = { ...conf_1.SQLite3DriverConfigDefaults, ...config };
30
+ this.config = { ...defaults, ...config };
31
+ if (this.config.filename === ':memory:') {
32
+ this.config.explicitWrite = true;
33
+ }
22
34
  this.prefix = this.config.prefix;
23
- this.connect();
35
+ if (store) {
36
+ this.store = store;
37
+ }
38
+ else {
39
+ this.connect();
40
+ }
41
+ }
42
+ /**
43
+ * This is used internally by Nymph. Don't call it yourself.
44
+ *
45
+ * @returns A clone of this instance.
46
+ */
47
+ clone() {
48
+ return new SQLite3Driver(this.config, this.store);
49
+ }
50
+ /**
51
+ * Connect to the SQLite3 database.
52
+ *
53
+ * @returns Whether this instance is connected to a SQLite3 database.
54
+ */
55
+ connect() {
56
+ if (this.store && this.store.connected) {
57
+ return Promise.resolve(true);
58
+ }
59
+ // Connecting
60
+ this._connect(false);
61
+ return Promise.resolve(this.store.connected);
24
62
  }
25
- async connect() {
26
- const { filename, fileMustExist, timeout, readonly, verbose } = this.config;
27
- if (!this.connected) {
63
+ _connect(write) {
64
+ const { filename, fileMustExist, timeout, explicitWrite, wal, verbose } = this.config;
65
+ try {
66
+ const setOptions = (link) => {
67
+ // Set database and connection options.
68
+ if (wal) {
69
+ link.pragma('journal_mode = WAL;');
70
+ }
71
+ link.pragma('encoding = "UTF-8";');
72
+ link.pragma('foreign_keys = 1;');
73
+ link.pragma('case_sensitive_like = 1;');
74
+ for (let pragma of this.config.pragmas) {
75
+ link.pragma(pragma);
76
+ }
77
+ // Create the preg_match and regexp functions.
78
+ link.function('regexp', { deterministic: true }, ((pattern, subject) => (this.posixRegexMatch(pattern, subject) ? 1 : 0)));
79
+ };
80
+ let link;
28
81
  try {
29
- this.link = new better_sqlite3_1.default(filename, {
30
- readonly,
82
+ link = new SQLite3(filename, {
83
+ readonly: !explicitWrite && !write,
31
84
  fileMustExist,
32
85
  timeout,
33
86
  verbose,
34
87
  });
35
- this.connected = true;
36
- this.link.pragma('encoding = "UTF-8";');
37
- this.link.pragma('foreign_keys = 1;');
38
- this.link.pragma('case_sensitive_like = 1;');
39
- this.link.function('regexp', { deterministic: true }, (pattern, subject) => this.posixRegexMatch(pattern, subject) ? 1 : 0);
40
88
  }
41
89
  catch (e) {
42
- this.connected = false;
43
- if (filename === ':memory:') {
44
- throw new nymph_1.NotConfiguredError("It seems the config hasn't been set up correctly.");
90
+ if (e.code === 'SQLITE_CANTOPEN' &&
91
+ !explicitWrite &&
92
+ !write &&
93
+ !this.config.fileMustExist) {
94
+ // This happens when the file doesn't exist and we attempt to open it
95
+ // readonly.
96
+ // First open it in write mode.
97
+ const writeLink = new SQLite3(filename, {
98
+ readonly: false,
99
+ fileMustExist,
100
+ timeout,
101
+ verbose,
102
+ });
103
+ setOptions(writeLink);
104
+ writeLink.close();
105
+ // Now open in readonly.
106
+ link = new SQLite3(filename, {
107
+ readonly: true,
108
+ fileMustExist,
109
+ timeout,
110
+ verbose,
111
+ });
45
112
  }
46
113
  else {
47
- throw new nymph_1.UnableToConnectError('Could not connect: ' + e?.message);
114
+ throw e;
115
+ }
116
+ }
117
+ if (!this.store) {
118
+ if (write) {
119
+ throw new Error('Tried to open in write without opening in read first.');
48
120
  }
121
+ this.store = new InternalStore(link);
122
+ }
123
+ else if (write) {
124
+ this.store.linkWrite = link;
125
+ }
126
+ else {
127
+ this.store.link = link;
128
+ }
129
+ this.store.connected = true;
130
+ setOptions(link);
131
+ }
132
+ catch (e) {
133
+ if (this.store) {
134
+ this.store.connected = false;
135
+ }
136
+ if (filename === ':memory:') {
137
+ throw new NotConfiguredError("It seems the config hasn't been set up correctly. Could not connect: " +
138
+ e?.message);
139
+ }
140
+ else {
141
+ throw new UnableToConnectError('Could not connect: ' + e?.message);
49
142
  }
50
143
  }
51
- return this.connected;
52
144
  }
145
+ /**
146
+ * Disconnect from the SQLite3 database.
147
+ *
148
+ * @returns Whether this instance is connected to a SQLite3 database.
149
+ */
53
150
  async disconnect() {
54
- if (this.connected) {
55
- this.link.exec('PRAGMA optimize;');
56
- this.link.close();
57
- this.connected = false;
151
+ if (this.store.connected) {
152
+ if (this.store.linkWrite && !this.config.explicitWrite) {
153
+ this.store.linkWrite.exec('PRAGMA optimize;');
154
+ this.store.linkWrite.close();
155
+ this.store.linkWrite = undefined;
156
+ }
157
+ if (this.config.explicitWrite) {
158
+ this.store.link.exec('PRAGMA optimize;');
159
+ }
160
+ this.store.link.close();
161
+ this.store.transactionsStarted = 0;
162
+ this.store.connected = false;
58
163
  }
59
- return this.connected;
164
+ return this.store.connected;
60
165
  }
61
166
  async inTransaction() {
62
- return this.transactionsStarted > 0;
167
+ return this.store.transactionsStarted > 0;
63
168
  }
169
+ /**
170
+ * Check connection status.
171
+ *
172
+ * @returns Whether this instance is connected to a SQLite3 database.
173
+ */
64
174
  isConnected() {
65
- return this.connected;
175
+ return this.store.connected;
66
176
  }
67
- checkReadOnlyMode() {
68
- if (this.config.readonly) {
69
- throw new nymph_1.InvalidParametersError('Attempt to write to SQLite3 DB in read only mode.');
70
- }
177
+ createEntitiesTable(etype) {
178
+ // Create the entity table.
179
+ 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);`);
180
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("cdate");`);
181
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("mdate");`);
182
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("tags");`);
183
+ }
184
+ createDataTable(etype) {
185
+ // Create the data table.
186
+ 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"));`);
187
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid");`);
188
+ 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");`);
189
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name");`);
190
+ 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");`);
191
+ 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");`);
192
+ 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");`);
193
+ 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");`);
194
+ 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");`);
195
+ 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;`);
196
+ 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;`);
197
+ 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;`);
198
+ 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\';`);
199
+ 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\';`);
71
200
  }
201
+ createReferencesTable(etype) {
202
+ // Create the references table.
203
+ 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"));`);
204
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid");`);
205
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("name");`);
206
+ 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");`);
207
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_reference`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("reference");`);
208
+ 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");`);
209
+ 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");`);
210
+ 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");`);
211
+ 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");`);
212
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_guid_reference_nameuser`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid", "reference") WHERE "name"=\'user\';`);
213
+ this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_guid_reference_namegroup`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid", "reference") WHERE "name"=\'group\';`);
214
+ }
215
+ createTokensTable(etype) {
216
+ // Create the tokens table.
217
+ 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"));`);
218
+ 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");`);
219
+ }
220
+ createUniquesTable(etype) {
221
+ // Create the unique strings table.
222
+ 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"));`);
223
+ }
224
+ /**
225
+ * Create entity tables in the database.
226
+ *
227
+ * @param etype The entity type to create a table for. If this is blank, the default tables are created.
228
+ */
72
229
  createTables(etype = null) {
73
- this.checkReadOnlyMode();
74
230
  this.startTransaction('nymph-tablecreation');
75
231
  try {
76
232
  if (etype != null) {
77
- 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);`);
78
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_cdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("cdate");`);
79
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_mdate`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("mdate");`);
80
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}entities_${etype}_id_tags`)} ON ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("tags");`);
81
- 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"));`);
82
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid");`);
83
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name");`);
84
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}data_${etype}_id_value`)} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("value");`);
85
- 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\';`);
86
- 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\';`);
87
- 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"));`);
88
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid");`);
89
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("name");`);
90
- 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;`);
91
- 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"));`);
92
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_guid`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("guid");`);
93
- this.queryRun(`CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}references_${etype}_id_name`)} ON ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} ("name");`);
94
- 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.createEntitiesTable(etype);
234
+ this.createDataTable(etype);
235
+ this.createReferencesTable(etype);
236
+ this.createTokensTable(etype);
237
+ this.createUniquesTable(etype);
95
238
  }
96
239
  else {
240
+ // Create the UID table.
97
241
  this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name" TEXT PRIMARY KEY NOT NULL, "cur_uid" INTEGER NOT NULL);`);
98
242
  }
99
- this.commit('nymph-tablecreation');
100
- return true;
101
243
  }
102
244
  catch (e) {
103
245
  this.rollback('nymph-tablecreation');
104
246
  throw e;
105
247
  }
248
+ this.commit('nymph-tablecreation');
249
+ return true;
106
250
  }
107
251
  query(runQuery, query, etypes = []) {
108
252
  try {
253
+ this.nymph.config.debugInfo('sqlite3:query', query);
109
254
  return runQuery();
110
255
  }
111
256
  catch (e) {
@@ -121,32 +266,45 @@ class SQLite3Driver extends nymph_1.NymphDriver {
121
266
  return runQuery();
122
267
  }
123
268
  catch (e2) {
124
- throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
269
+ throw new QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
125
270
  }
126
271
  }
272
+ else if (errorCode === 'SQLITE_CONSTRAINT_UNIQUE' &&
273
+ errorMsg.match(/^UNIQUE constraint failed: /)) {
274
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
275
+ }
127
276
  else {
128
- throw new nymph_1.QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
277
+ throw new QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
129
278
  }
130
279
  }
131
280
  }
132
- queryIter(query, { etypes = [], params = {}, } = {}) {
133
- return this.query(() => this.link.prepare(query).iterate(params), `${query} -- ${JSON.stringify(params)}`, etypes);
281
+ queryArray(query, { etypes = [], params = {}, } = {}) {
282
+ return this.query(() => (this.store.linkWrite || this.store.link)
283
+ .prepare(query)
284
+ .iterate(params), `${query} -- ${JSON.stringify(params)}`, etypes);
134
285
  }
135
286
  queryGet(query, { etypes = [], params = {}, } = {}) {
136
- return this.query(() => this.link.prepare(query).get(params), `${query} -- ${JSON.stringify(params)}`, etypes);
287
+ return this.query(() => (this.store.linkWrite || this.store.link).prepare(query).get(params), `${query} -- ${JSON.stringify(params)}`, etypes);
137
288
  }
138
289
  queryRun(query, { etypes = [], params = {}, } = {}) {
139
- return this.query(() => this.link.prepare(query).run(params), `${query} -- ${JSON.stringify(params)}`, etypes);
290
+ return this.query(() => (this.store.linkWrite || this.store.link).prepare(query).run(params), `${query} -- ${JSON.stringify(params)}`, etypes);
140
291
  }
141
292
  async commit(name) {
142
293
  if (name == null || typeof name !== 'string' || name.length === 0) {
143
- throw new nymph_1.InvalidParametersError('Transaction commit attempted without a name.');
294
+ throw new InvalidParametersError('Transaction commit attempted without a name.');
144
295
  }
145
- if (this.transactionsStarted === 0) {
296
+ if (this.store.transactionsStarted === 0) {
146
297
  return true;
147
298
  }
148
299
  this.queryRun(`RELEASE SAVEPOINT ${SQLite3Driver.escape(name)};`);
149
- this.transactionsStarted--;
300
+ this.store.transactionsStarted--;
301
+ if (this.store.transactionsStarted === 0 &&
302
+ this.store.linkWrite &&
303
+ !this.config.explicitWrite) {
304
+ this.store.linkWrite.exec('PRAGMA optimize;');
305
+ this.store.linkWrite.close();
306
+ this.store.linkWrite = undefined;
307
+ }
150
308
  return true;
151
309
  }
152
310
  async deleteEntityByID(guid, className) {
@@ -159,7 +317,6 @@ class SQLite3Driver extends nymph_1.NymphDriver {
159
317
  EntityClass = className;
160
318
  }
161
319
  const etype = EntityClass.ETYPE;
162
- this.checkReadOnlyMode();
163
320
  await this.startTransaction('nymph-delete');
164
321
  try {
165
322
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, {
@@ -174,108 +331,167 @@ class SQLite3Driver extends nymph_1.NymphDriver {
174
331
  guid,
175
332
  },
176
333
  });
177
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
334
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
178
335
  etypes: [etype],
179
336
  params: {
180
337
  guid,
181
338
  },
182
339
  });
183
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
340
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=@guid;`, {
341
+ etypes: [etype],
342
+ params: {
343
+ guid,
344
+ },
345
+ });
346
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
184
347
  etypes: [etype],
185
348
  params: {
186
349
  guid,
187
350
  },
188
351
  });
189
- await this.commit('nymph-delete');
190
- if (this.nymph.config.cache) {
191
- this.cleanCache(guid);
192
- }
193
- return true;
194
352
  }
195
353
  catch (e) {
354
+ this.nymph.config.debugError('sqlite3', `Delete entity error: "${e}"`);
196
355
  await this.rollback('nymph-delete');
197
356
  throw e;
198
357
  }
358
+ await this.commit('nymph-delete');
359
+ // Remove any cached versions of this entity.
360
+ if (this.nymph.config.cache) {
361
+ this.cleanCache(guid);
362
+ }
363
+ return true;
199
364
  }
200
365
  async deleteUID(name) {
201
366
  if (!name) {
202
- throw new nymph_1.InvalidParametersError('Name not given for UID');
367
+ throw new InvalidParametersError('Name not given for UID');
203
368
  }
204
- this.checkReadOnlyMode();
369
+ await this.startTransaction('nymph-delete-uid');
205
370
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
206
371
  params: {
207
372
  name,
208
373
  },
209
374
  });
375
+ await this.commit('nymph-delete-uid');
210
376
  return true;
211
377
  }
212
- async exportEntities(writeLine) {
213
- writeLine('#nex2');
214
- writeLine('# Nymph Entity Exchange v2');
215
- writeLine('# http://nymph.io');
216
- writeLine('#');
217
- writeLine('# Generation Time: ' + new Date().toLocaleString());
218
- writeLine('');
219
- writeLine('#');
220
- writeLine('# UIDs');
221
- writeLine('#');
222
- writeLine('');
223
- let uids = this.queryIter(`SELECT * FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
224
- for (const uid of uids) {
225
- writeLine(`<${uid.name}>[${uid.cur_uid}]`);
226
- }
227
- writeLine('');
228
- writeLine('#');
229
- writeLine('# Entities');
230
- writeLine('#');
231
- writeLine('');
232
- const tables = this.queryIter("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;");
378
+ async getEtypes() {
379
+ const tables = this.queryArray("SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @prefix;", {
380
+ params: {
381
+ prefix: this.prefix + 'entities_' + '%',
382
+ },
383
+ });
233
384
  const etypes = [];
234
385
  for (const table of tables) {
235
- if (table.name.startsWith(this.prefix + 'entities_')) {
236
- etypes.push(table.name.substr((this.prefix + 'entities_').length));
386
+ etypes.push(table.name.substr((this.prefix + 'entities_').length));
387
+ }
388
+ return etypes;
389
+ }
390
+ async *exportDataIterator() {
391
+ if (yield {
392
+ type: 'comment',
393
+ content: `#nex2
394
+ # Nymph Entity Exchange v2
395
+ # http://nymph.io
396
+ #
397
+ # Generation Time: ${new Date().toLocaleString()}
398
+ `,
399
+ }) {
400
+ return;
401
+ }
402
+ if (yield {
403
+ type: 'comment',
404
+ content: `
405
+
406
+ #
407
+ # UIDs
408
+ #
409
+
410
+ `,
411
+ }) {
412
+ return;
413
+ }
414
+ // Export UIDs.
415
+ let uids = this.queryArray(`SELECT * FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} ORDER BY "name";`);
416
+ for (const uid of uids) {
417
+ if (yield { type: 'uid', content: `<${uid.name}>[${uid.cur_uid}]\n` }) {
418
+ return;
237
419
  }
238
420
  }
421
+ if (yield {
422
+ type: 'comment',
423
+ content: `
424
+
425
+ #
426
+ # Entities
427
+ #
428
+
429
+ `,
430
+ }) {
431
+ return;
432
+ }
433
+ // Get the etypes.
434
+ const etypes = await this.getEtypes();
239
435
  for (const etype of etypes) {
240
- 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]();
436
+ // Export entities.
437
+ const dataIterator = this.queryArray(`SELECT e.*, 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]();
241
438
  let datum = dataIterator.next();
242
439
  while (!datum.done) {
243
440
  const guid = datum.value.guid;
244
441
  const tags = datum.value.tags.slice(1, -1);
245
442
  const cdate = datum.value.cdate;
246
443
  const mdate = datum.value.mdate;
247
- writeLine(`{${guid}}<${etype}>[${tags}]`);
248
- writeLine(`\tcdate=${JSON.stringify(cdate)}`);
249
- writeLine(`\tmdate=${JSON.stringify(mdate)}`);
250
- if (datum.value.dname != null) {
444
+ let currentEntityExport = [];
445
+ currentEntityExport.push(`{${guid}}<${etype}>[${tags}]`);
446
+ currentEntityExport.push(`\tcdate=${JSON.stringify(cdate)}`);
447
+ currentEntityExport.push(`\tmdate=${JSON.stringify(mdate)}`);
448
+ if (datum.value.name != null) {
449
+ // This do will keep going and adding the data until the
450
+ // next entity is reached. datum will end on the next entity.
251
451
  do {
252
- const value = datum.value.dvalue === 'N'
452
+ const value = datum.value.value === 'N'
253
453
  ? JSON.stringify(datum.value.number)
254
- : datum.value.dvalue === 'S'
454
+ : datum.value.value === 'S'
255
455
  ? JSON.stringify(datum.value.string)
256
- : datum.value.dvalue;
257
- writeLine(`\t${datum.value.dname}=${value}`);
456
+ : datum.value.value === 'J'
457
+ ? datum.value.json
458
+ : datum.value.value;
459
+ currentEntityExport.push(`\t${datum.value.name}=${value}`);
258
460
  datum = dataIterator.next();
259
461
  } while (!datum.done && datum.value.guid === guid);
260
462
  }
261
463
  else {
464
+ // Make sure that datum is incremented :)
262
465
  datum = dataIterator.next();
263
466
  }
467
+ currentEntityExport.push('');
468
+ if (yield { type: 'entity', content: currentEntityExport.join('\n') }) {
469
+ return;
470
+ }
264
471
  }
265
472
  }
266
- return;
267
473
  }
268
- makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = []) {
474
+ /**
475
+ * Generate the SQLite3 query.
476
+ * @param options The options array.
477
+ * @param formattedSelectors The formatted selector array.
478
+ * @param etype
479
+ * @param count Used to track internal params.
480
+ * @param params Used to store internal params.
481
+ * @param subquery Whether only a subquery should be returned.
482
+ * @returns The SQL query.
483
+ */
484
+ makeEntityQuery(options, formattedSelectors, etype, count = { i: 0 }, params = {}, subquery = false, tableSuffix = '', etypes = [], guidSelector = undefined) {
269
485
  if (typeof options.class?.alterOptions === 'function') {
270
486
  options = options.class.alterOptions(options);
271
487
  }
272
488
  const eTable = `e${tableSuffix}`;
273
489
  const dTable = `d${tableSuffix}`;
274
- const cTable = `c${tableSuffix}`;
275
490
  const fTable = `f${tableSuffix}`;
276
491
  const ieTable = `ie${tableSuffix}`;
492
+ const sTable = `s${tableSuffix}`;
277
493
  const sort = options.sort ?? 'cdate';
278
- const queryParts = this.iterateSelectorsForQuery(formattedSelectors, (key, value, typeIsOr, typeIsNot) => {
494
+ const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => {
279
495
  const clauseNot = key.startsWith('!');
280
496
  let curQuery = '';
281
497
  for (const curValue of value) {
@@ -288,7 +504,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
288
504
  }
289
505
  const guid = `param${++count.i}`;
290
506
  curQuery +=
291
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
507
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
292
508
  ieTable +
293
509
  '."guid"=@' +
294
510
  guid;
@@ -303,7 +519,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
303
519
  }
304
520
  const tag = `param${++count.i}`;
305
521
  curQuery +=
306
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
522
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
307
523
  ieTable +
308
524
  '."tags" LIKE @' +
309
525
  tag +
@@ -327,7 +543,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
327
543
  curQuery +=
328
544
  ieTable +
329
545
  '."guid" ' +
330
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
546
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
331
547
  'IN (SELECT "guid" FROM ' +
332
548
  SQLite3Driver.escape(this.prefix + 'data_' + etype) +
333
549
  ' WHERE "name"=@' +
@@ -344,7 +560,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
344
560
  }
345
561
  if (curVar === 'cdate') {
346
562
  curQuery +=
347
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
563
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
348
564
  '(' +
349
565
  ieTable +
350
566
  '."cdate" NOT NULL)';
@@ -352,7 +568,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
352
568
  }
353
569
  else if (curVar === 'mdate') {
354
570
  curQuery +=
355
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
571
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
356
572
  '(' +
357
573
  ieTable +
358
574
  '."mdate" NOT NULL)';
@@ -361,11 +577,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
361
577
  else {
362
578
  const name = `param${++count.i}`;
363
579
  curQuery +=
364
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
580
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
581
+ 'EXISTS (SELECT "guid" FROM ' +
582
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
583
+ ' WHERE "guid"=' +
365
584
  ieTable +
366
- '."guid" IN (SELECT "guid" FROM ' +
367
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
368
- ' WHERE "name"=@' +
585
+ '."guid" AND "name"=@' +
369
586
  name +
370
587
  ' AND "truthy"=1)';
371
588
  params[name] = curVar;
@@ -380,7 +597,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
380
597
  }
381
598
  const cdate = `param${++count.i}`;
382
599
  curQuery +=
383
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
600
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
384
601
  ieTable +
385
602
  '."cdate"=@' +
386
603
  cdate;
@@ -393,7 +610,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
393
610
  }
394
611
  const mdate = `param${++count.i}`;
395
612
  curQuery +=
396
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
613
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
397
614
  ieTable +
398
615
  '."mdate"=@' +
399
616
  mdate;
@@ -407,11 +624,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
407
624
  const name = `param${++count.i}`;
408
625
  const value = `param${++count.i}`;
409
626
  curQuery +=
410
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
627
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
628
+ 'EXISTS (SELECT "guid" FROM ' +
629
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
630
+ ' WHERE "guid"=' +
411
631
  ieTable +
412
- '."guid" IN (SELECT "guid" FROM ' +
413
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
414
- ' WHERE "name"=@' +
632
+ '."guid" AND "name"=@' +
415
633
  name +
416
634
  ' AND "number"=@' +
417
635
  value +
@@ -426,11 +644,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
426
644
  const name = `param${++count.i}`;
427
645
  const value = `param${++count.i}`;
428
646
  curQuery +=
429
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
647
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
648
+ 'EXISTS (SELECT "guid" FROM ' +
649
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
650
+ ' WHERE "guid"=' +
430
651
  ieTable +
431
- '."guid" IN (SELECT "guid" FROM ' +
432
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
433
- ' WHERE "name"=@' +
652
+ '."guid" AND "name"=@' +
434
653
  name +
435
654
  ' AND "string"=@' +
436
655
  value +
@@ -453,15 +672,16 @@ class SQLite3Driver extends nymph_1.NymphDriver {
453
672
  const name = `param${++count.i}`;
454
673
  const value = `param${++count.i}`;
455
674
  curQuery +=
456
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
457
- ieTable +
458
- '."guid" IN (SELECT "guid" FROM ' +
675
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
676
+ 'EXISTS (SELECT "guid" FROM ' +
459
677
  SQLite3Driver.escape(this.prefix + 'data_' + etype) +
460
- ' WHERE "name"=@' +
678
+ ' WHERE "guid"=' +
679
+ ieTable +
680
+ '."guid" AND "name"=@' +
461
681
  name +
462
- ' AND "value"=@' +
682
+ ' AND "json"=jsonb(@' +
463
683
  value +
464
- ')';
684
+ '))';
465
685
  params[name] = curValue[0];
466
686
  params[value] = svalue;
467
687
  }
@@ -474,9 +694,9 @@ class SQLite3Driver extends nymph_1.NymphDriver {
474
694
  }
475
695
  const cdate = `param${++count.i}`;
476
696
  curQuery +=
477
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
697
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
478
698
  ieTable +
479
- '."cdate"=' +
699
+ '."cdate"=@' +
480
700
  cdate;
481
701
  params[cdate] = Number(curValue[1]);
482
702
  break;
@@ -487,67 +707,163 @@ class SQLite3Driver extends nymph_1.NymphDriver {
487
707
  }
488
708
  const mdate = `param${++count.i}`;
489
709
  curQuery +=
490
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
710
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
491
711
  ieTable +
492
- '."mdate"=' +
712
+ '."mdate"=@' +
493
713
  mdate;
494
714
  params[mdate] = Number(curValue[1]);
495
715
  break;
496
716
  }
497
717
  else {
718
+ const containTableSuffix = makeTableSuffix();
498
719
  if (curQuery) {
499
720
  curQuery += typeIsOr ? ' OR ' : ' AND ';
500
721
  }
501
722
  let svalue;
502
- let stringValue;
503
723
  if (curValue[1] instanceof Object &&
504
724
  typeof curValue[1].toReference === 'function') {
505
725
  svalue = JSON.stringify(curValue[1].toReference());
506
- stringValue = `${curValue[1].toReference()}`;
507
726
  }
508
727
  else {
509
728
  svalue = JSON.stringify(curValue[1]);
510
- stringValue = `${curValue[1]}`;
511
729
  }
512
730
  const name = `param${++count.i}`;
513
731
  const value = `param${++count.i}`;
514
- if (typeof curValue[1] === 'string') {
515
- const stringParam = `param${++count.i}`;
516
- curQuery +=
517
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
518
- '(' +
519
- ieTable +
520
- '."guid" IN (SELECT "guid" FROM ' +
521
- SQLite3Driver.escape(this.prefix + 'data_' + etype) +
522
- ' WHERE "name"=@' +
523
- name +
524
- ' AND instr("value", @' +
525
- value +
526
- ')) OR ' +
527
- ieTable +
528
- '."guid" IN (SELECT "guid" FROM ' +
529
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
530
- ' WHERE "name"=@' +
531
- name +
532
- ' AND "string"=@' +
533
- stringParam +
534
- '))';
535
- params[stringParam] = stringValue;
732
+ curQuery +=
733
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
734
+ 'EXISTS (SELECT "guid" FROM ' +
735
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
736
+ ' d' +
737
+ containTableSuffix +
738
+ ' WHERE "guid"=' +
739
+ ieTable +
740
+ '."guid" AND "name"=@' +
741
+ name +
742
+ ' AND json(@' +
743
+ value +
744
+ ') IN (SELECT json_quote("value") FROM json_each(d' +
745
+ containTableSuffix +
746
+ '."json")))';
747
+ params[name] = curValue[0];
748
+ params[value] = svalue;
749
+ }
750
+ break;
751
+ case 'search':
752
+ case '!search':
753
+ if (curValue[0] === 'cdate' || curValue[0] === 'mdate') {
754
+ if (curQuery) {
755
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
536
756
  }
537
- else {
538
- curQuery +=
539
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
540
- ieTable +
541
- '."guid" IN (SELECT "guid" FROM ' +
542
- SQLite3Driver.escape(this.prefix + 'data_' + etype) +
543
- ' WHERE "name"=@' +
544
- name +
545
- ' AND instr("value", @' +
546
- value +
547
- '))';
757
+ curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(0)';
758
+ break;
759
+ }
760
+ else {
761
+ if (curQuery) {
762
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
763
+ }
764
+ const name = `param${++count.i}`;
765
+ const queryPartToken = (term) => {
766
+ const value = `param${++count.i}`;
767
+ params[value] = term.token;
768
+ return ('EXISTS (SELECT "guid" FROM ' +
769
+ SQLite3Driver.escape(this.prefix + 'tokens_' + etype) +
770
+ ' WHERE "guid"=' +
771
+ ieTable +
772
+ '."guid" AND "name"=@' +
773
+ name +
774
+ ' AND "token"=@' +
775
+ value +
776
+ (term.nostemmed ? ' AND "stem"=0' : '') +
777
+ ')');
778
+ };
779
+ const queryPartSeries = (series) => {
780
+ const tokenTableSuffix = makeTableSuffix();
781
+ const tokenParts = series.tokens.map((token, i) => {
782
+ const value = `param${++count.i}`;
783
+ params[value] = token.token;
784
+ return {
785
+ fromClause: i === 0
786
+ ? 'FROM ' +
787
+ SQLite3Driver.escape(this.prefix + 'tokens_' + etype) +
788
+ ' t' +
789
+ tokenTableSuffix +
790
+ '0'
791
+ : 'JOIN ' +
792
+ SQLite3Driver.escape(this.prefix + 'tokens_' + etype) +
793
+ ' t' +
794
+ tokenTableSuffix +
795
+ i +
796
+ ' ON t' +
797
+ tokenTableSuffix +
798
+ i +
799
+ '."guid" = t' +
800
+ tokenTableSuffix +
801
+ '0."guid" AND t' +
802
+ tokenTableSuffix +
803
+ i +
804
+ '."name" = t' +
805
+ tokenTableSuffix +
806
+ '0."name" AND t' +
807
+ tokenTableSuffix +
808
+ i +
809
+ '."position" = t' +
810
+ tokenTableSuffix +
811
+ '0."position" + ' +
812
+ i,
813
+ whereClause: 't' +
814
+ tokenTableSuffix +
815
+ i +
816
+ '."token"=@' +
817
+ value +
818
+ (token.nostemmed
819
+ ? ' AND t' + tokenTableSuffix + i + '."stem"=0'
820
+ : ''),
821
+ };
822
+ });
823
+ return ('EXISTS (SELECT t' +
824
+ tokenTableSuffix +
825
+ '0."guid" ' +
826
+ tokenParts.map((part) => part.fromClause).join(' ') +
827
+ ' WHERE t' +
828
+ tokenTableSuffix +
829
+ '0."guid"=' +
830
+ ieTable +
831
+ '."guid" AND t' +
832
+ tokenTableSuffix +
833
+ '0."name"=@' +
834
+ name +
835
+ ' AND ' +
836
+ tokenParts.map((part) => part.whereClause).join(' AND ') +
837
+ ')');
838
+ };
839
+ const queryPartTerm = (term) => {
840
+ if (term.type === 'series') {
841
+ return queryPartSeries(term);
842
+ }
843
+ else if (term.type === 'not') {
844
+ return 'NOT ' + queryPartTerm(term.operand);
845
+ }
846
+ else if (term.type === 'or') {
847
+ let queryParts = [];
848
+ for (let operand of term.operands) {
849
+ queryParts.push(queryPartTerm(operand));
850
+ }
851
+ return '(' + queryParts.join(' OR ') + ')';
852
+ }
853
+ return queryPartToken(term);
854
+ };
855
+ const parsedFTSQuery = this.tokenizer.parseSearchQuery(curValue[1]);
856
+ // Run through the query and add terms.
857
+ let termStrings = [];
858
+ for (let term of parsedFTSQuery) {
859
+ termStrings.push(queryPartTerm(term));
548
860
  }
861
+ curQuery +=
862
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
863
+ '(' +
864
+ termStrings.join(' AND ') +
865
+ ')';
549
866
  params[name] = curValue[0];
550
- params[value] = svalue;
551
867
  }
552
868
  break;
553
869
  case 'match':
@@ -558,7 +874,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
558
874
  }
559
875
  const cdate = `param${++count.i}`;
560
876
  curQuery +=
561
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
877
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
562
878
  '(' +
563
879
  ieTable +
564
880
  '."cdate" REGEXP @' +
@@ -573,7 +889,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
573
889
  }
574
890
  const mdate = `param${++count.i}`;
575
891
  curQuery +=
576
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
892
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
577
893
  '(' +
578
894
  ieTable +
579
895
  '."mdate" REGEXP @' +
@@ -589,11 +905,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
589
905
  const name = `param${++count.i}`;
590
906
  const value = `param${++count.i}`;
591
907
  curQuery +=
592
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
908
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
909
+ 'EXISTS (SELECT "guid" FROM ' +
910
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
911
+ ' WHERE "guid"=' +
593
912
  ieTable +
594
- '."guid" IN (SELECT "guid" FROM ' +
595
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
596
- ' WHERE "name"=@' +
913
+ '."guid" AND "name"=@' +
597
914
  name +
598
915
  ' AND "string" REGEXP @' +
599
916
  value +
@@ -610,7 +927,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
610
927
  }
611
928
  const cdate = `param${++count.i}`;
612
929
  curQuery +=
613
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
930
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
614
931
  '(' +
615
932
  ieTable +
616
933
  '."cdate" REGEXP @' +
@@ -625,7 +942,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
625
942
  }
626
943
  const mdate = `param${++count.i}`;
627
944
  curQuery +=
628
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
945
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
629
946
  '(' +
630
947
  ieTable +
631
948
  '."mdate" REGEXP @' +
@@ -641,11 +958,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
641
958
  const name = `param${++count.i}`;
642
959
  const value = `param${++count.i}`;
643
960
  curQuery +=
644
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
961
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
962
+ 'EXISTS (SELECT "guid" FROM ' +
963
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
964
+ ' WHERE "guid"=' +
645
965
  ieTable +
646
- '."guid" IN (SELECT "guid" FROM ' +
647
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
648
- ' WHERE "name"=@' +
966
+ '."guid" AND "name"=@' +
649
967
  name +
650
968
  ' AND lower("string") REGEXP lower(@' +
651
969
  value +
@@ -662,7 +980,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
662
980
  }
663
981
  const cdate = `param${++count.i}`;
664
982
  curQuery +=
665
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
983
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
666
984
  '(' +
667
985
  ieTable +
668
986
  '."cdate" LIKE @' +
@@ -677,7 +995,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
677
995
  }
678
996
  const mdate = `param${++count.i}`;
679
997
  curQuery +=
680
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
998
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
681
999
  '(' +
682
1000
  ieTable +
683
1001
  '."mdate" LIKE @' +
@@ -693,11 +1011,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
693
1011
  const name = `param${++count.i}`;
694
1012
  const value = `param${++count.i}`;
695
1013
  curQuery +=
696
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1014
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1015
+ 'EXISTS (SELECT "guid" FROM ' +
1016
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1017
+ ' WHERE "guid"=' +
697
1018
  ieTable +
698
- '."guid" IN (SELECT "guid" FROM ' +
699
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
700
- ' WHERE "name"=@' +
1019
+ '."guid" AND "name"=@' +
701
1020
  name +
702
1021
  ' AND "string" LIKE @' +
703
1022
  value +
@@ -714,7 +1033,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
714
1033
  }
715
1034
  const cdate = `param${++count.i}`;
716
1035
  curQuery +=
717
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1036
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
718
1037
  '(' +
719
1038
  ieTable +
720
1039
  '."cdate" LIKE @' +
@@ -729,7 +1048,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
729
1048
  }
730
1049
  const mdate = `param${++count.i}`;
731
1050
  curQuery +=
732
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1051
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
733
1052
  '(' +
734
1053
  ieTable +
735
1054
  '."mdate" LIKE @' +
@@ -745,11 +1064,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
745
1064
  const name = `param${++count.i}`;
746
1065
  const value = `param${++count.i}`;
747
1066
  curQuery +=
748
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1067
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1068
+ 'EXISTS (SELECT "guid" FROM ' +
1069
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1070
+ ' WHERE "guid"=' +
749
1071
  ieTable +
750
- '."guid" IN (SELECT "guid" FROM ' +
751
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
752
- ' WHERE "name"=@' +
1072
+ '."guid" AND "name"=@' +
753
1073
  name +
754
1074
  ' AND lower("string") LIKE lower(@' +
755
1075
  value +
@@ -766,7 +1086,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
766
1086
  }
767
1087
  const cdate = `param${++count.i}`;
768
1088
  curQuery +=
769
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1089
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
770
1090
  ieTable +
771
1091
  '."cdate">@' +
772
1092
  cdate;
@@ -779,7 +1099,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
779
1099
  }
780
1100
  const mdate = `param${++count.i}`;
781
1101
  curQuery +=
782
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1102
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
783
1103
  ieTable +
784
1104
  '."mdate">@' +
785
1105
  mdate;
@@ -793,11 +1113,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
793
1113
  const name = `param${++count.i}`;
794
1114
  const value = `param${++count.i}`;
795
1115
  curQuery +=
796
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1116
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1117
+ 'EXISTS (SELECT "guid" FROM ' +
1118
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1119
+ ' WHERE "guid"=' +
797
1120
  ieTable +
798
- '."guid" IN (SELECT "guid" FROM ' +
799
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
800
- ' WHERE "name"=@' +
1121
+ '."guid" AND "name"=@' +
801
1122
  name +
802
1123
  ' AND "number">@' +
803
1124
  value +
@@ -814,7 +1135,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
814
1135
  }
815
1136
  const cdate = `param${++count.i}`;
816
1137
  curQuery +=
817
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1138
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
818
1139
  ieTable +
819
1140
  '."cdate">=@' +
820
1141
  cdate;
@@ -827,7 +1148,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
827
1148
  }
828
1149
  const mdate = `param${++count.i}`;
829
1150
  curQuery +=
830
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1151
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
831
1152
  ieTable +
832
1153
  '."mdate">=@' +
833
1154
  mdate;
@@ -841,11 +1162,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
841
1162
  const name = `param${++count.i}`;
842
1163
  const value = `param${++count.i}`;
843
1164
  curQuery +=
844
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1165
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1166
+ 'EXISTS (SELECT "guid" FROM ' +
1167
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1168
+ ' WHERE "guid"=' +
845
1169
  ieTable +
846
- '."guid" IN (SELECT "guid" FROM ' +
847
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
848
- ' WHERE "name"=@' +
1170
+ '."guid" AND "name"=@' +
849
1171
  name +
850
1172
  ' AND "number">=@' +
851
1173
  value +
@@ -862,7 +1184,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
862
1184
  }
863
1185
  const cdate = `param${++count.i}`;
864
1186
  curQuery +=
865
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1187
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
866
1188
  ieTable +
867
1189
  '."cdate"<@' +
868
1190
  cdate;
@@ -875,7 +1197,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
875
1197
  }
876
1198
  const mdate = `param${++count.i}`;
877
1199
  curQuery +=
878
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1200
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
879
1201
  ieTable +
880
1202
  '."mdate"<@' +
881
1203
  mdate;
@@ -889,11 +1211,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
889
1211
  const name = `param${++count.i}`;
890
1212
  const value = `param${++count.i}`;
891
1213
  curQuery +=
892
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1214
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1215
+ 'EXISTS (SELECT "guid" FROM ' +
1216
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1217
+ ' WHERE "guid"=' +
893
1218
  ieTable +
894
- '."guid" IN (SELECT "guid" FROM ' +
895
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
896
- ' WHERE "name"=@' +
1219
+ '."guid" AND "name"=@' +
897
1220
  name +
898
1221
  ' AND "number"<@' +
899
1222
  value +
@@ -910,7 +1233,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
910
1233
  }
911
1234
  const cdate = `param${++count.i}`;
912
1235
  curQuery +=
913
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1236
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
914
1237
  ieTable +
915
1238
  '."cdate"<=@' +
916
1239
  cdate;
@@ -923,7 +1246,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
923
1246
  }
924
1247
  const mdate = `param${++count.i}`;
925
1248
  curQuery +=
926
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1249
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
927
1250
  ieTable +
928
1251
  '."mdate"<=@' +
929
1252
  mdate;
@@ -937,11 +1260,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
937
1260
  const name = `param${++count.i}`;
938
1261
  const value = `param${++count.i}`;
939
1262
  curQuery +=
940
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1263
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1264
+ 'EXISTS (SELECT "guid" FROM ' +
1265
+ SQLite3Driver.escape(this.prefix + 'data_' + etype) +
1266
+ ' WHERE "guid"=' +
941
1267
  ieTable +
942
- '."guid" IN (SELECT "guid" FROM ' +
943
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
944
- ' WHERE "name"=@' +
1268
+ '."guid" AND "name"=@' +
945
1269
  name +
946
1270
  ' AND "number"<=@' +
947
1271
  value +
@@ -968,11 +1292,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
968
1292
  const name = `param${++count.i}`;
969
1293
  const guid = `param${++count.i}`;
970
1294
  curQuery +=
971
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
972
- ieTable +
973
- '."guid" IN (SELECT "guid" FROM ' +
1295
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1296
+ 'EXISTS (SELECT "guid" FROM ' +
974
1297
  SQLite3Driver.escape(this.prefix + 'references_' + etype) +
975
- ' WHERE "name"=@' +
1298
+ ' WHERE "guid"=' +
1299
+ ieTable +
1300
+ '."guid" AND "name"=@' +
976
1301
  name +
977
1302
  ' AND "reference"=@' +
978
1303
  guid +
@@ -987,29 +1312,37 @@ class SQLite3Driver extends nymph_1.NymphDriver {
987
1312
  curQuery += typeIsOr ? ' OR ' : ' AND ';
988
1313
  }
989
1314
  curQuery +=
990
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1315
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
991
1316
  '(' +
992
1317
  subquery.query +
993
1318
  ')';
994
1319
  break;
995
1320
  case 'qref':
996
1321
  case '!qref':
1322
+ const referenceTableSuffix = makeTableSuffix();
997
1323
  const [qrefOptions, ...qrefSelectors] = curValue[1];
998
1324
  const QrefEntityClass = qrefOptions.class;
999
1325
  etypes.push(QrefEntityClass.ETYPE);
1000
- const qrefQuery = this.makeEntityQuery({ ...qrefOptions, return: 'guid', class: QrefEntityClass }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, (0, guid_1.makeTableSuffix)(), etypes);
1326
+ const qrefQuery = this.makeEntityQuery({ ...qrefOptions, return: 'guid', class: QrefEntityClass }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' + referenceTableSuffix + '."reference"');
1001
1327
  if (curQuery) {
1002
1328
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1003
1329
  }
1004
1330
  const qrefName = `param${++count.i}`;
1005
1331
  curQuery +=
1006
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1007
- ieTable +
1008
- '."guid" IN (SELECT "guid" FROM ' +
1332
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1333
+ 'EXISTS (SELECT "guid" FROM ' +
1009
1334
  SQLite3Driver.escape(this.prefix + 'references_' + etype) +
1010
- ' WHERE "name"=@' +
1335
+ ' r' +
1336
+ referenceTableSuffix +
1337
+ ' WHERE r' +
1338
+ referenceTableSuffix +
1339
+ '."guid"=' +
1340
+ ieTable +
1341
+ '."guid" AND r' +
1342
+ referenceTableSuffix +
1343
+ '."name"=@' +
1011
1344
  qrefName +
1012
- ' AND "reference" IN (' +
1345
+ ' AND EXISTS (' +
1013
1346
  qrefQuery.query +
1014
1347
  '))';
1015
1348
  params[qrefName] = curValue[0];
@@ -1019,18 +1352,31 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1019
1352
  return curQuery;
1020
1353
  });
1021
1354
  let sortBy;
1355
+ let sortByInner;
1356
+ let sortJoin = '';
1357
+ const order = options.reverse ? ' DESC' : '';
1022
1358
  switch (sort) {
1023
1359
  case 'mdate':
1024
- sortBy = '"mdate"';
1360
+ sortBy = `${eTable}."mdate"${order}`;
1361
+ sortByInner = `${ieTable}."mdate"${order}`;
1025
1362
  break;
1026
1363
  case 'cdate':
1364
+ sortBy = `${eTable}."cdate"${order}`;
1365
+ sortByInner = `${ieTable}."cdate"${order}`;
1366
+ break;
1027
1367
  default:
1028
- sortBy = '"cdate"';
1368
+ const name = `param${++count.i}`;
1369
+ sortJoin = `LEFT JOIN (
1370
+ SELECT "guid", "string", "number"
1371
+ FROM ${SQLite3Driver.escape(this.prefix + 'data_' + etype)}
1372
+ WHERE "name"=@${name}
1373
+ ORDER BY "number"${order}, "string"${order}
1374
+ ) ${sTable} USING ("guid")`;
1375
+ sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
1376
+ sortByInner = sortBy;
1377
+ params[name] = sort;
1029
1378
  break;
1030
1379
  }
1031
- if (options.reverse) {
1032
- sortBy += ' DESC';
1033
- }
1034
1380
  let query;
1035
1381
  if (queryParts.length) {
1036
1382
  if (subquery) {
@@ -1046,25 +1392,29 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1046
1392
  offset = ` OFFSET ${Math.floor(Number(options.offset))}`;
1047
1393
  }
1048
1394
  const whereClause = queryParts.join(') AND (');
1395
+ const guidClause = guidSelector
1396
+ ? `${ieTable}."guid"=${guidSelector} AND `
1397
+ : '';
1049
1398
  if (options.return === 'count') {
1050
1399
  if (limit || offset) {
1051
1400
  query = `SELECT COUNT("guid") AS "count" FROM (
1052
1401
  SELECT "guid"
1053
1402
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1054
- WHERE (${whereClause})${limit}${offset}
1403
+ WHERE ${guidClause}(${whereClause})${limit}${offset}
1055
1404
  )`;
1056
1405
  }
1057
1406
  else {
1058
1407
  query = `SELECT COUNT("guid") AS "count"
1059
1408
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1060
- WHERE (${whereClause})`;
1409
+ WHERE ${guidClause}(${whereClause})`;
1061
1410
  }
1062
1411
  }
1063
1412
  else if (options.return === 'guid') {
1064
1413
  query = `SELECT "guid"
1065
1414
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1066
- WHERE (${whereClause})
1067
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1415
+ ${sortJoin}
1416
+ WHERE ${guidClause}(${whereClause})
1417
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1068
1418
  }
1069
1419
  else {
1070
1420
  query = `SELECT
@@ -1074,18 +1424,20 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1074
1424
  ${eTable}."mdate",
1075
1425
  ${dTable}."name",
1076
1426
  ${dTable}."value",
1077
- ${cTable}."string",
1078
- ${cTable}."number"
1427
+ json(${dTable}."json") as "json",
1428
+ ${dTable}."string",
1429
+ ${dTable}."number"
1079
1430
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1080
1431
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1081
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} ${cTable} USING ("guid", "name")
1432
+ ${sortJoin}
1082
1433
  INNER JOIN (
1083
1434
  SELECT "guid"
1084
1435
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1085
- WHERE (${whereClause})
1086
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1436
+ ${sortJoin}
1437
+ WHERE ${guidClause}(${whereClause})
1438
+ ORDER BY ${sortByInner}${limit}${offset}
1087
1439
  ) ${fTable} USING ("guid")
1088
- ORDER BY ${eTable}.${sortBy}`;
1440
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1089
1441
  }
1090
1442
  }
1091
1443
  }
@@ -1102,22 +1454,27 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1102
1454
  if ('offset' in options) {
1103
1455
  offset = ` OFFSET ${Math.floor(Number(options.offset))}`;
1104
1456
  }
1457
+ const guidClause = guidSelector
1458
+ ? ` WHERE ${ieTable}."guid"=${guidSelector}`
1459
+ : '';
1105
1460
  if (options.return === 'count') {
1106
1461
  if (limit || offset) {
1107
1462
  query = `SELECT COUNT("guid") AS "count" FROM (
1108
1463
  SELECT "guid"
1109
- FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${limit}${offset}
1464
+ FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${guidClause}${limit}${offset}
1110
1465
  )`;
1111
1466
  }
1112
1467
  else {
1113
1468
  query = `SELECT COUNT("guid") AS "count"
1114
- FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}`;
1469
+ FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${guidClause}`;
1115
1470
  }
1116
1471
  }
1117
1472
  else if (options.return === 'guid') {
1118
1473
  query = `SELECT "guid"
1119
1474
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1120
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1475
+ ${sortJoin}
1476
+ ${guidClause}
1477
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1121
1478
  }
1122
1479
  else {
1123
1480
  if (limit || offset) {
@@ -1128,17 +1485,20 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1128
1485
  ${eTable}."mdate",
1129
1486
  ${dTable}."name",
1130
1487
  ${dTable}."value",
1131
- ${cTable}."string",
1132
- ${cTable}."number"
1488
+ json(${dTable}."json") as "json",
1489
+ ${dTable}."string",
1490
+ ${dTable}."number"
1133
1491
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1134
1492
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1135
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} c USING ("guid", "name")
1493
+ ${sortJoin}
1136
1494
  INNER JOIN (
1137
1495
  SELECT "guid"
1138
1496
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1139
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1497
+ ${sortJoin}
1498
+ ${guidClause}
1499
+ ORDER BY ${sortByInner}${limit}${offset}
1140
1500
  ) ${fTable} USING ("guid")
1141
- ORDER BY ${eTable}.${sortBy}`;
1501
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1142
1502
  }
1143
1503
  else {
1144
1504
  query = `SELECT
@@ -1148,12 +1508,14 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1148
1508
  ${eTable}."mdate",
1149
1509
  ${dTable}."name",
1150
1510
  ${dTable}."value",
1151
- ${cTable}."string",
1152
- ${cTable}."number"
1511
+ json(${dTable}."json") as "json",
1512
+ ${dTable}."string",
1513
+ ${dTable}."number"
1153
1514
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1154
1515
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1155
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} ${cTable} USING ("guid", "name")
1156
- ORDER BY ${eTable}.${sortBy}`;
1516
+ ${sortJoin}
1517
+ ${guidSelector ? `WHERE ${eTable}."guid"=${guidSelector}` : ''}
1518
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1157
1519
  }
1158
1520
  }
1159
1521
  }
@@ -1169,20 +1531,22 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1169
1531
  }
1170
1532
  performQuery(options, formattedSelectors, etype) {
1171
1533
  const { query, params, etypes } = this.makeEntityQuery(options, formattedSelectors, etype);
1172
- const result = this.queryIter(query, { etypes, params })[Symbol.iterator]();
1534
+ const result = this.queryArray(query, { etypes, params })[Symbol.iterator]();
1173
1535
  return {
1174
1536
  result,
1175
1537
  };
1176
1538
  }
1177
1539
  async getEntities(options = {}, ...selectors) {
1178
- return this.getEntitiesSync(options, ...selectors);
1179
- }
1180
- getEntitiesSync(options = {}, ...selectors) {
1181
- const { result, process } = this.getEntitesRowLike(options, selectors, (options, formattedSelectors, etype) => this.performQuery(options, formattedSelectors, etype), () => {
1540
+ const { result, process } = this.getEntitiesRowLike(options, selectors, ({ options, selectors, etype }) => this.performQuery(options, selectors, etype), () => {
1182
1541
  const next = result.next();
1183
1542
  return next.done ? null : next.value;
1184
1543
  }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
1185
- tags: row.tags.length > 2 ? row.tags.slice(1, -1).split(',') : [],
1544
+ tags: row.tags.length > 2
1545
+ ? row.tags
1546
+ .slice(1, -1)
1547
+ .split(',')
1548
+ .filter((tag) => tag)
1549
+ : [],
1186
1550
  cdate: Number(row.cdate),
1187
1551
  mdate: Number(row.mdate),
1188
1552
  }), (row) => ({
@@ -1191,13 +1555,19 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1191
1555
  ? JSON.stringify(row.number)
1192
1556
  : row.value === 'S'
1193
1557
  ? JSON.stringify(row.string)
1194
- : row.value,
1558
+ : row.value === 'J'
1559
+ ? row.json
1560
+ : row.value,
1195
1561
  }));
1196
- return process();
1562
+ const value = process();
1563
+ if (value instanceof Error) {
1564
+ throw value;
1565
+ }
1566
+ return value;
1197
1567
  }
1198
1568
  async getUID(name) {
1199
1569
  if (name == null) {
1200
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1570
+ throw new InvalidParametersError('Name not given for UID.');
1201
1571
  }
1202
1572
  const result = this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1203
1573
  params: {
@@ -1206,10 +1576,15 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1206
1576
  });
1207
1577
  return result?.cur_uid ?? null;
1208
1578
  }
1209
- async import(filename) {
1210
- this.checkReadOnlyMode();
1579
+ async importEntity(entity) {
1580
+ return await this.importEntityInternal(entity, false);
1581
+ }
1582
+ async importEntityTokens(entity) {
1583
+ return await this.importEntityInternal(entity, true);
1584
+ }
1585
+ async importEntityInternal({ guid, cdate, mdate, tags, sdata, etype, }, onlyTokens) {
1211
1586
  try {
1212
- return this.importFromFile(filename, async (guid, tags, sdata, etype) => {
1587
+ if (!onlyTokens) {
1213
1588
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, {
1214
1589
  etypes: [etype],
1215
1590
  params: {
@@ -1222,29 +1597,37 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1222
1597
  guid,
1223
1598
  },
1224
1599
  });
1225
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
1600
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
1226
1601
  etypes: [etype],
1227
1602
  params: {
1228
1603
  guid,
1229
1604
  },
1230
1605
  });
1231
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
1606
+ }
1607
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=@guid;`, {
1608
+ etypes: [etype],
1609
+ params: {
1610
+ guid,
1611
+ },
1612
+ });
1613
+ if (!onlyTokens) {
1614
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
1232
1615
  etypes: [etype],
1233
1616
  params: {
1234
1617
  guid,
1235
1618
  },
1236
1619
  });
1620
+ }
1621
+ if (!onlyTokens) {
1237
1622
  this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`, {
1238
1623
  etypes: [etype],
1239
1624
  params: {
1240
1625
  guid,
1241
1626
  tags: ',' + tags.join(',') + ',',
1242
- cdate: Number(JSON.parse(sdata.cdate)),
1243
- mdate: Number(JSON.parse(sdata.mdate)),
1627
+ cdate,
1628
+ mdate,
1244
1629
  },
1245
1630
  });
1246
- delete sdata.cdate;
1247
- delete sdata.mdate;
1248
1631
  for (const name in sdata) {
1249
1632
  const value = sdata[name];
1250
1633
  const uvalue = JSON.parse(value);
@@ -1255,23 +1638,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1255
1638
  ? 'N'
1256
1639
  : typeof uvalue === 'string'
1257
1640
  ? 'S'
1258
- : value;
1259
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`, {
1641
+ : 'J';
1642
+ const jsonValue = storageValue === 'J' ? value : null;
1643
+ 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);`, {
1260
1644
  etypes: [etype],
1261
1645
  params: {
1262
1646
  guid,
1263
1647
  name,
1264
1648
  storageValue,
1265
- },
1266
- });
1267
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`, {
1268
- etypes: [etype],
1269
- params: {
1270
- guid,
1271
- name,
1272
- truthy: uvalue ? 1 : 0,
1273
- string: `${uvalue}`,
1649
+ jsonValue,
1650
+ string: storageValue === 'J' ? null : `${uvalue}`,
1274
1651
  number: Number(uvalue),
1652
+ truthy: uvalue ? 1 : 0,
1275
1653
  },
1276
1654
  });
1277
1655
  const references = this.findReferences(value);
@@ -1286,41 +1664,113 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1286
1664
  });
1287
1665
  }
1288
1666
  }
1289
- }, async (name, curUid) => {
1290
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1291
- params: {
1292
- name,
1293
- },
1294
- });
1295
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
1296
- params: {
1297
- name,
1298
- curUid,
1299
- },
1667
+ }
1668
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
1669
+ for (let name in sdata) {
1670
+ let tokenString = null;
1671
+ try {
1672
+ tokenString = EntityClass.getFTSText(name, JSON.parse(sdata[name]));
1673
+ }
1674
+ catch (e) {
1675
+ // Ignore error.
1676
+ }
1677
+ if (tokenString != null) {
1678
+ const tokens = this.tokenizer.tokenize(tokenString);
1679
+ while (tokens.length) {
1680
+ const currentTokens = tokens.splice(0, 100);
1681
+ const params = {
1682
+ guid,
1683
+ name,
1684
+ };
1685
+ const values = [];
1686
+ for (let i = 0; i < currentTokens.length; i++) {
1687
+ const token = currentTokens[i];
1688
+ params['token' + i] = token.token;
1689
+ params['position' + i] = token.position;
1690
+ params['stem' + i] = token.stem ? 1 : 0;
1691
+ values.push('(@guid, @name, @token' +
1692
+ i +
1693
+ ', @position' +
1694
+ i +
1695
+ ', @stem' +
1696
+ i +
1697
+ ')');
1698
+ }
1699
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`, {
1700
+ etypes: [etype],
1701
+ params,
1702
+ });
1703
+ }
1704
+ }
1705
+ }
1706
+ if (!onlyTokens) {
1707
+ const uniques = await EntityClass.getUniques({
1708
+ guid,
1709
+ cdate,
1710
+ mdate,
1711
+ tags,
1712
+ data: {},
1713
+ sdata,
1300
1714
  });
1301
- }, async () => {
1302
- await this.startTransaction('nymph-import');
1303
- }, async () => {
1304
- await this.commit('nymph-import');
1715
+ for (const unique of uniques) {
1716
+ try {
1717
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (@guid, @unique);`, {
1718
+ etypes: [etype],
1719
+ params: {
1720
+ guid,
1721
+ unique,
1722
+ },
1723
+ });
1724
+ }
1725
+ catch (e) {
1726
+ if (e instanceof EntityUniqueConstraintError) {
1727
+ this.nymph.config.debugError('sqlite3', `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
1728
+ }
1729
+ throw e;
1730
+ }
1731
+ }
1732
+ }
1733
+ }
1734
+ catch (e) {
1735
+ this.nymph.config.debugError('sqlite3', `Import entity error: "${e}"`);
1736
+ throw e;
1737
+ }
1738
+ }
1739
+ async importUID({ name, value }) {
1740
+ try {
1741
+ await this.startTransaction(`nymph-import-uid-${name}`);
1742
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1743
+ params: {
1744
+ name,
1745
+ },
1746
+ });
1747
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @value);`, {
1748
+ params: {
1749
+ name,
1750
+ value,
1751
+ },
1305
1752
  });
1753
+ await this.commit(`nymph-import-uid-${name}`);
1306
1754
  }
1307
1755
  catch (e) {
1308
- await this.rollback('nymph-import');
1756
+ this.nymph.config.debugError('sqlite3', `Import UID error: "${e}"`);
1757
+ await this.rollback(`nymph-import-uid-${name}`);
1309
1758
  throw e;
1310
1759
  }
1311
1760
  }
1312
1761
  async newUID(name) {
1313
1762
  if (name == null) {
1314
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1763
+ throw new InvalidParametersError('Name not given for UID.');
1315
1764
  }
1316
- this.checkReadOnlyMode();
1317
1765
  await this.startTransaction('nymph-newuid');
1766
+ let curUid = undefined;
1318
1767
  try {
1319
- let curUid = this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1320
- params: {
1321
- name,
1322
- },
1323
- })?.cur_uid ?? null;
1768
+ curUid =
1769
+ this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1770
+ params: {
1771
+ name,
1772
+ },
1773
+ })?.cur_uid ?? null;
1324
1774
  if (curUid == null) {
1325
1775
  curUid = 1;
1326
1776
  this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
@@ -1339,41 +1789,50 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1339
1789
  },
1340
1790
  });
1341
1791
  }
1342
- await this.commit('nymph-newuid');
1343
- return curUid;
1344
1792
  }
1345
1793
  catch (e) {
1794
+ this.nymph.config.debugError('sqlite3', `New UID error: "${e}"`);
1346
1795
  await this.rollback('nymph-newuid');
1347
1796
  throw e;
1348
1797
  }
1798
+ await this.commit('nymph-newuid');
1799
+ return curUid;
1349
1800
  }
1350
1801
  async renameUID(oldName, newName) {
1351
1802
  if (oldName == null || newName == null) {
1352
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1803
+ throw new InvalidParametersError('Name not given for UID.');
1353
1804
  }
1354
- this.checkReadOnlyMode();
1805
+ await this.startTransaction('nymph-rename-uid');
1355
1806
  this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}uids`)} SET "name"=@newName WHERE "name"=@oldName;`, {
1356
1807
  params: {
1357
1808
  newName,
1358
1809
  oldName,
1359
1810
  },
1360
1811
  });
1812
+ await this.commit('nymph-rename-uid');
1361
1813
  return true;
1362
1814
  }
1363
1815
  async rollback(name) {
1364
1816
  if (name == null || typeof name !== 'string' || name.length === 0) {
1365
- throw new nymph_1.InvalidParametersError('Transaction rollback attempted without a name.');
1817
+ throw new InvalidParametersError('Transaction rollback attempted without a name.');
1366
1818
  }
1367
- if (this.transactionsStarted === 0) {
1819
+ if (this.store.transactionsStarted === 0) {
1368
1820
  return true;
1369
1821
  }
1370
1822
  this.queryRun(`ROLLBACK TO SAVEPOINT ${SQLite3Driver.escape(name)};`);
1371
- this.transactionsStarted--;
1823
+ this.store.transactionsStarted--;
1824
+ if (this.store.transactionsStarted === 0 &&
1825
+ this.store.linkWrite &&
1826
+ !this.config.explicitWrite) {
1827
+ this.store.linkWrite.exec('PRAGMA optimize;');
1828
+ this.store.linkWrite.close();
1829
+ this.store.linkWrite = undefined;
1830
+ }
1372
1831
  return true;
1373
1832
  }
1374
1833
  async saveEntity(entity) {
1375
- this.checkReadOnlyMode();
1376
- const insertData = (guid, data, sdata, etype) => {
1834
+ const insertData = (guid, data, sdata, uniques, etype) => {
1835
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
1377
1836
  const runInsertQuery = (name, value, svalue) => {
1378
1837
  if (value === undefined) {
1379
1838
  return;
@@ -1382,23 +1841,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1382
1841
  ? 'N'
1383
1842
  : typeof value === 'string'
1384
1843
  ? 'S'
1385
- : svalue;
1386
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`, {
1844
+ : 'J';
1845
+ const jsonValue = storageValue === 'J' ? svalue : null;
1846
+ 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);`, {
1387
1847
  etypes: [etype],
1388
1848
  params: {
1389
1849
  guid,
1390
1850
  name,
1391
1851
  storageValue,
1392
- },
1393
- });
1394
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`, {
1395
- etypes: [etype],
1396
- params: {
1397
- guid,
1398
- name,
1399
- truthy: value ? 1 : 0,
1400
- string: `${value}`,
1852
+ jsonValue,
1853
+ string: storageValue === 'J' ? null : `${value}`,
1401
1854
  number: Number(value),
1855
+ truthy: value ? 1 : 0,
1402
1856
  },
1403
1857
  });
1404
1858
  const references = this.findReferences(svalue);
@@ -1412,7 +1866,59 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1412
1866
  },
1413
1867
  });
1414
1868
  }
1869
+ let tokenString = null;
1870
+ try {
1871
+ tokenString = EntityClass.getFTSText(name, value);
1872
+ }
1873
+ catch (e) {
1874
+ // Ignore error.
1875
+ }
1876
+ if (tokenString != null) {
1877
+ const tokens = this.tokenizer.tokenize(tokenString);
1878
+ while (tokens.length) {
1879
+ const currentTokens = tokens.splice(0, 100);
1880
+ const params = {
1881
+ guid,
1882
+ name,
1883
+ };
1884
+ const values = [];
1885
+ for (let i = 0; i < currentTokens.length; i++) {
1886
+ const token = currentTokens[i];
1887
+ params['token' + i] = token.token;
1888
+ params['position' + i] = token.position;
1889
+ params['stem' + i] = token.stem ? 1 : 0;
1890
+ values.push('(@guid, @name, @token' +
1891
+ i +
1892
+ ', @position' +
1893
+ i +
1894
+ ', @stem' +
1895
+ i +
1896
+ ')');
1897
+ }
1898
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`, {
1899
+ etypes: [etype],
1900
+ params,
1901
+ });
1902
+ }
1903
+ }
1415
1904
  };
1905
+ for (const unique of uniques) {
1906
+ try {
1907
+ this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} ("guid", "unique") VALUES (@guid, @unique);`, {
1908
+ etypes: [etype],
1909
+ params: {
1910
+ guid,
1911
+ unique,
1912
+ },
1913
+ });
1914
+ }
1915
+ catch (e) {
1916
+ if (e instanceof EntityUniqueConstraintError) {
1917
+ this.nymph.config.debugError('sqlite3', `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`);
1918
+ }
1919
+ throw e;
1920
+ }
1921
+ }
1416
1922
  for (const name in data) {
1417
1923
  runInsertQuery(name, data[name], JSON.stringify(data[name]));
1418
1924
  }
@@ -1420,8 +1926,13 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1420
1926
  runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
1421
1927
  }
1422
1928
  };
1929
+ let inTransaction = false;
1423
1930
  try {
1424
- return this.saveEntityRowLike(entity, async (_entity, guid, tags, data, sdata, cdate, etype) => {
1931
+ return this.saveEntityRowLike(entity, async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
1932
+ if (Object.keys(data).length === 0 &&
1933
+ Object.keys(sdata).length === 0) {
1934
+ return false;
1935
+ }
1425
1936
  this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @cdate);`, {
1426
1937
  etypes: [etype],
1427
1938
  params: {
@@ -1430,9 +1941,13 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1430
1941
  cdate,
1431
1942
  },
1432
1943
  });
1433
- insertData(guid, data, sdata, etype);
1944
+ insertData(guid, data, sdata, uniques, etype);
1434
1945
  return true;
1435
- }, async (entity, guid, tags, data, sdata, mdate, etype) => {
1946
+ }, async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
1947
+ if (Object.keys(data).length === 0 &&
1948
+ Object.keys(sdata).length === 0) {
1949
+ return false;
1950
+ }
1436
1951
  const info = this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=@guid AND "mdate" <= @emdate;`, {
1437
1952
  etypes: [etype],
1438
1953
  params: {
@@ -1450,44 +1965,57 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1450
1965
  guid,
1451
1966
  },
1452
1967
  });
1453
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}comparisons_${etype}`)} WHERE "guid"=@guid;`, {
1968
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
1454
1969
  etypes: [etype],
1455
1970
  params: {
1456
1971
  guid,
1457
1972
  },
1458
1973
  });
1459
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}references_${etype}`)} WHERE "guid"=@guid;`, {
1974
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}tokens_${etype}`)} WHERE "guid"=@guid;`, {
1460
1975
  etypes: [etype],
1461
1976
  params: {
1462
1977
  guid,
1463
1978
  },
1464
1979
  });
1465
- insertData(guid, data, sdata, etype);
1980
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
1981
+ etypes: [etype],
1982
+ params: {
1983
+ guid,
1984
+ },
1985
+ });
1986
+ insertData(guid, data, sdata, uniques, etype);
1466
1987
  success = true;
1467
1988
  }
1468
1989
  return success;
1469
1990
  }, async () => {
1470
1991
  await this.startTransaction('nymph-save');
1992
+ inTransaction = true;
1471
1993
  }, async (success) => {
1472
- if (success) {
1473
- await this.commit('nymph-save');
1474
- }
1475
- else {
1476
- await this.rollback('nymph-save');
1994
+ if (inTransaction) {
1995
+ inTransaction = false;
1996
+ if (success) {
1997
+ await this.commit('nymph-save');
1998
+ }
1999
+ else {
2000
+ await this.rollback('nymph-save');
2001
+ }
1477
2002
  }
1478
2003
  return success;
1479
2004
  });
1480
2005
  }
1481
2006
  catch (e) {
1482
- await this.rollback('nymph-save');
2007
+ this.nymph.config.debugError('sqlite3', `Save entity error: "${e}"`);
2008
+ if (inTransaction) {
2009
+ await this.rollback('nymph-save');
2010
+ }
1483
2011
  throw e;
1484
2012
  }
1485
2013
  }
1486
2014
  async setUID(name, curUid) {
1487
2015
  if (name == null) {
1488
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2016
+ throw new InvalidParametersError('Name not given for UID.');
1489
2017
  }
1490
- this.checkReadOnlyMode();
2018
+ await this.startTransaction('nymph-set-uid');
1491
2019
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1492
2020
  params: {
1493
2021
  name,
@@ -1499,16 +2027,54 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1499
2027
  curUid,
1500
2028
  },
1501
2029
  });
2030
+ await this.commit('nymph-set-uid');
1502
2031
  return true;
1503
2032
  }
2033
+ async internalTransaction(name) {
2034
+ await this.startTransaction(name);
2035
+ }
1504
2036
  async startTransaction(name) {
1505
2037
  if (name == null || typeof name !== 'string' || name.length === 0) {
1506
- throw new nymph_1.InvalidParametersError('Transaction start attempted without a name.');
2038
+ throw new InvalidParametersError('Transaction start attempted without a name.');
2039
+ }
2040
+ if (!this.config.explicitWrite && !this.store.linkWrite) {
2041
+ this._connect(true);
1507
2042
  }
1508
2043
  this.queryRun(`SAVEPOINT ${SQLite3Driver.escape(name)};`);
1509
- this.transactionsStarted++;
2044
+ this.store.transactionsStarted++;
1510
2045
  return this.nymph;
1511
2046
  }
2047
+ async needsMigration() {
2048
+ const table = this.queryGet("SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @prefix LIMIT 1;", {
2049
+ params: {
2050
+ prefix: this.prefix + 'data_' + '%',
2051
+ },
2052
+ });
2053
+ if (table?.name) {
2054
+ const result = this.queryGet("SELECT 1 AS `exists` FROM pragma_table_info(@table) WHERE `name`='json';", {
2055
+ params: {
2056
+ table: table.name,
2057
+ },
2058
+ });
2059
+ if (!result?.exists) {
2060
+ return 'json';
2061
+ }
2062
+ }
2063
+ const table2 = this.queryGet("SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @tokenTable LIMIT 1;", {
2064
+ params: {
2065
+ tokenTable: this.prefix + 'tokens_' + '%',
2066
+ },
2067
+ });
2068
+ if (!table2 || !table2.name) {
2069
+ return 'tokens';
2070
+ }
2071
+ return false;
2072
+ }
2073
+ async liveMigration(_migrationType) {
2074
+ const etypes = await this.getEtypes();
2075
+ for (let etype of etypes) {
2076
+ this.createTokensTable(etype);
2077
+ }
2078
+ }
1512
2079
  }
1513
- exports.default = SQLite3Driver;
1514
2080
  //# sourceMappingURL=SQLite3Driver.js.map