@nymphjs/driver-sqlite3 1.0.0-beta.97 → 1.0.0-beta.99

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,4 +1,10 @@
1
1
  import SQLite3 from 'better-sqlite3';
2
+ import type {
3
+ SearchTerm,
4
+ SearchOrTerm,
5
+ SearchNotTerm,
6
+ SearchSeriesTerm,
7
+ } from '@sciactive/tokenizer';
2
8
  import {
3
9
  NymphDriver,
4
10
  type EntityConstructor,
@@ -220,6 +226,244 @@ export default class SQLite3Driver extends NymphDriver {
220
226
  return this.store.connected;
221
227
  }
222
228
 
229
+ private createEntitiesTable(etype: string) {
230
+ // Create the entity table.
231
+ this.queryRun(
232
+ `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
233
+ `${this.prefix}entities_${etype}`,
234
+ )} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL);`,
235
+ );
236
+ this.queryRun(
237
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
238
+ `${this.prefix}entities_${etype}_id_cdate`,
239
+ )} ON ${SQLite3Driver.escape(
240
+ `${this.prefix}entities_${etype}`,
241
+ )} ("cdate");`,
242
+ );
243
+ this.queryRun(
244
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
245
+ `${this.prefix}entities_${etype}_id_mdate`,
246
+ )} ON ${SQLite3Driver.escape(
247
+ `${this.prefix}entities_${etype}`,
248
+ )} ("mdate");`,
249
+ );
250
+ this.queryRun(
251
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
252
+ `${this.prefix}entities_${etype}_id_tags`,
253
+ )} ON ${SQLite3Driver.escape(
254
+ `${this.prefix}entities_${etype}`,
255
+ )} ("tags");`,
256
+ );
257
+ }
258
+
259
+ private createDataTable(etype: string) {
260
+ // Create the data table.
261
+ this.queryRun(
262
+ `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
263
+ `${this.prefix}data_${etype}`,
264
+ )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
265
+ `${this.prefix}entities_${etype}`,
266
+ )} ("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"));`,
267
+ );
268
+ this.queryRun(
269
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
270
+ `${this.prefix}data_${etype}_id_guid`,
271
+ )} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("guid");`,
272
+ );
273
+ this.queryRun(
274
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
275
+ `${this.prefix}data_${etype}_id_guid_name`,
276
+ )} ON ${SQLite3Driver.escape(
277
+ `${this.prefix}data_${etype}`,
278
+ )} ("guid", "name");`,
279
+ );
280
+ this.queryRun(
281
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
282
+ `${this.prefix}data_${etype}_id_name`,
283
+ )} ON ${SQLite3Driver.escape(`${this.prefix}data_${etype}`)} ("name");`,
284
+ );
285
+ this.queryRun(
286
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
287
+ `${this.prefix}data_${etype}_id_name_string`,
288
+ )} ON ${SQLite3Driver.escape(
289
+ `${this.prefix}data_${etype}`,
290
+ )} ("name", "string");`,
291
+ );
292
+ this.queryRun(
293
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
294
+ `${this.prefix}data_${etype}_id_name_number`,
295
+ )} ON ${SQLite3Driver.escape(
296
+ `${this.prefix}data_${etype}`,
297
+ )} ("name", "number");`,
298
+ );
299
+ this.queryRun(
300
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
301
+ `${this.prefix}data_${etype}_id_guid_name_number`,
302
+ )} ON ${SQLite3Driver.escape(
303
+ `${this.prefix}data_${etype}`,
304
+ )} ("guid", "name", "number");`,
305
+ );
306
+ this.queryRun(
307
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
308
+ `${this.prefix}data_${etype}_id_name_truthy`,
309
+ )} ON ${SQLite3Driver.escape(
310
+ `${this.prefix}data_${etype}`,
311
+ )} ("name", "truthy");`,
312
+ );
313
+ this.queryRun(
314
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
315
+ `${this.prefix}data_${etype}_id_guid_name_truthy`,
316
+ )} ON ${SQLite3Driver.escape(
317
+ `${this.prefix}data_${etype}`,
318
+ )} ("guid", "name", "truthy");`,
319
+ );
320
+ this.queryRun(
321
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
322
+ `${this.prefix}data_${etype}_id_acuserread`,
323
+ )} ON ${SQLite3Driver.escape(
324
+ `${this.prefix}data_${etype}`,
325
+ )} ("guid") WHERE "name"=\'acUser\' AND "number" >= 1;`,
326
+ );
327
+ this.queryRun(
328
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
329
+ `${this.prefix}data_${etype}_id_acgroupread`,
330
+ )} ON ${SQLite3Driver.escape(
331
+ `${this.prefix}data_${etype}`,
332
+ )} ("guid") WHERE "name"=\'acGroup\' AND "number" >= 1;`,
333
+ );
334
+ this.queryRun(
335
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
336
+ `${this.prefix}data_${etype}_id_acotherread`,
337
+ )} ON ${SQLite3Driver.escape(
338
+ `${this.prefix}data_${etype}`,
339
+ )} ("guid") WHERE "name"=\'acOther\' AND "number" >= 1;`,
340
+ );
341
+ this.queryRun(
342
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
343
+ `${this.prefix}data_${etype}_id_acuser`,
344
+ )} ON ${SQLite3Driver.escape(
345
+ `${this.prefix}data_${etype}`,
346
+ )} ("guid") WHERE "name"=\'user\';`,
347
+ );
348
+ this.queryRun(
349
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
350
+ `${this.prefix}data_${etype}_id_acgroup`,
351
+ )} ON ${SQLite3Driver.escape(
352
+ `${this.prefix}data_${etype}`,
353
+ )} ("guid") WHERE "name"=\'group\';`,
354
+ );
355
+ }
356
+
357
+ private createReferencesTable(etype: string) {
358
+ // Create the references table.
359
+ this.queryRun(
360
+ `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
361
+ `${this.prefix}references_${etype}`,
362
+ )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
363
+ `${this.prefix}entities_${etype}`,
364
+ )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`,
365
+ );
366
+ this.queryRun(
367
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
368
+ `${this.prefix}references_${etype}_id_guid`,
369
+ )} ON ${SQLite3Driver.escape(
370
+ `${this.prefix}references_${etype}`,
371
+ )} ("guid");`,
372
+ );
373
+ this.queryRun(
374
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
375
+ `${this.prefix}references_${etype}_id_name`,
376
+ )} ON ${SQLite3Driver.escape(
377
+ `${this.prefix}references_${etype}`,
378
+ )} ("name");`,
379
+ );
380
+ this.queryRun(
381
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
382
+ `${this.prefix}references_${etype}_id_name_reference`,
383
+ )} ON ${SQLite3Driver.escape(
384
+ `${this.prefix}references_${etype}`,
385
+ )} ("name", "reference");`,
386
+ );
387
+ this.queryRun(
388
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
389
+ `${this.prefix}references_${etype}_id_reference`,
390
+ )} ON ${SQLite3Driver.escape(
391
+ `${this.prefix}references_${etype}`,
392
+ )} ("reference");`,
393
+ );
394
+ this.queryRun(
395
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
396
+ `${this.prefix}references_${etype}_id_guid_name`,
397
+ )} ON ${SQLite3Driver.escape(
398
+ `${this.prefix}references_${etype}`,
399
+ )} ("guid", "name");`,
400
+ );
401
+ this.queryRun(
402
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
403
+ `${this.prefix}references_${etype}_id_guid_name_reference`,
404
+ )} ON ${SQLite3Driver.escape(
405
+ `${this.prefix}references_${etype}`,
406
+ )} ("guid", "name", "reference");`,
407
+ );
408
+ this.queryRun(
409
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
410
+ `${this.prefix}references_${etype}_id_reference_name_guid`,
411
+ )} ON ${SQLite3Driver.escape(
412
+ `${this.prefix}references_${etype}`,
413
+ )} ("reference", "name", "guid");`,
414
+ );
415
+ this.queryRun(
416
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
417
+ `${this.prefix}references_${etype}_id_reference_guid_name`,
418
+ )} ON ${SQLite3Driver.escape(
419
+ `${this.prefix}references_${etype}`,
420
+ )} ("reference", "guid", "name");`,
421
+ );
422
+ this.queryRun(
423
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
424
+ `${this.prefix}references_${etype}_id_guid_reference_nameuser`,
425
+ )} ON ${SQLite3Driver.escape(
426
+ `${this.prefix}references_${etype}`,
427
+ )} ("guid", "reference") WHERE "name"=\'user\';`,
428
+ );
429
+ this.queryRun(
430
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
431
+ `${this.prefix}references_${etype}_id_guid_reference_namegroup`,
432
+ )} ON ${SQLite3Driver.escape(
433
+ `${this.prefix}references_${etype}`,
434
+ )} ("guid", "reference") WHERE "name"=\'group\';`,
435
+ );
436
+ }
437
+
438
+ private createTokensTable(etype: string) {
439
+ // Create the tokens table.
440
+ this.queryRun(
441
+ `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
442
+ `${this.prefix}tokens_${etype}`,
443
+ )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
444
+ `${this.prefix}entities_${etype}`,
445
+ )} ("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"));`,
446
+ );
447
+ this.queryRun(
448
+ `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
449
+ `${this.prefix}tokens_${etype}_id_name_token`,
450
+ )} ON ${SQLite3Driver.escape(
451
+ `${this.prefix}tokens_${etype}`,
452
+ )} ("name", "token");`,
453
+ );
454
+ }
455
+
456
+ private createUniquesTable(etype: string) {
457
+ // Create the unique strings table.
458
+ this.queryRun(
459
+ `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
460
+ `${this.prefix}uniques_${etype}`,
461
+ )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
462
+ `${this.prefix}entities_${etype}`,
463
+ )} ("guid") ON DELETE CASCADE, "unique" TEXT NOT NULL UNIQUE, PRIMARY KEY("guid", "unique"));`,
464
+ );
465
+ }
466
+
223
467
  /**
224
468
  * Create entity tables in the database.
225
469
  *
@@ -229,141 +473,11 @@ export default class SQLite3Driver extends NymphDriver {
229
473
  this.startTransaction('nymph-tablecreation');
230
474
  try {
231
475
  if (etype != null) {
232
- // Create the entity table.
233
- this.queryRun(
234
- `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
235
- `${this.prefix}entities_${etype}`,
236
- )} ("guid" CHARACTER(24) PRIMARY KEY, "tags" TEXT, "cdate" REAL NOT NULL, "mdate" REAL NOT NULL);`,
237
- );
238
- this.queryRun(
239
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
240
- `${this.prefix}entities_${etype}_id_cdate`,
241
- )} ON ${SQLite3Driver.escape(
242
- `${this.prefix}entities_${etype}`,
243
- )} ("cdate");`,
244
- );
245
- this.queryRun(
246
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
247
- `${this.prefix}entities_${etype}_id_mdate`,
248
- )} ON ${SQLite3Driver.escape(
249
- `${this.prefix}entities_${etype}`,
250
- )} ("mdate");`,
251
- );
252
- this.queryRun(
253
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
254
- `${this.prefix}entities_${etype}_id_tags`,
255
- )} ON ${SQLite3Driver.escape(
256
- `${this.prefix}entities_${etype}`,
257
- )} ("tags");`,
258
- );
259
- // Create the data table.
260
- this.queryRun(
261
- `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
262
- `${this.prefix}data_${etype}`,
263
- )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
264
- `${this.prefix}entities_${etype}`,
265
- )} ("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"));`,
266
- );
267
- this.queryRun(
268
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
269
- `${this.prefix}data_${etype}_id_guid`,
270
- )} ON ${SQLite3Driver.escape(
271
- `${this.prefix}data_${etype}`,
272
- )} ("guid");`,
273
- );
274
- this.queryRun(
275
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
276
- `${this.prefix}data_${etype}_id_guid_name`,
277
- )} ON ${SQLite3Driver.escape(
278
- `${this.prefix}data_${etype}`,
279
- )} ("guid", "name");`,
280
- );
281
- this.queryRun(
282
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
283
- `${this.prefix}data_${etype}_id_guid__name_user`,
284
- )} ON ${SQLite3Driver.escape(
285
- `${this.prefix}data_${etype}`,
286
- )} ("guid") WHERE "name" = \'user\';`,
287
- );
288
- this.queryRun(
289
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
290
- `${this.prefix}data_${etype}_id_guid__name_group`,
291
- )} ON ${SQLite3Driver.escape(
292
- `${this.prefix}data_${etype}`,
293
- )} ("guid") WHERE "name" = \'group\';`,
294
- );
295
- this.queryRun(
296
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
297
- `${this.prefix}data_${etype}_id_name`,
298
- )} ON ${SQLite3Driver.escape(
299
- `${this.prefix}data_${etype}`,
300
- )} ("name");`,
301
- );
302
- this.queryRun(
303
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
304
- `${this.prefix}data_${etype}_id_name__truthy`,
305
- )} ON ${SQLite3Driver.escape(
306
- `${this.prefix}data_${etype}`,
307
- )} ("name") WHERE "truthy" = 1;`,
308
- );
309
- this.queryRun(
310
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
311
- `${this.prefix}data_${etype}_id_name__falsy`,
312
- )} ON ${SQLite3Driver.escape(
313
- `${this.prefix}data_${etype}`,
314
- )} ("name") WHERE "truthy" <> 1;`,
315
- );
316
- this.queryRun(
317
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
318
- `${this.prefix}data_${etype}_id_name_string`,
319
- )} ON ${SQLite3Driver.escape(
320
- `${this.prefix}data_${etype}`,
321
- )} ("name", "string");`,
322
- );
323
- this.queryRun(
324
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
325
- `${this.prefix}data_${etype}_id_name_number`,
326
- )} ON ${SQLite3Driver.escape(
327
- `${this.prefix}data_${etype}`,
328
- )} ("name", "number");`,
329
- );
330
- // Create the references table.
331
- this.queryRun(
332
- `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
333
- `${this.prefix}references_${etype}`,
334
- )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
335
- `${this.prefix}entities_${etype}`,
336
- )} ("guid") ON DELETE CASCADE, "name" TEXT NOT NULL, "reference" CHARACTER(24) NOT NULL, PRIMARY KEY("guid", "name", "reference"));`,
337
- );
338
- this.queryRun(
339
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
340
- `${this.prefix}references_${etype}_id_guid`,
341
- )} ON ${SQLite3Driver.escape(
342
- `${this.prefix}references_${etype}`,
343
- )} ("guid");`,
344
- );
345
- this.queryRun(
346
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
347
- `${this.prefix}references_${etype}_id_name`,
348
- )} ON ${SQLite3Driver.escape(
349
- `${this.prefix}references_${etype}`,
350
- )} ("name");`,
351
- );
352
- this.queryRun(
353
- `CREATE INDEX IF NOT EXISTS ${SQLite3Driver.escape(
354
- `${this.prefix}references_${etype}_id_name_reference`,
355
- )} ON ${SQLite3Driver.escape(
356
- `${this.prefix}references_${etype}`,
357
- )} ("name", "reference");`,
358
- );
359
- // Create the unique strings table.
360
- this.queryRun(
361
- `CREATE TABLE IF NOT EXISTS ${SQLite3Driver.escape(
362
- `${this.prefix}uniques_${etype}`,
363
- )} ("guid" CHARACTER(24) NOT NULL REFERENCES ${SQLite3Driver.escape(
364
- `${this.prefix}entities_${etype}`,
365
- )} ("guid") ON DELETE CASCADE, "unique" TEXT NOT NULL UNIQUE, PRIMARY KEY("guid", "unique"));`,
366
- );
476
+ this.createEntitiesTable(etype);
477
+ this.createDataTable(etype);
478
+ this.createReferencesTable(etype);
479
+ this.createTokensTable(etype);
480
+ this.createUniquesTable(etype);
367
481
  } else {
368
482
  // Create the UID table.
369
483
  this.queryRun(
@@ -541,6 +655,17 @@ export default class SQLite3Driver extends NymphDriver {
541
655
  },
542
656
  },
543
657
  );
658
+ this.queryRun(
659
+ `DELETE FROM ${SQLite3Driver.escape(
660
+ `${this.prefix}tokens_${etype}`,
661
+ )} WHERE "guid"=@guid;`,
662
+ {
663
+ etypes: [etype],
664
+ params: {
665
+ guid,
666
+ },
667
+ },
668
+ );
544
669
  this.queryRun(
545
670
  `DELETE FROM ${SQLite3Driver.escape(
546
671
  `${this.prefix}uniques_${etype}`,
@@ -585,6 +710,23 @@ export default class SQLite3Driver extends NymphDriver {
585
710
  return true;
586
711
  }
587
712
 
713
+ public async getEtypes() {
714
+ const tables: IterableIterator<any> = this.queryArray(
715
+ "SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @prefix;",
716
+ {
717
+ params: {
718
+ prefix: this.prefix + 'entities_' + '%',
719
+ },
720
+ },
721
+ );
722
+ const etypes: string[] = [];
723
+ for (const table of tables) {
724
+ etypes.push(table.name.substr((this.prefix + 'entities_').length));
725
+ }
726
+
727
+ return etypes;
728
+ }
729
+
588
730
  public async *exportDataIterator(): AsyncGenerator<
589
731
  { type: 'comment' | 'uid' | 'entity'; content: string },
590
732
  void,
@@ -647,18 +789,7 @@ export default class SQLite3Driver extends NymphDriver {
647
789
  }
648
790
 
649
791
  // Get the etypes.
650
- const tables: IterableIterator<any> = this.queryArray(
651
- "SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @prefix;",
652
- {
653
- params: {
654
- prefix: this.prefix + 'entities_' + '%',
655
- },
656
- },
657
- );
658
- const etypes = [];
659
- for (const table of tables) {
660
- etypes.push(table.name.substr((this.prefix + 'entities_').length));
661
- }
792
+ const etypes = await this.getEtypes();
662
793
 
663
794
  for (const etype of etypes) {
664
795
  // Export entities.
@@ -990,6 +1121,147 @@ export default class SQLite3Driver extends NymphDriver {
990
1121
  params[value] = svalue;
991
1122
  }
992
1123
  break;
1124
+ case 'search':
1125
+ case '!search':
1126
+ if (curValue[0] === 'cdate' || curValue[0] === 'mdate') {
1127
+ if (curQuery) {
1128
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
1129
+ }
1130
+ curQuery += (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(0)';
1131
+ break;
1132
+ } else {
1133
+ if (curQuery) {
1134
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
1135
+ }
1136
+
1137
+ const name = `param${++count.i}`;
1138
+
1139
+ const queryPartToken = (term: SearchTerm) => {
1140
+ const value = `param${++count.i}`;
1141
+ params[value] = term.token;
1142
+ return (
1143
+ 'EXISTS (SELECT "guid" FROM ' +
1144
+ SQLite3Driver.escape(this.prefix + 'tokens_' + etype) +
1145
+ ' WHERE "guid"=' +
1146
+ ieTable +
1147
+ '."guid" AND "name"=@' +
1148
+ name +
1149
+ ' AND "token"=@' +
1150
+ value +
1151
+ (term.nostemmed ? ' AND "stem"=0' : '') +
1152
+ ')'
1153
+ );
1154
+ };
1155
+
1156
+ const queryPartSeries = (series: SearchSeriesTerm) => {
1157
+ const tokenTableSuffix = makeTableSuffix();
1158
+ const tokenParts = series.tokens.map((token, i) => {
1159
+ const value = `param${++count.i}`;
1160
+ params[value] = token.token;
1161
+ return {
1162
+ fromClause:
1163
+ i === 0
1164
+ ? 'FROM ' +
1165
+ SQLite3Driver.escape(
1166
+ this.prefix + 'tokens_' + etype,
1167
+ ) +
1168
+ ' t' +
1169
+ tokenTableSuffix +
1170
+ '0'
1171
+ : 'JOIN ' +
1172
+ SQLite3Driver.escape(
1173
+ this.prefix + 'tokens_' + etype,
1174
+ ) +
1175
+ ' t' +
1176
+ tokenTableSuffix +
1177
+ i +
1178
+ ' ON t' +
1179
+ tokenTableSuffix +
1180
+ i +
1181
+ '."guid" = t' +
1182
+ tokenTableSuffix +
1183
+ '0."guid" AND t' +
1184
+ tokenTableSuffix +
1185
+ i +
1186
+ '."name" = t' +
1187
+ tokenTableSuffix +
1188
+ '0."name" AND t' +
1189
+ tokenTableSuffix +
1190
+ i +
1191
+ '."position" = t' +
1192
+ tokenTableSuffix +
1193
+ '0."position" + ' +
1194
+ i,
1195
+ whereClause:
1196
+ 't' +
1197
+ tokenTableSuffix +
1198
+ i +
1199
+ '."token"=@' +
1200
+ value +
1201
+ (token.nostemmed
1202
+ ? ' AND t' + tokenTableSuffix + i + '."stem"=0'
1203
+ : ''),
1204
+ };
1205
+ });
1206
+ return (
1207
+ 'EXISTS (SELECT t' +
1208
+ tokenTableSuffix +
1209
+ '0."guid" ' +
1210
+ tokenParts.map((part) => part.fromClause).join(' ') +
1211
+ ' WHERE t' +
1212
+ tokenTableSuffix +
1213
+ '0."guid"=' +
1214
+ ieTable +
1215
+ '."guid" AND t' +
1216
+ tokenTableSuffix +
1217
+ '0."name"=@' +
1218
+ name +
1219
+ ' AND ' +
1220
+ tokenParts.map((part) => part.whereClause).join(' AND ') +
1221
+ ')'
1222
+ );
1223
+ };
1224
+
1225
+ const queryPartTerm = (
1226
+ term:
1227
+ | SearchTerm
1228
+ | SearchOrTerm
1229
+ | SearchNotTerm
1230
+ | SearchSeriesTerm,
1231
+ ): string => {
1232
+ if (term.type === 'series') {
1233
+ return queryPartSeries(term);
1234
+ } else if (term.type === 'not') {
1235
+ return 'NOT ' + queryPartTerm(term.operand);
1236
+ } else if (term.type === 'or') {
1237
+ let queryParts: string[] = [];
1238
+ for (let operand of term.operands) {
1239
+ queryParts.push(queryPartTerm(operand));
1240
+ }
1241
+ return '(' + queryParts.join(' OR ') + ')';
1242
+ }
1243
+ return queryPartToken(term);
1244
+ };
1245
+
1246
+ const parsedFTSQuery = this.tokenizer.parseSearchQuery(
1247
+ curValue[1],
1248
+ );
1249
+
1250
+ // Run through the query and add terms.
1251
+ let termStrings: string[] = [];
1252
+ for (let term of parsedFTSQuery) {
1253
+ termStrings.push(queryPartTerm(term));
1254
+ }
1255
+
1256
+ curQuery +=
1257
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1258
+ '(' +
1259
+ termStrings.join(' AND ') +
1260
+ ')';
1261
+
1262
+ params[name] = curValue[0];
1263
+ }
1264
+ break;
993
1265
  case 'match':
994
1266
  case '!match':
995
1267
  if (curValue[0] === 'cdate') {
@@ -1782,14 +2054,18 @@ export default class SQLite3Driver extends NymphDriver {
1782
2054
  return (result?.cur_uid as number | null) ?? null;
1783
2055
  }
1784
2056
 
1785
- public async importEntity({
1786
- guid,
1787
- cdate,
1788
- mdate,
1789
- tags,
1790
- sdata,
1791
- etype,
1792
- }: {
2057
+ public async importEntity(entity: {
2058
+ guid: string;
2059
+ cdate: number;
2060
+ mdate: number;
2061
+ tags: string[];
2062
+ sdata: SerializedEntityData;
2063
+ etype: string;
2064
+ }) {
2065
+ return await this.importEntityInternal(entity, false);
2066
+ }
2067
+
2068
+ public async importEntityTokens(entity: {
1793
2069
  guid: string;
1794
2070
  cdate: number;
1795
2071
  mdate: number;
@@ -1797,43 +2073,66 @@ export default class SQLite3Driver extends NymphDriver {
1797
2073
  sdata: SerializedEntityData;
1798
2074
  etype: string;
1799
2075
  }) {
2076
+ return await this.importEntityInternal(entity, true);
2077
+ }
2078
+
2079
+ private async importEntityInternal(
2080
+ {
2081
+ guid,
2082
+ cdate,
2083
+ mdate,
2084
+ tags,
2085
+ sdata,
2086
+ etype,
2087
+ }: {
2088
+ guid: string;
2089
+ cdate: number;
2090
+ mdate: number;
2091
+ tags: string[];
2092
+ sdata: SerializedEntityData;
2093
+ etype: string;
2094
+ },
2095
+ onlyTokens: boolean,
2096
+ ) {
1800
2097
  try {
1801
- this.queryRun(
1802
- `DELETE FROM ${SQLite3Driver.escape(
1803
- `${this.prefix}entities_${etype}`,
1804
- )} WHERE "guid"=@guid;`,
1805
- {
1806
- etypes: [etype],
1807
- params: {
1808
- guid,
2098
+ if (!onlyTokens) {
2099
+ this.queryRun(
2100
+ `DELETE FROM ${SQLite3Driver.escape(
2101
+ `${this.prefix}entities_${etype}`,
2102
+ )} WHERE "guid"=@guid;`,
2103
+ {
2104
+ etypes: [etype],
2105
+ params: {
2106
+ guid,
2107
+ },
1809
2108
  },
1810
- },
1811
- );
1812
- this.queryRun(
1813
- `DELETE FROM ${SQLite3Driver.escape(
1814
- `${this.prefix}data_${etype}`,
1815
- )} WHERE "guid"=@guid;`,
1816
- {
1817
- etypes: [etype],
1818
- params: {
1819
- guid,
2109
+ );
2110
+ this.queryRun(
2111
+ `DELETE FROM ${SQLite3Driver.escape(
2112
+ `${this.prefix}data_${etype}`,
2113
+ )} WHERE "guid"=@guid;`,
2114
+ {
2115
+ etypes: [etype],
2116
+ params: {
2117
+ guid,
2118
+ },
1820
2119
  },
1821
- },
1822
- );
1823
- this.queryRun(
1824
- `DELETE FROM ${SQLite3Driver.escape(
1825
- `${this.prefix}references_${etype}`,
1826
- )} WHERE "guid"=@guid;`,
1827
- {
1828
- etypes: [etype],
1829
- params: {
1830
- guid,
2120
+ );
2121
+ this.queryRun(
2122
+ `DELETE FROM ${SQLite3Driver.escape(
2123
+ `${this.prefix}references_${etype}`,
2124
+ )} WHERE "guid"=@guid;`,
2125
+ {
2126
+ etypes: [etype],
2127
+ params: {
2128
+ guid,
2129
+ },
1831
2130
  },
1832
- },
1833
- );
2131
+ );
2132
+ }
1834
2133
  this.queryRun(
1835
2134
  `DELETE FROM ${SQLite3Driver.escape(
1836
- `${this.prefix}uniques_${etype}`,
2135
+ `${this.prefix}tokens_${etype}`,
1837
2136
  )} WHERE "guid"=@guid;`,
1838
2137
  {
1839
2138
  etypes: [etype],
@@ -1842,93 +2141,168 @@ export default class SQLite3Driver extends NymphDriver {
1842
2141
  },
1843
2142
  },
1844
2143
  );
1845
-
1846
- this.queryRun(
1847
- `INSERT INTO ${SQLite3Driver.escape(
1848
- `${this.prefix}entities_${etype}`,
1849
- )} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`,
1850
- {
1851
- etypes: [etype],
1852
- params: {
1853
- guid,
1854
- tags: ',' + tags.join(',') + ',',
1855
- cdate,
1856
- mdate,
2144
+ if (!onlyTokens) {
2145
+ this.queryRun(
2146
+ `DELETE FROM ${SQLite3Driver.escape(
2147
+ `${this.prefix}uniques_${etype}`,
2148
+ )} WHERE "guid"=@guid;`,
2149
+ {
2150
+ etypes: [etype],
2151
+ params: {
2152
+ guid,
2153
+ },
1857
2154
  },
1858
- },
1859
- );
1860
- for (const name in sdata) {
1861
- const value = sdata[name];
1862
- const uvalue = JSON.parse(value);
1863
- if (value === undefined) {
1864
- continue;
1865
- }
1866
- const storageValue =
1867
- typeof uvalue === 'number'
1868
- ? 'N'
1869
- : typeof uvalue === 'string'
1870
- ? 'S'
1871
- : 'J';
1872
- const jsonValue = storageValue === 'J' ? value : null;
2155
+ );
2156
+ }
2157
+
2158
+ if (!onlyTokens) {
1873
2159
  this.queryRun(
1874
2160
  `INSERT INTO ${SQLite3Driver.escape(
1875
- `${this.prefix}data_${etype}`,
1876
- )} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (@guid, @name, @storageValue, jsonb(@jsonValue), @string, @number, @truthy);`,
2161
+ `${this.prefix}entities_${etype}`,
2162
+ )} ("guid", "tags", "cdate", "mdate") VALUES (@guid, @tags, @cdate, @mdate);`,
1877
2163
  {
1878
2164
  etypes: [etype],
1879
2165
  params: {
1880
2166
  guid,
1881
- name,
1882
- storageValue,
1883
- jsonValue,
1884
- string: storageValue === 'J' ? null : `${uvalue}`,
1885
- number: Number(uvalue),
1886
- truthy: uvalue ? 1 : 0,
2167
+ tags: ',' + tags.join(',') + ',',
2168
+ cdate,
2169
+ mdate,
1887
2170
  },
1888
2171
  },
1889
2172
  );
1890
- const references = this.findReferences(value);
1891
- for (const reference of references) {
2173
+
2174
+ for (const name in sdata) {
2175
+ const value = sdata[name];
2176
+ const uvalue = JSON.parse(value);
2177
+ if (value === undefined) {
2178
+ continue;
2179
+ }
2180
+ const storageValue =
2181
+ typeof uvalue === 'number'
2182
+ ? 'N'
2183
+ : typeof uvalue === 'string'
2184
+ ? 'S'
2185
+ : 'J';
2186
+ const jsonValue = storageValue === 'J' ? value : null;
2187
+
1892
2188
  this.queryRun(
1893
2189
  `INSERT INTO ${SQLite3Driver.escape(
1894
- `${this.prefix}references_${etype}`,
1895
- )} ("guid", "name", "reference") VALUES (@guid, @name, @reference);`,
2190
+ `${this.prefix}data_${etype}`,
2191
+ )} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (@guid, @name, @storageValue, jsonb(@jsonValue), @string, @number, @truthy);`,
1896
2192
  {
1897
2193
  etypes: [etype],
1898
2194
  params: {
1899
2195
  guid,
1900
2196
  name,
1901
- reference,
2197
+ storageValue,
2198
+ jsonValue,
2199
+ string: storageValue === 'J' ? null : `${uvalue}`,
2200
+ number: Number(uvalue),
2201
+ truthy: uvalue ? 1 : 0,
1902
2202
  },
1903
2203
  },
1904
2204
  );
2205
+
2206
+ const references = this.findReferences(value);
2207
+ for (const reference of references) {
2208
+ this.queryRun(
2209
+ `INSERT INTO ${SQLite3Driver.escape(
2210
+ `${this.prefix}references_${etype}`,
2211
+ )} ("guid", "name", "reference") VALUES (@guid, @name, @reference);`,
2212
+ {
2213
+ etypes: [etype],
2214
+ params: {
2215
+ guid,
2216
+ name,
2217
+ reference,
2218
+ },
2219
+ },
2220
+ );
2221
+ }
1905
2222
  }
1906
2223
  }
1907
- const uniques = await this.nymph
1908
- .getEntityClassByEtype(etype)
1909
- .getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
1910
- for (const unique of uniques) {
2224
+
2225
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
2226
+
2227
+ for (let name in sdata) {
2228
+ let tokenString: string | null = null;
1911
2229
  try {
1912
- this.queryRun(
1913
- `INSERT INTO ${SQLite3Driver.escape(
1914
- `${this.prefix}uniques_${etype}`,
1915
- )} ("guid", "unique") VALUES (@guid, @unique);`,
1916
- {
1917
- etypes: [etype],
1918
- params: {
1919
- guid,
1920
- unique,
1921
- },
1922
- },
1923
- );
2230
+ tokenString = EntityClass.getFTSText(name, JSON.parse(sdata[name]));
1924
2231
  } catch (e: any) {
1925
- if (e instanceof EntityUniqueConstraintError) {
1926
- this.nymph.config.debugError(
1927
- 'sqlite3',
1928
- `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2232
+ // Ignore error.
2233
+ }
2234
+
2235
+ if (tokenString != null) {
2236
+ const tokens = this.tokenizer.tokenize(tokenString);
2237
+ while (tokens.length) {
2238
+ const currentTokens = tokens.splice(0, 100);
2239
+ const params: { [k: string]: any } = {
2240
+ guid,
2241
+ name,
2242
+ };
2243
+ const values: string[] = [];
2244
+
2245
+ for (let i = 0; i < currentTokens.length; i++) {
2246
+ const token = currentTokens[i];
2247
+ params['token' + i] = token.token;
2248
+ params['position' + i] = token.position;
2249
+ params['stem' + i] = token.stem ? 1 : 0;
2250
+ values.push(
2251
+ '(@guid, @name, @token' +
2252
+ i +
2253
+ ', @position' +
2254
+ i +
2255
+ ', @stem' +
2256
+ i +
2257
+ ')',
2258
+ );
2259
+ }
2260
+
2261
+ this.queryRun(
2262
+ `INSERT INTO ${SQLite3Driver.escape(
2263
+ `${this.prefix}tokens_${etype}`,
2264
+ )} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`,
2265
+ {
2266
+ etypes: [etype],
2267
+ params,
2268
+ },
1929
2269
  );
1930
2270
  }
1931
- throw e;
2271
+ }
2272
+ }
2273
+
2274
+ if (!onlyTokens) {
2275
+ const uniques = await EntityClass.getUniques({
2276
+ guid,
2277
+ cdate,
2278
+ mdate,
2279
+ tags,
2280
+ data: {},
2281
+ sdata,
2282
+ });
2283
+ for (const unique of uniques) {
2284
+ try {
2285
+ this.queryRun(
2286
+ `INSERT INTO ${SQLite3Driver.escape(
2287
+ `${this.prefix}uniques_${etype}`,
2288
+ )} ("guid", "unique") VALUES (@guid, @unique);`,
2289
+ {
2290
+ etypes: [etype],
2291
+ params: {
2292
+ guid,
2293
+ unique,
2294
+ },
2295
+ },
2296
+ );
2297
+ } catch (e: any) {
2298
+ if (e instanceof EntityUniqueConstraintError) {
2299
+ this.nymph.config.debugError(
2300
+ 'sqlite3',
2301
+ `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2302
+ );
2303
+ }
2304
+ throw e;
2305
+ }
1932
2306
  }
1933
2307
  }
1934
2308
  } catch (e: any) {
@@ -2079,6 +2453,8 @@ export default class SQLite3Driver extends NymphDriver {
2079
2453
  uniques: string[],
2080
2454
  etype: string,
2081
2455
  ) => {
2456
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
2457
+
2082
2458
  const runInsertQuery = (name: string, value: any, svalue: string) => {
2083
2459
  if (value === undefined) {
2084
2460
  return;
@@ -2090,6 +2466,7 @@ export default class SQLite3Driver extends NymphDriver {
2090
2466
  ? 'S'
2091
2467
  : 'J';
2092
2468
  const jsonValue = storageValue === 'J' ? svalue : null;
2469
+
2093
2470
  this.queryRun(
2094
2471
  `INSERT INTO ${SQLite3Driver.escape(
2095
2472
  `${this.prefix}data_${etype}`,
@@ -2107,6 +2484,7 @@ export default class SQLite3Driver extends NymphDriver {
2107
2484
  },
2108
2485
  },
2109
2486
  );
2487
+
2110
2488
  const references = this.findReferences(svalue);
2111
2489
  for (const reference of references) {
2112
2490
  this.queryRun(
@@ -2123,7 +2501,53 @@ export default class SQLite3Driver extends NymphDriver {
2123
2501
  },
2124
2502
  );
2125
2503
  }
2504
+
2505
+ let tokenString: string | null = null;
2506
+ try {
2507
+ tokenString = EntityClass.getFTSText(name, value);
2508
+ } catch (e: any) {
2509
+ // Ignore error.
2510
+ }
2511
+
2512
+ if (tokenString != null) {
2513
+ const tokens = this.tokenizer.tokenize(tokenString);
2514
+ while (tokens.length) {
2515
+ const currentTokens = tokens.splice(0, 100);
2516
+ const params: { [k: string]: any } = {
2517
+ guid,
2518
+ name,
2519
+ };
2520
+ const values: string[] = [];
2521
+
2522
+ for (let i = 0; i < currentTokens.length; i++) {
2523
+ const token = currentTokens[i];
2524
+ params['token' + i] = token.token;
2525
+ params['position' + i] = token.position;
2526
+ params['stem' + i] = token.stem ? 1 : 0;
2527
+ values.push(
2528
+ '(@guid, @name, @token' +
2529
+ i +
2530
+ ', @position' +
2531
+ i +
2532
+ ', @stem' +
2533
+ i +
2534
+ ')',
2535
+ );
2536
+ }
2537
+
2538
+ this.queryRun(
2539
+ `INSERT INTO ${SQLite3Driver.escape(
2540
+ `${this.prefix}tokens_${etype}`,
2541
+ )} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`,
2542
+ {
2543
+ etypes: [etype],
2544
+ params,
2545
+ },
2546
+ );
2547
+ }
2548
+ }
2126
2549
  };
2550
+
2127
2551
  for (const unique of uniques) {
2128
2552
  try {
2129
2553
  this.queryRun(
@@ -2227,6 +2651,17 @@ export default class SQLite3Driver extends NymphDriver {
2227
2651
  },
2228
2652
  },
2229
2653
  );
2654
+ this.queryRun(
2655
+ `DELETE FROM ${SQLite3Driver.escape(
2656
+ `${this.prefix}tokens_${etype}`,
2657
+ )} WHERE "guid"=@guid;`,
2658
+ {
2659
+ etypes: [etype],
2660
+ params: {
2661
+ guid,
2662
+ },
2663
+ },
2664
+ );
2230
2665
  this.queryRun(
2231
2666
  `DELETE FROM ${SQLite3Driver.escape(
2232
2667
  `${this.prefix}uniques_${etype}`,
@@ -2316,7 +2751,7 @@ export default class SQLite3Driver extends NymphDriver {
2316
2751
  return this.nymph;
2317
2752
  }
2318
2753
 
2319
- public async needsMigration(): Promise<boolean> {
2754
+ public async needsMigration(): Promise<'json' | 'tokens' | false> {
2320
2755
  const table: any = this.queryGet(
2321
2756
  "SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @prefix LIMIT 1;",
2322
2757
  {
@@ -2334,8 +2769,29 @@ export default class SQLite3Driver extends NymphDriver {
2334
2769
  },
2335
2770
  },
2336
2771
  );
2337
- return !result?.exists;
2772
+ if (!result?.exists) {
2773
+ return 'json';
2774
+ }
2775
+ }
2776
+ const table2: any = this.queryGet(
2777
+ "SELECT `name` FROM `sqlite_master` WHERE `type`='table' AND `name` LIKE @tokenTable LIMIT 1;",
2778
+ {
2779
+ params: {
2780
+ tokenTable: this.prefix + 'tokens_' + '%',
2781
+ },
2782
+ },
2783
+ );
2784
+ if (!table2 || !table2.name) {
2785
+ return 'tokens';
2338
2786
  }
2339
2787
  return false;
2340
2788
  }
2789
+
2790
+ public async liveMigration(_migrationType: 'tokenTables') {
2791
+ const etypes = await this.getEtypes();
2792
+
2793
+ for (let etype of etypes) {
2794
+ this.createTokensTable(etype);
2795
+ }
2796
+ }
2341
2797
  }