@workglow/postgres 0.2.30 → 0.2.32
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.
- package/README.md +33 -0
- package/dist/job-queue/PostgresQueueStorage.d.ts +12 -19
- package/dist/job-queue/PostgresQueueStorage.d.ts.map +1 -1
- package/dist/job-queue/PostgresRateLimiterStorage.d.ts +9 -19
- package/dist/job-queue/PostgresRateLimiterStorage.d.ts.map +1 -1
- package/dist/job-queue/browser.js +331 -165
- package/dist/job-queue/browser.js.map +8 -5
- package/dist/job-queue/common.d.ts +3 -0
- package/dist/job-queue/common.d.ts.map +1 -1
- package/dist/job-queue/node.js +331 -165
- package/dist/job-queue/node.js.map +8 -5
- package/dist/migrations/PostgresMigrationRunner.d.ts +31 -0
- package/dist/migrations/PostgresMigrationRunner.d.ts.map +1 -0
- package/dist/migrations/common.d.ts +9 -0
- package/dist/migrations/common.d.ts.map +1 -0
- package/dist/migrations/postgresQueueMigrations.d.ts +18 -0
- package/dist/migrations/postgresQueueMigrations.d.ts.map +1 -0
- package/dist/migrations/postgresRateLimiterMigrations.d.ts +11 -0
- package/dist/migrations/postgresRateLimiterMigrations.d.ts.map +1 -0
- package/dist/storage/PostgresTabularStorage.d.ts +175 -10
- package/dist/storage/PostgresTabularStorage.d.ts.map +1 -1
- package/dist/storage/PostgresVectorStorage.d.ts +52 -2
- package/dist/storage/PostgresVectorStorage.d.ts.map +1 -1
- package/dist/storage/browser.js +454 -75
- package/dist/storage/browser.js.map +4 -4
- package/dist/storage/common.d.ts +1 -0
- package/dist/storage/common.d.ts.map +1 -1
- package/dist/storage/node.js +564 -75
- package/dist/storage/node.js.map +6 -5
- package/package.json +7 -7
package/dist/storage/node.js
CHANGED
|
@@ -38,26 +38,55 @@ import { createServiceToken as createServiceToken2 } from "@workglow/util";
|
|
|
38
38
|
import { createServiceToken } from "@workglow/util";
|
|
39
39
|
import {
|
|
40
40
|
BaseSqlTabularStorage,
|
|
41
|
-
|
|
41
|
+
buildSearchWhere,
|
|
42
|
+
MIGRATIONS_TABLE,
|
|
43
|
+
PostgresDialect,
|
|
44
|
+
SqlTabularMigrationApplier,
|
|
42
45
|
pickCoveringIndex
|
|
43
46
|
} from "@workglow/storage";
|
|
44
47
|
var POSTGRES_TABULAR_REPOSITORY = createServiceToken("storage.tabularRepository.postgres");
|
|
48
|
+
function assertPositiveInt(value, label) {
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
return;
|
|
51
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
52
|
+
throw new Error(`VectorIndexOptions.${label} must be a positive integer; received ${String(value)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
45
55
|
|
|
46
56
|
class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
47
57
|
db;
|
|
48
|
-
constructor(db, table = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
49
|
-
super(table, schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
58
|
+
constructor(db, table = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing", tabularMigrations) {
|
|
59
|
+
super(table, schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, table);
|
|
50
60
|
this.db = db;
|
|
51
61
|
}
|
|
52
62
|
async setupDatabase() {
|
|
63
|
+
if (this.tabularMigrations && this.tabularMigrations.length > 0) {
|
|
64
|
+
const exists = await this.tableExistsAsync();
|
|
65
|
+
await this.createTableAndIndexes();
|
|
66
|
+
await this.applyTabularMigrations({ freshTable: !exists });
|
|
67
|
+
if (exists) {
|
|
68
|
+
await this.createDeclaredIndexes();
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await this.createTableAndIndexes();
|
|
73
|
+
}
|
|
74
|
+
async tableExistsAsync() {
|
|
75
|
+
const r = await this.db.query(`SELECT 1 AS x FROM information_schema.tables WHERE table_name = $1 LIMIT 1`, [this.table]);
|
|
76
|
+
return r.rows.length > 0;
|
|
77
|
+
}
|
|
78
|
+
async createTableAndIndexes() {
|
|
53
79
|
const sql = `
|
|
54
80
|
CREATE TABLE IF NOT EXISTS "${this.table}" (
|
|
55
81
|
${this.constructPrimaryKeyColumns('"')} ${this.constructValueColumns('"')},
|
|
56
|
-
PRIMARY KEY (${this.primaryKeyColumnList()})
|
|
82
|
+
PRIMARY KEY (${this.primaryKeyColumnList()})
|
|
57
83
|
)
|
|
58
84
|
`;
|
|
59
85
|
await this.db.query(sql);
|
|
60
86
|
await this.createVectorIndexes();
|
|
87
|
+
await this.createDeclaredIndexes();
|
|
88
|
+
}
|
|
89
|
+
async createDeclaredIndexes() {
|
|
61
90
|
const pkColumns = this.primaryKeyColumns();
|
|
62
91
|
const createdIndexes = new Set;
|
|
63
92
|
for (const columns of this.indexes) {
|
|
@@ -81,6 +110,15 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
81
110
|
}
|
|
82
111
|
}
|
|
83
112
|
}
|
|
113
|
+
async runMigrationDdl(sql) {
|
|
114
|
+
await this.db.query(sql);
|
|
115
|
+
}
|
|
116
|
+
async recordMigrationApplied(component, version, description) {
|
|
117
|
+
await this.db.query(`INSERT INTO ${MIGRATIONS_TABLE}(component, version, description) VALUES ($1, $2, $3)`, [component, version, description]);
|
|
118
|
+
}
|
|
119
|
+
getMigrationApplier() {
|
|
120
|
+
return new PostgresTabularMigrationApplierImpl(this);
|
|
121
|
+
}
|
|
84
122
|
isVectorFormat(format) {
|
|
85
123
|
if (!format)
|
|
86
124
|
return false;
|
|
@@ -292,32 +330,73 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
292
330
|
}
|
|
293
331
|
return vectorColumns;
|
|
294
332
|
}
|
|
333
|
+
getVectorIndexOptions() {
|
|
334
|
+
return {};
|
|
335
|
+
}
|
|
295
336
|
async createVectorIndexes() {
|
|
296
337
|
const vectorColumns = this.getVectorColumns();
|
|
297
338
|
if (vectorColumns.length === 0) {
|
|
298
339
|
return;
|
|
299
340
|
}
|
|
341
|
+
const opts = this.getVectorIndexOptions();
|
|
342
|
+
if (opts.hnsw && opts.ivfflat) {
|
|
343
|
+
throw new Error("VectorIndexOptions: only one of `hnsw` or `ivfflat` may be set; received both.");
|
|
344
|
+
}
|
|
345
|
+
assertPositiveInt(opts.hnsw?.m, "hnsw.m");
|
|
346
|
+
assertPositiveInt(opts.hnsw?.efConstruction, "hnsw.efConstruction");
|
|
347
|
+
assertPositiveInt(opts.hnsw?.efSearch, "hnsw.efSearch");
|
|
348
|
+
assertPositiveInt(opts.ivfflat?.lists, "ivfflat.lists");
|
|
349
|
+
assertPositiveInt(opts.ivfflat?.probes, "ivfflat.probes");
|
|
300
350
|
try {
|
|
301
351
|
await this.db.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
302
352
|
} catch (error) {
|
|
303
353
|
console.warn("pgvector extension not available, vector columns will use TEXT fallback:", error);
|
|
304
354
|
return;
|
|
305
355
|
}
|
|
356
|
+
const distance = opts.distance ?? "cosine";
|
|
357
|
+
const opClass = distance === "l2" ? "vector_l2_ops" : distance === "ip" ? "vector_ip_ops" : "vector_cosine_ops";
|
|
358
|
+
const tableId = PostgresDialect.quoteId(this.table);
|
|
359
|
+
if (opts.ivfflat) {
|
|
360
|
+
const { lists } = opts.ivfflat;
|
|
361
|
+
for (const { column } of vectorColumns) {
|
|
362
|
+
const indexId = PostgresDialect.quoteId(`${this.table}_${column}_ivfflat_idx`);
|
|
363
|
+
const columnId = PostgresDialect.quoteId(column);
|
|
364
|
+
try {
|
|
365
|
+
await this.db.query(`
|
|
366
|
+
CREATE INDEX IF NOT EXISTS ${indexId}
|
|
367
|
+
ON ${tableId}
|
|
368
|
+
USING ivfflat (${columnId} ${opClass})
|
|
369
|
+
WITH (lists = ${lists})
|
|
370
|
+
`);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.warn(`Failed to create IVFFlat index on ${column}:`, error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const hnsw = opts.hnsw ?? {};
|
|
378
|
+
const buildParams = [];
|
|
379
|
+
if (typeof hnsw.m === "number")
|
|
380
|
+
buildParams.push(`m = ${hnsw.m}`);
|
|
381
|
+
if (typeof hnsw.efConstruction === "number") {
|
|
382
|
+
buildParams.push(`ef_construction = ${hnsw.efConstruction}`);
|
|
383
|
+
}
|
|
384
|
+
const withClause = buildParams.length > 0 ? ` WITH (${buildParams.join(", ")})` : "";
|
|
306
385
|
for (const { column } of vectorColumns) {
|
|
307
|
-
const
|
|
386
|
+
const indexId = PostgresDialect.quoteId(`${this.table}_${column}_hnsw_idx`);
|
|
387
|
+
const columnId = PostgresDialect.quoteId(column);
|
|
308
388
|
try {
|
|
309
389
|
await this.db.query(`
|
|
310
|
-
CREATE INDEX IF NOT EXISTS
|
|
311
|
-
ON
|
|
312
|
-
USING hnsw (
|
|
390
|
+
CREATE INDEX IF NOT EXISTS ${indexId}
|
|
391
|
+
ON ${tableId}
|
|
392
|
+
USING hnsw (${columnId} ${opClass})${withClause}
|
|
313
393
|
`);
|
|
314
394
|
} catch (error) {
|
|
315
395
|
console.warn(`Failed to create HNSW index on ${column}:`, error);
|
|
316
396
|
}
|
|
317
397
|
}
|
|
318
398
|
}
|
|
319
|
-
|
|
320
|
-
const db = this.db;
|
|
399
|
+
buildPutSql(entity) {
|
|
321
400
|
const columnsToInsert = [];
|
|
322
401
|
const paramsToInsert = [];
|
|
323
402
|
const pkColumns = this.primaryKeyColumns();
|
|
@@ -359,7 +438,7 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
359
438
|
const placeholders = columnsToInsert.map((_, i) => `$${i + 1}`).join(", ");
|
|
360
439
|
const conflictClause = valueColumns.length > 0 ? `
|
|
361
440
|
ON CONFLICT (${this.primaryKeyColumnList('"')}) DO UPDATE
|
|
362
|
-
SET
|
|
441
|
+
SET
|
|
363
442
|
${valueColumns.map((col) => {
|
|
364
443
|
const colIdx = columnsToInsert.indexOf(String(col));
|
|
365
444
|
return `"${col}" = $${colIdx + 1}`;
|
|
@@ -371,22 +450,177 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
371
450
|
${conflictClause}
|
|
372
451
|
RETURNING *
|
|
373
452
|
`;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const
|
|
453
|
+
return { sql, params: paramsToInsert };
|
|
454
|
+
}
|
|
455
|
+
hydrateRow(row) {
|
|
456
|
+
const entity = row;
|
|
457
|
+
const record = entity;
|
|
378
458
|
for (const key in this.schema.properties) {
|
|
379
|
-
|
|
459
|
+
record[key] = this.sqlToJsValue(key, record[key]);
|
|
460
|
+
}
|
|
461
|
+
return entity;
|
|
462
|
+
}
|
|
463
|
+
async acquireConnection() {
|
|
464
|
+
const supportsConnect = typeof this.db.connect === "function";
|
|
465
|
+
if (supportsConnect) {
|
|
466
|
+
return await this.db.connect();
|
|
467
|
+
}
|
|
468
|
+
const dbAny = this.db;
|
|
469
|
+
const ctorName = dbAny.constructor?.name;
|
|
470
|
+
const looksLikePGlite = typeof dbAny.exec === "function" && dbAny.waitReady !== undefined;
|
|
471
|
+
const looksLikePGLitePool = ctorName === "PGLitePool";
|
|
472
|
+
if (!looksLikePGlite && !looksLikePGLitePool) {
|
|
473
|
+
throw new Error(`PostgresTabularStorage.putBulk requires a pg.Pool with connect() or a known single-connection wrapper (PGLitePool, PGlite); got ${ctorName ?? typeof this.db}. A multi-connection pool without connect() would dispatch BEGIN and the bracketed INSERTs to different sessions, breaking atomicity.`);
|
|
380
474
|
}
|
|
381
|
-
|
|
475
|
+
return {
|
|
476
|
+
query: this.db.query.bind(this.db),
|
|
477
|
+
release: () => {}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
mutexChain = Promise.resolve();
|
|
481
|
+
get serializeOps() {
|
|
482
|
+
return typeof this.db.connect !== "function";
|
|
483
|
+
}
|
|
484
|
+
async mutex(fn) {
|
|
485
|
+
if (!this.serializeOps)
|
|
486
|
+
return fn();
|
|
487
|
+
const prev = this.mutexChain;
|
|
488
|
+
let release;
|
|
489
|
+
this.mutexChain = new Promise((resolve) => {
|
|
490
|
+
release = resolve;
|
|
491
|
+
});
|
|
492
|
+
await prev;
|
|
493
|
+
try {
|
|
494
|
+
return await fn();
|
|
495
|
+
} finally {
|
|
496
|
+
release();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
inTransaction = false;
|
|
500
|
+
emitPut(entity) {
|
|
501
|
+
this.events.emit("put", entity);
|
|
502
|
+
}
|
|
503
|
+
async put(entity) {
|
|
504
|
+
return this.mutex(() => this._putInternal(entity));
|
|
505
|
+
}
|
|
506
|
+
async _putInternal(entity) {
|
|
507
|
+
const { sql, params } = this.buildPutSql(entity);
|
|
508
|
+
const result = await this.db.query(sql, params);
|
|
509
|
+
const updatedEntity = this.hydrateRow(result.rows[0]);
|
|
510
|
+
this.emitPut(updatedEntity);
|
|
382
511
|
return updatedEntity;
|
|
383
512
|
}
|
|
384
513
|
async putBulk(entities) {
|
|
514
|
+
return this.mutex(() => this._putBulkInternal(entities));
|
|
515
|
+
}
|
|
516
|
+
async _putBulkInternal(entities) {
|
|
385
517
|
if (entities.length === 0)
|
|
386
518
|
return [];
|
|
387
|
-
|
|
519
|
+
if (this.inTransaction) {
|
|
520
|
+
const updated = [];
|
|
521
|
+
for (const entity of entities) {
|
|
522
|
+
const { sql, params } = this.buildPutSql(entity);
|
|
523
|
+
const result = await this.db.query(sql, params);
|
|
524
|
+
updated.push(this.hydrateRow(result.rows[0]));
|
|
525
|
+
}
|
|
526
|
+
for (const entity of updated)
|
|
527
|
+
this.emitPut(entity);
|
|
528
|
+
return updated;
|
|
529
|
+
}
|
|
530
|
+
const conn = await this.acquireConnection();
|
|
531
|
+
const updatedEntities = [];
|
|
532
|
+
try {
|
|
533
|
+
await conn.query("BEGIN");
|
|
534
|
+
try {
|
|
535
|
+
for (const entity of entities) {
|
|
536
|
+
const { sql, params } = this.buildPutSql(entity);
|
|
537
|
+
const result = await conn.query(sql, params);
|
|
538
|
+
updatedEntities.push(this.hydrateRow(result.rows[0]));
|
|
539
|
+
}
|
|
540
|
+
await conn.query("COMMIT");
|
|
541
|
+
} catch (err) {
|
|
542
|
+
try {
|
|
543
|
+
await conn.query("ROLLBACK");
|
|
544
|
+
} catch {}
|
|
545
|
+
throw err;
|
|
546
|
+
}
|
|
547
|
+
} finally {
|
|
548
|
+
conn.release();
|
|
549
|
+
}
|
|
550
|
+
for (const entity of updatedEntities)
|
|
551
|
+
this.emitPut(entity);
|
|
552
|
+
return updatedEntities;
|
|
553
|
+
}
|
|
554
|
+
createTxView(txDb, deferredPutEvents) {
|
|
555
|
+
const target = this;
|
|
556
|
+
return new Proxy(target, {
|
|
557
|
+
get(t, prop, receiver) {
|
|
558
|
+
if (prop === "withTransaction") {
|
|
559
|
+
return () => {
|
|
560
|
+
throw new Error("PostgresTabularStorage.withTransaction does not support nesting. " + "Use SAVEPOINT directly or refactor to a single transaction.");
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
if (prop === "db")
|
|
564
|
+
return txDb;
|
|
565
|
+
if (prop === "inTransaction")
|
|
566
|
+
return true;
|
|
567
|
+
if (prop === "emitPut") {
|
|
568
|
+
return (entity) => deferredPutEvents.push(entity);
|
|
569
|
+
}
|
|
570
|
+
if (typeof prop === "string") {
|
|
571
|
+
const internal = t[`_${prop}Internal`];
|
|
572
|
+
if (typeof internal === "function") {
|
|
573
|
+
return (...args) => internal.apply(receiver, args);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const value = Reflect.get(t, prop, receiver);
|
|
577
|
+
return typeof value === "function" ? value.bind(receiver) : value;
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
async withTransaction(fn) {
|
|
582
|
+
const supportsConnect = typeof this.db.connect === "function";
|
|
583
|
+
if (supportsConnect) {
|
|
584
|
+
const client = await this.db.connect();
|
|
585
|
+
try {
|
|
586
|
+
return await this.runInTransaction(fn, { query: client.query.bind(client) });
|
|
587
|
+
} finally {
|
|
588
|
+
client.release();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (this.inTransaction) {
|
|
592
|
+
throw new Error("PostgresTabularStorage.withTransaction does not support nesting. " + "Use SAVEPOINT directly or refactor to a single transaction.");
|
|
593
|
+
}
|
|
594
|
+
return this.mutex(async () => {
|
|
595
|
+
this.inTransaction = true;
|
|
596
|
+
try {
|
|
597
|
+
return await this.runInTransaction(fn, { query: this.db.query.bind(this.db) });
|
|
598
|
+
} finally {
|
|
599
|
+
this.inTransaction = false;
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
async runInTransaction(fn, txDb) {
|
|
604
|
+
const deferredPutEvents = [];
|
|
605
|
+
await txDb.query("BEGIN");
|
|
606
|
+
let result;
|
|
607
|
+
try {
|
|
608
|
+
result = await fn(this.createTxView(txDb, deferredPutEvents));
|
|
609
|
+
await txDb.query("COMMIT");
|
|
610
|
+
} catch (err) {
|
|
611
|
+
try {
|
|
612
|
+
await txDb.query("ROLLBACK");
|
|
613
|
+
} catch {}
|
|
614
|
+
throw err;
|
|
615
|
+
}
|
|
616
|
+
for (const entity of deferredPutEvents)
|
|
617
|
+
this.events.emit("put", entity);
|
|
618
|
+
return result;
|
|
388
619
|
}
|
|
389
620
|
async get(key) {
|
|
621
|
+
return this.mutex(() => this._getInternal(key));
|
|
622
|
+
}
|
|
623
|
+
async _getInternal(key) {
|
|
390
624
|
const db = this.db;
|
|
391
625
|
const whereClauses = this.primaryKeyColumns().map((discriminatorKey, i) => `"${discriminatorKey}" = $${i + 1}`).join(" AND ");
|
|
392
626
|
const sql = `SELECT * FROM "${this.table}" WHERE ${whereClauses}`;
|
|
@@ -406,6 +640,9 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
406
640
|
return val;
|
|
407
641
|
}
|
|
408
642
|
async delete(value) {
|
|
643
|
+
return this.mutex(() => this._deleteInternal(value));
|
|
644
|
+
}
|
|
645
|
+
async _deleteInternal(value) {
|
|
409
646
|
const db = this.db;
|
|
410
647
|
const { key } = this.separateKeyValueFromCombined(value);
|
|
411
648
|
const whereClauses = this.primaryKeyColumns().map((key2, i) => `${key2} = $${i + 1}`).join(" AND ");
|
|
@@ -414,6 +651,9 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
414
651
|
this.events.emit("delete", key);
|
|
415
652
|
}
|
|
416
653
|
async getAll(options) {
|
|
654
|
+
return this.mutex(() => this._getAllInternal(options));
|
|
655
|
+
}
|
|
656
|
+
async _getAllInternal(options) {
|
|
417
657
|
this.validateGetAllOptions(options);
|
|
418
658
|
const db = this.db;
|
|
419
659
|
let sql = `SELECT * FROM "${this.table}"`;
|
|
@@ -443,18 +683,27 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
443
683
|
return;
|
|
444
684
|
}
|
|
445
685
|
async deleteAll() {
|
|
686
|
+
return this.mutex(() => this._deleteAllInternal());
|
|
687
|
+
}
|
|
688
|
+
async _deleteAllInternal() {
|
|
446
689
|
const db = this.db;
|
|
447
690
|
await db.query(`DELETE FROM "${this.table}"`);
|
|
448
691
|
this.events.emit("clearall");
|
|
449
692
|
}
|
|
450
693
|
async size() {
|
|
694
|
+
return this.mutex(() => this._sizeInternal());
|
|
695
|
+
}
|
|
696
|
+
async _sizeInternal() {
|
|
451
697
|
const db = this.db;
|
|
452
698
|
const result = await db.query(`SELECT COUNT(*) FROM "${this.table}"`);
|
|
453
699
|
return parseInt(result.rows[0].count, 10);
|
|
454
700
|
}
|
|
455
701
|
async count(criteria) {
|
|
702
|
+
return this.mutex(() => this._countInternal(criteria));
|
|
703
|
+
}
|
|
704
|
+
async _countInternal(criteria) {
|
|
456
705
|
if (!criteria || Object.keys(criteria).length === 0) {
|
|
457
|
-
return await this.
|
|
706
|
+
return await this._sizeInternal();
|
|
458
707
|
}
|
|
459
708
|
this.validateQueryParams(criteria);
|
|
460
709
|
const db = this.db;
|
|
@@ -463,6 +712,9 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
463
712
|
return parseInt(result.rows[0].count, 10);
|
|
464
713
|
}
|
|
465
714
|
async getBulk(offset, limit) {
|
|
715
|
+
return this.mutex(() => this._getBulkInternal(offset, limit));
|
|
716
|
+
}
|
|
717
|
+
async _getBulkInternal(offset, limit) {
|
|
466
718
|
const db = this.db;
|
|
467
719
|
const orderByClause = this.primaryKeyColumns().map((col) => `"${String(col)}"`).join(", ");
|
|
468
720
|
const result = await db.query(`SELECT * FROM "${this.table}" ORDER BY ${orderByClause} LIMIT $1 OFFSET $2`, [limit, offset]);
|
|
@@ -478,32 +730,52 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
478
730
|
return result.rows;
|
|
479
731
|
}
|
|
480
732
|
buildDeleteSearchWhere(criteria) {
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
733
|
+
const built = buildSearchWhere(PostgresDialect, criteria, this.schema.properties, (column, value) => this.jsToSqlValue(column, value));
|
|
734
|
+
return { whereClause: built.whereClause, params: built.params };
|
|
735
|
+
}
|
|
736
|
+
async getPage(request = {}) {
|
|
737
|
+
return this.mutex(() => this._getPageInternal(request));
|
|
738
|
+
}
|
|
739
|
+
async _getPageInternal(request) {
|
|
740
|
+
return this.runSqlPage(undefined, request, this.postgresDialect());
|
|
741
|
+
}
|
|
742
|
+
async queryPage(criteria, request = {}) {
|
|
743
|
+
return this.mutex(() => this._queryPageInternal(criteria, request));
|
|
744
|
+
}
|
|
745
|
+
async _queryPageInternal(criteria, request) {
|
|
746
|
+
this.validateQueryParams(criteria, undefined);
|
|
747
|
+
return this.runSqlPage(criteria, request, this.postgresDialect());
|
|
748
|
+
}
|
|
749
|
+
postgresDialect() {
|
|
750
|
+
return {
|
|
751
|
+
quote: '"',
|
|
752
|
+
placeholder: (index) => `$${index}`,
|
|
753
|
+
buildSearchWhere: (criteria, startIndex) => this.buildSearchWhereWithIndex(criteria, startIndex),
|
|
754
|
+
executeSelect: async (sql, params) => {
|
|
755
|
+
const result = await this.db.query(sql, params);
|
|
756
|
+
const rows = result.rows ?? [];
|
|
757
|
+
for (const row of rows) {
|
|
758
|
+
const record = row;
|
|
759
|
+
for (const k in this.schema.properties) {
|
|
760
|
+
record[k] = this.sqlToJsValue(k, record[k]);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return rows;
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
buildSearchWhereWithIndex(criteria, startIndex) {
|
|
768
|
+
const built = buildSearchWhere(PostgresDialect, criteria, this.schema.properties, (column, value) => this.jsToSqlValue(column, value), startIndex);
|
|
501
769
|
return {
|
|
502
|
-
whereClause:
|
|
503
|
-
params
|
|
770
|
+
whereClause: built.whereClause,
|
|
771
|
+
params: built.params,
|
|
772
|
+
nextIndex: startIndex + built.params.length
|
|
504
773
|
};
|
|
505
774
|
}
|
|
506
775
|
async deleteSearch(criteria) {
|
|
776
|
+
return this.mutex(() => this._deleteSearchInternal(criteria));
|
|
777
|
+
}
|
|
778
|
+
async _deleteSearchInternal(criteria) {
|
|
507
779
|
const criteriaKeys = Object.keys(criteria);
|
|
508
780
|
if (criteriaKeys.length === 0) {
|
|
509
781
|
return;
|
|
@@ -514,6 +786,9 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
514
786
|
this.events.emit("delete", criteriaKeys[0]);
|
|
515
787
|
}
|
|
516
788
|
async query(criteria, options) {
|
|
789
|
+
return this.mutex(() => this._queryInternal(criteria, options));
|
|
790
|
+
}
|
|
791
|
+
async _queryInternal(criteria, options) {
|
|
517
792
|
this.validateQueryParams(criteria, options);
|
|
518
793
|
const db = this.db;
|
|
519
794
|
let sql = `SELECT * FROM "${this.table}"`;
|
|
@@ -546,6 +821,9 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
546
821
|
return;
|
|
547
822
|
}
|
|
548
823
|
async queryIndex(criteria, options) {
|
|
824
|
+
return this.mutex(() => this._queryIndexInternal(criteria, options));
|
|
825
|
+
}
|
|
826
|
+
async _queryIndexInternal(criteria, options) {
|
|
549
827
|
this.validateSelect(options);
|
|
550
828
|
this.validateQueryParams(criteria, options);
|
|
551
829
|
const registered = this.indexes.map((cols2, i) => {
|
|
@@ -596,6 +874,51 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
596
874
|
}
|
|
597
875
|
}
|
|
598
876
|
|
|
877
|
+
class PostgresTabularMigrationApplierImpl extends SqlTabularMigrationApplier {
|
|
878
|
+
host;
|
|
879
|
+
constructor(host) {
|
|
880
|
+
super();
|
|
881
|
+
this.host = host;
|
|
882
|
+
}
|
|
883
|
+
dialectName() {
|
|
884
|
+
return "postgres";
|
|
885
|
+
}
|
|
886
|
+
table() {
|
|
887
|
+
return this.host.table;
|
|
888
|
+
}
|
|
889
|
+
storage() {
|
|
890
|
+
return this.host;
|
|
891
|
+
}
|
|
892
|
+
mapTypeToSQL(typeDef) {
|
|
893
|
+
return this.host.mapTypeToSQL(typeDef);
|
|
894
|
+
}
|
|
895
|
+
isNullableSchema(typeDef) {
|
|
896
|
+
return this.host.isNullable(typeDef);
|
|
897
|
+
}
|
|
898
|
+
async executeSql(sql) {
|
|
899
|
+
await this.host.db.query(sql);
|
|
900
|
+
}
|
|
901
|
+
async executeSqlTx(sql, tx) {
|
|
902
|
+
await tx.runMigrationDdl(sql);
|
|
903
|
+
}
|
|
904
|
+
async recordAppliedTx(component, version, description, tx) {
|
|
905
|
+
await tx.recordMigrationApplied(component, version, description ?? null);
|
|
906
|
+
}
|
|
907
|
+
async recordApplied(component, version, description) {
|
|
908
|
+
await this.host.db.query(`INSERT INTO ${MIGRATIONS_TABLE}(component, version, description) VALUES ($1, $2, $3)`, [component, version, description ?? null]);
|
|
909
|
+
}
|
|
910
|
+
async queryAppliedVersions(component) {
|
|
911
|
+
const r = await this.host.db.query(`SELECT version FROM ${MIGRATIONS_TABLE} WHERE component = $1`, [component]);
|
|
912
|
+
return new Set(r.rows.map((row) => Number(row.version)));
|
|
913
|
+
}
|
|
914
|
+
async probeTableExists() {
|
|
915
|
+
const r = await this.host.db.query(`SELECT 1 FROM information_schema.tables WHERE table_name = $1 LIMIT 1`, [
|
|
916
|
+
this.host.table
|
|
917
|
+
]);
|
|
918
|
+
return r.rows.length > 0;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
599
922
|
// src/storage/PostgresKvStorage.ts
|
|
600
923
|
import {
|
|
601
924
|
DefaultKeyValueKey,
|
|
@@ -617,7 +940,12 @@ class PostgresKvStorage extends KvViaTabularStorage {
|
|
|
617
940
|
}
|
|
618
941
|
// src/storage/PostgresVectorStorage.ts
|
|
619
942
|
import { cosineSimilarity } from "@workglow/util/schema";
|
|
620
|
-
import {
|
|
943
|
+
import {
|
|
944
|
+
PostgresDialect as PostgresDialect2,
|
|
945
|
+
StorageValidationError,
|
|
946
|
+
getMetadataProperty,
|
|
947
|
+
getVectorProperty
|
|
948
|
+
} from "@workglow/storage";
|
|
621
949
|
var SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
622
950
|
|
|
623
951
|
class PostgresVectorStorage extends PostgresTabularStorage {
|
|
@@ -625,10 +953,12 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
625
953
|
vectorCtor;
|
|
626
954
|
vectorPropertyName;
|
|
627
955
|
metadataPropertyName;
|
|
628
|
-
|
|
956
|
+
indexOptions;
|
|
957
|
+
constructor(db, table, schema, primaryKeyNames, indexes = [], dimensions, vectorCtor = Float32Array, indexOptions = {}) {
|
|
629
958
|
super(db, table, schema, primaryKeyNames, indexes);
|
|
630
959
|
this.vectorDimensions = dimensions;
|
|
631
960
|
this.vectorCtor = vectorCtor;
|
|
961
|
+
this.indexOptions = indexOptions;
|
|
632
962
|
const vectorProp = getVectorProperty(schema);
|
|
633
963
|
if (!vectorProp) {
|
|
634
964
|
throw new Error("Schema must have a property with type array and format TypedArray");
|
|
@@ -639,20 +969,58 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
639
969
|
getVectorDimensions() {
|
|
640
970
|
return this.vectorDimensions;
|
|
641
971
|
}
|
|
972
|
+
getVectorIndexOptions() {
|
|
973
|
+
return this.indexOptions;
|
|
974
|
+
}
|
|
975
|
+
get distance() {
|
|
976
|
+
return this.indexOptions.distance ?? "cosine";
|
|
977
|
+
}
|
|
978
|
+
get distanceOperator() {
|
|
979
|
+
switch (this.distance) {
|
|
980
|
+
case "l2":
|
|
981
|
+
return "<->";
|
|
982
|
+
case "ip":
|
|
983
|
+
return "<#>";
|
|
984
|
+
default:
|
|
985
|
+
return "<=>";
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
buildScoreExpr(vectorExpr, queryParam) {
|
|
989
|
+
const op = this.distanceOperator;
|
|
990
|
+
switch (this.distance) {
|
|
991
|
+
case "l2":
|
|
992
|
+
return `(1.0 / (1.0 + (${vectorExpr} ${op} ${queryParam}::vector)))`;
|
|
993
|
+
case "ip":
|
|
994
|
+
return `(-1.0 * (${vectorExpr} ${op} ${queryParam}::vector))`;
|
|
995
|
+
default:
|
|
996
|
+
return `(1 - (${vectorExpr} ${op} ${queryParam}::vector))`;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
getQueryTuning() {
|
|
1000
|
+
return {
|
|
1001
|
+
efSearch: this.indexOptions.hnsw?.efSearch,
|
|
1002
|
+
probes: this.indexOptions.ivfflat?.probes
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
642
1005
|
async similaritySearch(query, options = {}) {
|
|
643
1006
|
const { topK = 10, filter, scoreThreshold = 0 } = options;
|
|
644
1007
|
try {
|
|
645
1008
|
const queryVector = `[${Array.from(query).join(",")}]`;
|
|
646
|
-
const
|
|
647
|
-
const
|
|
1009
|
+
const vectorColRaw = String(this.vectorPropertyName);
|
|
1010
|
+
const vectorCol = PostgresDialect2.quoteId(vectorColRaw);
|
|
1011
|
+
const metadataColRaw = this.metadataPropertyName ? String(this.metadataPropertyName) : null;
|
|
1012
|
+
const metadataCol = metadataColRaw ? PostgresDialect2.quoteId(metadataColRaw) : null;
|
|
1013
|
+
const distOp = this.distanceOperator;
|
|
1014
|
+
const scoreExpr = this.buildScoreExpr(vectorCol, "$1");
|
|
648
1015
|
let sql = `
|
|
649
|
-
SELECT
|
|
1016
|
+
SELECT
|
|
650
1017
|
*,
|
|
651
|
-
|
|
1018
|
+
${scoreExpr} as score
|
|
652
1019
|
FROM "${this.table}"
|
|
653
1020
|
`;
|
|
654
1021
|
const params = [queryVector];
|
|
655
1022
|
let paramIndex = 2;
|
|
1023
|
+
let hasWhere = false;
|
|
656
1024
|
if (filter && Object.keys(filter).length > 0 && metadataCol) {
|
|
657
1025
|
const conditions = [];
|
|
658
1026
|
for (const [key, value] of Object.entries(filter)) {
|
|
@@ -663,21 +1031,22 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
663
1031
|
params.push(String(value));
|
|
664
1032
|
paramIndex++;
|
|
665
1033
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
sql += ` (1 - (${vectorCol} <=> $1::vector)) >= $${paramIndex}`;
|
|
671
|
-
params.push(scoreThreshold);
|
|
672
|
-
paramIndex++;
|
|
1034
|
+
if (conditions.length > 0) {
|
|
1035
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
1036
|
+
hasWhere = true;
|
|
1037
|
+
}
|
|
673
1038
|
}
|
|
674
|
-
sql +=
|
|
1039
|
+
sql += hasWhere ? " AND" : " WHERE";
|
|
1040
|
+
sql += ` ${scoreExpr} >= $${paramIndex}`;
|
|
1041
|
+
params.push(scoreThreshold);
|
|
1042
|
+
paramIndex++;
|
|
1043
|
+
sql += ` ORDER BY ${vectorCol} ${distOp} $1::vector LIMIT $${paramIndex}`;
|
|
675
1044
|
params.push(topK);
|
|
676
1045
|
const result = await this.db.query(sql, params);
|
|
677
1046
|
const results = [];
|
|
678
1047
|
for (const row of result.rows) {
|
|
679
1048
|
const vectorResult = await this.db.query(`SELECT ${vectorCol}::text FROM "${this.table}" WHERE ${this.getPrimaryKeyWhereClause()}`, this.getPrimaryKeyValues(row));
|
|
680
|
-
const vectorStr = vectorResult.rows[0]?.[
|
|
1049
|
+
const vectorStr = vectorResult.rows[0]?.[vectorColRaw] || "[]";
|
|
681
1050
|
const vectorArray = JSON.parse(vectorStr);
|
|
682
1051
|
results.push({
|
|
683
1052
|
...row,
|
|
@@ -702,19 +1071,24 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
702
1071
|
try {
|
|
703
1072
|
const queryVector = `[${Array.from(query).join(",")}]`;
|
|
704
1073
|
const tsQueryText = textQuery;
|
|
705
|
-
const
|
|
706
|
-
const
|
|
1074
|
+
const vectorColRaw = String(this.vectorPropertyName);
|
|
1075
|
+
const vectorCol = PostgresDialect2.quoteId(vectorColRaw);
|
|
1076
|
+
const metadataColRaw = this.metadataPropertyName ? String(this.metadataPropertyName) : null;
|
|
1077
|
+
const metadataCol = metadataColRaw ? PostgresDialect2.quoteId(metadataColRaw) : null;
|
|
1078
|
+
const vectorScoreExpr = this.buildScoreExpr(vectorCol, "$1");
|
|
1079
|
+
const combinedScoreExpr = `(
|
|
1080
|
+
$2 * ${vectorScoreExpr} +
|
|
1081
|
+
$3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
|
|
1082
|
+
)`;
|
|
707
1083
|
let sql = `
|
|
708
|
-
SELECT
|
|
1084
|
+
SELECT
|
|
709
1085
|
*,
|
|
710
|
-
|
|
711
|
-
$2 * (1 - (${vectorCol} <=> $1::vector)) +
|
|
712
|
-
$3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
|
|
713
|
-
) as score
|
|
1086
|
+
${combinedScoreExpr} as score
|
|
714
1087
|
FROM "${this.table}"
|
|
715
1088
|
`;
|
|
716
1089
|
const params = [queryVector, vectorWeight, 1 - vectorWeight, tsQueryText];
|
|
717
1090
|
let paramIndex = 5;
|
|
1091
|
+
let hasWhere = false;
|
|
718
1092
|
if (filter && Object.keys(filter).length > 0 && metadataCol) {
|
|
719
1093
|
const conditions = [];
|
|
720
1094
|
for (const [key, value] of Object.entries(filter)) {
|
|
@@ -725,24 +1099,22 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
725
1099
|
params.push(String(value));
|
|
726
1100
|
paramIndex++;
|
|
727
1101
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
sql += ` (
|
|
733
|
-
$2 * (1 - (${vectorCol} <=> $1::vector)) +
|
|
734
|
-
$3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
|
|
735
|
-
) >= $${paramIndex}`;
|
|
736
|
-
params.push(scoreThreshold);
|
|
737
|
-
paramIndex++;
|
|
1102
|
+
if (conditions.length > 0) {
|
|
1103
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
1104
|
+
hasWhere = true;
|
|
1105
|
+
}
|
|
738
1106
|
}
|
|
1107
|
+
sql += hasWhere ? " AND" : " WHERE";
|
|
1108
|
+
sql += ` ${combinedScoreExpr} >= $${paramIndex}`;
|
|
1109
|
+
params.push(scoreThreshold);
|
|
1110
|
+
paramIndex++;
|
|
739
1111
|
sql += ` ORDER BY score DESC LIMIT $${paramIndex}`;
|
|
740
1112
|
params.push(topK);
|
|
741
1113
|
const result = await this.db.query(sql, params);
|
|
742
1114
|
const results = [];
|
|
743
1115
|
for (const row of result.rows) {
|
|
744
1116
|
const vectorResult = await this.db.query(`SELECT ${vectorCol}::text FROM "${this.table}" WHERE ${this.getPrimaryKeyWhereClause()}`, this.getPrimaryKeyValues(row));
|
|
745
|
-
const vectorStr = vectorResult.rows[0]?.[
|
|
1117
|
+
const vectorStr = vectorResult.rows[0]?.[vectorColRaw] || "[]";
|
|
746
1118
|
const vectorArray = JSON.parse(vectorStr);
|
|
747
1119
|
results.push({
|
|
748
1120
|
...row,
|
|
@@ -759,7 +1131,13 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
759
1131
|
return this.hybridSearchFallback(query, options);
|
|
760
1132
|
}
|
|
761
1133
|
}
|
|
1134
|
+
assertFallbackSupportsDistance() {
|
|
1135
|
+
if (this.distance !== "cosine") {
|
|
1136
|
+
throw new Error(`PostgresVectorStorage: pgvector is unavailable and the in-memory ` + `fallback only supports cosine distance (configured: "${this.distance}"). ` + `Install pgvector or switch the storage to cosine distance.`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
762
1139
|
async searchFallback(query, options) {
|
|
1140
|
+
this.assertFallbackSupportsDistance();
|
|
763
1141
|
const { topK = 10, filter, scoreThreshold = 0 } = options;
|
|
764
1142
|
const allRows = await this.getAll() || [];
|
|
765
1143
|
const results = [];
|
|
@@ -779,6 +1157,7 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
779
1157
|
return topResults;
|
|
780
1158
|
}
|
|
781
1159
|
async hybridSearchFallback(query, options) {
|
|
1160
|
+
this.assertFallbackSupportsDistance();
|
|
782
1161
|
const { topK = 10, filter, scoreThreshold = 0, textQuery, vectorWeight = 0.7 } = options;
|
|
783
1162
|
const allRows = await this.getAll() || [];
|
|
784
1163
|
const results = [];
|
|
@@ -827,16 +1206,126 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
827
1206
|
return true;
|
|
828
1207
|
}
|
|
829
1208
|
}
|
|
1209
|
+
// src/migrations/PostgresMigrationRunner.ts
|
|
1210
|
+
import {
|
|
1211
|
+
MIGRATIONS_TABLE as MIGRATIONS_TABLE2,
|
|
1212
|
+
sortMigrations
|
|
1213
|
+
} from "@workglow/storage";
|
|
1214
|
+
|
|
1215
|
+
class PostgresMigrationRunner {
|
|
1216
|
+
db;
|
|
1217
|
+
constructor(db) {
|
|
1218
|
+
this.db = db;
|
|
1219
|
+
}
|
|
1220
|
+
async ensureBookkeepingTable() {
|
|
1221
|
+
await this.db.query(`
|
|
1222
|
+
CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE2} (
|
|
1223
|
+
component TEXT NOT NULL,
|
|
1224
|
+
version INTEGER NOT NULL,
|
|
1225
|
+
description TEXT,
|
|
1226
|
+
applied_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
1227
|
+
PRIMARY KEY (component, version)
|
|
1228
|
+
)
|
|
1229
|
+
`);
|
|
1230
|
+
}
|
|
1231
|
+
async appliedVersions(component) {
|
|
1232
|
+
const result = await this.db.query(`SELECT version FROM ${MIGRATIONS_TABLE2} WHERE component = $1`, [component]);
|
|
1233
|
+
return new Set(result.rows.map((r) => Number(r.version)));
|
|
1234
|
+
}
|
|
1235
|
+
async acquireClient() {
|
|
1236
|
+
const pool = this.db;
|
|
1237
|
+
if (typeof pool.connect === "function") {
|
|
1238
|
+
const client = await pool.connect();
|
|
1239
|
+
return { client, release: () => client.release() };
|
|
1240
|
+
}
|
|
1241
|
+
return { client: this.db, release: () => {
|
|
1242
|
+
return;
|
|
1243
|
+
} };
|
|
1244
|
+
}
|
|
1245
|
+
async run(migrations, options = {}) {
|
|
1246
|
+
await this.ensureBookkeepingTable();
|
|
1247
|
+
const sorted = sortMigrations(migrations);
|
|
1248
|
+
const applied = [];
|
|
1249
|
+
const cache = new Map;
|
|
1250
|
+
const onProgress = options.onProgress;
|
|
1251
|
+
for (const m of sorted) {
|
|
1252
|
+
let seen = cache.get(m.component);
|
|
1253
|
+
if (!seen) {
|
|
1254
|
+
seen = await this.appliedVersions(m.component);
|
|
1255
|
+
cache.set(m.component, seen);
|
|
1256
|
+
}
|
|
1257
|
+
if (seen.has(m.version))
|
|
1258
|
+
continue;
|
|
1259
|
+
onProgress?.({
|
|
1260
|
+
component: m.component,
|
|
1261
|
+
version: m.version,
|
|
1262
|
+
phase: "starting",
|
|
1263
|
+
description: m.description
|
|
1264
|
+
});
|
|
1265
|
+
const { client, release } = await this.acquireClient();
|
|
1266
|
+
try {
|
|
1267
|
+
await client.query("BEGIN");
|
|
1268
|
+
await m.up(client, (fraction) => {
|
|
1269
|
+
onProgress?.({
|
|
1270
|
+
component: m.component,
|
|
1271
|
+
version: m.version,
|
|
1272
|
+
phase: "running",
|
|
1273
|
+
description: m.description,
|
|
1274
|
+
fraction
|
|
1275
|
+
});
|
|
1276
|
+
});
|
|
1277
|
+
await client.query(`INSERT INTO ${MIGRATIONS_TABLE2}(component, version, description) VALUES ($1, $2, $3)`, [m.component, m.version, m.description ?? null]);
|
|
1278
|
+
await client.query("COMMIT");
|
|
1279
|
+
seen.add(m.version);
|
|
1280
|
+
applied.push(m);
|
|
1281
|
+
onProgress?.({
|
|
1282
|
+
component: m.component,
|
|
1283
|
+
version: m.version,
|
|
1284
|
+
phase: "completed",
|
|
1285
|
+
description: m.description,
|
|
1286
|
+
fraction: 1
|
|
1287
|
+
});
|
|
1288
|
+
} catch (err) {
|
|
1289
|
+
await client.query("ROLLBACK").catch(() => {
|
|
1290
|
+
return;
|
|
1291
|
+
});
|
|
1292
|
+
if (err?.code === "23505") {
|
|
1293
|
+
seen.add(m.version);
|
|
1294
|
+
onProgress?.({
|
|
1295
|
+
component: m.component,
|
|
1296
|
+
version: m.version,
|
|
1297
|
+
phase: "completed",
|
|
1298
|
+
description: m.description,
|
|
1299
|
+
fraction: 1
|
|
1300
|
+
});
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
onProgress?.({
|
|
1304
|
+
component: m.component,
|
|
1305
|
+
version: m.version,
|
|
1306
|
+
phase: "failed",
|
|
1307
|
+
description: m.description,
|
|
1308
|
+
error: err
|
|
1309
|
+
});
|
|
1310
|
+
throw err;
|
|
1311
|
+
} finally {
|
|
1312
|
+
release();
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return applied;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
830
1318
|
export {
|
|
831
1319
|
loadPostgres,
|
|
832
1320
|
getPostgres,
|
|
833
1321
|
createPool,
|
|
834
1322
|
PostgresVectorStorage,
|
|
835
1323
|
PostgresTabularStorage,
|
|
1324
|
+
PostgresMigrationRunner,
|
|
836
1325
|
PostgresKvStorage,
|
|
837
1326
|
Postgres,
|
|
838
1327
|
POSTGRES_TABULAR_REPOSITORY,
|
|
839
1328
|
POSTGRES_KV_REPOSITORY
|
|
840
1329
|
};
|
|
841
1330
|
|
|
842
|
-
//# debugId=
|
|
1331
|
+
//# debugId=1C3E72F1BE3B8A0064756E2164756E21
|