@mastra/pg 1.19.0-alpha.0 → 1.19.0-alpha.1
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/CHANGELOG.md +9 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +298 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +298 -24
- package/dist/index.js.map +1 -1
- package/dist/storage/client.d.ts +13 -1
- package/dist/storage/client.d.ts.map +1 -1
- package/dist/storage/db/index.d.ts +63 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/schema-snapshot.d.ts +71 -0
- package/dist/storage/db/schema-snapshot.d.ts.map +1 -0
- package/dist/storage/domains/agents/index.d.ts.map +1 -1
- package/dist/storage/domains/datasets/index.d.ts.map +1 -1
- package/dist/storage/domains/favorites/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 1.19.0-alpha.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Improved PostgresStore startup: init now reads the schema catalog up front (3 read-only queries) instead of issuing hundreds of per-object existence checks and no-op DDL statements. On an already-migrated database this cuts init from ~350 serialized queries to 6, dropping init time on a 50ms connection from ~18.5s to ~0.5s. Fixed init failing for roles without CREATE privileges when the schema already exists, and removed a table lock that could block writers while init re-created triggers that were already in place. Fresh and partially-migrated databases are set up exactly as before. ([#20394](https://github.com/mastra-ai/mastra/pull/20394))
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`594f7b2`](https://github.com/mastra-ai/mastra/commit/594f7b28f5263fb9982fd50d95c471fb971ea984), [`311f943`](https://github.com/mastra-ai/mastra/commit/311f943bee60e8fdf5c84499ea50e884276c936c), [`0c89896`](https://github.com/mastra-ai/mastra/commit/0c8989673fb7d106837098398131e570c6023b68), [`23b4238`](https://github.com/mastra-ai/mastra/commit/23b423844ad0bcf2a502a68dd62866d6160f9f6d), [`e320a76`](https://github.com/mastra-ai/mastra/commit/e320a763feaf65c6be3cebecf746defcbde161b3), [`03b4918`](https://github.com/mastra-ai/mastra/commit/03b4918c80d188ce375334c393e131c6e94bd7eb), [`14ef73a`](https://github.com/mastra-ai/mastra/commit/14ef73a4bbd73e7808414816eb0628ce1d80b5d7), [`1d677d5`](https://github.com/mastra-ai/mastra/commit/1d677d5f99d7db403f7828585e8c25f299f72628), [`93e28ec`](https://github.com/mastra-ai/mastra/commit/93e28ecce9031c02397e0ae8406593e5c7a95883), [`729dab4`](https://github.com/mastra-ai/mastra/commit/729dab408faccfaef0cbb048e5a4338f9172847e), [`484003d`](https://github.com/mastra-ai/mastra/commit/484003d33ff59330c86b19863e4a38732d7e4155), [`933d291`](https://github.com/mastra-ai/mastra/commit/933d291146b789c19442ad206f94da3e4be90c64)]:
|
|
10
|
+
- @mastra/core@1.56.0-alpha.3
|
|
11
|
+
|
|
3
12
|
## 1.19.0-alpha.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
package/dist/docs/SKILL.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1971,9 +1971,25 @@ var PinnedClientAdapter = class {
|
|
|
1971
1971
|
var RoutingDbClient = class {
|
|
1972
1972
|
#base;
|
|
1973
1973
|
#pinned = null;
|
|
1974
|
+
#schemaSnapshot = null;
|
|
1974
1975
|
constructor(base) {
|
|
1975
1976
|
this.#base = base;
|
|
1976
1977
|
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Catalog snapshot for the current init window, or `null` outside it.
|
|
1980
|
+
*
|
|
1981
|
+
* It lives here rather than on `PgDB` because every storage domain builds its
|
|
1982
|
+
* own `PgDB` over this one shared client — hanging the snapshot off the
|
|
1983
|
+
* client means one load serves all of them, and its lifetime lines up exactly
|
|
1984
|
+
* with the pinned-init window that `pin()`/`unpin()` already delimit.
|
|
1985
|
+
*/
|
|
1986
|
+
get schemaSnapshot() {
|
|
1987
|
+
return this.#schemaSnapshot;
|
|
1988
|
+
}
|
|
1989
|
+
/** Install (or clear, with `null`) the init-window catalog snapshot. */
|
|
1990
|
+
setSchemaSnapshot(snapshot) {
|
|
1991
|
+
this.#schemaSnapshot = snapshot;
|
|
1992
|
+
}
|
|
1977
1993
|
/** Returns the currently active client (pinned if set, otherwise base). */
|
|
1978
1994
|
get active() {
|
|
1979
1995
|
return this.#pinned ?? this.#base;
|
|
@@ -2074,6 +2090,67 @@ function isDuplicateSchemaError(error) {
|
|
|
2074
2090
|
return /schema .* already exists/i.test(message);
|
|
2075
2091
|
}
|
|
2076
2092
|
//#endregion
|
|
2093
|
+
//#region src/storage/db/schema-snapshot.ts
|
|
2094
|
+
/**
|
|
2095
|
+
* Returns the snapshot currently installed on `client`, but only when it was
|
|
2096
|
+
* taken from the schema the caller operates on. A store configured for another
|
|
2097
|
+
* schema (or a client with no snapshot at all) gets `null` and falls back to
|
|
2098
|
+
* probing the live catalog.
|
|
2099
|
+
*/
|
|
2100
|
+
function getSchemaSnapshot(client, schemaName) {
|
|
2101
|
+
const snapshot = client?.schemaSnapshot;
|
|
2102
|
+
if (!snapshot) return null;
|
|
2103
|
+
return snapshot.schemaName === (schemaName || "public") ? snapshot : null;
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Reads the catalog for `schemaName` in three queries. Must be called on the
|
|
2107
|
+
* pinned init client so the snapshot reflects what that connection will see.
|
|
2108
|
+
*/
|
|
2109
|
+
async function loadSchemaSnapshot(client, schemaName) {
|
|
2110
|
+
const schema = schemaName || "public";
|
|
2111
|
+
const [tableRows, columnRows, indexRows] = await Promise.all([
|
|
2112
|
+
client.manyOrNone(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = $1`, [schema]),
|
|
2113
|
+
client.manyOrNone(`SELECT c.relname AS table_name, a.attname AS column_name
|
|
2114
|
+
FROM pg_catalog.pg_class c
|
|
2115
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
2116
|
+
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
|
|
2117
|
+
WHERE n.nspname = $1
|
|
2118
|
+
AND c.relkind IN ('r', 'p')
|
|
2119
|
+
AND a.attnum > 0
|
|
2120
|
+
AND NOT a.attisdropped`, [schema]),
|
|
2121
|
+
client.manyOrNone(`SELECT c.relname AS indexname, i.indisreplident AS is_replica_identity, i.indisprimary AS is_primary
|
|
2122
|
+
FROM pg_catalog.pg_index i
|
|
2123
|
+
JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid
|
|
2124
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
2125
|
+
WHERE n.nspname = $1`, [schema])
|
|
2126
|
+
]);
|
|
2127
|
+
const columns = /* @__PURE__ */ new Map();
|
|
2128
|
+
for (const row of columnRows) {
|
|
2129
|
+
let set = columns.get(row.table_name);
|
|
2130
|
+
if (!set) {
|
|
2131
|
+
set = /* @__PURE__ */ new Set();
|
|
2132
|
+
columns.set(row.table_name, set);
|
|
2133
|
+
}
|
|
2134
|
+
set.add(row.column_name);
|
|
2135
|
+
}
|
|
2136
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
2137
|
+
const replicaIdentityIndexes = /* @__PURE__ */ new Set();
|
|
2138
|
+
const primaryKeyIndexes = /* @__PURE__ */ new Set();
|
|
2139
|
+
for (const row of indexRows) {
|
|
2140
|
+
indexes.add(row.indexname);
|
|
2141
|
+
if (row.is_replica_identity) replicaIdentityIndexes.add(row.indexname);
|
|
2142
|
+
if (row.is_primary) primaryKeyIndexes.add(row.indexname.toLowerCase());
|
|
2143
|
+
}
|
|
2144
|
+
return {
|
|
2145
|
+
schemaName: schema,
|
|
2146
|
+
tables: new Set(tableRows.map((r) => r.tablename)),
|
|
2147
|
+
columns,
|
|
2148
|
+
indexes,
|
|
2149
|
+
replicaIdentityIndexes,
|
|
2150
|
+
primaryKeyIndexes
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
//#endregion
|
|
2077
2154
|
//#region src/storage/db/index.ts
|
|
2078
2155
|
/**
|
|
2079
2156
|
* Resolves PgDomainConfig to a database client and schema.
|
|
@@ -2253,7 +2330,8 @@ function generateTimestampTriggerSQL(tableName, schemaName) {
|
|
|
2253
2330
|
schemaName: quotedSchemaName
|
|
2254
2331
|
});
|
|
2255
2332
|
const functionName = `${quotedSchemaName}.trigger_set_timestamps`;
|
|
2256
|
-
const
|
|
2333
|
+
const parsedTriggerName = (0, _mastra_core_utils.parseSqlIdentifier)(`${tableName}_timestamps`, "trigger name");
|
|
2334
|
+
const triggerName = `"${parsedTriggerName}"`;
|
|
2257
2335
|
return `CREATE OR REPLACE FUNCTION ${functionName}()
|
|
2258
2336
|
RETURNS TRIGGER AS $$
|
|
2259
2337
|
BEGIN
|
|
@@ -2272,12 +2350,38 @@ BEGIN
|
|
|
2272
2350
|
END;
|
|
2273
2351
|
$$ LANGUAGE plpgsql;
|
|
2274
2352
|
|
|
2275
|
-
|
|
2353
|
+
DO $mastra_timestamps_trigger$
|
|
2354
|
+
BEGIN
|
|
2355
|
+
-- Recreating the trigger unconditionally would take an ACCESS EXCLUSIVE
|
|
2356
|
+
-- lock on the table (DROP TRIGGER does, even when nothing changes), and
|
|
2357
|
+
-- init runs on every process start. Skip when the trigger is already
|
|
2358
|
+
-- exactly what the CREATE below would produce.
|
|
2359
|
+
--
|
|
2360
|
+
-- tgtype 23 = ROW (1) | BEFORE (2) | INSERT (4) | UPDATE (16), so a trigger
|
|
2361
|
+
-- whose timing or events differ still falls through and gets rebuilt. The
|
|
2362
|
+
-- behaviour itself lives in the function, which is replaced above on every
|
|
2363
|
+
-- init, so an upgraded function body lands without touching the trigger.
|
|
2364
|
+
IF NOT EXISTS (
|
|
2365
|
+
SELECT 1
|
|
2366
|
+
FROM pg_catalog.pg_trigger tg
|
|
2367
|
+
JOIN pg_catalog.pg_class c ON c.oid = tg.tgrelid
|
|
2368
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
2369
|
+
WHERE tg.tgname = ${`'${parsedTriggerName}'`}
|
|
2370
|
+
AND c.relname = ${`'${(0, _mastra_core_utils.parseSqlIdentifier)(tableName, "table name")}'`}
|
|
2371
|
+
AND n.nspname = ${schemaName ? `'${(0, _mastra_core_utils.parseSqlIdentifier)(schemaName, "schema name")}'` : `'public'`}
|
|
2372
|
+
AND NOT tg.tgisinternal
|
|
2373
|
+
AND tg.tgtype = 23
|
|
2374
|
+
AND tg.tgfoid = '${functionName}()'::regprocedure
|
|
2375
|
+
) THEN
|
|
2376
|
+
DROP TRIGGER IF EXISTS ${triggerName} ON ${fullTableName};
|
|
2276
2377
|
|
|
2277
|
-
CREATE TRIGGER ${triggerName}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2378
|
+
CREATE TRIGGER ${triggerName}
|
|
2379
|
+
BEFORE INSERT OR UPDATE ON ${fullTableName}
|
|
2380
|
+
FOR EACH ROW
|
|
2381
|
+
EXECUTE FUNCTION ${functionName}();
|
|
2382
|
+
END IF;
|
|
2383
|
+
END
|
|
2384
|
+
$mastra_timestamps_trigger$;`;
|
|
2281
2385
|
}
|
|
2282
2386
|
const schemaSetupRegistry = /* @__PURE__ */ new Map();
|
|
2283
2387
|
/**
|
|
@@ -2304,6 +2408,99 @@ var PgDB = class extends _mastra_core_base.MastraBase {
|
|
|
2304
2408
|
this.skipDefaultIndexes = config.skipDefaultIndexes;
|
|
2305
2409
|
}
|
|
2306
2410
|
/**
|
|
2411
|
+
* Catalog snapshot for the current init window, or `null` outside it.
|
|
2412
|
+
*
|
|
2413
|
+
* When non-null, the init-path methods below answer existence questions from
|
|
2414
|
+
* it instead of round-tripping to the server, and record the objects they
|
|
2415
|
+
* create so later callers in the same init see them. See
|
|
2416
|
+
* {@link SchemaSnapshot} for why it is scoped to init only.
|
|
2417
|
+
*/
|
|
2418
|
+
get schemaSnapshot() {
|
|
2419
|
+
return getSchemaSnapshot(this.client, this.schemaName);
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Whether the snapshot proves `generateTableSQL` would be a no-op for this
|
|
2423
|
+
* table — i.e. the CREATE statement can be skipped.
|
|
2424
|
+
*
|
|
2425
|
+
* For most tables that is just "the table exists". `workflow_snapshot` is the
|
|
2426
|
+
* exception: its generated SQL also carries a DO block that back-fills the
|
|
2427
|
+
* `(workflow_name, run_id)` unique constraint and promotes it to the table's
|
|
2428
|
+
* replica identity, so a table created by an older version still needs the
|
|
2429
|
+
* statement to run.
|
|
2430
|
+
*/
|
|
2431
|
+
snapshotShowsTableConverged(snapshot, tableName) {
|
|
2432
|
+
if (!snapshot.tables.has(tableName)) return false;
|
|
2433
|
+
if (tableName === _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT) {
|
|
2434
|
+
const constraintName = buildConstraintName({
|
|
2435
|
+
baseName: "mastra_workflow_snapshot_workflow_name_run_id_key",
|
|
2436
|
+
schemaName: this.schemaName ? (0, _mastra_core_utils.parseSqlIdentifier)(this.schemaName, "schema name") : void 0
|
|
2437
|
+
}).toLowerCase();
|
|
2438
|
+
return snapshot.indexes.has(constraintName) && snapshot.replicaIdentityIndexes.has(constraintName);
|
|
2439
|
+
}
|
|
2440
|
+
return true;
|
|
2441
|
+
}
|
|
2442
|
+
/** Column set for `tableName` in the snapshot, created empty if absent. */
|
|
2443
|
+
snapshotColumns(snapshot, tableName) {
|
|
2444
|
+
let columns = snapshot.columns.get(tableName);
|
|
2445
|
+
if (!columns) {
|
|
2446
|
+
columns = /* @__PURE__ */ new Set();
|
|
2447
|
+
snapshot.columns.set(tableName, columns);
|
|
2448
|
+
}
|
|
2449
|
+
return columns;
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Records an out-of-band `ALTER TABLE … RENAME TO` in the init snapshot.
|
|
2453
|
+
*
|
|
2454
|
+
* Init-time migrations that issue raw DDL on `this.client` (instead of going
|
|
2455
|
+
* through createTable/alterTable/createIndex, which maintain the snapshot
|
|
2456
|
+
* themselves) MUST report it through these `note*` methods. A snapshot that
|
|
2457
|
+
* still lists a renamed-away table makes a later createTable() in the same
|
|
2458
|
+
* init skip the rebuild the migration depends on — stranding data. No-op
|
|
2459
|
+
* outside the init window.
|
|
2460
|
+
*
|
|
2461
|
+
* Indexes riding along with a rename keep their names, so the snapshot's
|
|
2462
|
+
* index set stays accurate without changes here.
|
|
2463
|
+
*/
|
|
2464
|
+
noteTableRenamed(oldName, newName) {
|
|
2465
|
+
const snapshot = this.schemaSnapshot;
|
|
2466
|
+
if (snapshot) {
|
|
2467
|
+
if (snapshot.tables.delete(oldName)) snapshot.tables.add(newName);
|
|
2468
|
+
const columns = snapshot.columns.get(oldName);
|
|
2469
|
+
if (columns) {
|
|
2470
|
+
snapshot.columns.delete(oldName);
|
|
2471
|
+
snapshot.columns.set(newName, columns);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
this.tableColumnsCache.delete(oldName);
|
|
2475
|
+
this.tableColumnsCache.delete(newName);
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Records an out-of-band `DROP TABLE` in the init snapshot. See
|
|
2479
|
+
* {@link noteTableRenamed} for why raw-DDL migrations must call this.
|
|
2480
|
+
*
|
|
2481
|
+
* The dropped table's indexes vanish with it, but the snapshot's flat index
|
|
2482
|
+
* set cannot map names back to tables. Stale entries only make a later
|
|
2483
|
+
* createIndex() skip a recreate until the next init re-reads the catalog —
|
|
2484
|
+
* the same self-healing bound the rest of the snapshot design accepts.
|
|
2485
|
+
*/
|
|
2486
|
+
noteTableDropped(tableName) {
|
|
2487
|
+
const snapshot = this.schemaSnapshot;
|
|
2488
|
+
if (snapshot) {
|
|
2489
|
+
snapshot.tables.delete(tableName);
|
|
2490
|
+
snapshot.columns.delete(tableName);
|
|
2491
|
+
}
|
|
2492
|
+
this.tableColumnsCache.delete(tableName);
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* Records an out-of-band `ALTER TABLE … ADD COLUMN` in the init snapshot.
|
|
2496
|
+
* See {@link noteTableRenamed} for why raw-DDL migrations must call this.
|
|
2497
|
+
*/
|
|
2498
|
+
noteColumnAdded(tableName, column) {
|
|
2499
|
+
const snapshot = this.schemaSnapshot;
|
|
2500
|
+
if (snapshot) this.snapshotColumns(snapshot, tableName).add(column);
|
|
2501
|
+
this.tableColumnsCache.delete(tableName);
|
|
2502
|
+
}
|
|
2503
|
+
/**
|
|
2307
2504
|
* Gets the set of column names that actually exist in the database table.
|
|
2308
2505
|
* Results are cached; the cache is invalidated when alterTable() adds new columns.
|
|
2309
2506
|
*/
|
|
@@ -2330,6 +2527,12 @@ var PgDB = class extends _mastra_core_base.MastraBase {
|
|
|
2330
2527
|
}
|
|
2331
2528
|
async hasColumn(table, column) {
|
|
2332
2529
|
const schema = this.schemaName || "public";
|
|
2530
|
+
const snapshot = this.schemaSnapshot;
|
|
2531
|
+
if (snapshot) {
|
|
2532
|
+
const columns = snapshot.columns.get(table);
|
|
2533
|
+
if (!columns) return false;
|
|
2534
|
+
return columns.has(column) || columns.has(column.toLowerCase());
|
|
2535
|
+
}
|
|
2333
2536
|
return !!await this.client.oneOrNone(`SELECT 1 FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND (column_name = $3 OR column_name = $4)`, [
|
|
2334
2537
|
schema,
|
|
2335
2538
|
table,
|
|
@@ -2368,6 +2571,14 @@ var PgDB = class extends _mastra_core_base.MastraBase {
|
|
|
2368
2571
|
if (!this.schemaName) return;
|
|
2369
2572
|
let registryEntry = schemaSetupRegistry.get(this.schemaName);
|
|
2370
2573
|
if (registryEntry?.complete) return;
|
|
2574
|
+
const snapshot = this.schemaSnapshot;
|
|
2575
|
+
if (snapshot && snapshot.tables.size > 0) {
|
|
2576
|
+
schemaSetupRegistry.set(this.schemaName, {
|
|
2577
|
+
promise: null,
|
|
2578
|
+
complete: true
|
|
2579
|
+
});
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2371
2582
|
const quotedSchemaName = getSchemaName$6(this.schemaName);
|
|
2372
2583
|
if (!registryEntry?.promise) {
|
|
2373
2584
|
const schemaNameCapture = this.schemaName;
|
|
@@ -2472,16 +2683,28 @@ var PgDB = class extends _mastra_core_base.MastraBase {
|
|
|
2472
2683
|
try {
|
|
2473
2684
|
const timeZColumnNames = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => name);
|
|
2474
2685
|
if (this.schemaName) await this.setupSchema();
|
|
2475
|
-
const
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2686
|
+
const snapshot = this.schemaSnapshot;
|
|
2687
|
+
if (!snapshot || !this.snapshotShowsTableConverged(snapshot, tableName)) {
|
|
2688
|
+
const sql = generateTableSQL({
|
|
2689
|
+
tableName,
|
|
2690
|
+
schema,
|
|
2691
|
+
schemaName: this.schemaName,
|
|
2692
|
+
compositePrimaryKey
|
|
2693
|
+
});
|
|
2694
|
+
try {
|
|
2695
|
+
await this.client.none(sql);
|
|
2696
|
+
} catch (error) {
|
|
2697
|
+
if (!isDuplicateRelationError(error)) throw error;
|
|
2698
|
+
}
|
|
2699
|
+
if (snapshot) {
|
|
2700
|
+
snapshot.tables.add(tableName);
|
|
2701
|
+
const created = this.snapshotColumns(snapshot, tableName);
|
|
2702
|
+
for (const [name, def] of Object.entries(schema)) {
|
|
2703
|
+
const parsedName = (0, _mastra_core_utils.parseSqlIdentifier)(name, "column name");
|
|
2704
|
+
created.add(parsedName);
|
|
2705
|
+
if (def.type === "timestamp") created.add(`${parsedName}Z`);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2485
2708
|
}
|
|
2486
2709
|
await this.alterTable({
|
|
2487
2710
|
tableName,
|
|
@@ -2547,10 +2770,12 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2547
2770
|
const sqlType = mapToSqlType(columnDef.type);
|
|
2548
2771
|
const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${columnDef.nullable ? "" : "NOT NULL"} ${!columnDef.nullable ? this.getDefaultValue(columnDef.type) : ""}`.trim();
|
|
2549
2772
|
await this.client.none(alterSql);
|
|
2773
|
+
this.noteColumnAdded(_mastra_core_storage.TABLE_SPANS, columnName);
|
|
2550
2774
|
this.logger?.debug?.(`Added column '${columnName}' to ${fullTableName}`);
|
|
2551
2775
|
if (sqlType === "TIMESTAMP") {
|
|
2552
2776
|
const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}Z" TIMESTAMPTZ DEFAULT NOW()`.trim();
|
|
2553
2777
|
await this.client.none(timestampZSql);
|
|
2778
|
+
this.noteColumnAdded(_mastra_core_storage.TABLE_SPANS, `${columnName}Z`);
|
|
2554
2779
|
this.logger?.debug?.(`Added timezone column '${columnName}Z' to ${fullTableName}`);
|
|
2555
2780
|
}
|
|
2556
2781
|
}
|
|
@@ -2559,6 +2784,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2559
2784
|
if (!await this.hasColumn(_mastra_core_storage.TABLE_SPANS, tzColumnName)) {
|
|
2560
2785
|
const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${(0, _mastra_core_utils.parseSqlIdentifier)(tzColumnName, "column name")}" TIMESTAMPTZ DEFAULT NOW()`.trim();
|
|
2561
2786
|
await this.client.none(timestampZSql);
|
|
2787
|
+
this.noteColumnAdded(_mastra_core_storage.TABLE_SPANS, tzColumnName);
|
|
2562
2788
|
this.logger?.debug?.(`Added timezone column '${tzColumnName}' to ${fullTableName}`);
|
|
2563
2789
|
}
|
|
2564
2790
|
}
|
|
@@ -2681,6 +2907,12 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2681
2907
|
schemaName: (this.schemaName ? (0, _mastra_core_utils.parseSqlIdentifier)(this.schemaName, "schema name") : "") || void 0
|
|
2682
2908
|
});
|
|
2683
2909
|
const schemaFilter = this.schemaName || "public";
|
|
2910
|
+
const snapshot = this.schemaSnapshot;
|
|
2911
|
+
if (snapshot) return snapshot.primaryKeyIndexes.has(constraintName.toLowerCase());
|
|
2912
|
+
return this.spansPrimaryKeyExistsLive(constraintName, schemaFilter);
|
|
2913
|
+
}
|
|
2914
|
+
/** Live-catalog variant of {@link spansPrimaryKeyExists}, bypassing the snapshot. */
|
|
2915
|
+
async spansPrimaryKeyExistsLive(constraintName, schemaFilter) {
|
|
2684
2916
|
return (await this.client.oneOrNone(`SELECT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = lower($1) AND connamespace = (SELECT oid FROM pg_namespace WHERE nspname = $2)) as exists`, [constraintName, schemaFilter]))?.exists ?? false;
|
|
2685
2917
|
}
|
|
2686
2918
|
/**
|
|
@@ -2711,10 +2943,12 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2711
2943
|
ADD CONSTRAINT ${constraintName}
|
|
2712
2944
|
PRIMARY KEY ("traceId", "spanId")
|
|
2713
2945
|
`);
|
|
2946
|
+
this.schemaSnapshot?.primaryKeyIndexes.add(constraintName.toLowerCase());
|
|
2714
2947
|
this.logger?.info?.(`Added PRIMARY KEY constraint ${constraintName} to ${fullTableName}`);
|
|
2715
2948
|
} catch (error) {
|
|
2716
2949
|
if (isDuplicateRelationError(error)) {
|
|
2717
|
-
if (await this.
|
|
2950
|
+
if (await this.spansPrimaryKeyExistsLive(constraintName, schemaFilter)) {
|
|
2951
|
+
this.schemaSnapshot?.primaryKeyIndexes.add(constraintName.toLowerCase());
|
|
2718
2952
|
this.logger?.debug?.(`PRIMARY KEY constraint ${constraintName} was created by another process`);
|
|
2719
2953
|
return;
|
|
2720
2954
|
}
|
|
@@ -2796,16 +3030,25 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2796
3030
|
indexName: tableName,
|
|
2797
3031
|
schemaName: getSchemaName$6(this.schemaName)
|
|
2798
3032
|
});
|
|
3033
|
+
const snapshot = this.schemaSnapshot;
|
|
3034
|
+
const knownColumns = snapshot ? this.snapshotColumns(snapshot, tableName) : null;
|
|
2799
3035
|
try {
|
|
2800
3036
|
for (const columnName of ifNotExists) if (schema[columnName]) {
|
|
2801
3037
|
const columnDef = schema[columnName];
|
|
2802
3038
|
const parsedColumnName = (0, _mastra_core_utils.parseSqlIdentifier)(columnName, "column name");
|
|
2803
3039
|
const sqlType = mapToSqlType(columnDef.type);
|
|
2804
3040
|
const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${columnDef.nullable ? "" : "NOT NULL"} ${!columnDef.nullable ? this.getDefaultValue(columnDef.type) : ""}`.trim();
|
|
2805
|
-
|
|
3041
|
+
if (!knownColumns?.has(parsedColumnName)) {
|
|
3042
|
+
await this.client.none(alterSql);
|
|
3043
|
+
knownColumns?.add(parsedColumnName);
|
|
3044
|
+
}
|
|
2806
3045
|
if (sqlType === "TIMESTAMP") {
|
|
2807
|
-
const
|
|
2808
|
-
|
|
3046
|
+
const tzColumnName = `${parsedColumnName}Z`;
|
|
3047
|
+
if (!knownColumns?.has(tzColumnName)) {
|
|
3048
|
+
const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${tzColumnName}" TIMESTAMPTZ DEFAULT NOW()`.trim();
|
|
3049
|
+
await this.client.none(timestampZSql);
|
|
3050
|
+
knownColumns?.add(tzColumnName);
|
|
3051
|
+
}
|
|
2809
3052
|
}
|
|
2810
3053
|
this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
|
|
2811
3054
|
}
|
|
@@ -2891,7 +3134,10 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2891
3134
|
indexName: table,
|
|
2892
3135
|
schemaName: getSchemaName$6(this.schemaName)
|
|
2893
3136
|
});
|
|
2894
|
-
|
|
3137
|
+
const snapshot = this.schemaSnapshot;
|
|
3138
|
+
if (snapshot) {
|
|
3139
|
+
if (snapshot.indexes.has(name)) return;
|
|
3140
|
+
} else if (await this.client.oneOrNone(`SELECT 1 FROM pg_indexes
|
|
2895
3141
|
WHERE indexname = $1
|
|
2896
3142
|
AND schemaname = $2`, [name, schemaName])) return;
|
|
2897
3143
|
const uniqueStr = unique ? "UNIQUE " : "";
|
|
@@ -2913,6 +3159,7 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2913
3159
|
if (storage && Object.keys(storage).length > 0) withStr = ` WITH (${Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ")})`;
|
|
2914
3160
|
const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${`"${(0, _mastra_core_utils.parseSqlIdentifier)(name, "index name")}"`} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
|
|
2915
3161
|
await this.client.none(sql);
|
|
3162
|
+
snapshot?.indexes.add(name);
|
|
2916
3163
|
} catch (error) {
|
|
2917
3164
|
if (error instanceof Error && error.message.includes("CONCURRENTLY")) {
|
|
2918
3165
|
const retryOptions = {
|
|
@@ -2932,15 +3179,34 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
|
|
|
2932
3179
|
}, error);
|
|
2933
3180
|
}
|
|
2934
3181
|
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Runs a caller-built `CREATE INDEX IF NOT EXISTS` statement, unless the init
|
|
3184
|
+
* snapshot already proves `indexName` exists.
|
|
3185
|
+
*
|
|
3186
|
+
* `createIndex` covers the indexes described by {@link CreateIndexOptions};
|
|
3187
|
+
* this is for the two init paths that hand-write their statement (a partial
|
|
3188
|
+
* or otherwise non-standard index) and would otherwise send a no-op DDL on
|
|
3189
|
+
* every warm init.
|
|
3190
|
+
*/
|
|
3191
|
+
async createIndexFromStatement(indexName, sql) {
|
|
3192
|
+
const snapshot = this.schemaSnapshot;
|
|
3193
|
+
if (snapshot?.indexes.has(indexName)) return;
|
|
3194
|
+
await this.client.none(sql);
|
|
3195
|
+
snapshot?.indexes.add(indexName);
|
|
3196
|
+
}
|
|
2935
3197
|
async dropIndex(indexName) {
|
|
2936
3198
|
try {
|
|
2937
3199
|
const schemaName = this.schemaName || "public";
|
|
2938
|
-
|
|
3200
|
+
const snapshot = this.schemaSnapshot;
|
|
3201
|
+
if (snapshot) {
|
|
3202
|
+
if (!snapshot.indexes.has(indexName)) return;
|
|
3203
|
+
} else if (!await this.client.oneOrNone(`SELECT 1 FROM pg_indexes
|
|
2939
3204
|
WHERE indexname = $1
|
|
2940
3205
|
AND schemaname = $2`, [indexName, schemaName])) return;
|
|
2941
3206
|
const quotedIndexName = `"${(0, _mastra_core_utils.parseSqlIdentifier)(indexName, "index name")}"`;
|
|
2942
3207
|
const sql = `DROP INDEX IF EXISTS ${getSchemaName$6(this.schemaName)}.${quotedIndexName}`;
|
|
2943
3208
|
await this.client.none(sql);
|
|
3209
|
+
snapshot?.indexes.delete(indexName);
|
|
2944
3210
|
} catch (error) {
|
|
2945
3211
|
throw new _mastra_core_error.MastraError({
|
|
2946
3212
|
id: (0, _mastra_core_storage.createStorageErrorId)("PG", "INDEX_DROP", "FAILED"),
|
|
@@ -3437,7 +3703,9 @@ var AgentsPG = class AgentsPG extends _mastra_core_storage.AgentsStorage {
|
|
|
3437
3703
|
});
|
|
3438
3704
|
if (await this.#db.hasColumn(_mastra_core_storage.TABLE_AGENTS, "name")) {
|
|
3439
3705
|
await this.#db.client.none(`ALTER TABLE ${fullTableName} RENAME TO "${_mastra_core_storage.TABLE_AGENTS}_legacy"`);
|
|
3706
|
+
this.#db.noteTableRenamed(_mastra_core_storage.TABLE_AGENTS, `${_mastra_core_storage.TABLE_AGENTS}_legacy`);
|
|
3440
3707
|
await this.#db.client.none(`DROP TABLE IF EXISTS ${fullVersionsTableName}`);
|
|
3708
|
+
this.#db.noteTableDropped(_mastra_core_storage.TABLE_AGENT_VERSIONS);
|
|
3441
3709
|
}
|
|
3442
3710
|
if (!await this.#db.hasColumn(`${_mastra_core_storage.TABLE_AGENTS}_legacy`, "name")) return;
|
|
3443
3711
|
const oldAgents = await this.#db.client.manyOrNone(`SELECT * FROM ${legacyTableName}`);
|
|
@@ -3494,6 +3762,7 @@ var AgentsPG = class AgentsPG extends _mastra_core_storage.AgentsStorage {
|
|
|
3494
3762
|
]);
|
|
3495
3763
|
}
|
|
3496
3764
|
await this.#db.client.none(`DROP TABLE IF EXISTS ${legacyTableName}`);
|
|
3765
|
+
this.#db.noteTableDropped(`${_mastra_core_storage.TABLE_AGENTS}_legacy`);
|
|
3497
3766
|
}
|
|
3498
3767
|
/**
|
|
3499
3768
|
* Migrates the agent_versions table from the old snapshot-based schema (single `snapshot` JSON column)
|
|
@@ -3511,7 +3780,9 @@ var AgentsPG = class AgentsPG extends _mastra_core_storage.AgentsStorage {
|
|
|
3511
3780
|
schemaName: getSchemaName$5(this.#schema)
|
|
3512
3781
|
});
|
|
3513
3782
|
await this.#db.client.none(`DROP TABLE IF EXISTS ${fullVersionsTableName}`);
|
|
3783
|
+
this.#db.noteTableDropped(_mastra_core_storage.TABLE_AGENT_VERSIONS);
|
|
3514
3784
|
await this.#db.client.none(`DROP TABLE IF EXISTS ${legacyTableName}`);
|
|
3785
|
+
this.#db.noteTableDropped(`${_mastra_core_storage.TABLE_AGENTS}_legacy`);
|
|
3515
3786
|
}
|
|
3516
3787
|
/**
|
|
3517
3788
|
* Migrates the tools field from string[] format to JSONB format { "tool-key": { "description": "..." } }.
|
|
@@ -5039,6 +5310,7 @@ var DatasetsPG = class DatasetsPG extends _mastra_core_storage.DatasetsStorage {
|
|
|
5039
5310
|
schemaName: getSchemaName$5(this.#schema)
|
|
5040
5311
|
});
|
|
5041
5312
|
await this.#db.client.none(`ALTER TABLE ${fullTableName} ADD COLUMN "${column}" ${sqlType}`);
|
|
5313
|
+
this.#db.noteColumnAdded(table, column);
|
|
5042
5314
|
}
|
|
5043
5315
|
}
|
|
5044
5316
|
getDefaultIndexDefinitions() {
|
|
@@ -6908,7 +7180,7 @@ var FavoritesPG = class FavoritesPG extends _mastra_core_storage.FavoritesStorag
|
|
|
6908
7180
|
indexName: _mastra_core_storage.TABLE_FAVORITES,
|
|
6909
7181
|
schemaName: getSchemaName$5(this.#schema)
|
|
6910
7182
|
});
|
|
6911
|
-
await this.#db.
|
|
7183
|
+
await this.#db.createIndexFromStatement("idx_favorites_entity", `CREATE INDEX IF NOT EXISTS idx_favorites_entity ON ${fullTableName} ("entityType", "entityId")`);
|
|
6912
7184
|
}
|
|
6913
7185
|
async dangerouslyClearAll() {
|
|
6914
7186
|
const fullTableName = getTableName$5({
|
|
@@ -8404,7 +8676,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
|
|
|
8404
8676
|
indexName: OM_TABLE,
|
|
8405
8677
|
schemaName: getSchemaName$3(this.#schema)
|
|
8406
8678
|
});
|
|
8407
|
-
await this.#db.
|
|
8679
|
+
await this.#db.createIndexFromStatement("idx_om_lookup_key", `CREATE INDEX IF NOT EXISTS idx_om_lookup_key ON ${omTableName} ("lookupKey")`);
|
|
8408
8680
|
}
|
|
8409
8681
|
await this.createDefaultIndexes();
|
|
8410
8682
|
await this.createCustomIndexes();
|
|
@@ -20125,6 +20397,7 @@ var PostgresStore = class extends _mastra_core_storage.MastraCompositeStore {
|
|
|
20125
20397
|
pinnedClient = await this.#pool.connect();
|
|
20126
20398
|
const pinned = new PinnedClientAdapter(this.#pool, pinnedClient);
|
|
20127
20399
|
this.#db.pin(pinned);
|
|
20400
|
+
this.#db.setSchemaSnapshot(await loadSchemaSnapshot(pinned, this.schema));
|
|
20128
20401
|
await super.init();
|
|
20129
20402
|
this.isInitialized = true;
|
|
20130
20403
|
} catch (error) {
|
|
@@ -20136,6 +20409,7 @@ var PostgresStore = class extends _mastra_core_storage.MastraCompositeStore {
|
|
|
20136
20409
|
category: _mastra_core_error.ErrorCategory.THIRD_PARTY
|
|
20137
20410
|
}, error);
|
|
20138
20411
|
} finally {
|
|
20412
|
+
this.#db.setSchemaSnapshot(null);
|
|
20139
20413
|
if (pinnedClient) {
|
|
20140
20414
|
this.#db.unpin();
|
|
20141
20415
|
pinnedClient.release();
|