@nymphjs/driver-sqlite3 1.0.0-beta.6 → 1.0.0-beta.61

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,17 +1,19 @@
1
1
  import SQLite3 from 'better-sqlite3';
2
2
  import {
3
3
  NymphDriver,
4
- EntityConstructor,
5
- EntityData,
6
- EntityInterface,
7
- SerializedEntityData,
4
+ type EntityConstructor,
5
+ type EntityData,
6
+ type EntityInterface,
7
+ type EntityInstanceType,
8
+ type SerializedEntityData,
9
+ type FormattedSelector,
10
+ type Options,
11
+ type Selector,
12
+ EntityUniqueConstraintError,
8
13
  InvalidParametersError,
9
14
  NotConfiguredError,
10
15
  QueryFailedError,
11
16
  UnableToConnectError,
12
- FormattedSelector,
13
- Options,
14
- Selector,
15
17
  xor,
16
18
  } from '@nymphjs/nymph';
17
19
  import { makeTableSuffix } from '@nymphjs/guid';
@@ -21,32 +23,57 @@ import {
21
23
  SQLite3DriverConfigDefaults as defaults,
22
24
  } from './conf';
23
25
 
26
+ class InternalStore {
27
+ public link: SQLite3.Database;
28
+ public linkWrite?: SQLite3.Database;
29
+ public connected: boolean = false;
30
+ public transactionsStarted = 0;
31
+
32
+ constructor(link: SQLite3.Database) {
33
+ this.link = link;
34
+ }
35
+ }
36
+
24
37
  /**
25
38
  * The SQLite3 Nymph database driver.
26
39
  */
27
40
  export default class SQLite3Driver extends NymphDriver {
28
41
  public config: SQLite3DriverConfig;
29
42
  protected prefix: string;
30
- protected connected: boolean = false;
31
43
  // @ts-ignore: this is assigned in connect(), which is called by the constructor.
32
- protected link: SQLite3.Database;
33
- protected transactionsStarted = 0;
44
+ protected store: InternalStore;
34
45
 
35
46
  static escape(input: string) {
36
47
  if (input.indexOf('\x00') !== -1) {
37
48
  throw new InvalidParametersError(
38
- 'SQLite3 identifiers (like entity ETYPE) cannot contain null characters.'
49
+ 'SQLite3 identifiers (like entity ETYPE) cannot contain null characters.',
39
50
  );
40
51
  }
41
52
 
42
53
  return '"' + input.replace(/"/g, () => '""') + '"';
43
54
  }
44
55
 
45
- constructor(config: Partial<SQLite3DriverConfig>) {
56
+ constructor(config: Partial<SQLite3DriverConfig>, store?: InternalStore) {
46
57
  super();
47
58
  this.config = { ...defaults, ...config };
59
+ if (this.config.filename === ':memory:') {
60
+ this.config.explicitWrite = true;
61
+ }
48
62
  this.prefix = this.config.prefix;
49
- this.connect();
63
+ if (store) {
64
+ this.store = store;
65
+ } else {
66
+ this.connect();
67
+ }
68
+ }
69
+
70
+ /**
71
+ * This is used internally by Nymph. Don't call it yourself.
72
+ *
73
+ * @returns A clone of this instance.
74
+ */
75
+ public clone() {
76
+ return new SQLite3Driver(this.config, this.store);
50
77
  }
51
78
 
52
79
  /**
@@ -54,41 +81,107 @@ export default class SQLite3Driver extends NymphDriver {
54
81
  *
55
82
  * @returns Whether this instance is connected to a SQLite3 database.
56
83
  */
57
- public async connect() {
58
- const { filename, fileMustExist, timeout, readonly, verbose } = this.config;
84
+ public connect() {
85
+ if (this.store && this.store.connected) {
86
+ return Promise.resolve(true);
87
+ }
88
+
59
89
  // Connecting
60
- if (!this.connected) {
90
+ this._connect(false);
91
+
92
+ return Promise.resolve(this.store.connected);
93
+ }
94
+
95
+ private _connect(write: boolean) {
96
+ const { filename, fileMustExist, timeout, explicitWrite, wal, verbose } =
97
+ this.config;
98
+
99
+ try {
100
+ const setOptions = (link: SQLite3.Database) => {
101
+ // Set database and connection options.
102
+ if (wal) {
103
+ link.pragma('journal_mode = WAL;');
104
+ }
105
+ link.pragma('encoding = "UTF-8";');
106
+ link.pragma('foreign_keys = 1;');
107
+ link.pragma('case_sensitive_like = 1;');
108
+ for (let pragma of this.config.pragmas) {
109
+ link.pragma(pragma);
110
+ }
111
+ // Create the preg_match and regexp functions.
112
+ link.function('regexp', { deterministic: true }, ((
113
+ pattern: string,
114
+ subject: string,
115
+ ) => (this.posixRegexMatch(pattern, subject) ? 1 : 0)) as (
116
+ ...params: any[]
117
+ ) => any);
118
+ };
119
+
120
+ let link: SQLite3.Database;
61
121
  try {
62
- this.link = new SQLite3(filename, {
63
- readonly,
122
+ link = new SQLite3(filename, {
123
+ readonly: !explicitWrite && !write,
64
124
  fileMustExist,
65
125
  timeout,
66
126
  verbose,
67
127
  });
68
- this.connected = true;
69
- // Set database and connection options.
70
- this.link.pragma('encoding = "UTF-8";');
71
- this.link.pragma('foreign_keys = 1;');
72
- this.link.pragma('case_sensitive_like = 1;');
73
- // Create the preg_match and regexp functions.
74
- this.link.function(
75
- 'regexp',
76
- { deterministic: true },
77
- (pattern: string, subject: string) =>
78
- this.posixRegexMatch(pattern, subject) ? 1 : 0
79
- );
80
128
  } catch (e: any) {
81
- this.connected = false;
82
- if (filename === ':memory:') {
83
- throw new NotConfiguredError(
84
- "It seems the config hasn't been set up correctly."
85
- );
129
+ if (
130
+ e.code === 'SQLITE_CANTOPEN' &&
131
+ !explicitWrite &&
132
+ !write &&
133
+ !this.config.fileMustExist
134
+ ) {
135
+ // This happens when the file doesn't exist and we attempt to open it
136
+ // readonly.
137
+ // First open it in write mode.
138
+ const writeLink = new SQLite3(filename, {
139
+ readonly: false,
140
+ fileMustExist,
141
+ timeout,
142
+ verbose,
143
+ });
144
+ setOptions(writeLink);
145
+ writeLink.close();
146
+ // Now open in readonly.
147
+ link = new SQLite3(filename, {
148
+ readonly: true,
149
+ fileMustExist,
150
+ timeout,
151
+ verbose,
152
+ });
86
153
  } else {
87
- throw new UnableToConnectError('Could not connect: ' + e?.message);
154
+ throw e;
155
+ }
156
+ }
157
+
158
+ if (!this.store) {
159
+ if (write) {
160
+ throw new Error(
161
+ 'Tried to open in write without opening in read first.',
162
+ );
88
163
  }
164
+ this.store = new InternalStore(link);
165
+ } else if (write) {
166
+ this.store.linkWrite = link;
167
+ } else {
168
+ this.store.link = link;
169
+ }
170
+ this.store.connected = true;
171
+ setOptions(link);
172
+ } catch (e: any) {
173
+ if (this.store) {
174
+ this.store.connected = false;
175
+ }
176
+ if (filename === ':memory:') {
177
+ throw new NotConfiguredError(
178
+ "It seems the config hasn't been set up correctly. Could not connect: " +
179
+ e?.message,
180
+ );
181
+ } else {
182
+ throw new UnableToConnectError('Could not connect: ' + e?.message);
89
183
  }
90
184
  }
91
- return this.connected;
92
185
  }
93
186
 
94
187
  /**
@@ -97,16 +190,24 @@ export default class SQLite3Driver extends NymphDriver {
97
190
  * @returns Whether this instance is connected to a SQLite3 database.
98
191
  */
99
192
  public async disconnect() {
100
- if (this.connected) {
101
- this.link.exec('PRAGMA optimize;');
102
- this.link.close();
103
- this.connected = false;
193
+ if (this.store.connected) {
194
+ if (this.store.linkWrite && !this.config.explicitWrite) {
195
+ this.store.linkWrite.exec('PRAGMA optimize;');
196
+ this.store.linkWrite.close();
197
+ this.store.linkWrite = undefined;
198
+ }
199
+ if (this.config.explicitWrite) {
200
+ this.store.link.exec('PRAGMA optimize;');
201
+ }
202
+ this.store.link.close();
203
+ this.store.transactionsStarted = 0;
204
+ this.store.connected = false;
104
205
  }
105
- return this.connected;
206
+ return this.store.connected;
106
207
  }
107
208
 
108
209
  public async inTransaction() {
109
- return this.transactionsStarted > 0;
210
+ return this.store.transactionsStarted > 0;
110
211
  }
111
212
 
112
213
  /**
@@ -115,18 +216,7 @@ export default class SQLite3Driver extends NymphDriver {
115
216
  * @returns Whether this instance is connected to a SQLite3 database.
116
217
  */
117
218
  public isConnected() {
118
- return this.connected;
119
- }
120
-
121
- /**
122
- * Check if SQLite3 DB is read only and throw error if so.
123
- */
124
- private checkReadOnlyMode() {
125
- if (this.config.readonly) {
126
- throw new InvalidParametersError(
127
- 'Attempt to write to SQLite3 DB in read only mode.'
128
- );
129
- }
219
+ return this.store.connected;
130
220
  }
131
221
 
132
222
  /**
@@ -135,158 +225,173 @@ export default class SQLite3Driver extends NymphDriver {
135
225
  * @param etype The entity type to create a table for. If this is blank, the default tables are created.
136
226
  */
137
227
  private createTables(etype: string | null = null) {
138
- this.checkReadOnlyMode();
139
228
  this.startTransaction('nymph-tablecreation');
140
229
  try {
141
230
  if (etype != null) {
142
231
  // Create the entity table.
143
232
  this.queryRun(
144
233
  `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
145
- `${this.prefix}entities_${etype}`
146
- )} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL);`
234
+ `${this.prefix}entities_${etype}`,
235
+ )} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL);`,
147
236
  );
148
237
  this.queryRun(
149
238
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
150
- `${this.prefix}entities_${etype}_id_cdate`
239
+ `${this.prefix}entities_${etype}_id_cdate`,
151
240
  )} ON ${SQLite3Driver.escape(
152
- `${this.prefix}entities_${etype}`
153
- )} ("cdate");`
241
+ `${this.prefix}entities_${etype}`,
242
+ )} ("cdate");`,
154
243
  );
155
244
  this.queryRun(
156
245
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
157
- `${this.prefix}entities_${etype}_id_mdate`
246
+ `${this.prefix}entities_${etype}_id_mdate`,
158
247
  )} ON ${SQLite3Driver.escape(
159
- `${this.prefix}entities_${etype}`
160
- )} ("mdate");`
248
+ `${this.prefix}entities_${etype}`,
249
+ )} ("mdate");`,
161
250
  );
162
251
  this.queryRun(
163
252
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
164
- `${this.prefix}entities_${etype}_id_tags`
253
+ `${this.prefix}entities_${etype}_id_tags`,
165
254
  )} ON ${SQLite3Driver.escape(
166
- `${this.prefix}entities_${etype}`
167
- )} ("tags");`
255
+ `${this.prefix}entities_${etype}`,
256
+ )} ("tags");`,
168
257
  );
169
258
  // Create the data table.
170
259
  this.queryRun(
171
260
  `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
172
- `${this.prefix}data_${etype}`
261
+ `${this.prefix}data_${etype}`,
173
262
  )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
174
- `${this.prefix}entities_${etype}`
175
- )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "value" TEXT NOT NULL, PRIMARY KEY("guid", "name"));`
263
+ `${this.prefix}entities_${etype}`,
264
+ )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "value" TEXT NOT NULL, PRIMARY KEY("guid", "name"));`,
176
265
  );
177
266
  this.queryRun(
178
267
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
179
- `${this.prefix}data_${etype}_id_guid`
268
+ `${this.prefix}data_${etype}_id_guid`,
180
269
  )} ON ${SQLite3Driver.escape(
181
- `${this.prefix}data_${etype}`
182
- )} ("guid");`
270
+ `${this.prefix}data_${etype}`,
271
+ )} ("guid");`,
183
272
  );
184
273
  this.queryRun(
185
274
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
186
- `${this.prefix}data_${etype}_id_name`
275
+ `${this.prefix}data_${etype}_id_name`,
187
276
  )} ON ${SQLite3Driver.escape(
188
- `${this.prefix}data_${etype}`
189
- )} ("name");`
277
+ `${this.prefix}data_${etype}`,
278
+ )} ("name");`,
190
279
  );
191
280
  this.queryRun(
192
281
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
193
- `${this.prefix}data_${etype}_id_value`
282
+ `${this.prefix}data_${etype}_id_value`,
194
283
  )} ON ${SQLite3Driver.escape(
195
- `${this.prefix}data_${etype}`
196
- )} ("value");`
284
+ `${this.prefix}data_${etype}`,
285
+ )} ("value");`,
197
286
  );
198
287
  this.queryRun(
199
288
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
200
- `${this.prefix}data_${etype}_id_guid__name_user`
289
+ `${this.prefix}data_${etype}_id_guid__name_user`,
201
290
  )} ON ${SQLite3Driver.escape(
202
- `${this.prefix}data_${etype}`
203
- )} ("guid") WHERE "name" = \'user\';`
291
+ `${this.prefix}data_${etype}`,
292
+ )} ("guid") WHERE "name" = \'user\';`,
204
293
  );
205
294
  this.queryRun(
206
295
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
207
- `${this.prefix}data_${etype}_id_guid__name_group`
296
+ `${this.prefix}data_${etype}_id_guid__name_group`,
208
297
  )} ON ${SQLite3Driver.escape(
209
- `${this.prefix}data_${etype}`
210
- )} ("guid") WHERE "name" = \'group\';`
298
+ `${this.prefix}data_${etype}`,
299
+ )} ("guid") WHERE "name" = \'group\';`,
211
300
  );
212
301
  // Create the comparisons table.
213
302
  this.queryRun(
214
303
  `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
215
- `${this.prefix}comparisons_${etype}`
304
+ `${this.prefix}comparisons_${etype}`,
216
305
  )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
217
- `${this.prefix}entities_${etype}`
218
- )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "truthy" INTEGER, "string" TEXT, "number" REAL, PRIMARY KEY("guid", "name"));`
306
+ `${this.prefix}entities_${etype}`,
307
+ )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "truthy" INTEGER, "string" TEXT, "number" REAL, PRIMARY KEY("guid", "name"));`,
308
+ );
309
+ this.queryRun(
310
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
311
+ `${this.prefix}comparisons_${etype}_id_guid`,
312
+ )} ON ${SQLite3Driver.escape(
313
+ `${this.prefix}comparisons_${etype}`,
314
+ )} ("guid");`,
219
315
  );
220
316
  this.queryRun(
221
317
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
222
- `${this.prefix}comparisons_${etype}_id_guid`
318
+ `${this.prefix}comparisons_${etype}_id_name`,
223
319
  )} ON ${SQLite3Driver.escape(
224
- `${this.prefix}comparisons_${etype}`
225
- )} ("guid");`
320
+ `${this.prefix}comparisons_${etype}`,
321
+ )} ("name");`,
226
322
  );
227
323
  this.queryRun(
228
324
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
229
- `${this.prefix}comparisons_${etype}_id_name`
325
+ `${this.prefix}comparisons_${etype}_id_name__truthy`,
230
326
  )} ON ${SQLite3Driver.escape(
231
- `${this.prefix}comparisons_${etype}`
232
- )} ("name");`
327
+ `${this.prefix}comparisons_${etype}`,
328
+ )} ("name") WHERE "truthy" = 1;`,
233
329
  );
234
330
  this.queryRun(
235
331
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
236
- `${this.prefix}comparisons_${etype}_id_name__truthy`
332
+ `${this.prefix}comparisons_${etype}_id_string`,
237
333
  )} ON ${SQLite3Driver.escape(
238
- `${this.prefix}comparisons_${etype}`
239
- )} ("name") WHERE "truthy" = 1;`
334
+ `${this.prefix}comparisons_${etype}`,
335
+ )} ("string");`,
240
336
  );
241
337
  // Create the references table.
242
338
  this.queryRun(
243
339
  `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
244
- `${this.prefix}references_${etype}`
340
+ `${this.prefix}references_${etype}`,
245
341
  )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
246
- `${this.prefix}entities_${etype}`
247
- )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`
342
+ `${this.prefix}entities_${etype}`,
343
+ )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`,
248
344
  );
249
345
  this.queryRun(
250
346
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
251
- `${this.prefix}references_${etype}_id_guid`
347
+ `${this.prefix}references_${etype}_id_guid`,
252
348
  )} ON ${SQLite3Driver.escape(
253
- `${this.prefix}references_${etype}`
254
- )} ("guid");`
349
+ `${this.prefix}references_${etype}`,
350
+ )} ("guid");`,
255
351
  );
256
352
  this.queryRun(
257
353
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
258
- `${this.prefix}references_${etype}_id_name`
354
+ `${this.prefix}references_${etype}_id_name`,
259
355
  )} ON ${SQLite3Driver.escape(
260
- `${this.prefix}references_${etype}`
261
- )} ("name");`
356
+ `${this.prefix}references_${etype}`,
357
+ )} ("name");`,
262
358
  );
263
359
  this.queryRun(
264
360
  `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
265
- `${this.prefix}references_${etype}_id_reference`
361
+ `${this.prefix}references_${etype}_id_reference`,
266
362
  )} ON ${SQLite3Driver.escape(
267
- `${this.prefix}references_${etype}`
268
- )} ("reference");`
363
+ `${this.prefix}references_${etype}`,
364
+ )} ("reference");`,
365
+ );
366
+ // Create the unique strings table.
367
+ this.queryRun(
368
+ `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
369
+ `${this.prefix}uniques_${etype}`,
370
+ )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
371
+ `${this.prefix}entities_${etype}`,
372
+ )} ("guid") ON DELETE CASCADE, "unique" TEXT NOT NULL UNIQUE, PRIMARY KEY("guid", "unique"));`,
269
373
  );
270
374
  } else {
271
375
  // Create the UID table.
272
376
  this.queryRun(
273
377
  `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
274
- `${this.prefix}uids`
275
- )} ("name" TEXT PRIMARY KEY NOT NULL, "cur_uid" INTEGER NOT NULL);`
378
+ `${this.prefix}uids`,
379
+ )} ("name" TEXT PRIMARY KEY NOT NULL, "cur_uid" INTEGER NOT NULL);`,
276
380
  );
277
381
  }
278
- this.commit('nymph-tablecreation');
279
- return true;
280
382
  } catch (e: any) {
281
383
  this.rollback('nymph-tablecreation');
282
384
  throw e;
283
385
  }
386
+
387
+ this.commit('nymph-tablecreation');
388
+ return true;
284
389
  }
285
390
 
286
391
  private query<T extends () => any>(
287
392
  runQuery: T,
288
393
  query: string,
289
- etypes: string[] = []
394
+ etypes: string[] = [],
290
395
  ): ReturnType<T> {
291
396
  try {
292
397
  return runQuery();
@@ -306,13 +411,18 @@ export default class SQLite3Driver extends NymphDriver {
306
411
  } catch (e2: any) {
307
412
  throw new QueryFailedError(
308
413
  'Query failed: ' + e2?.code + ' - ' + e2?.message,
309
- query
414
+ query,
310
415
  );
311
416
  }
417
+ } else if (
418
+ errorCode === 'SQLITE_CONSTRAINT_UNIQUE' &&
419
+ errorMsg.match(/^UNIQUE constraint failed: /)
420
+ ) {
421
+ throw new EntityUniqueConstraintError(`Unique constraint violation.`);
312
422
  } else {
313
423
  throw new QueryFailedError(
314
424
  'Query failed: ' + e?.code + ' - ' + e?.message,
315
- query
425
+ query,
316
426
  );
317
427
  }
318
428
  }
@@ -323,12 +433,15 @@ export default class SQLite3Driver extends NymphDriver {
323
433
  {
324
434
  etypes = [],
325
435
  params = {},
326
- }: { etypes?: string[]; params?: { [k: string]: any } } = {}
436
+ }: { etypes?: string[]; params?: { [k: string]: any } } = {},
327
437
  ) {
328
438
  return this.query(
329
- () => this.link.prepare(query).iterate(params),
439
+ () =>
440
+ (this.store.linkWrite || this.store.link)
441
+ .prepare(query)
442
+ .iterate(params),
330
443
  `${query} -- ${JSON.stringify(params)}`,
331
- etypes
444
+ etypes,
332
445
  );
333
446
  }
334
447
 
@@ -337,12 +450,13 @@ export default class SQLite3Driver extends NymphDriver {
337
450
  {
338
451
  etypes = [],
339
452
  params = {},
340
- }: { etypes?: string[]; params?: { [k: string]: any } } = {}
453
+ }: { etypes?: string[]; params?: { [k: string]: any } } = {},
341
454
  ) {
342
455
  return this.query(
343
- () => this.link.prepare(query).get(params),
456
+ () =>
457
+ (this.store.linkWrite || this.store.link).prepare(query).get(params),
344
458
  `${query} -- ${JSON.stringify(params)}`,
345
- etypes
459
+ etypes,
346
460
  );
347
461
  }
348
462
 
@@ -351,32 +465,44 @@ export default class SQLite3Driver extends NymphDriver {
351
465
  {
352
466
  etypes = [],
353
467
  params = {},
354
- }: { etypes?: string[]; params?: { [k: string]: any } } = {}
468
+ }: { etypes?: string[]; params?: { [k: string]: any } } = {},
355
469
  ) {
356
470
  return this.query(
357
- () => this.link.prepare(query).run(params),
471
+ () =>
472
+ (this.store.linkWrite || this.store.link).prepare(query).run(params),
358
473
  `${query} -- ${JSON.stringify(params)}`,
359
- etypes
474
+ etypes,
360
475
  );
361
476
  }
362
477
 
363
478
  public async commit(name: string) {
364
479
  if (name == null || typeof name !== 'string' || name.length === 0) {
365
480
  throw new InvalidParametersError(
366
- 'Transaction commit attempted without a name.'
481
+ 'Transaction commit attempted without a name.',
367
482
  );
368
483
  }
369
- if (this.transactionsStarted === 0) {
484
+ if (this.store.transactionsStarted === 0) {
370
485
  return true;
371
486
  }
372
487
  this.queryRun(`RELEASE SAVEPOINT ${SQLite3Driver.escape(name)};`);
373
- this.transactionsStarted--;
488
+ this.store.transactionsStarted--;
489
+
490
+ if (
491
+ this.store.transactionsStarted === 0 &&
492
+ this.store.linkWrite &&
493
+ !this.config.explicitWrite
494
+ ) {
495
+ this.store.linkWrite.exec('PRAGMA optimize;');
496
+ this.store.linkWrite.close();
497
+ this.store.linkWrite = undefined;
498
+ }
499
+
374
500
  return true;
375
501
  }
376
502
 
377
503
  public async deleteEntityByID(
378
504
  guid: string,
379
- className?: EntityConstructor | string | null
505
+ className?: EntityConstructor | string | null,
380
506
  ) {
381
507
  let EntityClass: EntityConstructor;
382
508
  if (typeof className === 'string' || className == null) {
@@ -386,80 +512,93 @@ export default class SQLite3Driver extends NymphDriver {
386
512
  EntityClass = className;
387
513
  }
388
514
  const etype = EntityClass.ETYPE;
389
- this.checkReadOnlyMode();
390
515
  await this.startTransaction('nymph-delete');
391
516
  try {
392
517
  this.queryRun(
393
518
  `DELETE FROM ${SQLite3Driver.escape(
394
- `${this.prefix}entities_${etype}`
519
+ `${this.prefix}entities_${etype}`,
395
520
  )} WHERE "guid"=@guid;`,
396
521
  {
397
522
  etypes: [etype],
398
523
  params: {
399
524
  guid,
400
525
  },
401
- }
526
+ },
402
527
  );
403
528
  this.queryRun(
404
529
  `DELETE FROM ${SQLite3Driver.escape(
405
- `${this.prefix}data_${etype}`
530
+ `${this.prefix}data_${etype}`,
406
531
  )} WHERE "guid"=@guid;`,
407
532
  {
408
533
  etypes: [etype],
409
534
  params: {
410
535
  guid,
411
536
  },
412
- }
537
+ },
413
538
  );
414
539
  this.queryRun(
415
540
  `DELETE FROM ${SQLite3Driver.escape(
416
- `${this.prefix}comparisons_${etype}`
541
+ `${this.prefix}comparisons_${etype}`,
417
542
  )} WHERE "guid"=@guid;`,
418
543
  {
419
544
  etypes: [etype],
420
545
  params: {
421
546
  guid,
422
547
  },
423
- }
548
+ },
424
549
  );
425
550
  this.queryRun(
426
551
  `DELETE FROM ${SQLite3Driver.escape(
427
- `${this.prefix}references_${etype}`
552
+ `${this.prefix}references_${etype}`,
428
553
  )} WHERE "guid"=@guid;`,
429
554
  {
430
555
  etypes: [etype],
431
556
  params: {
432
557
  guid,
433
558
  },
434
- }
559
+ },
560
+ );
561
+ this.queryRun(
562
+ `DELETE FROM ${SQLite3Driver.escape(
563
+ `${this.prefix}uniques_${etype}`,
564
+ )} WHERE "guid"=@guid;`,
565
+ {
566
+ etypes: [etype],
567
+ params: {
568
+ guid,
569
+ },
570
+ },
435
571
  );
436
- await this.commit('nymph-delete');
437
- // Remove any cached versions of this entity.
438
- if (this.nymph.config.cache) {
439
- this.cleanCache(guid);
440
- }
441
- return true;
442
572
  } catch (e: any) {
573
+ this.nymph.config.debugError('sqlite3', `Delete entity error: "${e}"`);
443
574
  await this.rollback('nymph-delete');
444
575
  throw e;
445
576
  }
577
+
578
+ await this.commit('nymph-delete');
579
+ // Remove any cached versions of this entity.
580
+ if (this.nymph.config.cache) {
581
+ this.cleanCache(guid);
582
+ }
583
+ return true;
446
584
  }
447
585
 
448
586
  public async deleteUID(name: string) {
449
587
  if (!name) {
450
588
  throw new InvalidParametersError('Name not given for UID');
451
589
  }
452
- this.checkReadOnlyMode();
590
+ await this.startTransaction('nymph-delete-uid');
453
591
  this.queryRun(
454
592
  `DELETE FROM ${SQLite3Driver.escape(
455
- `${this.prefix}uids`
593
+ `${this.prefix}uids`,
456
594
  )} WHERE "name"=@name;`,
457
595
  {
458
596
  params: {
459
597
  name,
460
598
  },
461
- }
599
+ },
462
600
  );
601
+ await this.commit('nymph-delete-uid');
463
602
  return true;
464
603
  }
465
604
 
@@ -477,10 +616,10 @@ export default class SQLite3Driver extends NymphDriver {
477
616
  writeLine('');
478
617
 
479
618
  // Export UIDs.
480
- let uids = this.queryIter(
619
+ let uids: IterableIterator<any> = this.queryIter(
481
620
  `SELECT * FROM ${SQLite3Driver.escape(
482
- `${this.prefix}uids`
483
- )} ORDER BY "name";`
621
+ `${this.prefix}uids`,
622
+ )} ORDER BY "name";`,
484
623
  );
485
624
  for (const uid of uids) {
486
625
  writeLine(`<${uid.name}>[${uid.cur_uid}]`);
@@ -493,8 +632,8 @@ export default class SQLite3Driver extends NymphDriver {
493
632
  writeLine('');
494
633
 
495
634
  // Get the etypes.
496
- const tables = this.queryIter(
497
- "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;"
635
+ const tables: IterableIterator<any> = this.queryIter(
636
+ "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;",
498
637
  );
499
638
  const etypes = [];
500
639
  for (const table of tables) {
@@ -505,14 +644,14 @@ export default class SQLite3Driver extends NymphDriver {
505
644
 
506
645
  for (const etype of etypes) {
507
646
  // Export entities.
508
- const dataIterator = this.queryIter(
647
+ const dataIterator: IterableIterator<any> = this.queryIter(
509
648
  `SELECT e.*, d."name" AS "dname", d."value" AS "dvalue", c."string", c."number" FROM ${SQLite3Driver.escape(
510
- `${this.prefix}entities_${etype}`
649
+ `${this.prefix}entities_${etype}`,
511
650
  )} e LEFT JOIN ${SQLite3Driver.escape(
512
- `${this.prefix}data_${etype}`
651
+ `${this.prefix}data_${etype}`,
513
652
  )} d USING ("guid") INNER JOIN ${SQLite3Driver.escape(
514
- `${this.prefix}comparisons_${etype}`
515
- )} c USING ("guid", "name") ORDER BY e."guid";`
653
+ `${this.prefix}comparisons_${etype}`,
654
+ )} c USING ("guid", "name") ORDER BY e."guid";`,
516
655
  )[Symbol.iterator]();
517
656
  let datum = dataIterator.next();
518
657
  while (!datum.done) {
@@ -564,7 +703,7 @@ export default class SQLite3Driver extends NymphDriver {
564
703
  params: { [k: string]: any } = {},
565
704
  subquery = false,
566
705
  tableSuffix = '',
567
- etypes: string[] = []
706
+ etypes: string[] = [],
568
707
  ) {
569
708
  if (typeof options.class?.alterOptions === 'function') {
570
709
  options = options.class.alterOptions(options);
@@ -574,10 +713,11 @@ export default class SQLite3Driver extends NymphDriver {
574
713
  const cTable = `c${tableSuffix}`;
575
714
  const fTable = `f${tableSuffix}`;
576
715
  const ieTable = `ie${tableSuffix}`;
716
+ const sTable = `s${tableSuffix}`;
577
717
  const sort = options.sort ?? 'cdate';
578
718
  const queryParts = this.iterateSelectorsForQuery(
579
719
  formattedSelectors,
580
- (key, value, typeIsOr, typeIsNot) => {
720
+ ({ key, value, typeIsOr, typeIsNot }) => {
581
721
  const clauseNot = key.startsWith('!');
582
722
  let curQuery = '';
583
723
  for (const curValue of value) {
@@ -1267,7 +1407,7 @@ export default class SQLite3Driver extends NymphDriver {
1267
1407
  params,
1268
1408
  true,
1269
1409
  tableSuffix,
1270
- etypes
1410
+ etypes,
1271
1411
  );
1272
1412
  if (curQuery) {
1273
1413
  curQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -1282,7 +1422,7 @@ export default class SQLite3Driver extends NymphDriver {
1282
1422
  case '!qref':
1283
1423
  const [qrefOptions, ...qrefSelectors] = curValue[1] as [
1284
1424
  Options,
1285
- ...FormattedSelector[]
1425
+ ...FormattedSelector[],
1286
1426
  ];
1287
1427
  const QrefEntityClass = qrefOptions.class as EntityConstructor;
1288
1428
  etypes.push(QrefEntityClass.ETYPE);
@@ -1294,7 +1434,7 @@ export default class SQLite3Driver extends NymphDriver {
1294
1434
  params,
1295
1435
  false,
1296
1436
  makeTableSuffix(),
1297
- etypes
1437
+ etypes,
1298
1438
  );
1299
1439
  if (curQuery) {
1300
1440
  curQuery += typeIsOr ? ' OR ' : ' AND ';
@@ -1315,22 +1455,35 @@ export default class SQLite3Driver extends NymphDriver {
1315
1455
  }
1316
1456
  }
1317
1457
  return curQuery;
1318
- }
1458
+ },
1319
1459
  );
1320
1460
 
1321
1461
  let sortBy: string;
1462
+ let sortByInner: string;
1463
+ let sortJoin = '';
1464
+ const order = options.reverse ? ' DESC' : '';
1322
1465
  switch (sort) {
1323
1466
  case 'mdate':
1324
- sortBy = '"mdate"';
1467
+ sortBy = `${eTable}."mdate"${order}`;
1468
+ sortByInner = `${ieTable}."mdate"${order}`;
1325
1469
  break;
1326
1470
  case 'cdate':
1471
+ sortBy = `${eTable}."cdate"${order}`;
1472
+ sortByInner = `${ieTable}."cdate"${order}`;
1473
+ break;
1327
1474
  default:
1328
- sortBy = '"cdate"';
1475
+ const name = `param${++count.i}`;
1476
+ sortJoin = `LEFT JOIN (
1477
+ SELECT "guid", "string", "number"
1478
+ FROM ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)}
1479
+ WHERE "name"=@${name}
1480
+ ORDER BY "number"${order}, "string"${order}
1481
+ ) ${sTable} USING ("guid")`;
1482
+ sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
1483
+ sortByInner = sortBy;
1484
+ params[name] = sort;
1329
1485
  break;
1330
1486
  }
1331
- if (options.reverse) {
1332
- sortBy += ' DESC';
1333
- }
1334
1487
 
1335
1488
  let query: string;
1336
1489
  if (queryParts.length) {
@@ -1351,24 +1504,25 @@ export default class SQLite3Driver extends NymphDriver {
1351
1504
  query = `SELECT COUNT("guid") AS "count" FROM (
1352
1505
  SELECT "guid"
1353
1506
  FROM ${SQLite3Driver.escape(
1354
- this.prefix + 'entities_' + etype
1507
+ this.prefix + 'entities_' + etype,
1355
1508
  )} ${ieTable}
1356
1509
  WHERE (${whereClause})${limit}${offset}
1357
1510
  )`;
1358
1511
  } else {
1359
1512
  query = `SELECT COUNT("guid") AS "count"
1360
1513
  FROM ${SQLite3Driver.escape(
1361
- this.prefix + 'entities_' + etype
1514
+ this.prefix + 'entities_' + etype,
1362
1515
  )} ${ieTable}
1363
1516
  WHERE (${whereClause})`;
1364
1517
  }
1365
1518
  } else if (options.return === 'guid') {
1366
1519
  query = `SELECT "guid"
1367
1520
  FROM ${SQLite3Driver.escape(
1368
- this.prefix + 'entities_' + etype
1521
+ this.prefix + 'entities_' + etype,
1369
1522
  )} ${ieTable}
1523
+ ${sortJoin}
1370
1524
  WHERE (${whereClause})
1371
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1525
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1372
1526
  } else {
1373
1527
  query = `SELECT
1374
1528
  ${eTable}."guid",
@@ -1380,23 +1534,25 @@ export default class SQLite3Driver extends NymphDriver {
1380
1534
  ${cTable}."string",
1381
1535
  ${cTable}."number"
1382
1536
  FROM ${SQLite3Driver.escape(
1383
- this.prefix + 'entities_' + etype
1537
+ this.prefix + 'entities_' + etype,
1384
1538
  )} ${eTable}
1385
1539
  LEFT JOIN ${SQLite3Driver.escape(
1386
- this.prefix + 'data_' + etype
1540
+ this.prefix + 'data_' + etype,
1387
1541
  )} ${dTable} USING ("guid")
1388
1542
  INNER JOIN ${SQLite3Driver.escape(
1389
- this.prefix + 'comparisons_' + etype
1543
+ this.prefix + 'comparisons_' + etype,
1390
1544
  )} ${cTable} USING ("guid", "name")
1545
+ ${sortJoin}
1391
1546
  INNER JOIN (
1392
1547
  SELECT "guid"
1393
1548
  FROM ${SQLite3Driver.escape(
1394
- this.prefix + 'entities_' + etype
1549
+ this.prefix + 'entities_' + etype,
1395
1550
  )} ${ieTable}
1551
+ ${sortJoin}
1396
1552
  WHERE (${whereClause})
1397
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1553
+ ORDER BY ${sortByInner}${limit}${offset}
1398
1554
  ) ${fTable} USING ("guid")
1399
- ORDER BY ${eTable}.${sortBy}`;
1555
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1400
1556
  }
1401
1557
  }
1402
1558
  } else {
@@ -1416,21 +1572,22 @@ export default class SQLite3Driver extends NymphDriver {
1416
1572
  query = `SELECT COUNT("guid") AS "count" FROM (
1417
1573
  SELECT "guid"
1418
1574
  FROM ${SQLite3Driver.escape(
1419
- this.prefix + 'entities_' + etype
1575
+ this.prefix + 'entities_' + etype,
1420
1576
  )} ${ieTable}${limit}${offset}
1421
1577
  )`;
1422
1578
  } else {
1423
1579
  query = `SELECT COUNT("guid") AS "count"
1424
1580
  FROM ${SQLite3Driver.escape(
1425
- this.prefix + 'entities_' + etype
1581
+ this.prefix + 'entities_' + etype,
1426
1582
  )} ${ieTable}`;
1427
1583
  }
1428
1584
  } else if (options.return === 'guid') {
1429
1585
  query = `SELECT "guid"
1430
1586
  FROM ${SQLite3Driver.escape(
1431
- this.prefix + 'entities_' + etype
1587
+ this.prefix + 'entities_' + etype,
1432
1588
  )} ${ieTable}
1433
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1589
+ ${sortJoin}
1590
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1434
1591
  } else {
1435
1592
  if (limit || offset) {
1436
1593
  query = `SELECT
@@ -1443,22 +1600,24 @@ export default class SQLite3Driver extends NymphDriver {
1443
1600
  ${cTable}."string",
1444
1601
  ${cTable}."number"
1445
1602
  FROM ${SQLite3Driver.escape(
1446
- this.prefix + 'entities_' + etype
1603
+ this.prefix + 'entities_' + etype,
1447
1604
  )} ${eTable}
1448
1605
  LEFT JOIN ${SQLite3Driver.escape(
1449
- this.prefix + 'data_' + etype
1606
+ this.prefix + 'data_' + etype,
1450
1607
  )} ${dTable} USING ("guid")
1451
1608
  INNER JOIN ${SQLite3Driver.escape(
1452
- this.prefix + 'comparisons_' + etype
1609
+ this.prefix + 'comparisons_' + etype,
1453
1610
  )} c USING ("guid", "name")
1611
+ ${sortJoin}
1454
1612
  INNER JOIN (
1455
1613
  SELECT "guid"
1456
1614
  FROM ${SQLite3Driver.escape(
1457
- this.prefix + 'entities_' + etype
1615
+ this.prefix + 'entities_' + etype,
1458
1616
  )} ${ieTable}
1459
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1617
+ ${sortJoin}
1618
+ ORDER BY ${sortByInner}${limit}${offset}
1460
1619
  ) ${fTable} USING ("guid")
1461
- ORDER BY ${eTable}.${sortBy}`;
1620
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1462
1621
  } else {
1463
1622
  query = `SELECT
1464
1623
  ${eTable}."guid",
@@ -1470,15 +1629,16 @@ export default class SQLite3Driver extends NymphDriver {
1470
1629
  ${cTable}."string",
1471
1630
  ${cTable}."number"
1472
1631
  FROM ${SQLite3Driver.escape(
1473
- this.prefix + 'entities_' + etype
1632
+ this.prefix + 'entities_' + etype,
1474
1633
  )} ${eTable}
1475
1634
  LEFT JOIN ${SQLite3Driver.escape(
1476
- this.prefix + 'data_' + etype
1635
+ this.prefix + 'data_' + etype,
1477
1636
  )} ${dTable} USING ("guid")
1478
1637
  INNER JOIN ${SQLite3Driver.escape(
1479
- this.prefix + 'comparisons_' + etype
1638
+ this.prefix + 'comparisons_' + etype,
1480
1639
  )} ${cTable} USING ("guid", "name")
1481
- ORDER BY ${eTable}.${sortBy}`;
1640
+ ${sortJoin}
1641
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1482
1642
  }
1483
1643
  }
1484
1644
  }
@@ -1498,14 +1658,14 @@ export default class SQLite3Driver extends NymphDriver {
1498
1658
  protected performQuery(
1499
1659
  options: Options,
1500
1660
  formattedSelectors: FormattedSelector[],
1501
- etype: string
1661
+ etype: string,
1502
1662
  ): {
1503
1663
  result: any;
1504
1664
  } {
1505
1665
  const { query, params, etypes } = this.makeEntityQuery(
1506
1666
  options,
1507
1667
  formattedSelectors,
1508
- etype
1668
+ etype,
1509
1669
  );
1510
1670
  const result = this.queryIter(query, { etypes, params })[Symbol.iterator]();
1511
1671
  return {
@@ -1524,35 +1684,16 @@ export default class SQLite3Driver extends NymphDriver {
1524
1684
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
1525
1685
  options?: Options<T>,
1526
1686
  ...selectors: Selector[]
1527
- ): Promise<ReturnType<T['factorySync']>[]>;
1687
+ ): Promise<EntityInstanceType<T>[]>;
1528
1688
  public async getEntities<T extends EntityConstructor = EntityConstructor>(
1529
1689
  options: Options<T> = {},
1530
1690
  ...selectors: Selector[]
1531
- ): Promise<ReturnType<T['factorySync']>[] | string[] | number> {
1532
- return this.getEntitiesSync(options, ...selectors);
1533
- }
1534
-
1535
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1536
- options: Options<T> & { return: 'count' },
1537
- ...selectors: Selector[]
1538
- ): number;
1539
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1540
- options: Options<T> & { return: 'guid' },
1541
- ...selectors: Selector[]
1542
- ): string[];
1543
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1544
- options?: Options<T>,
1545
- ...selectors: Selector[]
1546
- ): ReturnType<T['factorySync']>[];
1547
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1548
- options: Options<T> = {},
1549
- ...selectors: Selector[]
1550
- ): ReturnType<T['factorySync']>[] | string[] | number {
1551
- const { result, process } = this.getEntitesRowLike<T>(
1691
+ ): Promise<EntityInstanceType<T>[] | string[] | number> {
1692
+ const { result, process } = this.getEntitiesRowLike<T>(
1552
1693
  options,
1553
1694
  selectors,
1554
- (options, formattedSelectors, etype) =>
1555
- this.performQuery(options, formattedSelectors, etype),
1695
+ ({ options, selectors, etype }) =>
1696
+ this.performQuery(options, selectors, etype),
1556
1697
  () => {
1557
1698
  const next: any = result.next();
1558
1699
  return next.done ? null : next.value;
@@ -1573,7 +1714,7 @@ export default class SQLite3Driver extends NymphDriver {
1573
1714
  : row.value === 'S'
1574
1715
  ? JSON.stringify(row.string)
1575
1716
  : row.value,
1576
- })
1717
+ }),
1577
1718
  );
1578
1719
  const value = process();
1579
1720
  if (value instanceof Error) {
@@ -1586,175 +1727,243 @@ export default class SQLite3Driver extends NymphDriver {
1586
1727
  if (name == null) {
1587
1728
  throw new InvalidParametersError('Name not given for UID.');
1588
1729
  }
1589
- const result = this.queryGet(
1730
+ const result: any = this.queryGet(
1590
1731
  `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1591
- `${this.prefix}uids`
1732
+ `${this.prefix}uids`,
1592
1733
  )} WHERE "name"=@name;`,
1593
1734
  {
1594
1735
  params: {
1595
1736
  name: name,
1596
1737
  },
1597
- }
1738
+ },
1598
1739
  );
1599
1740
  return (result?.cur_uid as number | null) ?? null;
1600
1741
  }
1601
1742
 
1602
- public async import(filename: string) {
1603
- this.checkReadOnlyMode();
1743
+ public async import(filename: string, transaction?: boolean) {
1604
1744
  try {
1605
1745
  return this.importFromFile(
1606
1746
  filename,
1607
1747
  async (guid, tags, sdata, etype) => {
1608
- this.queryRun(
1609
- `DELETE FROM ${SQLite3Driver.escape(
1610
- `${this.prefix}entities_${etype}`
1611
- )} WHERE "guid"=@guid;`,
1612
- {
1613
- etypes: [etype],
1614
- params: {
1615
- guid,
1616
- },
1617
- }
1618
- );
1619
- this.queryRun(
1620
- `DELETE FROM ${SQLite3Driver.escape(
1621
- `${this.prefix}data_${etype}`
1622
- )} WHERE "guid"=@guid;`,
1623
- {
1624
- etypes: [etype],
1625
- params: {
1626
- guid,
1748
+ try {
1749
+ await this.startTransaction(`nymph-import-entity-${guid}`);
1750
+
1751
+ const cdate = Number(JSON.parse(sdata.cdate));
1752
+ delete sdata.cdate;
1753
+ const mdate = Number(JSON.parse(sdata.mdate));
1754
+ delete sdata.mdate;
1755
+
1756
+ this.queryRun(
1757
+ `DELETE FROM ${SQLite3Driver.escape(
1758
+ `${this.prefix}entities_${etype}`,
1759
+ )} WHERE "guid"=@guid;`,
1760
+ {
1761
+ etypes: [etype],
1762
+ params: {
1763
+ guid,
1764
+ },
1627
1765
  },
1628
- }
1629
- );
1630
- this.queryRun(
1631
- `DELETE FROM ${SQLite3Driver.escape(
1632
- `${this.prefix}comparisons_${etype}`
1633
- )} WHERE "guid"=@guid;`,
1634
- {
1635
- etypes: [etype],
1636
- params: {
1637
- guid,
1766
+ );
1767
+ this.queryRun(
1768
+ `DELETE FROM ${SQLite3Driver.escape(
1769
+ `${this.prefix}data_${etype}`,
1770
+ )} WHERE "guid"=@guid;`,
1771
+ {
1772
+ etypes: [etype],
1773
+ params: {
1774
+ guid,
1775
+ },
1638
1776
  },
1639
- }
1640
- );
1641
- this.queryRun(
1642
- `DELETE FROM ${SQLite3Driver.escape(
1643
- `${this.prefix}references_${etype}`
1644
- )} WHERE "guid"=@guid;`,
1645
- {
1646
- etypes: [etype],
1647
- params: {
1648
- guid,
1777
+ );
1778
+ this.queryRun(
1779
+ `DELETE FROM ${SQLite3Driver.escape(
1780
+ `${this.prefix}comparisons_${etype}`,
1781
+ )} WHERE "guid"=@guid;`,
1782
+ {
1783
+ etypes: [etype],
1784
+ params: {
1785
+ guid,
1786
+ },
1649
1787
  },
1650
- }
1651
- );
1652
- this.queryRun(
1653
- `INSERT INTO ${SQLite3Driver.escape(
1654
- `${this.prefix}entities_${etype}`
1655
- )} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`,
1656
- {
1657
- etypes: [etype],
1658
- params: {
1659
- guid,
1660
- tags: ',' + tags.join(',') + ',',
1661
- cdate: Number(JSON.parse(sdata.cdate)),
1662
- mdate: Number(JSON.parse(sdata.mdate)),
1788
+ );
1789
+ this.queryRun(
1790
+ `DELETE FROM ${SQLite3Driver.escape(
1791
+ `${this.prefix}references_${etype}`,
1792
+ )} WHERE "guid"=@guid;`,
1793
+ {
1794
+ etypes: [etype],
1795
+ params: {
1796
+ guid,
1797
+ },
1663
1798
  },
1664
- }
1665
- );
1666
- delete sdata.cdate;
1667
- delete sdata.mdate;
1668
- for (const name in sdata) {
1669
- const value = sdata[name];
1670
- const uvalue = JSON.parse(value);
1671
- if (value === undefined) {
1672
- continue;
1673
- }
1674
- const storageValue =
1675
- typeof uvalue === 'number'
1676
- ? 'N'
1677
- : typeof uvalue === 'string'
1678
- ? 'S'
1679
- : value;
1799
+ );
1680
1800
  this.queryRun(
1681
- `INSERT INTO ${SQLite3Driver.escape(
1682
- `${this.prefix}data_${etype}`
1683
- )} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`,
1801
+ `DELETE FROM ${SQLite3Driver.escape(
1802
+ `${this.prefix}uniques_${etype}`,
1803
+ )} WHERE "guid"=@guid;`,
1684
1804
  {
1685
1805
  etypes: [etype],
1686
1806
  params: {
1687
1807
  guid,
1688
- name,
1689
- storageValue,
1690
1808
  },
1691
- }
1809
+ },
1692
1810
  );
1811
+
1693
1812
  this.queryRun(
1694
1813
  `INSERT INTO ${SQLite3Driver.escape(
1695
- `${this.prefix}comparisons_${etype}`
1696
- )} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`,
1814
+ `${this.prefix}entities_${etype}`,
1815
+ )} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`,
1697
1816
  {
1698
1817
  etypes: [etype],
1699
1818
  params: {
1700
1819
  guid,
1701
- name,
1702
- truthy: uvalue ? 1 : 0,
1703
- string: `${uvalue}`,
1704
- number: Number(uvalue),
1820
+ tags: ',' + tags.join(',') + ',',
1821
+ cdate,
1822
+ mdate,
1705
1823
  },
1706
- }
1824
+ },
1707
1825
  );
1708
- const references = this.findReferences(value);
1709
- for (const reference of references) {
1826
+ for (const name in sdata) {
1827
+ const value = sdata[name];
1828
+ const uvalue = JSON.parse(value);
1829
+ if (value === undefined) {
1830
+ continue;
1831
+ }
1832
+ const storageValue =
1833
+ typeof uvalue === 'number'
1834
+ ? 'N'
1835
+ : typeof uvalue === 'string'
1836
+ ? 'S'
1837
+ : value;
1710
1838
  this.queryRun(
1711
1839
  `INSERT INTO ${SQLite3Driver.escape(
1712
- `${this.prefix}references_${etype}`
1713
- )} ("guid", "name", "reference") VALUES (@guid, @name, @reference);`,
1840
+ `${this.prefix}data_${etype}`,
1841
+ )} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`,
1714
1842
  {
1715
1843
  etypes: [etype],
1716
1844
  params: {
1717
1845
  guid,
1718
1846
  name,
1719
- reference,
1847
+ storageValue,
1720
1848
  },
1721
- }
1849
+ },
1850
+ );
1851
+ this.queryRun(
1852
+ `INSERT INTO ${SQLite3Driver.escape(
1853
+ `${this.prefix}comparisons_${etype}`,
1854
+ )} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`,
1855
+ {
1856
+ etypes: [etype],
1857
+ params: {
1858
+ guid,
1859
+ name,
1860
+ truthy: uvalue ? 1 : 0,
1861
+ string: `${uvalue}`,
1862
+ number: Number(uvalue),
1863
+ },
1864
+ },
1722
1865
  );
1866
+ const references = this.findReferences(value);
1867
+ for (const reference of references) {
1868
+ this.queryRun(
1869
+ `INSERT INTO ${SQLite3Driver.escape(
1870
+ `${this.prefix}references_${etype}`,
1871
+ )} ("guid", "name", "reference") VALUES (@guid, @name, @reference);`,
1872
+ {
1873
+ etypes: [etype],
1874
+ params: {
1875
+ guid,
1876
+ name,
1877
+ reference,
1878
+ },
1879
+ },
1880
+ );
1881
+ }
1723
1882
  }
1883
+ const uniques = await this.nymph
1884
+ .getEntityClassByEtype(etype)
1885
+ .getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
1886
+ for (const unique of uniques) {
1887
+ try {
1888
+ this.queryRun(
1889
+ `INSERT INTO ${SQLite3Driver.escape(
1890
+ `${this.prefix}uniques_${etype}`,
1891
+ )} ("guid", "unique") VALUES (@guid, @unique);`,
1892
+ {
1893
+ etypes: [etype],
1894
+ params: {
1895
+ guid,
1896
+ unique,
1897
+ },
1898
+ },
1899
+ );
1900
+ } catch (e: any) {
1901
+ if (e instanceof EntityUniqueConstraintError) {
1902
+ this.nymph.config.debugError(
1903
+ 'sqlite3',
1904
+ `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
1905
+ );
1906
+ }
1907
+ throw e;
1908
+ }
1909
+ }
1910
+ await this.commit(`nymph-import-entity-${guid}`);
1911
+ } catch (e: any) {
1912
+ this.nymph.config.debugError(
1913
+ 'sqlite3',
1914
+ `Import entity error: "${e}"`,
1915
+ );
1916
+ await this.rollback(`nymph-import-entity-${guid}`);
1917
+ throw e;
1724
1918
  }
1725
1919
  },
1726
1920
  async (name, curUid) => {
1727
- this.queryRun(
1728
- `DELETE FROM ${SQLite3Driver.escape(
1729
- `${this.prefix}uids`
1730
- )} WHERE "name"=@name;`,
1731
- {
1732
- params: {
1733
- name,
1921
+ try {
1922
+ await this.startTransaction(`nymph-import-uid-${name}`);
1923
+ this.queryRun(
1924
+ `DELETE FROM ${SQLite3Driver.escape(
1925
+ `${this.prefix}uids`,
1926
+ )} WHERE "name"=@name;`,
1927
+ {
1928
+ params: {
1929
+ name,
1930
+ },
1734
1931
  },
1735
- }
1736
- );
1737
- this.queryRun(
1738
- `INSERT INTO ${SQLite3Driver.escape(
1739
- `${this.prefix}uids`
1740
- )} ("name", "cur_uid") VALUES (@name, @curUid);`,
1741
- {
1742
- params: {
1743
- name,
1744
- curUid,
1932
+ );
1933
+ this.queryRun(
1934
+ `INSERT INTO ${SQLite3Driver.escape(
1935
+ `${this.prefix}uids`,
1936
+ )} ("name", "cur_uid") VALUES (@name, @curUid);`,
1937
+ {
1938
+ params: {
1939
+ name,
1940
+ curUid,
1941
+ },
1745
1942
  },
1746
- }
1747
- );
1943
+ );
1944
+ await this.commit(`nymph-import-uid-${name}`);
1945
+ } catch (e: any) {
1946
+ this.nymph.config.debugError('sqlite3', `Import UID error: "${e}"`);
1947
+ await this.rollback(`nymph-import-uid-${name}`);
1948
+ throw e;
1949
+ }
1748
1950
  },
1749
1951
  async () => {
1750
- await this.startTransaction('nymph-import');
1952
+ if (transaction) {
1953
+ await this.startTransaction('nymph-import');
1954
+ }
1751
1955
  },
1752
1956
  async () => {
1753
- await this.commit('nymph-import');
1754
- }
1957
+ if (transaction) {
1958
+ await this.commit('nymph-import');
1959
+ }
1960
+ },
1755
1961
  );
1756
1962
  } catch (e: any) {
1757
- await this.rollback('nymph-import');
1963
+ this.nymph.config.debugError('sqlite3', `Import error: "${e}"`);
1964
+ if (transaction) {
1965
+ await this.rollback('nymph-import');
1966
+ }
1758
1967
  throw e;
1759
1968
  }
1760
1969
  }
@@ -1763,95 +1972,111 @@ export default class SQLite3Driver extends NymphDriver {
1763
1972
  if (name == null) {
1764
1973
  throw new InvalidParametersError('Name not given for UID.');
1765
1974
  }
1766
- this.checkReadOnlyMode();
1767
1975
  await this.startTransaction('nymph-newuid');
1976
+ let curUid: number | undefined = undefined;
1768
1977
  try {
1769
- let curUid =
1770
- this.queryGet(
1771
- `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1772
- `${this.prefix}uids`
1773
- )} WHERE "name"=@name;`,
1774
- {
1775
- params: {
1776
- name,
1978
+ curUid =
1979
+ (
1980
+ this.queryGet(
1981
+ `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1982
+ `${this.prefix}uids`,
1983
+ )} WHERE "name"=@name;`,
1984
+ {
1985
+ params: {
1986
+ name,
1987
+ },
1777
1988
  },
1778
- }
1989
+ ) as any
1779
1990
  )?.cur_uid ?? null;
1780
1991
  if (curUid == null) {
1781
1992
  curUid = 1;
1782
1993
  this.queryRun(
1783
1994
  `INSERT INTO ${SQLite3Driver.escape(
1784
- `${this.prefix}uids`
1995
+ `${this.prefix}uids`,
1785
1996
  )} ("name", "cur_uid") VALUES (@name, @curUid);`,
1786
1997
  {
1787
1998
  params: {
1788
1999
  name,
1789
2000
  curUid,
1790
2001
  },
1791
- }
2002
+ },
1792
2003
  );
1793
2004
  } else {
1794
2005
  curUid++;
1795
2006
  this.queryRun(
1796
2007
  `UPDATE ${SQLite3Driver.escape(
1797
- `${this.prefix}uids`
2008
+ `${this.prefix}uids`,
1798
2009
  )} SET "cur_uid"=@curUid WHERE "name"=@name;`,
1799
2010
  {
1800
2011
  params: {
1801
2012
  curUid,
1802
2013
  name,
1803
2014
  },
1804
- }
2015
+ },
1805
2016
  );
1806
2017
  }
1807
- await this.commit('nymph-newuid');
1808
- return curUid as number;
1809
2018
  } catch (e: any) {
2019
+ this.nymph.config.debugError('sqlite3', `New UID error: "${e}"`);
1810
2020
  await this.rollback('nymph-newuid');
1811
2021
  throw e;
1812
2022
  }
2023
+
2024
+ await this.commit('nymph-newuid');
2025
+ return curUid as number;
1813
2026
  }
1814
2027
 
1815
2028
  public async renameUID(oldName: string, newName: string) {
1816
2029
  if (oldName == null || newName == null) {
1817
2030
  throw new InvalidParametersError('Name not given for UID.');
1818
2031
  }
1819
- this.checkReadOnlyMode();
2032
+ await this.startTransaction('nymph-rename-uid');
1820
2033
  this.queryRun(
1821
2034
  `UPDATE ${SQLite3Driver.escape(
1822
- `${this.prefix}uids`
2035
+ `${this.prefix}uids`,
1823
2036
  )} SET "name"=@newName WHERE "name"=@oldName;`,
1824
2037
  {
1825
2038
  params: {
1826
2039
  newName,
1827
2040
  oldName,
1828
2041
  },
1829
- }
2042
+ },
1830
2043
  );
2044
+ await this.commit('nymph-rename-uid');
1831
2045
  return true;
1832
2046
  }
1833
2047
 
1834
2048
  public async rollback(name: string) {
1835
2049
  if (name == null || typeof name !== 'string' || name.length === 0) {
1836
2050
  throw new InvalidParametersError(
1837
- 'Transaction rollback attempted without a name.'
2051
+ 'Transaction rollback attempted without a name.',
1838
2052
  );
1839
2053
  }
1840
- if (this.transactionsStarted === 0) {
2054
+ if (this.store.transactionsStarted === 0) {
1841
2055
  return true;
1842
2056
  }
1843
2057
  this.queryRun(`ROLLBACK TO SAVEPOINT ${SQLite3Driver.escape(name)};`);
1844
- this.transactionsStarted--;
2058
+ this.store.transactionsStarted--;
2059
+
2060
+ if (
2061
+ this.store.transactionsStarted === 0 &&
2062
+ this.store.linkWrite &&
2063
+ !this.config.explicitWrite
2064
+ ) {
2065
+ this.store.linkWrite.exec('PRAGMA optimize;');
2066
+ this.store.linkWrite.close();
2067
+ this.store.linkWrite = undefined;
2068
+ }
2069
+
1845
2070
  return true;
1846
2071
  }
1847
2072
 
1848
2073
  public async saveEntity(entity: EntityInterface) {
1849
- this.checkReadOnlyMode();
1850
2074
  const insertData = (
1851
2075
  guid: string,
1852
2076
  data: EntityData,
1853
2077
  sdata: SerializedEntityData,
1854
- etype: string
2078
+ uniques: string[],
2079
+ etype: string,
1855
2080
  ) => {
1856
2081
  const runInsertQuery = (name: string, value: any, svalue: string) => {
1857
2082
  if (value === undefined) {
@@ -1865,7 +2090,7 @@ export default class SQLite3Driver extends NymphDriver {
1865
2090
  : svalue;
1866
2091
  this.queryRun(
1867
2092
  `INSERT INTO ${SQLite3Driver.escape(
1868
- `${this.prefix}data_${etype}`
2093
+ `${this.prefix}data_${etype}`,
1869
2094
  )} ("guid", "name", "value") VALUES (@guid, @name, @storageValue);`,
1870
2095
  {
1871
2096
  etypes: [etype],
@@ -1874,11 +2099,11 @@ export default class SQLite3Driver extends NymphDriver {
1874
2099
  name,
1875
2100
  storageValue,
1876
2101
  },
1877
- }
2102
+ },
1878
2103
  );
1879
2104
  this.queryRun(
1880
2105
  `INSERT INTO ${SQLite3Driver.escape(
1881
- `${this.prefix}comparisons_${etype}`
2106
+ `${this.prefix}comparisons_${etype}`,
1882
2107
  )} ("guid", "name", "truthy", "string", "number") VALUES (@guid, @name, @truthy, @string, @number);`,
1883
2108
  {
1884
2109
  etypes: [etype],
@@ -1889,13 +2114,13 @@ export default class SQLite3Driver extends NymphDriver {
1889
2114
  string: `${value}`,
1890
2115
  number: Number(value),
1891
2116
  },
1892
- }
2117
+ },
1893
2118
  );
1894
2119
  const references = this.findReferences(svalue);
1895
2120
  for (const reference of references) {
1896
2121
  this.queryRun(
1897
2122
  `INSERT INTO ${SQLite3Driver.escape(
1898
- `${this.prefix}references_${etype}`
2123
+ `${this.prefix}references_${etype}`,
1899
2124
  )} ("guid", "name", "reference") VALUES (@guid, @name, @reference);`,
1900
2125
  {
1901
2126
  etypes: [etype],
@@ -1904,10 +2129,34 @@ export default class SQLite3Driver extends NymphDriver {
1904
2129
  name,
1905
2130
  reference,
1906
2131
  },
1907
- }
2132
+ },
1908
2133
  );
1909
2134
  }
1910
2135
  };
2136
+ for (const unique of uniques) {
2137
+ try {
2138
+ this.queryRun(
2139
+ `INSERT INTO ${SQLite3Driver.escape(
2140
+ `${this.prefix}uniques_${etype}`,
2141
+ )} ("guid", "unique") VALUES (@guid, @unique);`,
2142
+ {
2143
+ etypes: [etype],
2144
+ params: {
2145
+ guid,
2146
+ unique,
2147
+ },
2148
+ },
2149
+ );
2150
+ } catch (e: any) {
2151
+ if (e instanceof EntityUniqueConstraintError) {
2152
+ this.nymph.config.debugError(
2153
+ 'sqlite3',
2154
+ `Save entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2155
+ );
2156
+ }
2157
+ throw e;
2158
+ }
2159
+ }
1911
2160
  for (const name in data) {
1912
2161
  runInsertQuery(name, data[name], JSON.stringify(data[name]));
1913
2162
  }
@@ -1915,13 +2164,20 @@ export default class SQLite3Driver extends NymphDriver {
1915
2164
  runInsertQuery(name, JSON.parse(sdata[name]), sdata[name]);
1916
2165
  }
1917
2166
  };
2167
+ let inTransaction = false;
1918
2168
  try {
1919
2169
  return this.saveEntityRowLike(
1920
2170
  entity,
1921
- async (_entity, guid, tags, data, sdata, cdate, etype) => {
2171
+ async ({ guid, tags, data, sdata, uniques, cdate, etype }) => {
2172
+ if (
2173
+ Object.keys(data).length === 0 &&
2174
+ Object.keys(sdata).length === 0
2175
+ ) {
2176
+ return false;
2177
+ }
1922
2178
  this.queryRun(
1923
2179
  `INSERT INTO ${SQLite3Driver.escape(
1924
- `${this.prefix}entities_${etype}`
2180
+ `${this.prefix}entities_${etype}`,
1925
2181
  )} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @cdate);`,
1926
2182
  {
1927
2183
  etypes: [etype],
@@ -1930,15 +2186,21 @@ export default class SQLite3Driver extends NymphDriver {
1930
2186
  tags: ',' + tags.join(',') + ',',
1931
2187
  cdate,
1932
2188
  },
1933
- }
2189
+ },
1934
2190
  );
1935
- insertData(guid, data, sdata, etype);
2191
+ insertData(guid, data, sdata, uniques, etype);
1936
2192
  return true;
1937
2193
  },
1938
- async (entity, guid, tags, data, sdata, mdate, etype) => {
2194
+ async ({ entity, guid, tags, data, sdata, uniques, mdate, etype }) => {
2195
+ if (
2196
+ Object.keys(data).length === 0 &&
2197
+ Object.keys(sdata).length === 0
2198
+ ) {
2199
+ return false;
2200
+ }
1939
2201
  const info = this.queryRun(
1940
2202
  `UPDATE ${SQLite3Driver.escape(
1941
- `${this.prefix}entities_${etype}`
2203
+ `${this.prefix}entities_${etype}`,
1942
2204
  )} SET "tags"=@tags, "mdate"=@mdate WHERE "guid"=@guid AND "mdate" <= @emdate;`,
1943
2205
  {
1944
2206
  etypes: [etype],
@@ -1948,62 +2210,80 @@ export default class SQLite3Driver extends NymphDriver {
1948
2210
  guid,
1949
2211
  emdate: Number(entity.mdate),
1950
2212
  },
1951
- }
2213
+ },
1952
2214
  );
1953
2215
  let success = false;
1954
2216
  if (info.changes === 1) {
1955
2217
  this.queryRun(
1956
2218
  `DELETE FROM ${SQLite3Driver.escape(
1957
- `${this.prefix}data_${etype}`
2219
+ `${this.prefix}data_${etype}`,
1958
2220
  )} WHERE "guid"=@guid;`,
1959
2221
  {
1960
2222
  etypes: [etype],
1961
2223
  params: {
1962
2224
  guid,
1963
2225
  },
1964
- }
2226
+ },
1965
2227
  );
1966
2228
  this.queryRun(
1967
2229
  `DELETE FROM ${SQLite3Driver.escape(
1968
- `${this.prefix}comparisons_${etype}`
2230
+ `${this.prefix}comparisons_${etype}`,
1969
2231
  )} WHERE "guid"=@guid;`,
1970
2232
  {
1971
2233
  etypes: [etype],
1972
2234
  params: {
1973
2235
  guid,
1974
2236
  },
1975
- }
2237
+ },
1976
2238
  );
1977
2239
  this.queryRun(
1978
2240
  `DELETE FROM ${SQLite3Driver.escape(
1979
- `${this.prefix}references_${etype}`
2241
+ `${this.prefix}references_${etype}`,
1980
2242
  )} WHERE "guid"=@guid;`,
1981
2243
  {
1982
2244
  etypes: [etype],
1983
2245
  params: {
1984
2246
  guid,
1985
2247
  },
1986
- }
2248
+ },
1987
2249
  );
1988
- insertData(guid, data, sdata, etype);
2250
+ this.queryRun(
2251
+ `DELETE FROM ${SQLite3Driver.escape(
2252
+ `${this.prefix}uniques_${etype}`,
2253
+ )} WHERE "guid"=@guid;`,
2254
+ {
2255
+ etypes: [etype],
2256
+ params: {
2257
+ guid,
2258
+ },
2259
+ },
2260
+ );
2261
+ insertData(guid, data, sdata, uniques, etype);
1989
2262
  success = true;
1990
2263
  }
1991
2264
  return success;
1992
2265
  },
1993
2266
  async () => {
1994
2267
  await this.startTransaction('nymph-save');
2268
+ inTransaction = true;
1995
2269
  },
1996
2270
  async (success) => {
1997
- if (success) {
1998
- await this.commit('nymph-save');
1999
- } else {
2000
- await this.rollback('nymph-save');
2271
+ if (inTransaction) {
2272
+ inTransaction = false;
2273
+ if (success) {
2274
+ await this.commit('nymph-save');
2275
+ } else {
2276
+ await this.rollback('nymph-save');
2277
+ }
2001
2278
  }
2002
2279
  return success;
2003
- }
2280
+ },
2004
2281
  );
2005
2282
  } catch (e: any) {
2006
- await this.rollback('nymph-save');
2283
+ this.nymph.config.debugError('sqlite3', `Save entity error: "${e}"`);
2284
+ if (inTransaction) {
2285
+ await this.rollback('nymph-save');
2286
+ }
2007
2287
  throw e;
2008
2288
  }
2009
2289
  }
@@ -2012,39 +2292,43 @@ export default class SQLite3Driver extends NymphDriver {
2012
2292
  if (name == null) {
2013
2293
  throw new InvalidParametersError('Name not given for UID.');
2014
2294
  }
2015
- this.checkReadOnlyMode();
2295
+ await this.startTransaction('nymph-set-uid');
2016
2296
  this.queryRun(
2017
2297
  `DELETE FROM ${SQLite3Driver.escape(
2018
- `${this.prefix}uids`
2298
+ `${this.prefix}uids`,
2019
2299
  )} WHERE "name"=@name;`,
2020
2300
  {
2021
2301
  params: {
2022
2302
  name,
2023
2303
  },
2024
- }
2304
+ },
2025
2305
  );
2026
2306
  this.queryRun(
2027
2307
  `INSERT INTO ${SQLite3Driver.escape(
2028
- `${this.prefix}uids`
2308
+ `${this.prefix}uids`,
2029
2309
  )} ("name", "cur_uid") VALUES (@name, @curUid);`,
2030
2310
  {
2031
2311
  params: {
2032
2312
  name,
2033
2313
  curUid,
2034
2314
  },
2035
- }
2315
+ },
2036
2316
  );
2317
+ await this.commit('nymph-set-uid');
2037
2318
  return true;
2038
2319
  }
2039
2320
 
2040
2321
  public async startTransaction(name: string) {
2041
2322
  if (name == null || typeof name !== 'string' || name.length === 0) {
2042
2323
  throw new InvalidParametersError(
2043
- 'Transaction start attempted without a name.'
2324
+ 'Transaction start attempted without a name.',
2044
2325
  );
2045
2326
  }
2327
+ if (!this.config.explicitWrite && !this.store.linkWrite) {
2328
+ this._connect(true);
2329
+ }
2046
2330
  this.queryRun(`SAVEPOINT ${SQLite3Driver.escape(name)};`);
2047
- this.transactionsStarted++;
2331
+ this.store.transactionsStarted++;
2048
2332
  return this.nymph;
2049
2333
  }
2050
2334
  }