@mastra/pg 1.7.2 → 1.8.0
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 +124 -0
- package/dist/docs/SKILL.md +14 -14
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-semantic-recall.md +9 -9
- package/dist/docs/references/docs-memory-storage.md +1 -1
- package/dist/docs/references/docs-memory-working-memory.md +20 -20
- package/dist/docs/references/docs-rag-overview.md +2 -2
- package/dist/docs/references/docs-rag-retrieval.md +4 -4
- package/dist/docs/references/docs-rag-vector-databases.md +13 -13
- package/dist/docs/references/reference-memory-memory-class.md +1 -1
- package/dist/docs/references/reference-processors-message-history-processor.md +1 -1
- package/dist/docs/references/reference-rag-metadata-filters.md +15 -15
- package/dist/docs/references/reference-storage-composite.md +1 -1
- package/dist/docs/references/reference-storage-dynamodb.md +7 -7
- package/dist/docs/references/reference-storage-postgresql.md +7 -7
- package/dist/docs/references/reference-tools-vector-query-tool.md +12 -12
- package/dist/docs/references/reference-vectors-pg.md +23 -21
- package/dist/index.cjs +379 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +379 -91
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +13 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/datasets/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +7 -2
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/vector/index.d.ts +44 -10
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/types.d.ts +32 -2
- package/dist/vector/types.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -724,11 +724,22 @@ var PgVector = class extends MastraVector {
|
|
|
724
724
|
}
|
|
725
725
|
return major > 0 || major === 0 && minor >= 7;
|
|
726
726
|
}
|
|
727
|
+
/** Checks if pgvector >= 0.7.0 (required for bit type). */
|
|
728
|
+
supportsBit() {
|
|
729
|
+
return this.supportsHalfvec();
|
|
730
|
+
}
|
|
731
|
+
/** Checks if pgvector >= 0.7.0 (required for sparsevec type). */
|
|
732
|
+
supportsSparsevec() {
|
|
733
|
+
return this.supportsHalfvec();
|
|
734
|
+
}
|
|
727
735
|
/**
|
|
728
736
|
* Gets the properly qualified vector type name
|
|
729
|
-
* @param vectorType - The type of vector storage
|
|
737
|
+
* @param vectorType - The type of vector storage
|
|
730
738
|
*/
|
|
731
|
-
getVectorTypeName(vectorType = "vector") {
|
|
739
|
+
getVectorTypeName(vectorType = "vector", dimension) {
|
|
740
|
+
if (vectorType === "bit") {
|
|
741
|
+
return dimension ? `bit(${dimension})` : "bit";
|
|
742
|
+
}
|
|
732
743
|
if (this.vectorExtensionSchema) {
|
|
733
744
|
if (this.vectorExtensionSchema === "pg_catalog") {
|
|
734
745
|
return vectorType;
|
|
@@ -739,20 +750,76 @@ var PgVector = class extends MastraVector {
|
|
|
739
750
|
return vectorType;
|
|
740
751
|
}
|
|
741
752
|
/**
|
|
742
|
-
*
|
|
743
|
-
*
|
|
753
|
+
* Returns the operator class, distance operator, and score expression for a
|
|
754
|
+
* standard (non-bit) vector type prefix and metric.
|
|
744
755
|
*/
|
|
745
|
-
|
|
746
|
-
const prefix = vectorType === "halfvec" ? "halfvec" : "vector";
|
|
756
|
+
getMetricOps(prefix, metric) {
|
|
747
757
|
switch (metric) {
|
|
748
|
-
case "cosine":
|
|
749
|
-
return `${prefix}_cosine_ops`;
|
|
750
758
|
case "euclidean":
|
|
751
|
-
return
|
|
759
|
+
return {
|
|
760
|
+
operatorClass: `${prefix}_l2_ops`,
|
|
761
|
+
distanceOperator: "<->",
|
|
762
|
+
scoreExpr: (d) => `1.0 / (1.0 + (${d}))`
|
|
763
|
+
};
|
|
752
764
|
case "dotproduct":
|
|
753
|
-
return
|
|
765
|
+
return {
|
|
766
|
+
operatorClass: `${prefix}_ip_ops`,
|
|
767
|
+
distanceOperator: "<#>",
|
|
768
|
+
scoreExpr: (d) => `(${d}) * -1`
|
|
769
|
+
};
|
|
770
|
+
default:
|
|
771
|
+
return {
|
|
772
|
+
operatorClass: `${prefix}_cosine_ops`,
|
|
773
|
+
distanceOperator: "<=>",
|
|
774
|
+
scoreExpr: (d) => `1 - (${d})`
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Returns all vector-type-specific operations for the given vectorType and metric.
|
|
780
|
+
*/
|
|
781
|
+
getVectorOps(vectorType, metric) {
|
|
782
|
+
switch (vectorType) {
|
|
783
|
+
case "bit":
|
|
784
|
+
return {
|
|
785
|
+
operatorClass: metric === "jaccard" ? "bit_jaccard_ops" : "bit_hamming_ops",
|
|
786
|
+
distanceOperator: metric === "jaccard" ? "<%>" : "<~>",
|
|
787
|
+
scoreExpr: (d) => metric === "jaccard" ? `1 - (${d})` : `1 - ((${d})::float / bit_length(embedding))`,
|
|
788
|
+
formatVector: (v) => v.map((x) => x ? "1" : "0").join(""),
|
|
789
|
+
parseEmbedding: (e) => e.split("").map((c) => c === "1" ? 1 : 0)
|
|
790
|
+
};
|
|
791
|
+
case "sparsevec":
|
|
792
|
+
return {
|
|
793
|
+
...this.getMetricOps("sparsevec", metric),
|
|
794
|
+
formatVector: (v, dimension) => {
|
|
795
|
+
const dim = dimension ?? v.length;
|
|
796
|
+
const nonZero = v.map((val, i) => val !== 0 ? `${i + 1}:${val}` : null).filter(Boolean).join(",");
|
|
797
|
+
return `{${nonZero}}/${dim}`;
|
|
798
|
+
},
|
|
799
|
+
parseEmbedding: (e) => {
|
|
800
|
+
const match = e.match(/^\{([^}]*)\}\/(\d+)$/);
|
|
801
|
+
if (!match) return [];
|
|
802
|
+
const dim = parseInt(match[2], 10);
|
|
803
|
+
const result = new Array(dim).fill(0);
|
|
804
|
+
const entries = match[1];
|
|
805
|
+
if (entries.trim()) {
|
|
806
|
+
for (const entry of entries.split(",")) {
|
|
807
|
+
const [idxStr, valStr] = entry.split(":");
|
|
808
|
+
const idx = parseInt(idxStr.trim(), 10) - 1;
|
|
809
|
+
result[idx] = parseFloat(valStr.trim());
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
case "halfvec":
|
|
816
|
+
case "vector":
|
|
754
817
|
default:
|
|
755
|
-
return
|
|
818
|
+
return {
|
|
819
|
+
...this.getMetricOps(vectorType === "halfvec" ? "halfvec" : "vector", metric),
|
|
820
|
+
formatVector: (v) => `[${v.join(",")}]`,
|
|
821
|
+
parseEmbedding: (e) => JSON.parse(e)
|
|
822
|
+
};
|
|
756
823
|
}
|
|
757
824
|
}
|
|
758
825
|
getTableName(indexName) {
|
|
@@ -855,10 +922,12 @@ var PgVector = class extends MastraVector {
|
|
|
855
922
|
const client = await this.pool.connect();
|
|
856
923
|
try {
|
|
857
924
|
await client.query("BEGIN");
|
|
858
|
-
const vectorStr = `[${queryVector.join(",")}]`;
|
|
859
925
|
const translatedFilter = this.transformFilter(filter);
|
|
860
926
|
const { sql: filterQuery, values: filterValues } = buildFilterQuery(translatedFilter, minScore, topK);
|
|
861
927
|
const indexInfo = await this.getIndexInfo({ indexName });
|
|
928
|
+
const metric = indexInfo.metric ?? "cosine";
|
|
929
|
+
const ops = this.getVectorOps(indexInfo.vectorType, metric);
|
|
930
|
+
const vectorStr = ops.formatVector(queryVector, indexInfo.dimension);
|
|
862
931
|
if (indexInfo.type === "hnsw") {
|
|
863
932
|
const calculatedEf = ef ?? Math.max(topK, (indexInfo?.config?.m ?? 16) * topK);
|
|
864
933
|
const searchEf = Math.min(1e3, Math.max(1, calculatedEf));
|
|
@@ -868,12 +937,14 @@ var PgVector = class extends MastraVector {
|
|
|
868
937
|
await client.query(`SET LOCAL ivfflat.probes = ${probes}`);
|
|
869
938
|
}
|
|
870
939
|
const { tableName } = this.getTableName(indexName);
|
|
871
|
-
const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType);
|
|
940
|
+
const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType, indexInfo.dimension);
|
|
941
|
+
const distanceExpr = `embedding ${ops.distanceOperator} '${vectorStr}'::${qualifiedVectorType}`;
|
|
942
|
+
const scoreExpr = ops.scoreExpr(distanceExpr);
|
|
872
943
|
const query = `
|
|
873
944
|
WITH vector_scores AS (
|
|
874
945
|
SELECT
|
|
875
946
|
vector_id as id,
|
|
876
|
-
|
|
947
|
+
${scoreExpr} as score,
|
|
877
948
|
metadata
|
|
878
949
|
${includeVector ? ", embedding" : ""}
|
|
879
950
|
FROM ${tableName}
|
|
@@ -890,7 +961,7 @@ var PgVector = class extends MastraVector {
|
|
|
890
961
|
id,
|
|
891
962
|
score,
|
|
892
963
|
metadata,
|
|
893
|
-
...includeVector && embedding && { vector:
|
|
964
|
+
...includeVector && embedding && { vector: ops.parseEmbedding(embedding) }
|
|
894
965
|
}));
|
|
895
966
|
} catch (error) {
|
|
896
967
|
await client.query("ROLLBACK");
|
|
@@ -939,8 +1010,10 @@ var PgVector = class extends MastraVector {
|
|
|
939
1010
|
}
|
|
940
1011
|
const vectorIds = ids || vectors.map(() => crypto.randomUUID());
|
|
941
1012
|
const indexInfo = await this.getIndexInfo({ indexName });
|
|
942
|
-
const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType);
|
|
1013
|
+
const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType, indexInfo.dimension);
|
|
1014
|
+
const ops = this.getVectorOps(indexInfo.vectorType, indexInfo.metric ?? "cosine");
|
|
943
1015
|
for (let i = 0; i < vectors.length; i++) {
|
|
1016
|
+
const vectorStr = ops.formatVector(vectors[i], indexInfo.dimension);
|
|
944
1017
|
const query = `
|
|
945
1018
|
INSERT INTO ${tableName} (vector_id, embedding, metadata)
|
|
946
1019
|
VALUES ($1, $2::${qualifiedVectorType}, $3::jsonb)
|
|
@@ -950,7 +1023,7 @@ var PgVector = class extends MastraVector {
|
|
|
950
1023
|
metadata = $3::jsonb
|
|
951
1024
|
RETURNING embedding::text
|
|
952
1025
|
`;
|
|
953
|
-
await client.query(query, [vectorIds[i],
|
|
1026
|
+
await client.query(query, [vectorIds[i], vectorStr, JSON.stringify(metadata?.[i] || {})]);
|
|
954
1027
|
}
|
|
955
1028
|
await client.query("COMMIT");
|
|
956
1029
|
this.logger?.debug(`Upserted ${vectors.length} vectors to ${indexName}`, {
|
|
@@ -1006,9 +1079,17 @@ var PgVector = class extends MastraVector {
|
|
|
1006
1079
|
dimension,
|
|
1007
1080
|
metric,
|
|
1008
1081
|
type,
|
|
1009
|
-
vectorType = "vector"
|
|
1082
|
+
vectorType = "vector",
|
|
1083
|
+
metadataIndexes
|
|
1010
1084
|
}) {
|
|
1011
|
-
const input =
|
|
1085
|
+
const input = JSON.stringify([
|
|
1086
|
+
indexName,
|
|
1087
|
+
dimension,
|
|
1088
|
+
metric,
|
|
1089
|
+
type || "ivfflat",
|
|
1090
|
+
vectorType,
|
|
1091
|
+
metadataIndexes?.toSorted() ?? []
|
|
1092
|
+
]);
|
|
1012
1093
|
return (await this.hasher).h32(input);
|
|
1013
1094
|
}
|
|
1014
1095
|
cachedIndexExists(indexName, newKey) {
|
|
@@ -1059,11 +1140,13 @@ var PgVector = class extends MastraVector {
|
|
|
1059
1140
|
async createIndex({
|
|
1060
1141
|
indexName,
|
|
1061
1142
|
dimension,
|
|
1062
|
-
metric = "cosine",
|
|
1143
|
+
metric: rawMetric = "cosine",
|
|
1063
1144
|
indexConfig = {},
|
|
1064
1145
|
buildIndex = true,
|
|
1065
|
-
vectorType = "vector"
|
|
1146
|
+
vectorType = "vector",
|
|
1147
|
+
metadataIndexes
|
|
1066
1148
|
}) {
|
|
1149
|
+
const metric = vectorType === "bit" && rawMetric !== "hamming" && rawMetric !== "jaccard" ? "hamming" : rawMetric;
|
|
1067
1150
|
const { tableName } = this.getTableName(indexName);
|
|
1068
1151
|
try {
|
|
1069
1152
|
if (!indexName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
|
@@ -1072,8 +1155,17 @@ var PgVector = class extends MastraVector {
|
|
|
1072
1155
|
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
1073
1156
|
throw new Error("Dimension must be a positive integer");
|
|
1074
1157
|
}
|
|
1075
|
-
if (vectorType !== "vector" && vectorType !== "halfvec") {
|
|
1076
|
-
throw new Error('vectorType must be "vector" or "
|
|
1158
|
+
if (vectorType !== "vector" && vectorType !== "halfvec" && vectorType !== "bit" && vectorType !== "sparsevec") {
|
|
1159
|
+
throw new Error('vectorType must be "vector", "halfvec", "bit", or "sparsevec"');
|
|
1160
|
+
}
|
|
1161
|
+
if (vectorType === "bit" && dimension > 64e3) {
|
|
1162
|
+
throw new Error("bit vectors support up to 64,000 dimensions for indexes");
|
|
1163
|
+
}
|
|
1164
|
+
if ((metric === "hamming" || metric === "jaccard") && vectorType !== "bit") {
|
|
1165
|
+
throw new Error(`${metric} metric is only valid with vectorType 'bit'`);
|
|
1166
|
+
}
|
|
1167
|
+
if (indexConfig?.type === "ivfflat" && vectorType === "bit" && metric === "jaccard") {
|
|
1168
|
+
throw new Error("IVFFlat indexes do not support Jaccard distance for bit vectors. Use HNSW instead.");
|
|
1077
1169
|
}
|
|
1078
1170
|
} catch (error) {
|
|
1079
1171
|
const mastraError = new MastraError(
|
|
@@ -1095,7 +1187,8 @@ var PgVector = class extends MastraVector {
|
|
|
1095
1187
|
dimension,
|
|
1096
1188
|
type: indexConfig.type,
|
|
1097
1189
|
metric,
|
|
1098
|
-
vectorType
|
|
1190
|
+
vectorType,
|
|
1191
|
+
metadataIndexes
|
|
1099
1192
|
});
|
|
1100
1193
|
if (this.cachedIndexExists(indexName, indexCacheKey)) {
|
|
1101
1194
|
return;
|
|
@@ -1123,6 +1216,33 @@ var PgVector = class extends MastraVector {
|
|
|
1123
1216
|
}
|
|
1124
1217
|
});
|
|
1125
1218
|
}
|
|
1219
|
+
if (vectorType === "bit" && !this.supportsBit()) {
|
|
1220
|
+
throw new MastraError({
|
|
1221
|
+
id: createVectorErrorId("PG", "CREATE_INDEX", "VECTOR_TYPE_NOT_SUPPORTED"),
|
|
1222
|
+
text: `${vectorType} type requires pgvector >= 0.7.0, but version ${this.vectorExtensionVersion || "unknown"} is installed. Either upgrade pgvector or use vectorType: 'vector'.`,
|
|
1223
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1224
|
+
category: ErrorCategory.USER,
|
|
1225
|
+
details: {
|
|
1226
|
+
indexName,
|
|
1227
|
+
vectorType,
|
|
1228
|
+
installedVersion: this.vectorExtensionVersion
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
if (vectorType === "sparsevec" && !this.supportsSparsevec()) {
|
|
1233
|
+
throw new MastraError({
|
|
1234
|
+
id: createVectorErrorId("PG", "CREATE_INDEX", "VECTOR_TYPE_NOT_SUPPORTED"),
|
|
1235
|
+
text: `${vectorType} type requires pgvector >= 0.7.0, but version ${this.vectorExtensionVersion || "unknown"} is installed. Either upgrade pgvector or use vectorType: 'vector'.`,
|
|
1236
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1237
|
+
category: ErrorCategory.USER,
|
|
1238
|
+
details: {
|
|
1239
|
+
indexName,
|
|
1240
|
+
requestedVectorType: vectorType,
|
|
1241
|
+
pgvectorVersion: this.vectorExtensionVersion || "unknown",
|
|
1242
|
+
requiredVersion: "0.7.0"
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1126
1246
|
if (this.schema && this.vectorExtensionSchema && this.schema !== this.vectorExtensionSchema && this.vectorExtensionSchema !== "pg_catalog") {
|
|
1127
1247
|
await client.query(`SET search_path TO ${this.getSchemaName()}, "${this.vectorExtensionSchema}"`);
|
|
1128
1248
|
}
|
|
@@ -1140,6 +1260,9 @@ var PgVector = class extends MastraVector {
|
|
|
1140
1260
|
if (buildIndex) {
|
|
1141
1261
|
await this.setupIndex({ indexName, metric, indexConfig, vectorType }, client);
|
|
1142
1262
|
}
|
|
1263
|
+
if (metadataIndexes?.length) {
|
|
1264
|
+
await this.createMetadataIndexes(tableName, indexName, metadataIndexes);
|
|
1265
|
+
}
|
|
1143
1266
|
} catch (error) {
|
|
1144
1267
|
this.createdIndexes.delete(indexName);
|
|
1145
1268
|
this.indexVectorTypes.delete(indexName);
|
|
@@ -1163,10 +1286,10 @@ var PgVector = class extends MastraVector {
|
|
|
1163
1286
|
throw mastraError;
|
|
1164
1287
|
});
|
|
1165
1288
|
}
|
|
1166
|
-
async buildIndex({ indexName, metric = "cosine", indexConfig }) {
|
|
1289
|
+
async buildIndex({ indexName, metric = "cosine", indexConfig, vectorType }) {
|
|
1167
1290
|
const client = await this.pool.connect();
|
|
1168
1291
|
try {
|
|
1169
|
-
await this.setupIndex({ indexName, metric, indexConfig }, client);
|
|
1292
|
+
await this.setupIndex({ indexName, metric, indexConfig, vectorType }, client);
|
|
1170
1293
|
} catch (error) {
|
|
1171
1294
|
const mastraError = new MastraError(
|
|
1172
1295
|
{
|
|
@@ -1189,7 +1312,26 @@ var PgVector = class extends MastraVector {
|
|
|
1189
1312
|
const mutex = this.getMutexByName(`build-${indexName}`);
|
|
1190
1313
|
await mutex.runExclusive(async () => {
|
|
1191
1314
|
const isConfigEmpty = !indexConfig || Object.keys(indexConfig).length === 0 || !indexConfig.type && !indexConfig.ivf && !indexConfig.hnsw;
|
|
1192
|
-
const
|
|
1315
|
+
const defaultIndexType = vectorType === "sparsevec" ? "hnsw" : "ivfflat";
|
|
1316
|
+
const indexType = isConfigEmpty ? defaultIndexType : indexConfig.type || defaultIndexType;
|
|
1317
|
+
if (indexType === "ivfflat" && vectorType === "sparsevec") {
|
|
1318
|
+
throw new MastraError({
|
|
1319
|
+
id: createVectorErrorId("PG", "BUILD_INDEX", "UNSUPPORTED_INDEX_TYPE"),
|
|
1320
|
+
text: `IVFFlat indexes do not support sparsevec type. Use HNSW instead.`,
|
|
1321
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1322
|
+
category: ErrorCategory.USER,
|
|
1323
|
+
details: { indexName, vectorType, indexType }
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (indexType === "ivfflat" && vectorType === "bit" && metric === "jaccard") {
|
|
1327
|
+
throw new MastraError({
|
|
1328
|
+
id: createVectorErrorId("PG", "BUILD_INDEX", "UNSUPPORTED_INDEX_TYPE"),
|
|
1329
|
+
text: `IVFFlat indexes do not support Jaccard distance for bit vectors. Use HNSW instead.`,
|
|
1330
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
1331
|
+
category: ErrorCategory.USER,
|
|
1332
|
+
details: { indexName, vectorType, indexType, metric }
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1193
1335
|
const { tableName, vectorIndexName } = this.getTableName(indexName);
|
|
1194
1336
|
let existingIndexInfo = null;
|
|
1195
1337
|
let dimension = 0;
|
|
@@ -1247,7 +1389,7 @@ var PgVector = class extends MastraVector {
|
|
|
1247
1389
|
return;
|
|
1248
1390
|
}
|
|
1249
1391
|
const effectiveVectorType = existingIndexInfo?.vectorType ?? vectorType;
|
|
1250
|
-
const metricOp = this.
|
|
1392
|
+
const metricOp = this.getVectorOps(effectiveVectorType, metric).operatorClass;
|
|
1251
1393
|
let indexSQL;
|
|
1252
1394
|
if (indexType === "hnsw") {
|
|
1253
1395
|
const m = indexConfig.hnsw?.m ?? 8;
|
|
@@ -1279,6 +1421,21 @@ var PgVector = class extends MastraVector {
|
|
|
1279
1421
|
await client.query(indexSQL);
|
|
1280
1422
|
});
|
|
1281
1423
|
}
|
|
1424
|
+
async createMetadataIndexes(tableName, indexName, metadataFields) {
|
|
1425
|
+
const hasher = await this.hasher;
|
|
1426
|
+
for (const field of metadataFields) {
|
|
1427
|
+
const fieldHash = hasher.h32(field).toString(16);
|
|
1428
|
+
const prefix = indexName.slice(0, 63 - "_md__idx".length - fieldHash.length);
|
|
1429
|
+
const metadataIdxName = `"${prefix}_md_${fieldHash}_idx"`;
|
|
1430
|
+
const escapedField = field.replace(/'/g, "''");
|
|
1431
|
+
await this.pool.query(
|
|
1432
|
+
`
|
|
1433
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS ${metadataIdxName}
|
|
1434
|
+
ON ${tableName} ((metadata->>'${escapedField}'))
|
|
1435
|
+
`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1282
1439
|
async installVectorExtension(client) {
|
|
1283
1440
|
if (this.vectorExtensionInstalled) {
|
|
1284
1441
|
return;
|
|
@@ -1365,7 +1522,7 @@ var PgVector = class extends MastraVector {
|
|
|
1365
1522
|
WHERE c.table_schema = t.table_schema
|
|
1366
1523
|
AND c.table_name = t.table_name
|
|
1367
1524
|
AND c.column_name = 'embedding'
|
|
1368
|
-
AND c.udt_name IN ('vector', 'halfvec')
|
|
1525
|
+
AND c.udt_name IN ('vector', 'halfvec', 'bit', 'sparsevec')
|
|
1369
1526
|
)
|
|
1370
1527
|
AND EXISTS (
|
|
1371
1528
|
SELECT 1
|
|
@@ -1408,14 +1565,15 @@ var PgVector = class extends MastraVector {
|
|
|
1408
1565
|
FROM information_schema.columns
|
|
1409
1566
|
WHERE table_schema = $1
|
|
1410
1567
|
AND table_name = $2
|
|
1411
|
-
AND udt_name IN ('vector', 'halfvec')
|
|
1568
|
+
AND udt_name IN ('vector', 'halfvec', 'bit', 'sparsevec')
|
|
1412
1569
|
LIMIT 1;
|
|
1413
1570
|
`;
|
|
1414
1571
|
const tableExists = await client.query(tableExistsQuery, [this.schema || "public", indexName]);
|
|
1415
1572
|
if (tableExists.rows.length === 0) {
|
|
1416
1573
|
throw new Error(`Vector table ${tableName} does not exist`);
|
|
1417
1574
|
}
|
|
1418
|
-
const
|
|
1575
|
+
const udtName = tableExists.rows[0].udt_name;
|
|
1576
|
+
const vectorType = udtName === "halfvec" ? "halfvec" : udtName === "bit" ? "bit" : udtName === "sparsevec" ? "sparsevec" : "vector";
|
|
1419
1577
|
const dimensionQuery = `
|
|
1420
1578
|
SELECT atttypmod as dimension
|
|
1421
1579
|
FROM pg_attribute
|
|
@@ -1447,7 +1605,7 @@ var PgVector = class extends MastraVector {
|
|
|
1447
1605
|
index_def: "",
|
|
1448
1606
|
operator_class: "cosine"
|
|
1449
1607
|
};
|
|
1450
|
-
const metric = operator_class.includes("l2") ? "euclidean" : operator_class.includes("ip") ? "dotproduct" : "cosine";
|
|
1608
|
+
const metric = operator_class.includes("hamming") ? "hamming" : operator_class.includes("jaccard") ? "jaccard" : operator_class.includes("l2") ? "euclidean" : operator_class.includes("ip") ? "dotproduct" : "cosine";
|
|
1451
1609
|
const config = {};
|
|
1452
1610
|
if (index_method === "hnsw") {
|
|
1453
1611
|
const m = index_def.match(/m\s*=\s*'?(\d+)'?/)?.[1];
|
|
@@ -1582,13 +1740,14 @@ var PgVector = class extends MastraVector {
|
|
|
1582
1740
|
client = await this.pool.connect();
|
|
1583
1741
|
const { tableName } = this.getTableName(indexName);
|
|
1584
1742
|
const indexInfo = await this.getIndexInfo({ indexName });
|
|
1585
|
-
const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType);
|
|
1743
|
+
const qualifiedVectorType = this.getVectorTypeName(indexInfo.vectorType, indexInfo.dimension);
|
|
1744
|
+
const ops = this.getVectorOps(indexInfo.vectorType, indexInfo.metric ?? "cosine");
|
|
1586
1745
|
let updateParts = [];
|
|
1587
1746
|
let values = [];
|
|
1588
1747
|
let valueIndex = 1;
|
|
1589
1748
|
if (update.vector) {
|
|
1590
1749
|
updateParts.push(`embedding = $${valueIndex}::${qualifiedVectorType}`);
|
|
1591
|
-
values.push(
|
|
1750
|
+
values.push(ops.formatVector(update.vector, indexInfo.dimension));
|
|
1592
1751
|
valueIndex++;
|
|
1593
1752
|
}
|
|
1594
1753
|
if (update.metadata) {
|
|
@@ -2156,6 +2315,8 @@ var PgDB = class extends MastraBase {
|
|
|
2156
2315
|
client;
|
|
2157
2316
|
schemaName;
|
|
2158
2317
|
skipDefaultIndexes;
|
|
2318
|
+
/** Cache of actual table columns: tableName -> Set<columnName> */
|
|
2319
|
+
tableColumnsCache = /* @__PURE__ */ new Map();
|
|
2159
2320
|
constructor(config) {
|
|
2160
2321
|
super({
|
|
2161
2322
|
component: "STORAGE",
|
|
@@ -2165,6 +2326,40 @@ var PgDB = class extends MastraBase {
|
|
|
2165
2326
|
this.schemaName = config.schemaName;
|
|
2166
2327
|
this.skipDefaultIndexes = config.skipDefaultIndexes;
|
|
2167
2328
|
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Gets the set of column names that actually exist in the database table.
|
|
2331
|
+
* Results are cached; the cache is invalidated when alterTable() adds new columns.
|
|
2332
|
+
*/
|
|
2333
|
+
async getTableColumns(tableName) {
|
|
2334
|
+
const cached = this.tableColumnsCache.get(tableName);
|
|
2335
|
+
if (cached) return cached;
|
|
2336
|
+
const schema = this.schemaName || "public";
|
|
2337
|
+
const rows = await this.client.manyOrNone(
|
|
2338
|
+
`SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2`,
|
|
2339
|
+
[schema, tableName]
|
|
2340
|
+
);
|
|
2341
|
+
const columns = new Set(rows.map((r) => r.column_name));
|
|
2342
|
+
if (columns.size > 0) {
|
|
2343
|
+
this.tableColumnsCache.set(tableName, columns);
|
|
2344
|
+
}
|
|
2345
|
+
return columns;
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Filters a record to only include columns that exist in the actual database table.
|
|
2349
|
+
* Unknown columns are silently dropped to ensure forward compatibility when newer
|
|
2350
|
+
* domain packages add fields that haven't been migrated yet.
|
|
2351
|
+
*/
|
|
2352
|
+
async filterRecordToKnownColumns(tableName, record) {
|
|
2353
|
+
const knownColumns = await this.getTableColumns(tableName);
|
|
2354
|
+
if (knownColumns.size === 0) return record;
|
|
2355
|
+
const filtered = {};
|
|
2356
|
+
for (const [key, value] of Object.entries(record)) {
|
|
2357
|
+
if (knownColumns.has(key)) {
|
|
2358
|
+
filtered[key] = value;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
return filtered;
|
|
2362
|
+
}
|
|
2168
2363
|
async hasColumn(table, column) {
|
|
2169
2364
|
const schema = this.schemaName || "public";
|
|
2170
2365
|
const result = await this.client.oneOrNone(
|
|
@@ -2281,9 +2476,11 @@ var PgDB = class extends MastraBase {
|
|
|
2281
2476
|
async insert({ tableName, record }) {
|
|
2282
2477
|
try {
|
|
2283
2478
|
this.addTimestampZColumns(record);
|
|
2479
|
+
const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
|
|
2284
2480
|
const schemaName = getSchemaName(this.schemaName);
|
|
2285
|
-
const columns = Object.keys(
|
|
2286
|
-
|
|
2481
|
+
const columns = Object.keys(filteredRecord).map((col) => parseSqlIdentifier(col, "column name"));
|
|
2482
|
+
if (columns.length === 0) return;
|
|
2483
|
+
const values = this.prepareValuesForInsert(filteredRecord, tableName);
|
|
2287
2484
|
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
|
2288
2485
|
const fullTableName = getTableName({ indexName: tableName, schemaName });
|
|
2289
2486
|
const columnList = columns.map((c) => `"${c}"`).join(", ");
|
|
@@ -2419,6 +2616,8 @@ Note: This migration may take some time for large tables.
|
|
|
2419
2616
|
},
|
|
2420
2617
|
error
|
|
2421
2618
|
);
|
|
2619
|
+
} finally {
|
|
2620
|
+
this.tableColumnsCache.delete(tableName);
|
|
2422
2621
|
}
|
|
2423
2622
|
}
|
|
2424
2623
|
async setupTimestampTriggers(tableName) {
|
|
@@ -2738,6 +2937,8 @@ Note: This migration may take some time for large tables.
|
|
|
2738
2937
|
},
|
|
2739
2938
|
error
|
|
2740
2939
|
);
|
|
2940
|
+
} finally {
|
|
2941
|
+
this.tableColumnsCache.delete(tableName);
|
|
2741
2942
|
}
|
|
2742
2943
|
}
|
|
2743
2944
|
async load({ tableName, keys }) {
|
|
@@ -2814,6 +3015,8 @@ Note: This migration may take some time for large tables.
|
|
|
2814
3015
|
},
|
|
2815
3016
|
error
|
|
2816
3017
|
);
|
|
3018
|
+
} finally {
|
|
3019
|
+
this.tableColumnsCache.delete(tableName);
|
|
2817
3020
|
}
|
|
2818
3021
|
}
|
|
2819
3022
|
async createIndex(options) {
|
|
@@ -3058,10 +3261,12 @@ Note: This migration may take some time for large tables.
|
|
|
3058
3261
|
data
|
|
3059
3262
|
}) {
|
|
3060
3263
|
try {
|
|
3264
|
+
const filteredData = await this.filterRecordToKnownColumns(tableName, data);
|
|
3265
|
+
if (Object.keys(filteredData).length === 0) return;
|
|
3061
3266
|
const setColumns = [];
|
|
3062
3267
|
const setValues = [];
|
|
3063
3268
|
let paramIndex = 1;
|
|
3064
|
-
Object.entries(
|
|
3269
|
+
Object.entries(filteredData).forEach(([key, value]) => {
|
|
3065
3270
|
const parsedKey = parseSqlIdentifier(key, "column name");
|
|
3066
3271
|
setColumns.push(`"${parsedKey}" = $${paramIndex++}`);
|
|
3067
3272
|
setValues.push(this.prepareValue(value, key, tableName));
|
|
@@ -4120,9 +4325,18 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4120
4325
|
compositePrimaryKey: TABLE_CONFIGS[TABLE_DATASET_ITEMS]?.compositePrimaryKey
|
|
4121
4326
|
});
|
|
4122
4327
|
await this.#db.createTable({ tableName: TABLE_DATASET_VERSIONS, schema: DATASET_VERSIONS_SCHEMA });
|
|
4328
|
+
await this.#addColumnIfNotExists(TABLE_DATASETS, "requestContextSchema", "JSONB");
|
|
4329
|
+
await this.#addColumnIfNotExists(TABLE_DATASET_ITEMS, "requestContext", "JSONB");
|
|
4123
4330
|
await this.createDefaultIndexes();
|
|
4124
4331
|
await this.createCustomIndexes();
|
|
4125
4332
|
}
|
|
4333
|
+
async #addColumnIfNotExists(table, column, sqlType) {
|
|
4334
|
+
const exists = await this.#db.hasColumn(table, column);
|
|
4335
|
+
if (!exists) {
|
|
4336
|
+
const fullTableName = getTableName2({ indexName: table, schemaName: getSchemaName2(this.#schema) });
|
|
4337
|
+
await this.#db.client.none(`ALTER TABLE ${fullTableName} ADD COLUMN "${column}" ${sqlType}`);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4126
4340
|
getDefaultIndexDefinitions() {
|
|
4127
4341
|
return [
|
|
4128
4342
|
{ name: "idx_dataset_items_dataset_validto", table: TABLE_DATASET_ITEMS, columns: ["datasetId", "validTo"] },
|
|
@@ -4178,6 +4392,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4178
4392
|
metadata: row.metadata ? safelyParseJSON(row.metadata) : void 0,
|
|
4179
4393
|
inputSchema: row.inputSchema ? safelyParseJSON(row.inputSchema) : void 0,
|
|
4180
4394
|
groundTruthSchema: row.groundTruthSchema ? safelyParseJSON(row.groundTruthSchema) : void 0,
|
|
4395
|
+
requestContextSchema: row.requestContextSchema ? safelyParseJSON(row.requestContextSchema) : void 0,
|
|
4181
4396
|
version: row.version,
|
|
4182
4397
|
createdAt: ensureDate(row.createdAtZ || row.createdAt),
|
|
4183
4398
|
updatedAt: ensureDate(row.updatedAtZ || row.updatedAt)
|
|
@@ -4190,6 +4405,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4190
4405
|
datasetVersion: row.datasetVersion,
|
|
4191
4406
|
input: safelyParseJSON(row.input),
|
|
4192
4407
|
groundTruth: row.groundTruth ? safelyParseJSON(row.groundTruth) : void 0,
|
|
4408
|
+
requestContext: row.requestContext ? safelyParseJSON(row.requestContext) : void 0,
|
|
4193
4409
|
metadata: row.metadata ? safelyParseJSON(row.metadata) : void 0,
|
|
4194
4410
|
createdAt: ensureDate(row.createdAtZ || row.createdAt),
|
|
4195
4411
|
updatedAt: ensureDate(row.updatedAtZ || row.updatedAt)
|
|
@@ -4204,6 +4420,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4204
4420
|
isDeleted: Boolean(row.isDeleted),
|
|
4205
4421
|
input: safelyParseJSON(row.input),
|
|
4206
4422
|
groundTruth: row.groundTruth ? safelyParseJSON(row.groundTruth) : void 0,
|
|
4423
|
+
requestContext: row.requestContext ? safelyParseJSON(row.requestContext) : void 0,
|
|
4207
4424
|
metadata: row.metadata ? safelyParseJSON(row.metadata) : void 0,
|
|
4208
4425
|
createdAt: ensureDate(row.createdAtZ || row.createdAt),
|
|
4209
4426
|
updatedAt: ensureDate(row.updatedAtZ || row.updatedAt)
|
|
@@ -4232,6 +4449,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4232
4449
|
metadata: input.metadata ?? null,
|
|
4233
4450
|
inputSchema: input.inputSchema ?? null,
|
|
4234
4451
|
groundTruthSchema: input.groundTruthSchema ?? null,
|
|
4452
|
+
requestContextSchema: input.requestContextSchema ?? null,
|
|
4235
4453
|
version: 0,
|
|
4236
4454
|
createdAt: nowIso,
|
|
4237
4455
|
updatedAt: nowIso
|
|
@@ -4244,6 +4462,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4244
4462
|
metadata: input.metadata,
|
|
4245
4463
|
inputSchema: input.inputSchema ?? void 0,
|
|
4246
4464
|
groundTruthSchema: input.groundTruthSchema ?? void 0,
|
|
4465
|
+
requestContextSchema: input.requestContextSchema ?? void 0,
|
|
4247
4466
|
version: 0,
|
|
4248
4467
|
createdAt: now,
|
|
4249
4468
|
updatedAt: now
|
|
@@ -4311,6 +4530,10 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4311
4530
|
setClauses.push(`"groundTruthSchema" = $${paramIndex++}`);
|
|
4312
4531
|
values.push(args.groundTruthSchema === null ? null : JSON.stringify(args.groundTruthSchema));
|
|
4313
4532
|
}
|
|
4533
|
+
if (args.requestContextSchema !== void 0) {
|
|
4534
|
+
setClauses.push(`"requestContextSchema" = $${paramIndex++}`);
|
|
4535
|
+
values.push(args.requestContextSchema === null ? null : JSON.stringify(args.requestContextSchema));
|
|
4536
|
+
}
|
|
4314
4537
|
values.push(args.id);
|
|
4315
4538
|
await this.#db.client.none(
|
|
4316
4539
|
`UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE "id" = $${paramIndex}`,
|
|
@@ -4323,6 +4546,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4323
4546
|
metadata: args.metadata ?? existing.metadata,
|
|
4324
4547
|
inputSchema: (args.inputSchema !== void 0 ? args.inputSchema : existing.inputSchema) ?? void 0,
|
|
4325
4548
|
groundTruthSchema: (args.groundTruthSchema !== void 0 ? args.groundTruthSchema : existing.groundTruthSchema) ?? void 0,
|
|
4549
|
+
requestContextSchema: (args.requestContextSchema !== void 0 ? args.requestContextSchema : existing.requestContextSchema) ?? void 0,
|
|
4326
4550
|
updatedAt: new Date(now)
|
|
4327
4551
|
};
|
|
4328
4552
|
} catch (error) {
|
|
@@ -4438,13 +4662,14 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4438
4662
|
);
|
|
4439
4663
|
newVersion = row.version;
|
|
4440
4664
|
await t.none(
|
|
4441
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10)`,
|
|
4665
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10,$11)`,
|
|
4442
4666
|
[
|
|
4443
4667
|
id,
|
|
4444
4668
|
args.datasetId,
|
|
4445
4669
|
newVersion,
|
|
4446
4670
|
JSON.stringify(args.input),
|
|
4447
4671
|
jsonbArg(args.groundTruth),
|
|
4672
|
+
jsonbArg(args.requestContext),
|
|
4448
4673
|
jsonbArg(args.metadata),
|
|
4449
4674
|
nowIso,
|
|
4450
4675
|
nowIso,
|
|
@@ -4463,6 +4688,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4463
4688
|
datasetVersion: newVersion,
|
|
4464
4689
|
input: args.input,
|
|
4465
4690
|
groundTruth: args.groundTruth,
|
|
4691
|
+
requestContext: args.requestContext,
|
|
4466
4692
|
metadata: args.metadata,
|
|
4467
4693
|
createdAt: now,
|
|
4468
4694
|
updatedAt: now
|
|
@@ -4509,6 +4735,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4509
4735
|
const nowIso = now.toISOString();
|
|
4510
4736
|
const mergedInput = args.input ?? existing.input;
|
|
4511
4737
|
const mergedGroundTruth = args.groundTruth ?? existing.groundTruth;
|
|
4738
|
+
const mergedRequestContext = args.requestContext ?? existing.requestContext;
|
|
4512
4739
|
const mergedMetadata = args.metadata ?? existing.metadata;
|
|
4513
4740
|
let newVersion;
|
|
4514
4741
|
await this.#db.client.tx(async (t) => {
|
|
@@ -4522,13 +4749,14 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4522
4749
|
[newVersion, args.id]
|
|
4523
4750
|
);
|
|
4524
4751
|
await t.none(
|
|
4525
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10)`,
|
|
4752
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10,$11)`,
|
|
4526
4753
|
[
|
|
4527
4754
|
args.id,
|
|
4528
4755
|
args.datasetId,
|
|
4529
4756
|
newVersion,
|
|
4530
4757
|
JSON.stringify(mergedInput),
|
|
4531
4758
|
jsonbArg(mergedGroundTruth),
|
|
4759
|
+
jsonbArg(mergedRequestContext),
|
|
4532
4760
|
jsonbArg(mergedMetadata),
|
|
4533
4761
|
existing.createdAt.toISOString(),
|
|
4534
4762
|
existing.createdAt.toISOString(),
|
|
@@ -4546,6 +4774,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4546
4774
|
datasetVersion: newVersion,
|
|
4547
4775
|
input: mergedInput,
|
|
4548
4776
|
groundTruth: mergedGroundTruth,
|
|
4777
|
+
requestContext: mergedRequestContext,
|
|
4549
4778
|
metadata: mergedMetadata,
|
|
4550
4779
|
updatedAt: now
|
|
4551
4780
|
};
|
|
@@ -4592,13 +4821,14 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4592
4821
|
[newVersion, id]
|
|
4593
4822
|
);
|
|
4594
4823
|
await t.none(
|
|
4595
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10)`,
|
|
4824
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10,$11)`,
|
|
4596
4825
|
[
|
|
4597
4826
|
id,
|
|
4598
4827
|
datasetId,
|
|
4599
4828
|
newVersion,
|
|
4600
4829
|
JSON.stringify(existing.input),
|
|
4601
4830
|
jsonbArg(existing.groundTruth),
|
|
4831
|
+
jsonbArg(existing.requestContext),
|
|
4602
4832
|
jsonbArg(existing.metadata),
|
|
4603
4833
|
existing.createdAt.toISOString(),
|
|
4604
4834
|
existing.createdAt.toISOString(),
|
|
@@ -4653,13 +4883,14 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4653
4883
|
newVersion = row.version;
|
|
4654
4884
|
for (const { id, input: itemInput } of itemsWithIds) {
|
|
4655
4885
|
await t.none(
|
|
4656
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10)`,
|
|
4886
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,false,$4,$5,$6,$7,$8,$9,$10,$11)`,
|
|
4657
4887
|
[
|
|
4658
4888
|
id,
|
|
4659
4889
|
input.datasetId,
|
|
4660
4890
|
newVersion,
|
|
4661
4891
|
JSON.stringify(itemInput.input),
|
|
4662
4892
|
jsonbArg(itemInput.groundTruth),
|
|
4893
|
+
jsonbArg(itemInput.requestContext),
|
|
4663
4894
|
jsonbArg(itemInput.metadata),
|
|
4664
4895
|
nowIso,
|
|
4665
4896
|
nowIso,
|
|
@@ -4679,6 +4910,7 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4679
4910
|
datasetVersion: newVersion,
|
|
4680
4911
|
input: itemInput.input,
|
|
4681
4912
|
groundTruth: itemInput.groundTruth,
|
|
4913
|
+
requestContext: itemInput.requestContext,
|
|
4682
4914
|
metadata: itemInput.metadata,
|
|
4683
4915
|
createdAt: now,
|
|
4684
4916
|
updatedAt: now
|
|
@@ -4734,13 +4966,14 @@ var DatasetsPG = class _DatasetsPG extends DatasetsStorage {
|
|
|
4734
4966
|
[newVersion, item.id]
|
|
4735
4967
|
);
|
|
4736
4968
|
await t.none(
|
|
4737
|
-
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10)`,
|
|
4969
|
+
`INSERT INTO ${itemsTable} ("id","datasetId","datasetVersion","validTo","isDeleted","input","groundTruth","requestContext","metadata","createdAt","createdAtZ","updatedAt","updatedAtZ") VALUES ($1,$2,$3,NULL,true,$4,$5,$6,$7,$8,$9,$10,$11)`,
|
|
4738
4970
|
[
|
|
4739
4971
|
item.id,
|
|
4740
4972
|
input.datasetId,
|
|
4741
4973
|
newVersion,
|
|
4742
4974
|
JSON.stringify(item.input),
|
|
4743
4975
|
jsonbArg(item.groundTruth),
|
|
4976
|
+
jsonbArg(item.requestContext),
|
|
4744
4977
|
jsonbArg(item.metadata),
|
|
4745
4978
|
item.createdAt.toISOString(),
|
|
4746
4979
|
item.createdAt.toISOString(),
|
|
@@ -7147,39 +7380,73 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
7147
7380
|
* issues on large tables (see GitHub issue #11150). The old approach required
|
|
7148
7381
|
* scanning and sorting ALL messages in a thread to assign row numbers.
|
|
7149
7382
|
*
|
|
7150
|
-
* The
|
|
7151
|
-
* fetch
|
|
7383
|
+
* The current approach uses two phases for optimal performance:
|
|
7384
|
+
* 1. Batch-fetch all target messages' metadata (thread_id, createdAt) in one query
|
|
7385
|
+
* 2. Build cursor subqueries using "createdAt" directly (not COALESCE) so that
|
|
7386
|
+
* the existing (thread_id, createdAt DESC) index can be used for index scans
|
|
7387
|
+
* instead of sequential scans. This fixes GitHub issue #11702 where semantic
|
|
7388
|
+
* recall latency scaled linearly with message count (~30s for 7.4k messages).
|
|
7152
7389
|
*/
|
|
7390
|
+
_sortMessages(messages, field, direction) {
|
|
7391
|
+
return messages.sort((a, b) => {
|
|
7392
|
+
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
7393
|
+
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
7394
|
+
if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
|
|
7395
|
+
if (aValue == null) return 1;
|
|
7396
|
+
if (bValue == null) return -1;
|
|
7397
|
+
if (aValue === bValue) {
|
|
7398
|
+
return a.id.localeCompare(b.id);
|
|
7399
|
+
}
|
|
7400
|
+
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
7401
|
+
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
7402
|
+
}
|
|
7403
|
+
return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
|
|
7404
|
+
});
|
|
7405
|
+
}
|
|
7153
7406
|
async _getIncludedMessages({ include }) {
|
|
7154
7407
|
if (!include || include.length === 0) return null;
|
|
7155
7408
|
const tableName = getTableName3({ indexName: TABLE_MESSAGES, schemaName: getSchemaName3(this.#schema) });
|
|
7156
7409
|
const selectColumns = `id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
|
|
7410
|
+
const targetIds = include.map((inc) => inc.id).filter(Boolean);
|
|
7411
|
+
if (targetIds.length === 0) return null;
|
|
7412
|
+
const idPlaceholders = targetIds.map((_, i) => "$" + (i + 1)).join(", ");
|
|
7413
|
+
const targetRows = await this.#db.client.manyOrNone(`SELECT id, thread_id, "createdAt" FROM ${tableName} WHERE id IN (${idPlaceholders})`, targetIds);
|
|
7414
|
+
if (targetRows.length === 0) return null;
|
|
7415
|
+
const targetMap = new Map(targetRows.map((r) => [r.id, { threadId: r.thread_id, createdAt: r.createdAt }]));
|
|
7157
7416
|
const unionQueries = [];
|
|
7158
7417
|
const params = [];
|
|
7159
7418
|
let paramIdx = 1;
|
|
7160
7419
|
for (const inc of include) {
|
|
7161
7420
|
const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
|
|
7421
|
+
const target = targetMap.get(id);
|
|
7422
|
+
if (!target) continue;
|
|
7423
|
+
const p1 = "$" + paramIdx;
|
|
7424
|
+
const p2 = "$" + (paramIdx + 1);
|
|
7425
|
+
const p3 = "$" + (paramIdx + 2);
|
|
7162
7426
|
unionQueries.push(`(
|
|
7163
7427
|
SELECT ${selectColumns}
|
|
7164
7428
|
FROM ${tableName} m
|
|
7165
|
-
WHERE m.thread_id =
|
|
7166
|
-
AND
|
|
7167
|
-
ORDER BY
|
|
7168
|
-
LIMIT
|
|
7429
|
+
WHERE m.thread_id = ${p1}
|
|
7430
|
+
AND m."createdAt" <= ${p2}
|
|
7431
|
+
ORDER BY m."createdAt" DESC, m.id DESC
|
|
7432
|
+
LIMIT ${p3}
|
|
7169
7433
|
)`);
|
|
7170
|
-
params.push(
|
|
7171
|
-
paramIdx +=
|
|
7434
|
+
params.push(target.threadId, target.createdAt, withPreviousMessages + 1);
|
|
7435
|
+
paramIdx += 3;
|
|
7172
7436
|
if (withNextMessages > 0) {
|
|
7437
|
+
const p4 = "$" + paramIdx;
|
|
7438
|
+
const p5 = "$" + (paramIdx + 1);
|
|
7439
|
+
const p6 = "$" + (paramIdx + 2);
|
|
7173
7440
|
unionQueries.push(`(
|
|
7174
7441
|
SELECT ${selectColumns}
|
|
7175
7442
|
FROM ${tableName} m
|
|
7176
|
-
WHERE m.thread_id =
|
|
7177
|
-
AND
|
|
7178
|
-
ORDER BY
|
|
7179
|
-
LIMIT
|
|
7443
|
+
WHERE m.thread_id = ${p4}
|
|
7444
|
+
AND m."createdAt" > ${p5}
|
|
7445
|
+
ORDER BY m."createdAt" ASC, m.id ASC
|
|
7446
|
+
LIMIT ${p6}
|
|
7180
7447
|
)`);
|
|
7181
|
-
params.push(
|
|
7182
|
-
paramIdx +=
|
|
7448
|
+
params.push(target.threadId, target.createdAt, withNextMessages);
|
|
7449
|
+
paramIdx += 3;
|
|
7183
7450
|
}
|
|
7184
7451
|
}
|
|
7185
7452
|
if (unionQueries.length === 0) return null;
|
|
@@ -7187,7 +7454,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
7187
7454
|
if (unionQueries.length === 1) {
|
|
7188
7455
|
finalQuery = unionQueries[0].slice(1, -1);
|
|
7189
7456
|
} else {
|
|
7190
|
-
finalQuery = `SELECT * FROM (${unionQueries.join(" UNION ALL ")}) AS combined ORDER BY "createdAt" ASC`;
|
|
7457
|
+
finalQuery = `SELECT * FROM (${unionQueries.join(" UNION ALL ")}) AS combined ORDER BY "createdAt" ASC, id ASC`;
|
|
7191
7458
|
}
|
|
7192
7459
|
const includedRows = await this.#db.client.manyOrNone(finalQuery, params);
|
|
7193
7460
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7301,6 +7568,24 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
7301
7568
|
queryParams.push(filter.dateRange.end);
|
|
7302
7569
|
}
|
|
7303
7570
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
7571
|
+
if (perPage === 0 && (!include || include.length === 0)) {
|
|
7572
|
+
return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
|
|
7573
|
+
}
|
|
7574
|
+
if (perPage === 0 && include && include.length > 0) {
|
|
7575
|
+
const includeMessages = await this._getIncludedMessages({ include });
|
|
7576
|
+
if (!includeMessages || includeMessages.length === 0) {
|
|
7577
|
+
return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
|
|
7578
|
+
}
|
|
7579
|
+
const messagesWithParsedContent2 = includeMessages.map((row) => this.parseRow(row));
|
|
7580
|
+
const list2 = new MessageList().add(messagesWithParsedContent2, "memory");
|
|
7581
|
+
return {
|
|
7582
|
+
messages: this._sortMessages(list2.get.all.db(), field, direction),
|
|
7583
|
+
total: 0,
|
|
7584
|
+
page,
|
|
7585
|
+
perPage: perPageForResponse,
|
|
7586
|
+
hasMore: false
|
|
7587
|
+
};
|
|
7588
|
+
}
|
|
7304
7589
|
const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
|
|
7305
7590
|
const countResult = await this.#db.client.one(countQuery, queryParams);
|
|
7306
7591
|
const total = parseInt(countResult.count, 10);
|
|
@@ -7331,21 +7616,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
7331
7616
|
}
|
|
7332
7617
|
const messagesWithParsedContent = messages.map((row) => this.parseRow(row));
|
|
7333
7618
|
const list = new MessageList().add(messagesWithParsedContent, "memory");
|
|
7334
|
-
|
|
7335
|
-
finalMessages = finalMessages.sort((a, b) => {
|
|
7336
|
-
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
7337
|
-
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
7338
|
-
if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
|
|
7339
|
-
if (aValue == null) return 1;
|
|
7340
|
-
if (bValue == null) return -1;
|
|
7341
|
-
if (aValue === bValue) {
|
|
7342
|
-
return a.id.localeCompare(b.id);
|
|
7343
|
-
}
|
|
7344
|
-
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
7345
|
-
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
7346
|
-
}
|
|
7347
|
-
return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
|
|
7348
|
-
});
|
|
7619
|
+
const finalMessages = this._sortMessages(list.get.all.db(), field, direction);
|
|
7349
7620
|
const threadIdSet = new Set(threadIds);
|
|
7350
7621
|
const returnedThreadMessageIds = new Set(
|
|
7351
7622
|
finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
|
|
@@ -7434,6 +7705,30 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
7434
7705
|
queryParams.push(filter.dateRange.end);
|
|
7435
7706
|
}
|
|
7436
7707
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
7708
|
+
if (perPage === 0 && (!include || include.length === 0)) {
|
|
7709
|
+
return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
|
|
7710
|
+
}
|
|
7711
|
+
if (perPage === 0 && include && include.length > 0) {
|
|
7712
|
+
const includeMessages = await this._getIncludedMessages({ include });
|
|
7713
|
+
if (!includeMessages || includeMessages.length === 0) {
|
|
7714
|
+
return {
|
|
7715
|
+
messages: [],
|
|
7716
|
+
total: 0,
|
|
7717
|
+
page,
|
|
7718
|
+
perPage: perPageForResponse,
|
|
7719
|
+
hasMore: false
|
|
7720
|
+
};
|
|
7721
|
+
}
|
|
7722
|
+
const messagesWithParsedContent2 = includeMessages.map((row) => this.parseRow(row));
|
|
7723
|
+
const list2 = new MessageList().add(messagesWithParsedContent2, "memory");
|
|
7724
|
+
return {
|
|
7725
|
+
messages: this._sortMessages(list2.get.all.db(), field, direction),
|
|
7726
|
+
total: 0,
|
|
7727
|
+
page,
|
|
7728
|
+
perPage: perPageForResponse,
|
|
7729
|
+
hasMore: false
|
|
7730
|
+
};
|
|
7731
|
+
}
|
|
7437
7732
|
const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
|
|
7438
7733
|
const countResult = await this.#db.client.one(countQuery, queryParams);
|
|
7439
7734
|
const total = parseInt(countResult.count, 10);
|
|
@@ -7464,21 +7759,7 @@ var MemoryPG = class _MemoryPG extends MemoryStorage {
|
|
|
7464
7759
|
}
|
|
7465
7760
|
const messagesWithParsedContent = messages.map((row) => this.parseRow(row));
|
|
7466
7761
|
const list = new MessageList().add(messagesWithParsedContent, "memory");
|
|
7467
|
-
|
|
7468
|
-
finalMessages = finalMessages.sort((a, b) => {
|
|
7469
|
-
const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
|
|
7470
|
-
const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
|
|
7471
|
-
if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
|
|
7472
|
-
if (aValue == null) return 1;
|
|
7473
|
-
if (bValue == null) return -1;
|
|
7474
|
-
if (aValue === bValue) {
|
|
7475
|
-
return a.id.localeCompare(b.id);
|
|
7476
|
-
}
|
|
7477
|
-
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
7478
|
-
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
7479
|
-
}
|
|
7480
|
-
return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
|
|
7481
|
-
});
|
|
7762
|
+
const finalMessages = this._sortMessages(list.get.all.db(), field, direction);
|
|
7482
7763
|
const hasMore = perPageInput !== false && offset + perPage < total;
|
|
7483
7764
|
return {
|
|
7484
7765
|
messages: finalMessages,
|
|
@@ -8932,6 +9213,11 @@ var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
|
|
|
8932
9213
|
}
|
|
8933
9214
|
async init() {
|
|
8934
9215
|
await this.#db.createTable({ tableName: TABLE_SPANS, schema: TABLE_SCHEMAS[TABLE_SPANS] });
|
|
9216
|
+
await this.#db.alterTable({
|
|
9217
|
+
tableName: TABLE_SPANS,
|
|
9218
|
+
schema: TABLE_SCHEMAS[TABLE_SPANS],
|
|
9219
|
+
ifNotExists: ["requestContext"]
|
|
9220
|
+
});
|
|
8935
9221
|
await this.createDefaultIndexes();
|
|
8936
9222
|
await this.createCustomIndexes();
|
|
8937
9223
|
}
|
|
@@ -9286,7 +9572,8 @@ var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
|
|
|
9286
9572
|
}
|
|
9287
9573
|
async listTraces(args) {
|
|
9288
9574
|
const { filters, pagination, orderBy } = listTracesArgsSchema.parse(args);
|
|
9289
|
-
const
|
|
9575
|
+
const page = pagination?.page ?? 0;
|
|
9576
|
+
const perPage = pagination?.perPage ?? 10;
|
|
9290
9577
|
const tableName = getTableName2({
|
|
9291
9578
|
indexName: TABLE_SPANS,
|
|
9292
9579
|
schemaName: getSchemaName2(this.#schema)
|
|
@@ -9408,10 +9695,11 @@ var ObservabilityPG = class _ObservabilityPG extends ObservabilityStorage {
|
|
|
9408
9695
|
}
|
|
9409
9696
|
}
|
|
9410
9697
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
9411
|
-
const
|
|
9412
|
-
const
|
|
9698
|
+
const orderField = orderBy?.field ?? "startedAt";
|
|
9699
|
+
const sortField = `${orderField}Z`;
|
|
9700
|
+
const sortDirection = orderBy?.direction ?? "DESC";
|
|
9413
9701
|
let orderClause;
|
|
9414
|
-
if (
|
|
9702
|
+
if (orderField === "endedAt") {
|
|
9415
9703
|
const nullsOrder = sortDirection === "DESC" ? "NULLS FIRST" : "NULLS LAST";
|
|
9416
9704
|
orderClause = `ORDER BY r."${sortField}" ${sortDirection} ${nullsOrder}`;
|
|
9417
9705
|
} else {
|