@nymphjs/driver-postgresql 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.
@@ -2,6 +2,12 @@ import pg from 'pg';
2
2
  import type { Pool, PoolClient, PoolConfig, QueryResult } from 'pg';
3
3
  import format from 'pg-format';
4
4
  import Cursor from 'pg-cursor';
5
+ import type {
6
+ SearchTerm,
7
+ SearchOrTerm,
8
+ SearchNotTerm,
9
+ SearchSeriesTerm,
10
+ } from '@sciactive/tokenizer';
5
11
  import {
6
12
  NymphDriver,
7
13
  type EntityConstructor,
@@ -239,81 +245,88 @@ export default class PostgreSQLDriver extends NymphDriver {
239
245
  return this.connected;
240
246
  }
241
247
 
242
- /**
243
- * Create entity tables in the database.
244
- *
245
- * @param etype The entity type to create a table for. If this is blank, the default tables are created.
246
- * @returns True on success, false on failure.
247
- */
248
- private async createTables(etype: string | null = null) {
249
- const connection = await this.getConnection(true);
250
- if (etype != null) {
251
- // Create the entity table.
252
- await this.queryRun(
253
- `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
254
- `${this.prefix}entities_${etype}`,
255
- )} (
248
+ private async createEntitiesTable(
249
+ etype: string,
250
+ connection: PostgreSQLDriverConnection,
251
+ ) {
252
+ // Create the entity table.
253
+ await this.queryRun(
254
+ `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
255
+ `${this.prefix}entities_${etype}`,
256
+ )} (
256
257
  "guid" BYTEA NOT NULL,
257
258
  "tags" TEXT[],
258
259
  "cdate" DOUBLE PRECISION NOT NULL,
259
260
  "mdate" DOUBLE PRECISION NOT NULL,
260
261
  PRIMARY KEY ("guid")
261
262
  ) WITH ( OIDS=FALSE );`,
262
- { connection },
263
- );
264
- await this.queryRun(
265
- `ALTER TABLE ${PostgreSQLDriver.escape(
266
- `${this.prefix}entities_${etype}`,
267
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
268
- { connection },
269
- );
270
- await this.queryRun(
271
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
272
- `${this.prefix}entities_${etype}_id_cdate`,
273
- )};`,
274
- { connection },
275
- );
276
- await this.queryRun(
277
- `CREATE INDEX ${PostgreSQLDriver.escape(
278
- `${this.prefix}entities_${etype}_id_cdate`,
279
- )} ON ${PostgreSQLDriver.escape(
280
- `${this.prefix}entities_${etype}`,
281
- )} USING btree ("cdate");`,
282
- { connection },
283
- );
284
- await this.queryRun(
285
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
286
- `${this.prefix}entities_${etype}_id_mdate`,
287
- )};`,
288
- { connection },
289
- );
290
- await this.queryRun(
291
- `CREATE INDEX ${PostgreSQLDriver.escape(
292
- `${this.prefix}entities_${etype}_id_mdate`,
293
- )} ON ${PostgreSQLDriver.escape(
294
- `${this.prefix}entities_${etype}`,
295
- )} USING btree ("mdate");`,
296
- { connection },
297
- );
298
- await this.queryRun(
299
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
300
- `${this.prefix}entities_${etype}_id_tags`,
301
- )};`,
302
- { connection },
303
- );
304
- await this.queryRun(
305
- `CREATE INDEX ${PostgreSQLDriver.escape(
306
- `${this.prefix}entities_${etype}_id_tags`,
307
- )} ON ${PostgreSQLDriver.escape(
308
- `${this.prefix}entities_${etype}`,
309
- )} USING gin ("tags");`,
310
- { connection },
311
- );
312
- // Create the data table.
313
- await this.queryRun(
314
- `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
315
- `${this.prefix}data_${etype}`,
316
- )} (
263
+ { connection },
264
+ );
265
+ await this.queryRun(
266
+ `ALTER TABLE ${PostgreSQLDriver.escape(
267
+ `${this.prefix}entities_${etype}`,
268
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
269
+ { connection },
270
+ );
271
+ await this.queryRun(
272
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
273
+ `${this.prefix}entities_${etype}_id_cdate`,
274
+ )};`,
275
+ { connection },
276
+ );
277
+ await this.queryRun(
278
+ `CREATE INDEX ${PostgreSQLDriver.escape(
279
+ `${this.prefix}entities_${etype}_id_cdate`,
280
+ )} ON ${PostgreSQLDriver.escape(
281
+ `${this.prefix}entities_${etype}`,
282
+ )} USING btree ("cdate");`,
283
+ { connection },
284
+ );
285
+ await this.queryRun(
286
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
287
+ `${this.prefix}entities_${etype}_id_mdate`,
288
+ )};`,
289
+ { connection },
290
+ );
291
+ await this.queryRun(
292
+ `CREATE INDEX ${PostgreSQLDriver.escape(
293
+ `${this.prefix}entities_${etype}_id_mdate`,
294
+ )} ON ${PostgreSQLDriver.escape(
295
+ `${this.prefix}entities_${etype}`,
296
+ )} USING btree ("mdate");`,
297
+ { connection },
298
+ );
299
+ await this.queryRun(
300
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
301
+ `${this.prefix}entities_${etype}_id_tags`,
302
+ )};`,
303
+ { connection },
304
+ );
305
+ await this.queryRun(
306
+ `CREATE INDEX ${PostgreSQLDriver.escape(
307
+ `${this.prefix}entities_${etype}_id_tags`,
308
+ )} ON ${PostgreSQLDriver.escape(
309
+ `${this.prefix}entities_${etype}`,
310
+ )} USING gin ("tags");`,
311
+ { connection },
312
+ );
313
+ await this.queryRun(
314
+ `ALTER TABLE ${PostgreSQLDriver.escape(
315
+ `${this.prefix}entities_${etype}`,
316
+ )} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`,
317
+ { connection },
318
+ );
319
+ }
320
+
321
+ private async createDataTable(
322
+ etype: string,
323
+ connection: PostgreSQLDriverConnection,
324
+ ) {
325
+ // Create the data table.
326
+ await this.queryRun(
327
+ `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
328
+ `${this.prefix}data_${etype}`,
329
+ )} (
317
330
  "guid" BYTEA NOT NULL,
318
331
  "name" TEXT NOT NULL,
319
332
  "value" CHARACTER(1) NOT NULL,
@@ -327,173 +340,269 @@ export default class PostgreSQLDriver extends NymphDriver {
327
340
  `${this.prefix}entities_${etype}`,
328
341
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
329
342
  ) WITH ( OIDS=FALSE );`,
330
- { connection },
331
- );
332
- await this.queryRun(
333
- `ALTER TABLE ${PostgreSQLDriver.escape(
334
- `${this.prefix}data_${etype}`,
335
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
336
- { connection },
337
- );
338
- await this.queryRun(
339
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
340
- `${this.prefix}data_${etype}_id_guid`,
341
- )};`,
342
- { connection },
343
- );
344
- await this.queryRun(
345
- `CREATE INDEX ${PostgreSQLDriver.escape(
346
- `${this.prefix}data_${etype}_id_guid`,
347
- )} ON ${PostgreSQLDriver.escape(
348
- `${this.prefix}data_${etype}`,
349
- )} USING btree ("guid");`,
350
- { connection },
351
- );
352
- await this.queryRun(
353
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
354
- `${this.prefix}data_${etype}_id_guid_name`,
355
- )};`,
356
- { connection },
357
- );
358
- await this.queryRun(
359
- `CREATE INDEX ${PostgreSQLDriver.escape(
360
- `${this.prefix}data_${etype}_id_guid_name`,
361
- )} ON ${PostgreSQLDriver.escape(
362
- `${this.prefix}data_${etype}`,
363
- )} USING btree ("guid", "name");`,
364
- { connection },
365
- );
366
- await this.queryRun(
367
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
368
- `${this.prefix}data_${etype}_id_guid_name__user`,
369
- )};`,
370
- { connection },
371
- );
372
- await this.queryRun(
373
- `CREATE INDEX ${PostgreSQLDriver.escape(
374
- `${this.prefix}data_${etype}_id_guid_name__user`,
375
- )} ON ${PostgreSQLDriver.escape(
376
- `${this.prefix}data_${etype}`,
377
- )} USING btree ("guid") WHERE "name" = 'user'::text;`,
378
- { connection },
379
- );
380
- await this.queryRun(
381
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
382
- `${this.prefix}data_${etype}_id_guid_name__group`,
383
- )};`,
384
- { connection },
385
- );
386
- await this.queryRun(
387
- `CREATE INDEX ${PostgreSQLDriver.escape(
388
- `${this.prefix}data_${etype}_id_guid_name__group`,
389
- )} ON ${PostgreSQLDriver.escape(
390
- `${this.prefix}data_${etype}`,
391
- )} USING btree ("guid") WHERE "name" = 'group'::text;`,
392
- { connection },
393
- );
394
- await this.queryRun(
395
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
396
- `${this.prefix}data_${etype}_id_name`,
397
- )};`,
398
- { connection },
399
- );
400
- await this.queryRun(
401
- `CREATE INDEX ${PostgreSQLDriver.escape(
402
- `${this.prefix}data_${etype}_id_name`,
403
- )} ON ${PostgreSQLDriver.escape(
404
- `${this.prefix}data_${etype}`,
405
- )} USING btree ("name");`,
406
- { connection },
407
- );
408
- await this.queryRun(
409
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
410
- `${this.prefix}data_${etype}_id_name_string`,
411
- )};`,
412
- { connection },
413
- );
414
- await this.queryRun(
415
- `CREATE INDEX ${PostgreSQLDriver.escape(
416
- `${this.prefix}data_${etype}_id_name_string`,
417
- )} ON ${PostgreSQLDriver.escape(
418
- `${this.prefix}data_${etype}`,
419
- )} USING btree ("name", LEFT("string", 512));`,
420
- { connection },
421
- );
422
- await this.queryRun(
423
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
424
- `${this.prefix}data_${etype}_id_name_number`,
425
- )};`,
426
- { connection },
427
- );
428
- await this.queryRun(
429
- `CREATE INDEX ${PostgreSQLDriver.escape(
430
- `${this.prefix}data_${etype}_id_name_number`,
431
- )} ON ${PostgreSQLDriver.escape(
432
- `${this.prefix}data_${etype}`,
433
- )} USING btree ("name", "number");`,
434
- { connection },
435
- );
436
- await this.queryRun(
437
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
438
- `${this.prefix}data_${etype}_id_name_truthy`,
439
- )};`,
440
- { connection },
441
- );
442
- await this.queryRun(
443
- `CREATE INDEX ${PostgreSQLDriver.escape(
444
- `${this.prefix}data_${etype}_id_name_truthy`,
445
- )} ON ${PostgreSQLDriver.escape(
446
- `${this.prefix}data_${etype}`,
447
- )} USING btree ("name") WHERE "truthy" = TRUE;`,
448
- { connection },
449
- );
450
- await this.queryRun(
451
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
452
- `${this.prefix}data_${etype}_id_name_falsy`,
453
- )};`,
454
- { connection },
455
- );
456
- await this.queryRun(
457
- `CREATE INDEX ${PostgreSQLDriver.escape(
458
- `${this.prefix}data_${etype}_id_name_falsy`,
459
- )} ON ${PostgreSQLDriver.escape(
460
- `${this.prefix}data_${etype}`,
461
- )} USING btree ("name") WHERE "truthy" <> TRUE;`,
462
- { connection },
463
- );
464
- await this.queryRun(
465
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
466
- `${this.prefix}data_${etype}_id_string`,
467
- )};`,
468
- { connection },
469
- );
470
- await this.queryRun(
471
- `CREATE INDEX ${PostgreSQLDriver.escape(
472
- `${this.prefix}data_${etype}_id_string`,
473
- )} ON ${PostgreSQLDriver.escape(
474
- `${this.prefix}data_${etype}`,
475
- )} USING gin ("string" gin_trgm_ops);`,
476
- { connection },
477
- );
478
- await this.queryRun(
479
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
480
- `${this.prefix}data_${etype}_id_json`,
481
- )};`,
482
- { connection },
483
- );
484
- await this.queryRun(
485
- `CREATE INDEX ${PostgreSQLDriver.escape(
486
- `${this.prefix}data_${etype}_id_json`,
487
- )} ON ${PostgreSQLDriver.escape(
488
- `${this.prefix}data_${etype}`,
489
- )} USING gin ("json");`,
490
- { connection },
491
- );
492
- // Create the references table.
493
- await this.queryRun(
494
- `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
495
- `${this.prefix}references_${etype}`,
496
- )} (
343
+ { connection },
344
+ );
345
+ await this.queryRun(
346
+ `ALTER TABLE ${PostgreSQLDriver.escape(
347
+ `${this.prefix}data_${etype}`,
348
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
349
+ { connection },
350
+ );
351
+ await this.queryRun(
352
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
353
+ `${this.prefix}data_${etype}_id_guid`,
354
+ )};`,
355
+ { connection },
356
+ );
357
+ await this.queryRun(
358
+ `CREATE INDEX ${PostgreSQLDriver.escape(
359
+ `${this.prefix}data_${etype}_id_guid`,
360
+ )} ON ${PostgreSQLDriver.escape(
361
+ `${this.prefix}data_${etype}`,
362
+ )} USING btree ("guid");`,
363
+ { connection },
364
+ );
365
+ await this.queryRun(
366
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
367
+ `${this.prefix}data_${etype}_id_guid_name`,
368
+ )};`,
369
+ { connection },
370
+ );
371
+ await this.queryRun(
372
+ `CREATE INDEX ${PostgreSQLDriver.escape(
373
+ `${this.prefix}data_${etype}_id_guid_name`,
374
+ )} ON ${PostgreSQLDriver.escape(
375
+ `${this.prefix}data_${etype}`,
376
+ )} USING btree ("guid", "name");`,
377
+ { connection },
378
+ );
379
+ await this.queryRun(
380
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
381
+ `${this.prefix}data_${etype}_id_guid_name__user`,
382
+ )};`,
383
+ { connection },
384
+ );
385
+ await this.queryRun(
386
+ `CREATE INDEX ${PostgreSQLDriver.escape(
387
+ `${this.prefix}data_${etype}_id_guid_name__user`,
388
+ )} ON ${PostgreSQLDriver.escape(
389
+ `${this.prefix}data_${etype}`,
390
+ )} USING btree ("guid") WHERE "name" = 'user'::text;`,
391
+ { connection },
392
+ );
393
+ await this.queryRun(
394
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
395
+ `${this.prefix}data_${etype}_id_guid_name__group`,
396
+ )};`,
397
+ { connection },
398
+ );
399
+ await this.queryRun(
400
+ `CREATE INDEX ${PostgreSQLDriver.escape(
401
+ `${this.prefix}data_${etype}_id_guid_name__group`,
402
+ )} ON ${PostgreSQLDriver.escape(
403
+ `${this.prefix}data_${etype}`,
404
+ )} USING btree ("guid") WHERE "name" = 'group'::text;`,
405
+ { connection },
406
+ );
407
+ await this.queryRun(
408
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
409
+ `${this.prefix}data_${etype}_id_name`,
410
+ )};`,
411
+ { connection },
412
+ );
413
+ await this.queryRun(
414
+ `CREATE INDEX ${PostgreSQLDriver.escape(
415
+ `${this.prefix}data_${etype}_id_name`,
416
+ )} ON ${PostgreSQLDriver.escape(
417
+ `${this.prefix}data_${etype}`,
418
+ )} USING btree ("name");`,
419
+ { connection },
420
+ );
421
+ await this.queryRun(
422
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
423
+ `${this.prefix}data_${etype}_id_name_string`,
424
+ )};`,
425
+ { connection },
426
+ );
427
+ await this.queryRun(
428
+ `CREATE INDEX ${PostgreSQLDriver.escape(
429
+ `${this.prefix}data_${etype}_id_name_string`,
430
+ )} ON ${PostgreSQLDriver.escape(
431
+ `${this.prefix}data_${etype}`,
432
+ )} USING btree ("name", LEFT("string", 512));`,
433
+ { connection },
434
+ );
435
+ await this.queryRun(
436
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
437
+ `${this.prefix}data_${etype}_id_name_number`,
438
+ )};`,
439
+ { connection },
440
+ );
441
+ await this.queryRun(
442
+ `CREATE INDEX ${PostgreSQLDriver.escape(
443
+ `${this.prefix}data_${etype}_id_name_number`,
444
+ )} ON ${PostgreSQLDriver.escape(
445
+ `${this.prefix}data_${etype}`,
446
+ )} USING btree ("name", "number");`,
447
+ { connection },
448
+ );
449
+ await this.queryRun(
450
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
451
+ `${this.prefix}data_${etype}_id_guid_name_number`,
452
+ )};`,
453
+ { connection },
454
+ );
455
+ await this.queryRun(
456
+ `CREATE INDEX ${PostgreSQLDriver.escape(
457
+ `${this.prefix}data_${etype}_id_guid_name_number`,
458
+ )} ON ${PostgreSQLDriver.escape(
459
+ `${this.prefix}data_${etype}`,
460
+ )} USING btree ("guid", "name", "number");`,
461
+ { connection },
462
+ );
463
+ await this.queryRun(
464
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
465
+ `${this.prefix}data_${etype}_id_name_truthy`,
466
+ )};`,
467
+ { connection },
468
+ );
469
+ await this.queryRun(
470
+ `CREATE INDEX ${PostgreSQLDriver.escape(
471
+ `${this.prefix}data_${etype}_id_name_truthy`,
472
+ )} ON ${PostgreSQLDriver.escape(
473
+ `${this.prefix}data_${etype}`,
474
+ )} USING btree ("name", "truthy");`,
475
+ { connection },
476
+ );
477
+ await this.queryRun(
478
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
479
+ `${this.prefix}data_${etype}_id_guid_name_truthy`,
480
+ )};`,
481
+ { connection },
482
+ );
483
+ await this.queryRun(
484
+ `CREATE INDEX ${PostgreSQLDriver.escape(
485
+ `${this.prefix}data_${etype}_id_guid_name_truthy`,
486
+ )} ON ${PostgreSQLDriver.escape(
487
+ `${this.prefix}data_${etype}`,
488
+ )} USING btree ("guid", "name", "truthy");`,
489
+ { connection },
490
+ );
491
+ await this.queryRun(
492
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
493
+ `${this.prefix}data_${etype}_id_string`,
494
+ )};`,
495
+ { connection },
496
+ );
497
+ await this.queryRun(
498
+ `CREATE INDEX ${PostgreSQLDriver.escape(
499
+ `${this.prefix}data_${etype}_id_string`,
500
+ )} ON ${PostgreSQLDriver.escape(
501
+ `${this.prefix}data_${etype}`,
502
+ )} USING gin ("string" gin_trgm_ops);`,
503
+ { connection },
504
+ );
505
+ await this.queryRun(
506
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
507
+ `${this.prefix}data_${etype}_id_json`,
508
+ )};`,
509
+ { connection },
510
+ );
511
+ await this.queryRun(
512
+ `CREATE INDEX ${PostgreSQLDriver.escape(
513
+ `${this.prefix}data_${etype}_id_json`,
514
+ )} ON ${PostgreSQLDriver.escape(
515
+ `${this.prefix}data_${etype}`,
516
+ )} USING gin ("json");`,
517
+ { connection },
518
+ );
519
+ await this.queryRun(
520
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
521
+ `${this.prefix}data_${etype}_id_acuserread`,
522
+ )};`,
523
+ { connection },
524
+ );
525
+ await this.queryRun(
526
+ `CREATE INDEX ${PostgreSQLDriver.escape(
527
+ `${this.prefix}data_${etype}_id_acuserread`,
528
+ )} ON ${PostgreSQLDriver.escape(
529
+ `${this.prefix}data_${etype}`,
530
+ )} USING btree ("guid") WHERE "name"='acUser' AND "number" >= 1;`,
531
+ { connection },
532
+ );
533
+ await this.queryRun(
534
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
535
+ `${this.prefix}data_${etype}_id_acgroupread`,
536
+ )};`,
537
+ { connection },
538
+ );
539
+ await this.queryRun(
540
+ `CREATE INDEX ${PostgreSQLDriver.escape(
541
+ `${this.prefix}data_${etype}_id_acgroupread`,
542
+ )} ON ${PostgreSQLDriver.escape(
543
+ `${this.prefix}data_${etype}`,
544
+ )} USING btree ("guid") WHERE "name"='acGroup' AND "number" >= 1;`,
545
+ { connection },
546
+ );
547
+ await this.queryRun(
548
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
549
+ `${this.prefix}data_${etype}_id_acotherread`,
550
+ )};`,
551
+ { connection },
552
+ );
553
+ await this.queryRun(
554
+ `CREATE INDEX ${PostgreSQLDriver.escape(
555
+ `${this.prefix}data_${etype}_id_acotherread`,
556
+ )} ON ${PostgreSQLDriver.escape(
557
+ `${this.prefix}data_${etype}`,
558
+ )} USING btree ("guid") WHERE "name"='acOther' AND "number" >= 1;`,
559
+ { connection },
560
+ );
561
+ await this.queryRun(
562
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
563
+ `${this.prefix}data_${etype}_id_acuser`,
564
+ )};`,
565
+ { connection },
566
+ );
567
+ await this.queryRun(
568
+ `CREATE INDEX ${PostgreSQLDriver.escape(
569
+ `${this.prefix}data_${etype}_id_acuser`,
570
+ )} ON ${PostgreSQLDriver.escape(
571
+ `${this.prefix}data_${etype}`,
572
+ )} USING btree ("guid") WHERE "name"='user';`,
573
+ { connection },
574
+ );
575
+ await this.queryRun(
576
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
577
+ `${this.prefix}data_${etype}_id_acgroup`,
578
+ )};`,
579
+ { connection },
580
+ );
581
+ await this.queryRun(
582
+ `CREATE INDEX ${PostgreSQLDriver.escape(
583
+ `${this.prefix}data_${etype}_id_acgroup`,
584
+ )} ON ${PostgreSQLDriver.escape(
585
+ `${this.prefix}data_${etype}`,
586
+ )} USING btree ("guid") WHERE "name"='group';`,
587
+ { connection },
588
+ );
589
+ await this.queryRun(
590
+ `ALTER TABLE ${PostgreSQLDriver.escape(
591
+ `${this.prefix}data_${etype}`,
592
+ )} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`,
593
+ { connection },
594
+ );
595
+ }
596
+
597
+ private async createReferencesTable(
598
+ etype: string,
599
+ connection: PostgreSQLDriverConnection,
600
+ ) {
601
+ // Create the references table.
602
+ await this.queryRun(
603
+ `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
604
+ `${this.prefix}references_${etype}`,
605
+ )} (
497
606
  "guid" BYTEA NOT NULL,
498
607
  "name" TEXT NOT NULL,
499
608
  "reference" BYTEA NOT NULL,
@@ -503,61 +612,207 @@ export default class PostgreSQLDriver extends NymphDriver {
503
612
  `${this.prefix}entities_${etype}`,
504
613
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
505
614
  ) WITH ( OIDS=FALSE );`,
506
- { connection },
507
- );
508
- await this.queryRun(
509
- `ALTER TABLE ${PostgreSQLDriver.escape(
510
- `${this.prefix}references_${etype}`,
511
- )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
512
- { connection },
513
- );
514
- await this.queryRun(
515
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
516
- `${this.prefix}references_${etype}_id_guid`,
517
- )};`,
518
- { connection },
519
- );
520
- await this.queryRun(
521
- `CREATE INDEX ${PostgreSQLDriver.escape(
522
- `${this.prefix}references_${etype}_id_guid`,
523
- )} ON ${PostgreSQLDriver.escape(
524
- `${this.prefix}references_${etype}`,
525
- )} USING btree ("guid");`,
526
- { connection },
527
- );
528
- await this.queryRun(
529
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
530
- `${this.prefix}references_${etype}_id_name`,
531
- )};`,
532
- { connection },
533
- );
534
- await this.queryRun(
535
- `CREATE INDEX ${PostgreSQLDriver.escape(
536
- `${this.prefix}references_${etype}_id_name`,
537
- )} ON ${PostgreSQLDriver.escape(
538
- `${this.prefix}references_${etype}`,
539
- )} USING btree ("name");`,
540
- { connection },
541
- );
542
- await this.queryRun(
543
- `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
544
- `${this.prefix}references_${etype}_id_name_reference`,
545
- )};`,
546
- { connection },
547
- );
548
- await this.queryRun(
549
- `CREATE INDEX ${PostgreSQLDriver.escape(
550
- `${this.prefix}references_${etype}_id_name_reference`,
551
- )} ON ${PostgreSQLDriver.escape(
552
- `${this.prefix}references_${etype}`,
553
- )} USING btree ("name", "reference");`,
554
- { connection },
555
- );
556
- // Create the unique strings table.
557
- await this.queryRun(
558
- `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
559
- `${this.prefix}uniques_${etype}`,
560
- )} (
615
+ { connection },
616
+ );
617
+ await this.queryRun(
618
+ `ALTER TABLE ${PostgreSQLDriver.escape(
619
+ `${this.prefix}references_${etype}`,
620
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
621
+ { connection },
622
+ );
623
+ await this.queryRun(
624
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
625
+ `${this.prefix}references_${etype}_id_guid`,
626
+ )};`,
627
+ { connection },
628
+ );
629
+ await this.queryRun(
630
+ `CREATE INDEX ${PostgreSQLDriver.escape(
631
+ `${this.prefix}references_${etype}_id_guid`,
632
+ )} ON ${PostgreSQLDriver.escape(
633
+ `${this.prefix}references_${etype}`,
634
+ )} USING btree ("guid");`,
635
+ { connection },
636
+ );
637
+ await this.queryRun(
638
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
639
+ `${this.prefix}references_${etype}_id_name`,
640
+ )};`,
641
+ { connection },
642
+ );
643
+ await this.queryRun(
644
+ `CREATE INDEX ${PostgreSQLDriver.escape(
645
+ `${this.prefix}references_${etype}_id_name`,
646
+ )} ON ${PostgreSQLDriver.escape(
647
+ `${this.prefix}references_${etype}`,
648
+ )} USING btree ("name");`,
649
+ { connection },
650
+ );
651
+ await this.queryRun(
652
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
653
+ `${this.prefix}references_${etype}_id_name_reference`,
654
+ )};`,
655
+ { connection },
656
+ );
657
+ await this.queryRun(
658
+ `CREATE INDEX ${PostgreSQLDriver.escape(
659
+ `${this.prefix}references_${etype}_id_name_reference`,
660
+ )} ON ${PostgreSQLDriver.escape(
661
+ `${this.prefix}references_${etype}`,
662
+ )} USING btree ("name", "reference");`,
663
+ { connection },
664
+ );
665
+ await this.queryRun(
666
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
667
+ `${this.prefix}references_${etype}_id_reference`,
668
+ )};`,
669
+ { connection },
670
+ );
671
+ await this.queryRun(
672
+ `CREATE INDEX ${PostgreSQLDriver.escape(
673
+ `${this.prefix}references_${etype}_id_reference`,
674
+ )} ON ${PostgreSQLDriver.escape(
675
+ `${this.prefix}references_${etype}`,
676
+ )} USING btree ("reference");`,
677
+ { connection },
678
+ );
679
+ await this.queryRun(
680
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
681
+ `${this.prefix}references_${etype}_id_guid_name_reference`,
682
+ )};`,
683
+ { connection },
684
+ );
685
+ await this.queryRun(
686
+ `CREATE INDEX ${PostgreSQLDriver.escape(
687
+ `${this.prefix}references_${etype}_id_guid_name_reference`,
688
+ )} ON ${PostgreSQLDriver.escape(
689
+ `${this.prefix}references_${etype}`,
690
+ )} USING btree ("guid", "name", "reference");`,
691
+ { connection },
692
+ );
693
+ await this.queryRun(
694
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
695
+ `${this.prefix}references_${etype}_id_reference_name_guid`,
696
+ )};`,
697
+ { connection },
698
+ );
699
+ await this.queryRun(
700
+ `CREATE INDEX ${PostgreSQLDriver.escape(
701
+ `${this.prefix}references_${etype}_id_reference_name_guid`,
702
+ )} ON ${PostgreSQLDriver.escape(
703
+ `${this.prefix}references_${etype}`,
704
+ )} USING btree ("reference", "name", "guid");`,
705
+ { connection },
706
+ );
707
+ await this.queryRun(
708
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
709
+ `${this.prefix}references_${etype}_id_reference_guid_name`,
710
+ )};`,
711
+ { connection },
712
+ );
713
+ await this.queryRun(
714
+ `CREATE INDEX ${PostgreSQLDriver.escape(
715
+ `${this.prefix}references_${etype}_id_reference_guid_name`,
716
+ )} ON ${PostgreSQLDriver.escape(
717
+ `${this.prefix}references_${etype}`,
718
+ )} USING btree ("reference", "guid", "name");`,
719
+ { connection },
720
+ );
721
+ await this.queryRun(
722
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
723
+ `${this.prefix}references_${etype}_id_guid_reference_nameuser`,
724
+ )};`,
725
+ { connection },
726
+ );
727
+ await this.queryRun(
728
+ `CREATE INDEX ${PostgreSQLDriver.escape(
729
+ `${this.prefix}references_${etype}_id_guid_reference_nameuser`,
730
+ )} ON ${PostgreSQLDriver.escape(
731
+ `${this.prefix}references_${etype}`,
732
+ )} USING btree ("guid", "reference") WHERE "name"='user';`,
733
+ { connection },
734
+ );
735
+ await this.queryRun(
736
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
737
+ `${this.prefix}references_${etype}_id_guid_reference_namegroup`,
738
+ )};`,
739
+ { connection },
740
+ );
741
+ await this.queryRun(
742
+ `CREATE INDEX ${PostgreSQLDriver.escape(
743
+ `${this.prefix}references_${etype}_id_guid_reference_namegroup`,
744
+ )} ON ${PostgreSQLDriver.escape(
745
+ `${this.prefix}references_${etype}`,
746
+ )} USING btree ("guid", "reference") WHERE "name"='group';`,
747
+ { connection },
748
+ );
749
+ await this.queryRun(
750
+ `ALTER TABLE ${PostgreSQLDriver.escape(
751
+ `${this.prefix}references_${etype}`,
752
+ )} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`,
753
+ { connection },
754
+ );
755
+ }
756
+
757
+ private async createTokensTable(
758
+ etype: string,
759
+ connection: PostgreSQLDriverConnection,
760
+ ) {
761
+ // Create the tokens table.
762
+ await this.queryRun(
763
+ `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
764
+ `${this.prefix}tokens_${etype}`,
765
+ )} (
766
+ "guid" BYTEA NOT NULL,
767
+ "name" TEXT NOT NULL,
768
+ "token" INTEGER NOT NULL,
769
+ "position" INTEGER NOT NULL,
770
+ "stem" BOOLEAN NOT NULL,
771
+ PRIMARY KEY ("guid", "name", "token", "position"),
772
+ FOREIGN KEY ("guid")
773
+ REFERENCES ${PostgreSQLDriver.escape(
774
+ `${this.prefix}entities_${etype}`,
775
+ )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
776
+ ) WITH ( OIDS=FALSE );`,
777
+ { connection },
778
+ );
779
+ await this.queryRun(
780
+ `ALTER TABLE ${PostgreSQLDriver.escape(
781
+ `${this.prefix}tokens_${etype}`,
782
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
783
+ { connection },
784
+ );
785
+ await this.queryRun(
786
+ `DROP INDEX IF EXISTS ${PostgreSQLDriver.escape(
787
+ `${this.prefix}tokens_${etype}_id_name_token`,
788
+ )};`,
789
+ { connection },
790
+ );
791
+ await this.queryRun(
792
+ `CREATE INDEX ${PostgreSQLDriver.escape(
793
+ `${this.prefix}tokens_${etype}_id_name_token`,
794
+ )} ON ${PostgreSQLDriver.escape(
795
+ `${this.prefix}tokens_${etype}`,
796
+ )} USING btree ("name", "token");`,
797
+ { connection },
798
+ );
799
+ await this.queryRun(
800
+ `ALTER TABLE ${PostgreSQLDriver.escape(
801
+ `${this.prefix}tokens_${etype}`,
802
+ )} SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_analyze_scale_factor = 0.05 );`,
803
+ { connection },
804
+ );
805
+ }
806
+
807
+ private async createUniquesTable(
808
+ etype: string,
809
+ connection: PostgreSQLDriverConnection,
810
+ ) {
811
+ // Create the unique strings table.
812
+ await this.queryRun(
813
+ `CREATE TABLE IF NOT EXISTS ${PostgreSQLDriver.escape(
814
+ `${this.prefix}uniques_${etype}`,
815
+ )} (
561
816
  "guid" BYTEA NOT NULL,
562
817
  "unique" TEXT NOT NULL UNIQUE,
563
818
  PRIMARY KEY ("guid", "unique"),
@@ -566,8 +821,30 @@ export default class PostgreSQLDriver extends NymphDriver {
566
821
  `${this.prefix}entities_${etype}`,
567
822
  )} ("guid") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE
568
823
  ) WITH ( OIDS=FALSE );`,
569
- { connection },
570
- );
824
+ { connection },
825
+ );
826
+ await this.queryRun(
827
+ `ALTER TABLE ${PostgreSQLDriver.escape(
828
+ `${this.prefix}uniques_${etype}`,
829
+ )} OWNER TO ${PostgreSQLDriver.escape(this.config.user)};`,
830
+ { connection },
831
+ );
832
+ }
833
+
834
+ /**
835
+ * Create entity tables in the database.
836
+ *
837
+ * @param etype The entity type to create a table for. If this is blank, the default tables are created.
838
+ * @returns True on success, false on failure.
839
+ */
840
+ private async createTables(etype: string | null = null) {
841
+ const connection = await this.getConnection(true);
842
+ if (etype != null) {
843
+ await this.createEntitiesTable(etype, connection);
844
+ await this.createDataTable(etype, connection);
845
+ await this.createReferencesTable(etype, connection);
846
+ await this.createTokensTable(etype, connection);
847
+ await this.createUniquesTable(etype, connection);
571
848
  } else {
572
849
  // Add trigram extensions.
573
850
  try {
@@ -883,6 +1160,17 @@ export default class PostgreSQLDriver extends NymphDriver {
883
1160
  },
884
1161
  },
885
1162
  );
1163
+ await this.queryRun(
1164
+ `DELETE FROM ${PostgreSQLDriver.escape(
1165
+ `${this.prefix}tokens_${etype}`,
1166
+ )} WHERE "guid"=decode(@guid, 'hex');`,
1167
+ {
1168
+ etypes: [etype],
1169
+ params: {
1170
+ guid,
1171
+ },
1172
+ },
1173
+ );
886
1174
  await this.queryRun(
887
1175
  `DELETE FROM ${PostgreSQLDriver.escape(
888
1176
  `${this.prefix}uniques_${etype}`,
@@ -925,6 +1213,24 @@ export default class PostgreSQLDriver extends NymphDriver {
925
1213
  return true;
926
1214
  }
927
1215
 
1216
+ public async getEtypes() {
1217
+ const tables = await this.queryArray(
1218
+ 'SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @prefix;',
1219
+ {
1220
+ params: {
1221
+ db: this.config.database,
1222
+ prefix: this.prefix + 'entities_' + '%',
1223
+ },
1224
+ },
1225
+ );
1226
+ const etypes: string[] = [];
1227
+ for (const table of tables) {
1228
+ etypes.push(table.table_name.substr((this.prefix + 'entities_').length));
1229
+ }
1230
+
1231
+ return etypes;
1232
+ }
1233
+
928
1234
  public async *exportDataIterator(): AsyncGenerator<
929
1235
  { type: 'comment' | 'uid' | 'entity'; content: string },
930
1236
  void,
@@ -987,19 +1293,7 @@ export default class PostgreSQLDriver extends NymphDriver {
987
1293
  }
988
1294
 
989
1295
  // Get the etypes.
990
- const tables = await this.queryArray(
991
- 'SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @prefix;',
992
- {
993
- params: {
994
- db: this.config.database,
995
- prefix: this.prefix + 'entities_' + '%',
996
- },
997
- },
998
- );
999
- const etypes = [];
1000
- for (const table of tables) {
1001
- etypes.push(table.table_name.substr((this.prefix + 'entities_').length));
1002
- }
1296
+ const etypes = await this.getEtypes();
1003
1297
 
1004
1298
  for (const etype of etypes) {
1005
1299
  // Export entities.
@@ -1341,6 +1635,148 @@ export default class PostgreSQLDriver extends NymphDriver {
1341
1635
  params[value] = PostgreSQLDriver.escapeNullSequences(svalue);
1342
1636
  }
1343
1637
  break;
1638
+ case 'search':
1639
+ case '!search':
1640
+ if (curValue[0] === 'cdate' || curValue[0] === 'mdate') {
1641
+ if (curQuery) {
1642
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
1643
+ }
1644
+ curQuery +=
1645
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') + '(FALSE)';
1646
+ break;
1647
+ } else {
1648
+ if (curQuery) {
1649
+ curQuery += typeIsOr ? ' OR ' : ' AND ';
1650
+ }
1651
+
1652
+ const name = `param${++count.i}`;
1653
+
1654
+ const queryPartToken = (term: SearchTerm) => {
1655
+ const value = `param${++count.i}`;
1656
+ params[value] = term.token;
1657
+ return (
1658
+ 'EXISTS (SELECT "guid" FROM ' +
1659
+ PostgreSQLDriver.escape(this.prefix + 'tokens_' + etype) +
1660
+ ' WHERE "guid"=' +
1661
+ ieTable +
1662
+ '."guid" AND "name"=@' +
1663
+ name +
1664
+ ' AND "token"=@' +
1665
+ value +
1666
+ (term.nostemmed ? ' AND "stem"=FALSE' : '') +
1667
+ ')'
1668
+ );
1669
+ };
1670
+
1671
+ const queryPartSeries = (series: SearchSeriesTerm) => {
1672
+ const tokenTableSuffix = makeTableSuffix();
1673
+ const tokenParts = series.tokens.map((token, i) => {
1674
+ const value = `param${++count.i}`;
1675
+ params[value] = token.token;
1676
+ return {
1677
+ fromClause:
1678
+ i === 0
1679
+ ? 'FROM ' +
1680
+ PostgreSQLDriver.escape(
1681
+ this.prefix + 'tokens_' + etype,
1682
+ ) +
1683
+ ' t' +
1684
+ tokenTableSuffix +
1685
+ '0'
1686
+ : 'JOIN ' +
1687
+ PostgreSQLDriver.escape(
1688
+ this.prefix + 'tokens_' + etype,
1689
+ ) +
1690
+ ' t' +
1691
+ tokenTableSuffix +
1692
+ i +
1693
+ ' ON t' +
1694
+ tokenTableSuffix +
1695
+ i +
1696
+ '."guid" = t' +
1697
+ tokenTableSuffix +
1698
+ '0."guid" AND t' +
1699
+ tokenTableSuffix +
1700
+ i +
1701
+ '."name" = t' +
1702
+ tokenTableSuffix +
1703
+ '0."name" AND t' +
1704
+ tokenTableSuffix +
1705
+ i +
1706
+ '."position" = t' +
1707
+ tokenTableSuffix +
1708
+ '0."position" + ' +
1709
+ i,
1710
+ whereClause:
1711
+ 't' +
1712
+ tokenTableSuffix +
1713
+ i +
1714
+ '."token"=@' +
1715
+ value +
1716
+ (token.nostemmed
1717
+ ? ' AND t' + tokenTableSuffix + i + '."stem"=FALSE'
1718
+ : ''),
1719
+ };
1720
+ });
1721
+ return (
1722
+ 'EXISTS (SELECT t' +
1723
+ tokenTableSuffix +
1724
+ '0."guid" ' +
1725
+ tokenParts.map((part) => part.fromClause).join(' ') +
1726
+ ' WHERE t' +
1727
+ tokenTableSuffix +
1728
+ '0."guid"=' +
1729
+ ieTable +
1730
+ '."guid" AND t' +
1731
+ tokenTableSuffix +
1732
+ '0."name"=@' +
1733
+ name +
1734
+ ' AND ' +
1735
+ tokenParts.map((part) => part.whereClause).join(' AND ') +
1736
+ ')'
1737
+ );
1738
+ };
1739
+
1740
+ const queryPartTerm = (
1741
+ term:
1742
+ | SearchTerm
1743
+ | SearchOrTerm
1744
+ | SearchNotTerm
1745
+ | SearchSeriesTerm,
1746
+ ): string => {
1747
+ if (term.type === 'series') {
1748
+ return queryPartSeries(term);
1749
+ } else if (term.type === 'not') {
1750
+ return 'NOT ' + queryPartTerm(term.operand);
1751
+ } else if (term.type === 'or') {
1752
+ let queryParts: string[] = [];
1753
+ for (let operand of term.operands) {
1754
+ queryParts.push(queryPartTerm(operand));
1755
+ }
1756
+ return '(' + queryParts.join(' OR ') + ')';
1757
+ }
1758
+ return queryPartToken(term);
1759
+ };
1760
+
1761
+ const parsedFTSQuery = this.tokenizer.parseSearchQuery(
1762
+ curValue[1],
1763
+ );
1764
+
1765
+ // Run through the query and add terms.
1766
+ let termStrings: string[] = [];
1767
+ for (let term of parsedFTSQuery) {
1768
+ termStrings.push(queryPartTerm(term));
1769
+ }
1770
+
1771
+ curQuery +=
1772
+ (xor(typeIsNot, clauseNot) ? 'NOT ' : '') +
1773
+ '(' +
1774
+ termStrings.join(' AND ') +
1775
+ ')';
1776
+
1777
+ params[name] = curValue[0];
1778
+ }
1779
+ break;
1344
1780
  case 'match':
1345
1781
  case '!match':
1346
1782
  if (curValue[0] === 'cdate') {
@@ -2179,14 +2615,18 @@ export default class PostgreSQLDriver extends NymphDriver {
2179
2615
  return result?.cur_uid == null ? null : Number(result.cur_uid);
2180
2616
  }
2181
2617
 
2182
- public async importEntity({
2183
- guid,
2184
- cdate,
2185
- mdate,
2186
- tags,
2187
- sdata,
2188
- etype,
2189
- }: {
2618
+ public async importEntity(entity: {
2619
+ guid: string;
2620
+ cdate: number;
2621
+ mdate: number;
2622
+ tags: string[];
2623
+ sdata: SerializedEntityData;
2624
+ etype: string;
2625
+ }) {
2626
+ return await this.importEntityInternal(entity, false);
2627
+ }
2628
+
2629
+ public async importEntityTokens(entity: {
2190
2630
  guid: string;
2191
2631
  cdate: number;
2192
2632
  mdate: number;
@@ -2194,51 +2634,74 @@ export default class PostgreSQLDriver extends NymphDriver {
2194
2634
  sdata: SerializedEntityData;
2195
2635
  etype: string;
2196
2636
  }) {
2637
+ return await this.importEntityInternal(entity, true);
2638
+ }
2639
+
2640
+ private async importEntityInternal(
2641
+ {
2642
+ guid,
2643
+ cdate,
2644
+ mdate,
2645
+ tags,
2646
+ sdata,
2647
+ etype,
2648
+ }: {
2649
+ guid: string;
2650
+ cdate: number;
2651
+ mdate: number;
2652
+ tags: string[];
2653
+ sdata: SerializedEntityData;
2654
+ etype: string;
2655
+ },
2656
+ onlyTokens: boolean,
2657
+ ) {
2197
2658
  try {
2198
2659
  let promises = [];
2199
- promises.push(
2200
- this.queryRun(
2201
- `DELETE FROM ${PostgreSQLDriver.escape(
2202
- `${this.prefix}entities_${etype}`,
2203
- )} WHERE "guid"=decode(@guid, 'hex');`,
2204
- {
2205
- etypes: [etype],
2206
- params: {
2207
- guid,
2660
+ if (!onlyTokens) {
2661
+ promises.push(
2662
+ this.queryRun(
2663
+ `DELETE FROM ${PostgreSQLDriver.escape(
2664
+ `${this.prefix}entities_${etype}`,
2665
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2666
+ {
2667
+ etypes: [etype],
2668
+ params: {
2669
+ guid,
2670
+ },
2208
2671
  },
2209
- },
2210
- ),
2211
- );
2212
- promises.push(
2213
- this.queryRun(
2214
- `DELETE FROM ${PostgreSQLDriver.escape(
2215
- `${this.prefix}data_${etype}`,
2216
- )} WHERE "guid"=decode(@guid, 'hex');`,
2217
- {
2218
- etypes: [etype],
2219
- params: {
2220
- guid,
2672
+ ),
2673
+ );
2674
+ promises.push(
2675
+ this.queryRun(
2676
+ `DELETE FROM ${PostgreSQLDriver.escape(
2677
+ `${this.prefix}data_${etype}`,
2678
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2679
+ {
2680
+ etypes: [etype],
2681
+ params: {
2682
+ guid,
2683
+ },
2221
2684
  },
2222
- },
2223
- ),
2224
- );
2225
- promises.push(
2226
- this.queryRun(
2227
- `DELETE FROM ${PostgreSQLDriver.escape(
2228
- `${this.prefix}references_${etype}`,
2229
- )} WHERE "guid"=decode(@guid, 'hex');`,
2230
- {
2231
- etypes: [etype],
2232
- params: {
2233
- guid,
2685
+ ),
2686
+ );
2687
+ promises.push(
2688
+ this.queryRun(
2689
+ `DELETE FROM ${PostgreSQLDriver.escape(
2690
+ `${this.prefix}references_${etype}`,
2691
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2692
+ {
2693
+ etypes: [etype],
2694
+ params: {
2695
+ guid,
2696
+ },
2234
2697
  },
2235
- },
2236
- ),
2237
- );
2698
+ ),
2699
+ );
2700
+ }
2238
2701
  promises.push(
2239
2702
  this.queryRun(
2240
2703
  `DELETE FROM ${PostgreSQLDriver.escape(
2241
- `${this.prefix}uniques_${etype}`,
2704
+ `${this.prefix}tokens_${etype}`,
2242
2705
  )} WHERE "guid"=decode(@guid, 'hex');`,
2243
2706
  {
2244
2707
  etypes: [etype],
@@ -2248,106 +2711,188 @@ export default class PostgreSQLDriver extends NymphDriver {
2248
2711
  },
2249
2712
  ),
2250
2713
  );
2251
- await Promise.all(promises);
2252
- promises = [];
2253
- await this.queryRun(
2254
- `INSERT INTO ${PostgreSQLDriver.escape(
2255
- `${this.prefix}entities_${etype}`,
2256
- )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`,
2257
- {
2258
- etypes: [etype],
2259
- params: {
2260
- guid,
2261
- tags,
2262
- cdate: isNaN(cdate) ? null : cdate,
2263
- mdate: isNaN(mdate) ? null : mdate,
2264
- },
2265
- },
2266
- );
2267
- for (const name in sdata) {
2268
- const value = sdata[name];
2269
- const uvalue = JSON.parse(value);
2270
- if (value === undefined) {
2271
- continue;
2272
- }
2273
- const storageValue =
2274
- typeof uvalue === 'number'
2275
- ? 'N'
2276
- : typeof uvalue === 'string'
2277
- ? 'S'
2278
- : 'J';
2279
- const jsonValue =
2280
- storageValue === 'J'
2281
- ? PostgreSQLDriver.escapeNullSequences(value)
2282
- : null;
2714
+ if (!onlyTokens) {
2283
2715
  promises.push(
2284
2716
  this.queryRun(
2285
- `INSERT INTO ${PostgreSQLDriver.escape(
2286
- `${this.prefix}data_${etype}`,
2287
- )} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (decode(@guid, 'hex'), @name, @storageValue, @jsonValue, @string, @number, @truthy);`,
2717
+ `DELETE FROM ${PostgreSQLDriver.escape(
2718
+ `${this.prefix}uniques_${etype}`,
2719
+ )} WHERE "guid"=decode(@guid, 'hex');`,
2288
2720
  {
2289
2721
  etypes: [etype],
2290
2722
  params: {
2291
2723
  guid,
2292
- name,
2293
- storageValue,
2294
- jsonValue,
2295
- string:
2296
- storageValue === 'J'
2297
- ? null
2298
- : PostgreSQLDriver.escapeNulls(`${uvalue}`),
2299
- number: isNaN(Number(uvalue)) ? null : Number(uvalue),
2300
- truthy: !!uvalue,
2301
2724
  },
2302
2725
  },
2303
2726
  ),
2304
2727
  );
2305
- const references = this.findReferences(value);
2306
- for (const reference of references) {
2728
+ }
2729
+
2730
+ await Promise.all(promises);
2731
+ promises = [];
2732
+
2733
+ if (!onlyTokens) {
2734
+ await this.queryRun(
2735
+ `INSERT INTO ${PostgreSQLDriver.escape(
2736
+ `${this.prefix}entities_${etype}`,
2737
+ )} ("guid", "tags", "cdate", "mdate") VALUES (decode(@guid, 'hex'), @tags, @cdate, @mdate);`,
2738
+ {
2739
+ etypes: [etype],
2740
+ params: {
2741
+ guid,
2742
+ tags,
2743
+ cdate: isNaN(cdate) ? null : cdate,
2744
+ mdate: isNaN(mdate) ? null : mdate,
2745
+ },
2746
+ },
2747
+ );
2748
+
2749
+ for (const name in sdata) {
2750
+ const value = sdata[name];
2751
+ const uvalue = JSON.parse(value);
2752
+ if (value === undefined) {
2753
+ continue;
2754
+ }
2755
+ const storageValue =
2756
+ typeof uvalue === 'number'
2757
+ ? 'N'
2758
+ : typeof uvalue === 'string'
2759
+ ? 'S'
2760
+ : 'J';
2761
+ const jsonValue =
2762
+ storageValue === 'J'
2763
+ ? PostgreSQLDriver.escapeNullSequences(value)
2764
+ : null;
2765
+
2307
2766
  promises.push(
2308
2767
  this.queryRun(
2309
2768
  `INSERT INTO ${PostgreSQLDriver.escape(
2310
- `${this.prefix}references_${etype}`,
2311
- )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2769
+ `${this.prefix}data_${etype}`,
2770
+ )} ("guid", "name", "value", "json", "string", "number", "truthy") VALUES (decode(@guid, 'hex'), @name, @storageValue, @jsonValue, @string, @number, @truthy);`,
2312
2771
  {
2313
2772
  etypes: [etype],
2314
2773
  params: {
2315
2774
  guid,
2316
2775
  name,
2317
- reference,
2776
+ storageValue,
2777
+ jsonValue,
2778
+ string:
2779
+ storageValue === 'J'
2780
+ ? null
2781
+ : PostgreSQLDriver.escapeNulls(`${uvalue}`),
2782
+ number: isNaN(Number(uvalue)) ? null : Number(uvalue),
2783
+ truthy: !!uvalue,
2318
2784
  },
2319
2785
  },
2320
2786
  ),
2321
2787
  );
2788
+
2789
+ const references = this.findReferences(value);
2790
+ for (const reference of references) {
2791
+ promises.push(
2792
+ this.queryRun(
2793
+ `INSERT INTO ${PostgreSQLDriver.escape(
2794
+ `${this.prefix}references_${etype}`,
2795
+ )} ("guid", "name", "reference") VALUES (decode(@guid, 'hex'), @name, decode(@reference, 'hex'));`,
2796
+ {
2797
+ etypes: [etype],
2798
+ params: {
2799
+ guid,
2800
+ name,
2801
+ reference,
2802
+ },
2803
+ },
2804
+ ),
2805
+ );
2806
+ }
2322
2807
  }
2323
2808
  }
2324
- const uniques = await this.nymph
2325
- .getEntityClassByEtype(etype)
2326
- .getUniques({ guid, cdate, mdate, tags, data: {}, sdata });
2327
- for (const unique of uniques) {
2328
- promises.push(
2329
- this.queryRun(
2330
- `INSERT INTO ${PostgreSQLDriver.escape(
2331
- `${this.prefix}uniques_${etype}`,
2332
- )} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`,
2333
- {
2334
- etypes: [etype],
2335
- params: {
2336
- guid,
2337
- unique,
2338
- },
2339
- },
2340
- ).catch((e: any) => {
2341
- if (e instanceof EntityUniqueConstraintError) {
2342
- this.nymph.config.debugError(
2343
- 'postgresql',
2344
- `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2809
+
2810
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
2811
+
2812
+ for (let name in sdata) {
2813
+ let tokenString: string | null = null;
2814
+ try {
2815
+ tokenString = EntityClass.getFTSText(name, JSON.parse(sdata[name]));
2816
+ } catch (e: any) {
2817
+ // Ignore error.
2818
+ }
2819
+
2820
+ if (tokenString != null) {
2821
+ const tokens = this.tokenizer.tokenize(tokenString);
2822
+ while (tokens.length) {
2823
+ const currentTokens = tokens.splice(0, 100);
2824
+ const params: { [k: string]: any } = {
2825
+ guid,
2826
+ name,
2827
+ };
2828
+ const values: string[] = [];
2829
+
2830
+ for (let i = 0; i < currentTokens.length; i++) {
2831
+ const token = currentTokens[i];
2832
+ params['token' + i] = token.token;
2833
+ params['position' + i] = token.position;
2834
+ params['stem' + i] = token.stem;
2835
+ values.push(
2836
+ "(decode(@guid, 'hex'), @name, @token" +
2837
+ i +
2838
+ ', @position' +
2839
+ i +
2840
+ ', @stem' +
2841
+ i +
2842
+ ')',
2345
2843
  );
2346
2844
  }
2347
- return e;
2348
- }),
2349
- );
2845
+
2846
+ promises.push(
2847
+ this.queryRun(
2848
+ `INSERT INTO ${PostgreSQLDriver.escape(
2849
+ `${this.prefix}tokens_${etype}`,
2850
+ )} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`,
2851
+ {
2852
+ etypes: [etype],
2853
+ params,
2854
+ },
2855
+ ),
2856
+ );
2857
+ }
2858
+ }
2859
+ }
2860
+
2861
+ if (!onlyTokens) {
2862
+ const uniques = await EntityClass.getUniques({
2863
+ guid,
2864
+ cdate,
2865
+ mdate,
2866
+ tags,
2867
+ data: {},
2868
+ sdata,
2869
+ });
2870
+ for (const unique of uniques) {
2871
+ promises.push(
2872
+ this.queryRun(
2873
+ `INSERT INTO ${PostgreSQLDriver.escape(
2874
+ `${this.prefix}uniques_${etype}`,
2875
+ )} ("guid", "unique") VALUES (decode(@guid, 'hex'), @unique);`,
2876
+ {
2877
+ etypes: [etype],
2878
+ params: {
2879
+ guid,
2880
+ unique,
2881
+ },
2882
+ },
2883
+ ).catch((e: any) => {
2884
+ if (e instanceof EntityUniqueConstraintError) {
2885
+ this.nymph.config.debugError(
2886
+ 'postgresql',
2887
+ `Import entity unique constraint violation for GUID "${guid}" on etype "${etype}": "${unique}"`,
2888
+ );
2889
+ }
2890
+ return e;
2891
+ }),
2892
+ );
2893
+ }
2350
2894
  }
2895
+
2351
2896
  await Promise.all(promises);
2352
2897
  } catch (e: any) {
2353
2898
  this.nymph.config.debugError('postgresql', `Import entity error: "${e}"`);
@@ -2491,6 +3036,8 @@ export default class PostgreSQLDriver extends NymphDriver {
2491
3036
  uniques: string[],
2492
3037
  etype: string,
2493
3038
  ) => {
3039
+ const EntityClass = this.nymph.getEntityClassByEtype(etype);
3040
+
2494
3041
  const runInsertQuery = async (
2495
3042
  name: string,
2496
3043
  value: any,
@@ -2509,7 +3056,9 @@ export default class PostgreSQLDriver extends NymphDriver {
2509
3056
  storageValue === 'J'
2510
3057
  ? PostgreSQLDriver.escapeNullSequences(svalue)
2511
3058
  : null;
3059
+
2512
3060
  const promises = [];
3061
+
2513
3062
  promises.push(
2514
3063
  this.queryRun(
2515
3064
  `INSERT INTO ${PostgreSQLDriver.escape(
@@ -2532,6 +3081,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2532
3081
  },
2533
3082
  ),
2534
3083
  );
3084
+
2535
3085
  const references = this.findReferences(svalue);
2536
3086
  for (const reference of references) {
2537
3087
  promises.push(
@@ -2550,8 +3100,57 @@ export default class PostgreSQLDriver extends NymphDriver {
2550
3100
  ),
2551
3101
  );
2552
3102
  }
3103
+
3104
+ let tokenString: string | null = null;
3105
+ try {
3106
+ tokenString = EntityClass.getFTSText(name, value);
3107
+ } catch (e: any) {
3108
+ // Ignore error.
3109
+ }
3110
+
3111
+ if (tokenString != null) {
3112
+ const tokens = this.tokenizer.tokenize(tokenString);
3113
+ while (tokens.length) {
3114
+ const currentTokens = tokens.splice(0, 100);
3115
+ const params: { [k: string]: any } = {
3116
+ guid,
3117
+ name,
3118
+ };
3119
+ const values: string[] = [];
3120
+
3121
+ for (let i = 0; i < currentTokens.length; i++) {
3122
+ const token = currentTokens[i];
3123
+ params['token' + i] = token.token;
3124
+ params['position' + i] = token.position;
3125
+ params['stem' + i] = token.stem;
3126
+ values.push(
3127
+ "(decode(@guid, 'hex'), @name, @token" +
3128
+ i +
3129
+ ', @position' +
3130
+ i +
3131
+ ', @stem' +
3132
+ i +
3133
+ ')',
3134
+ );
3135
+ }
3136
+
3137
+ promises.push(
3138
+ this.queryRun(
3139
+ `INSERT INTO ${PostgreSQLDriver.escape(
3140
+ `${this.prefix}tokens_${etype}`,
3141
+ )} ("guid", "name", "token", "position", "stem") VALUES ${values.join(', ')};`,
3142
+ {
3143
+ etypes: [etype],
3144
+ params,
3145
+ },
3146
+ ),
3147
+ );
3148
+ }
3149
+ }
3150
+
2553
3151
  await Promise.all(promises);
2554
3152
  };
3153
+
2555
3154
  for (const unique of uniques) {
2556
3155
  try {
2557
3156
  await this.queryRun(
@@ -2657,6 +3256,19 @@ export default class PostgreSQLDriver extends NymphDriver {
2657
3256
  },
2658
3257
  ),
2659
3258
  );
3259
+ promises.push(
3260
+ this.queryRun(
3261
+ `SELECT 1 FROM ${PostgreSQLDriver.escape(
3262
+ `${this.prefix}tokens_${etype}`,
3263
+ )} WHERE "guid"=decode(@guid, 'hex') FOR UPDATE;`,
3264
+ {
3265
+ etypes: [etype],
3266
+ params: {
3267
+ guid,
3268
+ },
3269
+ },
3270
+ ),
3271
+ );
2660
3272
  promises.push(
2661
3273
  this.queryRun(
2662
3274
  `SELECT 1 FROM ${PostgreSQLDriver.escape(
@@ -2714,6 +3326,19 @@ export default class PostgreSQLDriver extends NymphDriver {
2714
3326
  },
2715
3327
  ),
2716
3328
  );
3329
+ promises.push(
3330
+ this.queryRun(
3331
+ `DELETE FROM ${PostgreSQLDriver.escape(
3332
+ `${this.prefix}tokens_${etype}`,
3333
+ )} WHERE "guid"=decode(@guid, 'hex');`,
3334
+ {
3335
+ etypes: [etype],
3336
+ params: {
3337
+ guid,
3338
+ },
3339
+ },
3340
+ ),
3341
+ );
2717
3342
  promises.push(
2718
3343
  this.queryRun(
2719
3344
  `DELETE FROM ${PostgreSQLDriver.escape(
@@ -2834,7 +3459,7 @@ export default class PostgreSQLDriver extends NymphDriver {
2834
3459
  return nymph;
2835
3460
  }
2836
3461
 
2837
- public async needsMigration(): Promise<boolean> {
3462
+ public async needsMigration(): Promise<'json' | 'tokens' | false> {
2838
3463
  const table = await this.queryGet(
2839
3464
  'SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @prefix LIMIT 1;',
2840
3465
  {
@@ -2854,8 +3479,32 @@ export default class PostgreSQLDriver extends NymphDriver {
2854
3479
  },
2855
3480
  },
2856
3481
  );
2857
- return !result?.exists;
3482
+ if (!result?.exists) {
3483
+ return 'json';
3484
+ }
3485
+ }
3486
+ const table2 = await this.queryGet(
3487
+ 'SELECT "table_name" AS "table_name" FROM "information_schema"."tables" WHERE "table_catalog"=@db AND "table_schema"=\'public\' AND "table_name" LIKE @tokenTable LIMIT 1;',
3488
+ {
3489
+ params: {
3490
+ db: this.config.database,
3491
+ tokenTable: this.prefix + 'tokens_' + '%',
3492
+ },
3493
+ },
3494
+ );
3495
+ if (!table2 || !table2.table_name) {
3496
+ return 'tokens';
2858
3497
  }
2859
3498
  return false;
2860
3499
  }
3500
+
3501
+ public async liveMigration(_migrationType: 'tokenTables') {
3502
+ const etypes = await this.getEtypes();
3503
+
3504
+ const connection = await this.getConnection(true);
3505
+ for (let etype of etypes) {
3506
+ await this.createTokensTable(etype, connection);
3507
+ }
3508
+ connection.done();
3509
+ }
2861
3510
  }