@nymphjs/driver-sqlite3 1.0.0-beta.10 → 1.0.0-beta.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,29 +1,36 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
7
- const nymph_1 = require("@nymphjs/nymph");
8
- const guid_1 = require("@nymphjs/guid");
9
- const conf_1 = require("./conf");
1
+ import 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
  }
24
28
  constructor(config, store) {
25
29
  super();
26
- this.config = { ...conf_1.SQLite3DriverConfigDefaults, ...config };
30
+ this.config = { ...defaults, ...config };
31
+ if (this.config.filename === ':memory:') {
32
+ this.config.explicitWrite = true;
33
+ }
27
34
  this.prefix = this.config.prefix;
28
35
  if (store) {
29
36
  this.store = store;
@@ -32,53 +39,126 @@ class SQLite3Driver extends nymph_1.NymphDriver {
32
39
  this.connect();
33
40
  }
34
41
  }
42
+ /**
43
+ * This is used internally by Nymph. Don't call it yourself.
44
+ *
45
+ * @returns A clone of this instance.
46
+ */
35
47
  clone() {
36
48
  return new SQLite3Driver(this.config, this.store);
37
49
  }
38
- async connect() {
39
- const { filename, fileMustExist, timeout, readonly, wal, verbose } = this.config;
50
+ /**
51
+ * Connect to the SQLite3 database.
52
+ *
53
+ * @returns Whether this instance is connected to a SQLite3 database.
54
+ */
55
+ connect() {
40
56
  if (this.store && this.store.connected) {
41
- return true;
57
+ return Promise.resolve(true);
42
58
  }
59
+ // Connecting
60
+ this._connect(false);
61
+ return Promise.resolve(this.store.connected);
62
+ }
63
+ _connect(write) {
64
+ const { filename, fileMustExist, timeout, explicitWrite, wal, verbose } = this.config;
43
65
  try {
44
- const link = new better_sqlite3_1.default(filename, {
45
- readonly,
46
- fileMustExist,
47
- timeout,
48
- verbose,
49
- });
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;
81
+ try {
82
+ link = new SQLite3(filename, {
83
+ readonly: !explicitWrite && !write,
84
+ fileMustExist,
85
+ timeout,
86
+ verbose,
87
+ });
88
+ }
89
+ catch (e) {
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
+ });
112
+ }
113
+ else {
114
+ throw e;
115
+ }
116
+ }
50
117
  if (!this.store) {
118
+ if (write) {
119
+ throw new Error('Tried to open in write without opening in read first.');
120
+ }
51
121
  this.store = new InternalStore(link);
52
122
  }
123
+ else if (write) {
124
+ this.store.linkWrite = link;
125
+ }
53
126
  else {
54
127
  this.store.link = link;
55
128
  }
56
129
  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);
130
+ setOptions(link);
64
131
  }
65
132
  catch (e) {
66
133
  if (this.store) {
67
134
  this.store.connected = false;
68
135
  }
69
136
  if (filename === ':memory:') {
70
- throw new nymph_1.NotConfiguredError("It seems the config hasn't been set up correctly.");
137
+ throw new NotConfiguredError("It seems the config hasn't been set up correctly. Could not connect: " +
138
+ e?.message);
71
139
  }
72
140
  else {
73
- throw new nymph_1.UnableToConnectError('Could not connect: ' + e?.message);
141
+ throw new UnableToConnectError('Could not connect: ' + e?.message);
74
142
  }
75
143
  }
76
- return this.store.connected;
77
144
  }
145
+ /**
146
+ * Disconnect from the SQLite3 database.
147
+ *
148
+ * @returns Whether this instance is connected to a SQLite3 database.
149
+ */
78
150
  async disconnect() {
79
151
  if (this.store.connected) {
80
- this.store.link.exec('PRAGMA optimize;');
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
+ }
81
160
  this.store.link.close();
161
+ this.store.transactionsStarted = 0;
82
162
  this.store.connected = false;
83
163
  }
84
164
  return this.store.connected;
@@ -86,51 +166,91 @@ class SQLite3Driver extends nymph_1.NymphDriver {
86
166
  async inTransaction() {
87
167
  return this.store.transactionsStarted > 0;
88
168
  }
169
+ /**
170
+ * Check connection status.
171
+ *
172
+ * @returns Whether this instance is connected to a SQLite3 database.
173
+ */
89
174
  isConnected() {
90
175
  return this.store.connected;
91
176
  }
92
- checkReadOnlyMode() {
93
- if (this.config.readonly) {
94
- throw new nymph_1.InvalidParametersError('Attempt to write to SQLite3 DB in read only mode.');
95
- }
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\';`);
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");`);
96
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
+ */
97
229
  createTables(etype = null) {
98
- this.checkReadOnlyMode();
99
230
  this.startTransaction('nymph-tablecreation');
100
231
  try {
101
232
  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");`);
233
+ this.createEntitiesTable(etype);
234
+ this.createDataTable(etype);
235
+ this.createReferencesTable(etype);
236
+ this.createTokensTable(etype);
237
+ this.createUniquesTable(etype);
120
238
  }
121
239
  else {
240
+ // Create the UID table.
122
241
  this.queryRun(`CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name" TEXT PRIMARY KEY NOT NULL, "cur_uid" INTEGER NOT NULL);`);
123
242
  }
124
- this.commit('nymph-tablecreation');
125
- return true;
126
243
  }
127
244
  catch (e) {
128
245
  this.rollback('nymph-tablecreation');
129
246
  throw e;
130
247
  }
248
+ this.commit('nymph-tablecreation');
249
+ return true;
131
250
  }
132
251
  query(runQuery, query, etypes = []) {
133
252
  try {
253
+ this.nymph.config.debugInfo('sqlite3:query', query);
134
254
  return runQuery();
135
255
  }
136
256
  catch (e) {
@@ -146,32 +266,45 @@ class SQLite3Driver extends nymph_1.NymphDriver {
146
266
  return runQuery();
147
267
  }
148
268
  catch (e2) {
149
- throw new nymph_1.QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
269
+ throw new QueryFailedError('Query failed: ' + e2?.code + ' - ' + e2?.message, query);
150
270
  }
151
271
  }
272
+ else if (errorCode === 'SQLITE_CONSTRAINT_UNIQUE' &&
273
+ errorMsg.match(/^UNIQUE constraint failed: /)) {
274
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
275
+ }
152
276
  else {
153
- throw new nymph_1.QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
277
+ throw new QueryFailedError('Query failed: ' + e?.code + ' - ' + e?.message, query);
154
278
  }
155
279
  }
156
280
  }
157
- queryIter(query, { etypes = [], params = {}, } = {}) {
158
- return this.query(() => this.store.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);
159
285
  }
160
286
  queryGet(query, { etypes = [], params = {}, } = {}) {
161
- return this.query(() => this.store.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);
162
288
  }
163
289
  queryRun(query, { etypes = [], params = {}, } = {}) {
164
- return this.query(() => this.store.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);
165
291
  }
166
292
  async commit(name) {
167
293
  if (name == null || typeof name !== 'string' || name.length === 0) {
168
- throw new nymph_1.InvalidParametersError('Transaction commit attempted without a name.');
294
+ throw new InvalidParametersError('Transaction commit attempted without a name.');
169
295
  }
170
296
  if (this.store.transactionsStarted === 0) {
171
297
  return true;
172
298
  }
173
299
  this.queryRun(`RELEASE SAVEPOINT ${SQLite3Driver.escape(name)};`);
174
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
+ }
175
308
  return true;
176
309
  }
177
310
  async deleteEntityByID(guid, className) {
@@ -184,7 +317,6 @@ class SQLite3Driver extends nymph_1.NymphDriver {
184
317
  EntityClass = className;
185
318
  }
186
319
  const etype = EntityClass.ETYPE;
187
- this.checkReadOnlyMode();
188
320
  await this.startTransaction('nymph-delete');
189
321
  try {
190
322
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, {
@@ -199,108 +331,167 @@ class SQLite3Driver extends nymph_1.NymphDriver {
199
331
  guid,
200
332
  },
201
333
  });
202
- 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;`, {
203
335
  etypes: [etype],
204
336
  params: {
205
337
  guid,
206
338
  },
207
339
  });
208
- 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;`, {
209
347
  etypes: [etype],
210
348
  params: {
211
349
  guid,
212
350
  },
213
351
  });
214
- await this.commit('nymph-delete');
215
- if (this.nymph.config.cache) {
216
- this.cleanCache(guid);
217
- }
218
- return true;
219
352
  }
220
353
  catch (e) {
354
+ this.nymph.config.debugError('sqlite3', `Delete entity error: "${e}"`);
221
355
  await this.rollback('nymph-delete');
222
356
  throw e;
223
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;
224
364
  }
225
365
  async deleteUID(name) {
226
366
  if (!name) {
227
- throw new nymph_1.InvalidParametersError('Name not given for UID');
367
+ throw new InvalidParametersError('Name not given for UID');
228
368
  }
229
- this.checkReadOnlyMode();
369
+ await this.startTransaction('nymph-delete-uid');
230
370
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
231
371
  params: {
232
372
  name,
233
373
  },
234
374
  });
375
+ await this.commit('nymph-delete-uid');
235
376
  return true;
236
377
  }
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;");
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
+ });
258
384
  const etypes = [];
259
385
  for (const table of tables) {
260
- if (table.name.startsWith(this.prefix + 'entities_')) {
261
- 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;
262
419
  }
263
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();
264
435
  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]();
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]();
266
438
  let datum = dataIterator.next();
267
439
  while (!datum.done) {
268
440
  const guid = datum.value.guid;
269
441
  const tags = datum.value.tags.slice(1, -1);
270
442
  const cdate = datum.value.cdate;
271
443
  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) {
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.
276
451
  do {
277
- const value = datum.value.dvalue === 'N'
452
+ const value = datum.value.value === 'N'
278
453
  ? JSON.stringify(datum.value.number)
279
- : datum.value.dvalue === 'S'
454
+ : datum.value.value === 'S'
280
455
  ? JSON.stringify(datum.value.string)
281
- : datum.value.dvalue;
282
- 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}`);
283
460
  datum = dataIterator.next();
284
461
  } while (!datum.done && datum.value.guid === guid);
285
462
  }
286
463
  else {
464
+ // Make sure that datum is incremented :)
287
465
  datum = dataIterator.next();
288
466
  }
467
+ currentEntityExport.push('');
468
+ if (yield { type: 'entity', content: currentEntityExport.join('\n') }) {
469
+ return;
470
+ }
289
471
  }
290
472
  }
291
- return;
292
473
  }
293
- 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) {
294
485
  if (typeof options.class?.alterOptions === 'function') {
295
486
  options = options.class.alterOptions(options);
296
487
  }
297
488
  const eTable = `e${tableSuffix}`;
298
489
  const dTable = `d${tableSuffix}`;
299
- const cTable = `c${tableSuffix}`;
300
490
  const fTable = `f${tableSuffix}`;
301
491
  const ieTable = `ie${tableSuffix}`;
492
+ const sTable = `s${tableSuffix}`;
302
493
  const sort = options.sort ?? 'cdate';
303
- const queryParts = this.iterateSelectorsForQuery(formattedSelectors, (key, value, typeIsOr, typeIsNot) => {
494
+ const queryParts = this.iterateSelectorsForQuery(formattedSelectors, ({ key, value, typeIsOr, typeIsNot }) => {
304
495
  const clauseNot = key.startsWith('!');
305
496
  let curQuery = '';
306
497
  for (const curValue of value) {
@@ -313,7 +504,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
313
504
  }
314
505
  const guid = `param${++count.i}`;
315
506
  curQuery +=
316
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
507
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
317
508
  ieTable +
318
509
  '."guid"=@' +
319
510
  guid;
@@ -328,7 +519,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
328
519
  }
329
520
  const tag = `param${++count.i}`;
330
521
  curQuery +=
331
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
522
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
332
523
  ieTable +
333
524
  '."tags" LIKE @' +
334
525
  tag +
@@ -352,7 +543,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
352
543
  curQuery +=
353
544
  ieTable +
354
545
  '."guid" ' +
355
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
546
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
356
547
  'IN (SELECT "guid" FROM ' +
357
548
  SQLite3Driver.escape(this.prefix + 'data_' + etype) +
358
549
  ' WHERE "name"=@' +
@@ -369,7 +560,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
369
560
  }
370
561
  if (curVar === 'cdate') {
371
562
  curQuery +=
372
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
563
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
373
564
  '(' +
374
565
  ieTable +
375
566
  '."cdate" NOT NULL)';
@@ -377,7 +568,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
377
568
  }
378
569
  else if (curVar === 'mdate') {
379
570
  curQuery +=
380
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
571
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
381
572
  '(' +
382
573
  ieTable +
383
574
  '."mdate" NOT NULL)';
@@ -386,11 +577,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
386
577
  else {
387
578
  const name = `param${++count.i}`;
388
579
  curQuery +=
389
- ((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"=' +
390
584
  ieTable +
391
- '."guid" IN (SELECT "guid" FROM ' +
392
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
393
- ' WHERE "name"=@' +
585
+ '."guid" AND "name"=@' +
394
586
  name +
395
587
  ' AND "truthy"=1)';
396
588
  params[name] = curVar;
@@ -405,7 +597,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
405
597
  }
406
598
  const cdate = `param${++count.i}`;
407
599
  curQuery +=
408
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
600
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
409
601
  ieTable +
410
602
  '."cdate"=@' +
411
603
  cdate;
@@ -418,7 +610,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
418
610
  }
419
611
  const mdate = `param${++count.i}`;
420
612
  curQuery +=
421
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
613
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
422
614
  ieTable +
423
615
  '."mdate"=@' +
424
616
  mdate;
@@ -432,11 +624,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
432
624
  const name = `param${++count.i}`;
433
625
  const value = `param${++count.i}`;
434
626
  curQuery +=
435
- ((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"=' +
436
631
  ieTable +
437
- '."guid" IN (SELECT "guid" FROM ' +
438
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
439
- ' WHERE "name"=@' +
632
+ '."guid" AND "name"=@' +
440
633
  name +
441
634
  ' AND "number"=@' +
442
635
  value +
@@ -451,11 +644,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
451
644
  const name = `param${++count.i}`;
452
645
  const value = `param${++count.i}`;
453
646
  curQuery +=
454
- ((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"=' +
455
651
  ieTable +
456
- '."guid" IN (SELECT "guid" FROM ' +
457
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
458
- ' WHERE "name"=@' +
652
+ '."guid" AND "name"=@' +
459
653
  name +
460
654
  ' AND "string"=@' +
461
655
  value +
@@ -478,15 +672,16 @@ class SQLite3Driver extends nymph_1.NymphDriver {
478
672
  const name = `param${++count.i}`;
479
673
  const value = `param${++count.i}`;
480
674
  curQuery +=
481
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
482
- ieTable +
483
- '."guid" IN (SELECT "guid" FROM ' +
675
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
676
+ 'EXISTS (SELECT "guid" FROM ' +
484
677
  SQLite3Driver.escape(this.prefix + 'data_' + etype) +
485
- ' WHERE "name"=@' +
678
+ ' WHERE "guid"=' +
679
+ ieTable +
680
+ '."guid" AND "name"=@' +
486
681
  name +
487
- ' AND "value"=@' +
682
+ ' AND "json"=jsonb(@' +
488
683
  value +
489
- ')';
684
+ '))';
490
685
  params[name] = curValue[0];
491
686
  params[value] = svalue;
492
687
  }
@@ -499,9 +694,9 @@ class SQLite3Driver extends nymph_1.NymphDriver {
499
694
  }
500
695
  const cdate = `param${++count.i}`;
501
696
  curQuery +=
502
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
697
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
503
698
  ieTable +
504
- '."cdate"=' +
699
+ '."cdate"=@' +
505
700
  cdate;
506
701
  params[cdate] = Number(curValue[1]);
507
702
  break;
@@ -512,67 +707,163 @@ class SQLite3Driver extends nymph_1.NymphDriver {
512
707
  }
513
708
  const mdate = `param${++count.i}`;
514
709
  curQuery +=
515
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
710
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
516
711
  ieTable +
517
- '."mdate"=' +
712
+ '."mdate"=@' +
518
713
  mdate;
519
714
  params[mdate] = Number(curValue[1]);
520
715
  break;
521
716
  }
522
717
  else {
718
+ const containTableSuffix = makeTableSuffix();
523
719
  if (curQuery) {
524
720
  curQuery += typeIsOr ? ' OR ' : ' AND ';
525
721
  }
526
722
  let svalue;
527
- let stringValue;
528
723
  if (curValue[1] instanceof Object &&
529
724
  typeof curValue[1].toReference === 'function') {
530
725
  svalue = JSON.stringify(curValue[1].toReference());
531
- stringValue = `${curValue[1].toReference()}`;
532
726
  }
533
727
  else {
534
728
  svalue = JSON.stringify(curValue[1]);
535
- stringValue = `${curValue[1]}`;
536
729
  }
537
730
  const name = `param${++count.i}`;
538
731
  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
- '(' +
544
- ieTable +
545
- '."guid" IN (SELECT "guid" FROM ' +
546
- SQLite3Driver.escape(this.prefix + 'data_' + etype) +
547
- ' WHERE "name"=@' +
548
- name +
549
- ' AND instr("value", @' +
550
- value +
551
- ')) OR ' +
552
- ieTable +
553
- '."guid" IN (SELECT "guid" FROM ' +
554
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
555
- ' WHERE "name"=@' +
556
- name +
557
- ' AND "string"=@' +
558
- stringParam +
559
- '))';
560
- 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 ';
561
756
  }
562
- else {
563
- 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
- '))';
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));
573
860
  }
861
+ curQuery +=
862
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
863
+ '(' +
864
+ termStrings.join(' AND ') +
865
+ ')';
574
866
  params[name] = curValue[0];
575
- params[value] = svalue;
576
867
  }
577
868
  break;
578
869
  case 'match':
@@ -583,7 +874,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
583
874
  }
584
875
  const cdate = `param${++count.i}`;
585
876
  curQuery +=
586
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
877
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
587
878
  '(' +
588
879
  ieTable +
589
880
  '."cdate" REGEXP @' +
@@ -598,7 +889,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
598
889
  }
599
890
  const mdate = `param${++count.i}`;
600
891
  curQuery +=
601
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
892
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
602
893
  '(' +
603
894
  ieTable +
604
895
  '."mdate" REGEXP @' +
@@ -614,11 +905,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
614
905
  const name = `param${++count.i}`;
615
906
  const value = `param${++count.i}`;
616
907
  curQuery +=
617
- ((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"=' +
618
912
  ieTable +
619
- '."guid" IN (SELECT "guid" FROM ' +
620
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
621
- ' WHERE "name"=@' +
913
+ '."guid" AND "name"=@' +
622
914
  name +
623
915
  ' AND "string" REGEXP @' +
624
916
  value +
@@ -635,7 +927,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
635
927
  }
636
928
  const cdate = `param${++count.i}`;
637
929
  curQuery +=
638
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
930
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
639
931
  '(' +
640
932
  ieTable +
641
933
  '."cdate" REGEXP @' +
@@ -650,7 +942,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
650
942
  }
651
943
  const mdate = `param${++count.i}`;
652
944
  curQuery +=
653
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
945
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
654
946
  '(' +
655
947
  ieTable +
656
948
  '."mdate" REGEXP @' +
@@ -666,11 +958,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
666
958
  const name = `param${++count.i}`;
667
959
  const value = `param${++count.i}`;
668
960
  curQuery +=
669
- ((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"=' +
670
965
  ieTable +
671
- '."guid" IN (SELECT "guid" FROM ' +
672
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
673
- ' WHERE "name"=@' +
966
+ '."guid" AND "name"=@' +
674
967
  name +
675
968
  ' AND lower("string") REGEXP lower(@' +
676
969
  value +
@@ -687,7 +980,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
687
980
  }
688
981
  const cdate = `param${++count.i}`;
689
982
  curQuery +=
690
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
983
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
691
984
  '(' +
692
985
  ieTable +
693
986
  '."cdate" LIKE @' +
@@ -702,7 +995,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
702
995
  }
703
996
  const mdate = `param${++count.i}`;
704
997
  curQuery +=
705
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
998
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
706
999
  '(' +
707
1000
  ieTable +
708
1001
  '."mdate" LIKE @' +
@@ -718,11 +1011,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
718
1011
  const name = `param${++count.i}`;
719
1012
  const value = `param${++count.i}`;
720
1013
  curQuery +=
721
- ((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"=' +
722
1018
  ieTable +
723
- '."guid" IN (SELECT "guid" FROM ' +
724
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
725
- ' WHERE "name"=@' +
1019
+ '."guid" AND "name"=@' +
726
1020
  name +
727
1021
  ' AND "string" LIKE @' +
728
1022
  value +
@@ -739,7 +1033,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
739
1033
  }
740
1034
  const cdate = `param${++count.i}`;
741
1035
  curQuery +=
742
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1036
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
743
1037
  '(' +
744
1038
  ieTable +
745
1039
  '."cdate" LIKE @' +
@@ -754,7 +1048,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
754
1048
  }
755
1049
  const mdate = `param${++count.i}`;
756
1050
  curQuery +=
757
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1051
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
758
1052
  '(' +
759
1053
  ieTable +
760
1054
  '."mdate" LIKE @' +
@@ -770,11 +1064,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
770
1064
  const name = `param${++count.i}`;
771
1065
  const value = `param${++count.i}`;
772
1066
  curQuery +=
773
- ((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"=' +
774
1071
  ieTable +
775
- '."guid" IN (SELECT "guid" FROM ' +
776
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
777
- ' WHERE "name"=@' +
1072
+ '."guid" AND "name"=@' +
778
1073
  name +
779
1074
  ' AND lower("string") LIKE lower(@' +
780
1075
  value +
@@ -791,7 +1086,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
791
1086
  }
792
1087
  const cdate = `param${++count.i}`;
793
1088
  curQuery +=
794
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1089
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
795
1090
  ieTable +
796
1091
  '."cdate">@' +
797
1092
  cdate;
@@ -804,7 +1099,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
804
1099
  }
805
1100
  const mdate = `param${++count.i}`;
806
1101
  curQuery +=
807
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1102
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
808
1103
  ieTable +
809
1104
  '."mdate">@' +
810
1105
  mdate;
@@ -818,11 +1113,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
818
1113
  const name = `param${++count.i}`;
819
1114
  const value = `param${++count.i}`;
820
1115
  curQuery +=
821
- ((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"=' +
822
1120
  ieTable +
823
- '."guid" IN (SELECT "guid" FROM ' +
824
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
825
- ' WHERE "name"=@' +
1121
+ '."guid" AND "name"=@' +
826
1122
  name +
827
1123
  ' AND "number">@' +
828
1124
  value +
@@ -839,7 +1135,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
839
1135
  }
840
1136
  const cdate = `param${++count.i}`;
841
1137
  curQuery +=
842
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1138
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
843
1139
  ieTable +
844
1140
  '."cdate">=@' +
845
1141
  cdate;
@@ -852,7 +1148,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
852
1148
  }
853
1149
  const mdate = `param${++count.i}`;
854
1150
  curQuery +=
855
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1151
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
856
1152
  ieTable +
857
1153
  '."mdate">=@' +
858
1154
  mdate;
@@ -866,11 +1162,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
866
1162
  const name = `param${++count.i}`;
867
1163
  const value = `param${++count.i}`;
868
1164
  curQuery +=
869
- ((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"=' +
870
1169
  ieTable +
871
- '."guid" IN (SELECT "guid" FROM ' +
872
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
873
- ' WHERE "name"=@' +
1170
+ '."guid" AND "name"=@' +
874
1171
  name +
875
1172
  ' AND "number">=@' +
876
1173
  value +
@@ -887,7 +1184,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
887
1184
  }
888
1185
  const cdate = `param${++count.i}`;
889
1186
  curQuery +=
890
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1187
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
891
1188
  ieTable +
892
1189
  '."cdate"<@' +
893
1190
  cdate;
@@ -900,7 +1197,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
900
1197
  }
901
1198
  const mdate = `param${++count.i}`;
902
1199
  curQuery +=
903
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1200
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
904
1201
  ieTable +
905
1202
  '."mdate"<@' +
906
1203
  mdate;
@@ -914,11 +1211,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
914
1211
  const name = `param${++count.i}`;
915
1212
  const value = `param${++count.i}`;
916
1213
  curQuery +=
917
- ((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"=' +
918
1218
  ieTable +
919
- '."guid" IN (SELECT "guid" FROM ' +
920
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
921
- ' WHERE "name"=@' +
1219
+ '."guid" AND "name"=@' +
922
1220
  name +
923
1221
  ' AND "number"<@' +
924
1222
  value +
@@ -935,7 +1233,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
935
1233
  }
936
1234
  const cdate = `param${++count.i}`;
937
1235
  curQuery +=
938
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1236
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
939
1237
  ieTable +
940
1238
  '."cdate"<=@' +
941
1239
  cdate;
@@ -948,7 +1246,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
948
1246
  }
949
1247
  const mdate = `param${++count.i}`;
950
1248
  curQuery +=
951
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1249
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
952
1250
  ieTable +
953
1251
  '."mdate"<=@' +
954
1252
  mdate;
@@ -962,11 +1260,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
962
1260
  const name = `param${++count.i}`;
963
1261
  const value = `param${++count.i}`;
964
1262
  curQuery +=
965
- ((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"=' +
966
1267
  ieTable +
967
- '."guid" IN (SELECT "guid" FROM ' +
968
- SQLite3Driver.escape(this.prefix + 'comparisons_' + etype) +
969
- ' WHERE "name"=@' +
1268
+ '."guid" AND "name"=@' +
970
1269
  name +
971
1270
  ' AND "number"<=@' +
972
1271
  value +
@@ -993,11 +1292,12 @@ class SQLite3Driver extends nymph_1.NymphDriver {
993
1292
  const name = `param${++count.i}`;
994
1293
  const guid = `param${++count.i}`;
995
1294
  curQuery +=
996
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
997
- ieTable +
998
- '."guid" IN (SELECT "guid" FROM ' +
1295
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1296
+ 'EXISTS (SELECT "guid" FROM ' +
999
1297
  SQLite3Driver.escape(this.prefix + 'references_' + etype) +
1000
- ' WHERE "name"=@' +
1298
+ ' WHERE "guid"=' +
1299
+ ieTable +
1300
+ '."guid" AND "name"=@' +
1001
1301
  name +
1002
1302
  ' AND "reference"=@' +
1003
1303
  guid +
@@ -1012,29 +1312,37 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1012
1312
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1013
1313
  }
1014
1314
  curQuery +=
1015
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1315
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1016
1316
  '(' +
1017
1317
  subquery.query +
1018
1318
  ')';
1019
1319
  break;
1020
1320
  case 'qref':
1021
1321
  case '!qref':
1322
+ const referenceTableSuffix = makeTableSuffix();
1022
1323
  const [qrefOptions, ...qrefSelectors] = curValue[1];
1023
1324
  const QrefEntityClass = qrefOptions.class;
1024
1325
  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);
1326
+ const qrefQuery = this.makeEntityQuery({ ...qrefOptions, return: 'guid', class: QrefEntityClass }, qrefSelectors, QrefEntityClass.ETYPE, count, params, false, makeTableSuffix(), etypes, 'r' + referenceTableSuffix + '."reference"');
1026
1327
  if (curQuery) {
1027
1328
  curQuery += typeIsOr ? ' OR ' : ' AND ';
1028
1329
  }
1029
1330
  const qrefName = `param${++count.i}`;
1030
1331
  curQuery +=
1031
- ((0, nymph_1.xor)(typeIsNot, clauseNot) ? 'NOT ' : '') +
1032
- ieTable +
1033
- '."guid" IN (SELECT "guid" FROM ' +
1332
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1333
+ 'EXISTS (SELECT "guid" FROM ' +
1034
1334
  SQLite3Driver.escape(this.prefix + 'references_' + etype) +
1035
- ' WHERE "name"=@' +
1335
+ ' r' +
1336
+ referenceTableSuffix +
1337
+ ' WHERE r' +
1338
+ referenceTableSuffix +
1339
+ '."guid"=' +
1340
+ ieTable +
1341
+ '."guid" AND r' +
1342
+ referenceTableSuffix +
1343
+ '."name"=@' +
1036
1344
  qrefName +
1037
- ' AND "reference" IN (' +
1345
+ ' AND EXISTS (' +
1038
1346
  qrefQuery.query +
1039
1347
  '))';
1040
1348
  params[qrefName] = curValue[0];
@@ -1044,18 +1352,31 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1044
1352
  return curQuery;
1045
1353
  });
1046
1354
  let sortBy;
1355
+ let sortByInner;
1356
+ let sortJoin = '';
1357
+ const order = options.reverse ? ' DESC' : '';
1047
1358
  switch (sort) {
1048
1359
  case 'mdate':
1049
- sortBy = '"mdate"';
1360
+ sortBy = `${eTable}."mdate"${order}`;
1361
+ sortByInner = `${ieTable}."mdate"${order}`;
1050
1362
  break;
1051
1363
  case 'cdate':
1364
+ sortBy = `${eTable}."cdate"${order}`;
1365
+ sortByInner = `${ieTable}."cdate"${order}`;
1366
+ break;
1052
1367
  default:
1053
- 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;
1054
1378
  break;
1055
1379
  }
1056
- if (options.reverse) {
1057
- sortBy += ' DESC';
1058
- }
1059
1380
  let query;
1060
1381
  if (queryParts.length) {
1061
1382
  if (subquery) {
@@ -1071,25 +1392,29 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1071
1392
  offset = ` OFFSET ${Math.floor(Number(options.offset))}`;
1072
1393
  }
1073
1394
  const whereClause = queryParts.join(') AND (');
1395
+ const guidClause = guidSelector
1396
+ ? `${ieTable}."guid"=${guidSelector} AND `
1397
+ : '';
1074
1398
  if (options.return === 'count') {
1075
1399
  if (limit || offset) {
1076
1400
  query = `SELECT COUNT("guid") AS "count" FROM (
1077
1401
  SELECT "guid"
1078
1402
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1079
- WHERE (${whereClause})${limit}${offset}
1403
+ WHERE ${guidClause}(${whereClause})${limit}${offset}
1080
1404
  )`;
1081
1405
  }
1082
1406
  else {
1083
1407
  query = `SELECT COUNT("guid") AS "count"
1084
1408
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1085
- WHERE (${whereClause})`;
1409
+ WHERE ${guidClause}(${whereClause})`;
1086
1410
  }
1087
1411
  }
1088
1412
  else if (options.return === 'guid') {
1089
1413
  query = `SELECT "guid"
1090
1414
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1091
- WHERE (${whereClause})
1092
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1415
+ ${sortJoin}
1416
+ WHERE ${guidClause}(${whereClause})
1417
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1093
1418
  }
1094
1419
  else {
1095
1420
  query = `SELECT
@@ -1099,18 +1424,20 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1099
1424
  ${eTable}."mdate",
1100
1425
  ${dTable}."name",
1101
1426
  ${dTable}."value",
1102
- ${cTable}."string",
1103
- ${cTable}."number"
1427
+ json(${dTable}."json") as "json",
1428
+ ${dTable}."string",
1429
+ ${dTable}."number"
1104
1430
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1105
1431
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1106
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} ${cTable} USING ("guid", "name")
1432
+ ${sortJoin}
1107
1433
  INNER JOIN (
1108
1434
  SELECT "guid"
1109
1435
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1110
- WHERE (${whereClause})
1111
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1436
+ ${sortJoin}
1437
+ WHERE ${guidClause}(${whereClause})
1438
+ ORDER BY ${sortByInner}${limit}${offset}
1112
1439
  ) ${fTable} USING ("guid")
1113
- ORDER BY ${eTable}.${sortBy}`;
1440
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1114
1441
  }
1115
1442
  }
1116
1443
  }
@@ -1127,22 +1454,27 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1127
1454
  if ('offset' in options) {
1128
1455
  offset = ` OFFSET ${Math.floor(Number(options.offset))}`;
1129
1456
  }
1457
+ const guidClause = guidSelector
1458
+ ? ` WHERE ${ieTable}."guid"=${guidSelector}`
1459
+ : '';
1130
1460
  if (options.return === 'count') {
1131
1461
  if (limit || offset) {
1132
1462
  query = `SELECT COUNT("guid") AS "count" FROM (
1133
1463
  SELECT "guid"
1134
- FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${limit}${offset}
1464
+ FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${guidClause}${limit}${offset}
1135
1465
  )`;
1136
1466
  }
1137
1467
  else {
1138
1468
  query = `SELECT COUNT("guid") AS "count"
1139
- FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}`;
1469
+ FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}${guidClause}`;
1140
1470
  }
1141
1471
  }
1142
1472
  else if (options.return === 'guid') {
1143
1473
  query = `SELECT "guid"
1144
1474
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1145
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1475
+ ${sortJoin}
1476
+ ${guidClause}
1477
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1146
1478
  }
1147
1479
  else {
1148
1480
  if (limit || offset) {
@@ -1153,17 +1485,20 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1153
1485
  ${eTable}."mdate",
1154
1486
  ${dTable}."name",
1155
1487
  ${dTable}."value",
1156
- ${cTable}."string",
1157
- ${cTable}."number"
1488
+ json(${dTable}."json") as "json",
1489
+ ${dTable}."string",
1490
+ ${dTable}."number"
1158
1491
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1159
1492
  LEFT JOIN ${SQLite3Driver.escape(this.prefix + 'data_' + etype)} ${dTable} USING ("guid")
1160
- INNER JOIN ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)} c USING ("guid", "name")
1493
+ ${sortJoin}
1161
1494
  INNER JOIN (
1162
1495
  SELECT "guid"
1163
1496
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${ieTable}
1164
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1497
+ ${sortJoin}
1498
+ ${guidClause}
1499
+ ORDER BY ${sortByInner}${limit}${offset}
1165
1500
  ) ${fTable} USING ("guid")
1166
- ORDER BY ${eTable}.${sortBy}`;
1501
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1167
1502
  }
1168
1503
  else {
1169
1504
  query = `SELECT
@@ -1173,12 +1508,14 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1173
1508
  ${eTable}."mdate",
1174
1509
  ${dTable}."name",
1175
1510
  ${dTable}."value",
1176
- ${cTable}."string",
1177
- ${cTable}."number"
1511
+ json(${dTable}."json") as "json",
1512
+ ${dTable}."string",
1513
+ ${dTable}."number"
1178
1514
  FROM ${SQLite3Driver.escape(this.prefix + 'entities_' + etype)} ${eTable}
1179
1515
  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}`;
1516
+ ${sortJoin}
1517
+ ${guidSelector ? `WHERE ${eTable}."guid"=${guidSelector}` : ''}
1518
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1182
1519
  }
1183
1520
  }
1184
1521
  }
@@ -1194,20 +1531,22 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1194
1531
  }
1195
1532
  performQuery(options, formattedSelectors, etype) {
1196
1533
  const { query, params, etypes } = this.makeEntityQuery(options, formattedSelectors, etype);
1197
- const result = this.queryIter(query, { etypes, params })[Symbol.iterator]();
1534
+ const result = this.queryArray(query, { etypes, params })[Symbol.iterator]();
1198
1535
  return {
1199
1536
  result,
1200
1537
  };
1201
1538
  }
1202
1539
  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), () => {
1540
+ const { result, process } = this.getEntitiesRowLike(options, selectors, ({ options, selectors, etype }) => this.performQuery(options, selectors, etype), () => {
1207
1541
  const next = result.next();
1208
1542
  return next.done ? null : next.value;
1209
1543
  }, () => undefined, (row) => Number(row.count), (row) => row.guid, (row) => ({
1210
- 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
+ : [],
1211
1550
  cdate: Number(row.cdate),
1212
1551
  mdate: Number(row.mdate),
1213
1552
  }), (row) => ({
@@ -1216,7 +1555,9 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1216
1555
  ? JSON.stringify(row.number)
1217
1556
  : row.value === 'S'
1218
1557
  ? JSON.stringify(row.string)
1219
- : row.value,
1558
+ : row.value === 'J'
1559
+ ? row.json
1560
+ : row.value,
1220
1561
  }));
1221
1562
  const value = process();
1222
1563
  if (value instanceof Error) {
@@ -1226,7 +1567,7 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1226
1567
  }
1227
1568
  async getUID(name) {
1228
1569
  if (name == null) {
1229
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1570
+ throw new InvalidParametersError('Name not given for UID.');
1230
1571
  }
1231
1572
  const result = this.queryGet(`SELECT "cur_uid" FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1232
1573
  params: {
@@ -1235,10 +1576,15 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1235
1576
  });
1236
1577
  return result?.cur_uid ?? null;
1237
1578
  }
1238
- async import(filename) {
1239
- 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) {
1240
1586
  try {
1241
- return this.importFromFile(filename, async (guid, tags, sdata, etype) => {
1587
+ if (!onlyTokens) {
1242
1588
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} WHERE "guid"=@guid;`, {
1243
1589
  etypes: [etype],
1244
1590
  params: {
@@ -1251,29 +1597,37 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1251
1597
  guid,
1252
1598
  },
1253
1599
  });
1254
- 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;`, {
1255
1601
  etypes: [etype],
1256
1602
  params: {
1257
1603
  guid,
1258
1604
  },
1259
1605
  });
1260
- 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;`, {
1261
1615
  etypes: [etype],
1262
1616
  params: {
1263
1617
  guid,
1264
1618
  },
1265
1619
  });
1620
+ }
1621
+ if (!onlyTokens) {
1266
1622
  this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`, {
1267
1623
  etypes: [etype],
1268
1624
  params: {
1269
1625
  guid,
1270
1626
  tags: ',' + tags.join(',') + ',',
1271
- cdate: Number(JSON.parse(sdata.cdate)),
1272
- mdate: Number(JSON.parse(sdata.mdate)),
1627
+ cdate,
1628
+ mdate,
1273
1629
  },
1274
1630
  });
1275
- delete sdata.cdate;
1276
- delete sdata.mdate;
1277
1631
  for (const name in sdata) {
1278
1632
  const value = sdata[name];
1279
1633
  const uvalue = JSON.parse(value);
@@ -1284,23 +1638,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1284
1638
  ? 'N'
1285
1639
  : typeof uvalue === 'string'
1286
1640
  ? 'S'
1287
- : value;
1288
- 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);`, {
1289
1644
  etypes: [etype],
1290
1645
  params: {
1291
1646
  guid,
1292
1647
  name,
1293
1648
  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}`,
1649
+ jsonValue,
1650
+ string: storageValue === 'J' ? null : `${uvalue}`,
1303
1651
  number: Number(uvalue),
1652
+ truthy: uvalue ? 1 : 0,
1304
1653
  },
1305
1654
  });
1306
1655
  const references = this.findReferences(value);
@@ -1315,41 +1664,113 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1315
1664
  });
1316
1665
  }
1317
1666
  }
1318
- }, async (name, curUid) => {
1319
- this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1320
- params: {
1321
- name,
1322
- },
1323
- });
1324
- this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
1325
- params: {
1326
- name,
1327
- curUid,
1328
- },
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,
1329
1714
  });
1330
- }, async () => {
1331
- await this.startTransaction('nymph-import');
1332
- }, async () => {
1333
- 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
+ },
1334
1752
  });
1753
+ await this.commit(`nymph-import-uid-${name}`);
1335
1754
  }
1336
1755
  catch (e) {
1337
- await this.rollback('nymph-import');
1756
+ this.nymph.config.debugError('sqlite3', `Import UID error: "${e}"`);
1757
+ await this.rollback(`nymph-import-uid-${name}`);
1338
1758
  throw e;
1339
1759
  }
1340
1760
  }
1341
1761
  async newUID(name) {
1342
1762
  if (name == null) {
1343
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1763
+ throw new InvalidParametersError('Name not given for UID.');
1344
1764
  }
1345
- this.checkReadOnlyMode();
1346
1765
  await this.startTransaction('nymph-newuid');
1766
+ let curUid = undefined;
1347
1767
  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;
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;
1353
1774
  if (curUid == null) {
1354
1775
  curUid = 1;
1355
1776
  this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}uids`)} ("name", "cur_uid") VALUES (@name, @curUid);`, {
@@ -1368,41 +1789,50 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1368
1789
  },
1369
1790
  });
1370
1791
  }
1371
- await this.commit('nymph-newuid');
1372
- return curUid;
1373
1792
  }
1374
1793
  catch (e) {
1794
+ this.nymph.config.debugError('sqlite3', `New UID error: "${e}"`);
1375
1795
  await this.rollback('nymph-newuid');
1376
1796
  throw e;
1377
1797
  }
1798
+ await this.commit('nymph-newuid');
1799
+ return curUid;
1378
1800
  }
1379
1801
  async renameUID(oldName, newName) {
1380
1802
  if (oldName == null || newName == null) {
1381
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
1803
+ throw new InvalidParametersError('Name not given for UID.');
1382
1804
  }
1383
- this.checkReadOnlyMode();
1805
+ await this.startTransaction('nymph-rename-uid');
1384
1806
  this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}uids`)} SET "name"=@newName WHERE "name"=@oldName;`, {
1385
1807
  params: {
1386
1808
  newName,
1387
1809
  oldName,
1388
1810
  },
1389
1811
  });
1812
+ await this.commit('nymph-rename-uid');
1390
1813
  return true;
1391
1814
  }
1392
1815
  async rollback(name) {
1393
1816
  if (name == null || typeof name !== 'string' || name.length === 0) {
1394
- throw new nymph_1.InvalidParametersError('Transaction rollback attempted without a name.');
1817
+ throw new InvalidParametersError('Transaction rollback attempted without a name.');
1395
1818
  }
1396
1819
  if (this.store.transactionsStarted === 0) {
1397
1820
  return true;
1398
1821
  }
1399
1822
  this.queryRun(`ROLLBACK TO SAVEPOINT ${SQLite3Driver.escape(name)};`);
1400
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
+ }
1401
1831
  return true;
1402
1832
  }
1403
1833
  async saveEntity(entity) {
1404
- this.checkReadOnlyMode();
1405
- const insertData = (guid, data, sdata, etype) => {
1834
+ const insertData = (guid, data, sdata, uniques, etype) => {
1835
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
1406
1836
  const runInsertQuery = (name, value, svalue) => {
1407
1837
  if (value === undefined) {
1408
1838
  return;
@@ -1411,23 +1841,18 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1411
1841
  ? 'N'
1412
1842
  : typeof value === 'string'
1413
1843
  ? 'S'
1414
- : svalue;
1415
- 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);`, {
1416
1847
  etypes: [etype],
1417
1848
  params: {
1418
1849
  guid,
1419
1850
  name,
1420
1851
  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}`,
1852
+ jsonValue,
1853
+ string: storageValue === 'J' ? null : `${value}`,
1430
1854
  number: Number(value),
1855
+ truthy: value ? 1 : 0,
1431
1856
  },
1432
1857
  });
1433
1858
  const references = this.findReferences(svalue);
@@ -1441,7 +1866,59 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1441
1866
  },
1442
1867
  });
1443
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
+ }
1444
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
+ }
1445
1922
  for (const name in data) {
1446
1923
  runInsertQuery(name, data[name], JSON.stringify(data[name]));
1447
1924
  }
@@ -1449,8 +1926,13 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1449
1926
  runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
1450
1927
  }
1451
1928
  };
1929
+ let inTransaction = false;
1452
1930
  try {
1453
- 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
+ }
1454
1936
  this.queryRun(`INSERT INTO ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @cdate);`, {
1455
1937
  etypes: [etype],
1456
1938
  params: {
@@ -1459,9 +1941,13 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1459
1941
  cdate,
1460
1942
  },
1461
1943
  });
1462
- insertData(guid, data, sdata, etype);
1944
+ insertData(guid, data, sdata, uniques, etype);
1463
1945
  return true;
1464
- }, 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
+ }
1465
1951
  const info = this.queryRun(`UPDATE ${SQLite3Driver.escape(`${this.prefix}entities_${etype}`)} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=@guid AND "mdate" <= @emdate;`, {
1466
1952
  etypes: [etype],
1467
1953
  params: {
@@ -1479,44 +1965,57 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1479
1965
  guid,
1480
1966
  },
1481
1967
  });
1482
- 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;`, {
1483
1969
  etypes: [etype],
1484
1970
  params: {
1485
1971
  guid,
1486
1972
  },
1487
1973
  });
1488
- 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;`, {
1975
+ etypes: [etype],
1976
+ params: {
1977
+ guid,
1978
+ },
1979
+ });
1980
+ this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uniques_${etype}`)} WHERE "guid"=@guid;`, {
1489
1981
  etypes: [etype],
1490
1982
  params: {
1491
1983
  guid,
1492
1984
  },
1493
1985
  });
1494
- insertData(guid, data, sdata, etype);
1986
+ insertData(guid, data, sdata, uniques, etype);
1495
1987
  success = true;
1496
1988
  }
1497
1989
  return success;
1498
1990
  }, async () => {
1499
1991
  await this.startTransaction('nymph-save');
1992
+ inTransaction = true;
1500
1993
  }, async (success) => {
1501
- if (success) {
1502
- await this.commit('nymph-save');
1503
- }
1504
- else {
1505
- 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
+ }
1506
2002
  }
1507
2003
  return success;
1508
2004
  });
1509
2005
  }
1510
2006
  catch (e) {
1511
- 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
+ }
1512
2011
  throw e;
1513
2012
  }
1514
2013
  }
1515
2014
  async setUID(name, curUid) {
1516
2015
  if (name == null) {
1517
- throw new nymph_1.InvalidParametersError('Name not given for UID.');
2016
+ throw new InvalidParametersError('Name not given for UID.');
1518
2017
  }
1519
- this.checkReadOnlyMode();
2018
+ await this.startTransaction('nymph-set-uid');
1520
2019
  this.queryRun(`DELETE FROM ${SQLite3Driver.escape(`${this.prefix}uids`)} WHERE "name"=@name;`, {
1521
2020
  params: {
1522
2021
  name,
@@ -1528,16 +2027,54 @@ class SQLite3Driver extends nymph_1.NymphDriver {
1528
2027
  curUid,
1529
2028
  },
1530
2029
  });
2030
+ await this.commit('nymph-set-uid');
1531
2031
  return true;
1532
2032
  }
2033
+ async internalTransaction(name) {
2034
+ await this.startTransaction(name);
2035
+ }
1533
2036
  async startTransaction(name) {
1534
2037
  if (name == null || typeof name !== 'string' || name.length === 0) {
1535
- 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);
1536
2042
  }
1537
2043
  this.queryRun(`SAVEPOINT ${SQLite3Driver.escape(name)};`);
1538
2044
  this.store.transactionsStarted++;
1539
2045
  return this.nymph;
1540
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
+ }
1541
2079
  }
1542
- exports.default = SQLite3Driver;
1543
2080
  //# sourceMappingURL=SQLite3Driver.js.map