ydb-qdrant 5.2.1 → 7.0.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.
Files changed (60) hide show
  1. package/README.md +2 -2
  2. package/dist/config/env.d.ts +9 -3
  3. package/dist/config/env.js +16 -5
  4. package/dist/package/api.d.ts +2 -2
  5. package/dist/package/api.js +2 -2
  6. package/dist/qdrant/QdrantTypes.d.ts +19 -0
  7. package/dist/qdrant/QdrantTypes.js +1 -0
  8. package/dist/repositories/collectionsRepo.d.ts +12 -7
  9. package/dist/repositories/collectionsRepo.js +157 -39
  10. package/dist/repositories/collectionsRepo.one-table.js +47 -129
  11. package/dist/repositories/pointsRepo.d.ts +5 -7
  12. package/dist/repositories/pointsRepo.js +6 -3
  13. package/dist/repositories/pointsRepo.one-table/Delete.d.ts +2 -0
  14. package/dist/repositories/pointsRepo.one-table/Delete.js +111 -0
  15. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.d.ts +11 -0
  16. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.js +32 -0
  17. package/dist/repositories/pointsRepo.one-table/Search/Approximate.d.ts +18 -0
  18. package/dist/repositories/pointsRepo.one-table/Search/Approximate.js +119 -0
  19. package/dist/repositories/pointsRepo.one-table/Search/Exact.d.ts +17 -0
  20. package/dist/repositories/pointsRepo.one-table/Search/Exact.js +101 -0
  21. package/dist/repositories/pointsRepo.one-table/Search/index.d.ts +8 -0
  22. package/dist/repositories/pointsRepo.one-table/Search/index.js +30 -0
  23. package/dist/repositories/pointsRepo.one-table/Upsert.d.ts +2 -0
  24. package/dist/repositories/pointsRepo.one-table/Upsert.js +100 -0
  25. package/dist/repositories/pointsRepo.one-table.d.ts +3 -13
  26. package/dist/repositories/pointsRepo.one-table.js +3 -403
  27. package/dist/routes/collections.js +61 -7
  28. package/dist/routes/points.js +71 -3
  29. package/dist/server.d.ts +1 -0
  30. package/dist/server.js +70 -2
  31. package/dist/services/CollectionService.d.ts +9 -0
  32. package/dist/services/CollectionService.js +13 -1
  33. package/dist/services/CollectionService.shared.d.ts +1 -0
  34. package/dist/services/CollectionService.shared.js +3 -3
  35. package/dist/services/PointsService.d.ts +8 -10
  36. package/dist/services/PointsService.js +82 -5
  37. package/dist/types.d.ts +85 -8
  38. package/dist/types.js +43 -17
  39. package/dist/utils/normalization.d.ts +1 -0
  40. package/dist/utils/normalization.js +15 -13
  41. package/dist/utils/retry.js +29 -19
  42. package/dist/utils/tenant.d.ts +2 -2
  43. package/dist/utils/tenant.js +21 -6
  44. package/dist/utils/typeGuards.d.ts +1 -0
  45. package/dist/utils/typeGuards.js +3 -0
  46. package/dist/utils/vectorBinary.js +88 -9
  47. package/dist/ydb/QueryDiagnostics.d.ts +6 -0
  48. package/dist/ydb/QueryDiagnostics.js +52 -0
  49. package/dist/ydb/SessionPool.d.ts +36 -0
  50. package/dist/ydb/SessionPool.js +248 -0
  51. package/dist/ydb/bulkUpsert.d.ts +6 -0
  52. package/dist/ydb/bulkUpsert.js +52 -0
  53. package/dist/ydb/client.d.ts +17 -16
  54. package/dist/ydb/client.js +427 -62
  55. package/dist/ydb/helpers.d.ts +0 -2
  56. package/dist/ydb/helpers.js +0 -7
  57. package/dist/ydb/schema.js +172 -54
  58. package/package.json +12 -7
  59. package/dist/repositories/collectionsRepo.shared.d.ts +0 -2
  60. package/dist/repositories/collectionsRepo.shared.js +0 -23
@@ -0,0 +1,111 @@
1
+ import { withSession } from "../../ydb/client.js";
2
+ import { buildPathSegmentsWhereClause } from "./PathSegmentsFilter.js";
3
+ import { Uint32, Utf8 } from "@ydbjs/value/primitive";
4
+ import { withRetry, isTransientYdbError } from "../../utils/retry.js";
5
+ import { UPSERT_OPERATION_TIMEOUT_MS } from "../../config/env.js";
6
+ import { attachQueryDiagnostics } from "../../ydb/QueryDiagnostics.js";
7
+ const DELETE_FILTER_SELECT_BATCH_SIZE = 1000;
8
+ export async function deletePointsOneTable(tableName, ids, uid) {
9
+ let deleted = 0;
10
+ await withSession(async (sql, signal) => {
11
+ for (const id of ids) {
12
+ await withRetry(async () => {
13
+ await attachQueryDiagnostics(sql `
14
+ DELETE FROM ${sql.identifier(tableName)}
15
+ WHERE uid = $uid AND point_id = $id;
16
+ `, {
17
+ operation: "deletePointsOneTable",
18
+ tableName,
19
+ uid,
20
+ pointId: String(id),
21
+ })
22
+ .parameter("uid", new Utf8(uid))
23
+ .parameter("id", new Utf8(String(id)))
24
+ .idempotent(true)
25
+ .timeout(UPSERT_OPERATION_TIMEOUT_MS)
26
+ .signal(signal);
27
+ }, {
28
+ isTransient: isTransientYdbError,
29
+ context: {
30
+ operation: "deletePointsOneTable",
31
+ tableName,
32
+ uid,
33
+ pointId: String(id),
34
+ },
35
+ });
36
+ deleted += 1;
37
+ }
38
+ });
39
+ return deleted;
40
+ }
41
+ function toCount(value) {
42
+ if (typeof value === "number" && Number.isFinite(value))
43
+ return value;
44
+ if (typeof value === "bigint") {
45
+ const n = Number(value);
46
+ return Number.isFinite(n) ? n : 0;
47
+ }
48
+ if (typeof value === "string") {
49
+ const n = Number(value);
50
+ return Number.isFinite(n) ? n : 0;
51
+ }
52
+ return 0;
53
+ }
54
+ export async function deletePointsByPathSegmentsOneTable(tableName, uid, paths) {
55
+ if (paths.length === 0) {
56
+ return 0;
57
+ }
58
+ const { whereSql, params: whereParams } = buildPathSegmentsWhereClause(paths);
59
+ let deleted = 0;
60
+ await withSession(async (sql, signal) => {
61
+ // Best-effort loop: stop when there are no more matching rows.
62
+ // Use limited batches to avoid per-operation buffer limits.
63
+ while (true) {
64
+ const [rows] = await withRetry(async () => {
65
+ let q = attachQueryDiagnostics(sql `
66
+ $to_delete = (
67
+ SELECT uid, point_id
68
+ FROM ${sql.identifier(tableName)}
69
+ WHERE uid = $uid AND ${sql.unsafe(whereSql)}
70
+ LIMIT $limit
71
+ );
72
+
73
+ DELETE FROM ${sql.identifier(tableName)} ON
74
+ SELECT uid, point_id FROM $to_delete;
75
+
76
+ SELECT COUNT(*) AS deleted FROM $to_delete;
77
+ `, {
78
+ operation: "deletePointsByPathSegmentsOneTable",
79
+ tableName,
80
+ uid,
81
+ batchLimit: DELETE_FILTER_SELECT_BATCH_SIZE,
82
+ pathsCount: paths.length,
83
+ })
84
+ .idempotent(true)
85
+ .timeout(UPSERT_OPERATION_TIMEOUT_MS)
86
+ .signal(signal)
87
+ .parameter("uid", new Utf8(uid))
88
+ .parameter("limit", new Uint32(DELETE_FILTER_SELECT_BATCH_SIZE));
89
+ for (const [key, value] of Object.entries(whereParams)) {
90
+ q = q.parameter(key, value);
91
+ }
92
+ return await q;
93
+ }, {
94
+ isTransient: isTransientYdbError,
95
+ context: {
96
+ operation: "deletePointsByPathSegmentsOneTable",
97
+ tableName,
98
+ uid,
99
+ batchLimit: DELETE_FILTER_SELECT_BATCH_SIZE,
100
+ pathsCount: paths.length,
101
+ },
102
+ });
103
+ const batchDeleted = toCount(rows[0]?.deleted);
104
+ if (batchDeleted <= 0) {
105
+ break;
106
+ }
107
+ deleted += batchDeleted;
108
+ }
109
+ });
110
+ return deleted;
111
+ }
@@ -0,0 +1,11 @@
1
+ import type { Value } from "@ydbjs/value";
2
+ type QueryParams = Record<string, Value>;
3
+ export declare function buildPathSegmentsWhereClause(paths: Array<Array<string>>): {
4
+ whereSql: string;
5
+ params: QueryParams;
6
+ };
7
+ export declare function buildPathSegmentsFilter(paths: Array<Array<string>> | undefined): {
8
+ whereSql: string;
9
+ whereParams: QueryParams;
10
+ } | undefined;
11
+ export {};
@@ -0,0 +1,32 @@
1
+ import { Utf8 } from "@ydbjs/value/primitive";
2
+ export function buildPathSegmentsWhereClause(paths) {
3
+ const params = {};
4
+ const orGroups = [];
5
+ for (let pIdx = 0; pIdx < paths.length; pIdx += 1) {
6
+ const segs = paths[pIdx];
7
+ if (segs.length === 0) {
8
+ throw new Error("delete-by-filter: empty path segments");
9
+ }
10
+ const andParts = [];
11
+ for (let sIdx = 0; sIdx < segs.length; sIdx += 1) {
12
+ const paramName = `$p${pIdx}_${sIdx}`;
13
+ // payload is JsonDocument; JSON_VALUE supports JsonPath access.
14
+ // Security: path segment values are always bound as parameters (see `params[paramName]`)
15
+ // and MUST NOT be interpolated into `whereSql`. The only dynamic part in the SQL text
16
+ // below is the numeric segment index (sIdx) and the internal parameter name.
17
+ andParts.push(`JSON_VALUE(payload, '$.pathSegments."${sIdx}"') = ${paramName}`);
18
+ params[paramName] = new Utf8(segs[sIdx]);
19
+ }
20
+ orGroups.push(`(${andParts.join(" AND ")})`);
21
+ }
22
+ return {
23
+ whereSql: orGroups.length === 1 ? orGroups[0] : `(${orGroups.join(" OR ")})`,
24
+ params,
25
+ };
26
+ }
27
+ export function buildPathSegmentsFilter(paths) {
28
+ if (!paths || paths.length === 0)
29
+ return undefined;
30
+ const { whereSql, params: whereParams } = buildPathSegmentsWhereClause(paths);
31
+ return { whereSql, whereParams };
32
+ }
@@ -0,0 +1,18 @@
1
+ import type { DistanceKind } from "../../../types";
2
+ import type { QdrantPayload } from "../../../qdrant/QdrantTypes.js";
3
+ export declare function searchPointsOneTableApproximate(args: {
4
+ tableName: string;
5
+ queryVector: number[];
6
+ top: number;
7
+ withPayload: boolean | undefined;
8
+ distance: DistanceKind;
9
+ dimension: number;
10
+ uid: string;
11
+ overfetchMultiplier: number;
12
+ timeoutMs: number;
13
+ filterPaths?: Array<Array<string>>;
14
+ }): Promise<Array<{
15
+ id: string;
16
+ score: number;
17
+ payload?: QdrantPayload;
18
+ }>>;
@@ -0,0 +1,119 @@
1
+ import { withSession } from "../../../ydb/client.js";
2
+ import { buildVectorBinaryParams } from "../../../ydb/helpers.js";
3
+ import { Bytes, Uint32, Utf8 } from "@ydbjs/value/primitive";
4
+ import { mapDistanceToBitKnnFn, mapDistanceToKnnFn, } from "../../../utils/distance.js";
5
+ import { buildPathSegmentsFilter } from "../PathSegmentsFilter.js";
6
+ import { attachQueryDiagnostics } from "../../../ydb/QueryDiagnostics.js";
7
+ import { isRecord } from "../../../utils/typeGuards.js";
8
+ function assertVectorDimension(vector, dimension, messagePrefix = "Vector dimension mismatch") {
9
+ if (vector.length !== dimension) {
10
+ throw new Error(`${messagePrefix}: got ${vector.length}, expected ${dimension}`);
11
+ }
12
+ }
13
+ function parsePayloadJson(payloadText) {
14
+ if (isRecord(payloadText)) {
15
+ return payloadText;
16
+ }
17
+ if (typeof payloadText !== "string" || payloadText.length === 0) {
18
+ return undefined;
19
+ }
20
+ try {
21
+ return JSON.parse(payloadText);
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ function buildApproxSearchParams(args) {
28
+ const safeTop = args.top > 0 ? args.top : 1;
29
+ const rawCandidateLimit = safeTop * args.overfetchMultiplier;
30
+ const candidateLimit = Math.max(safeTop, rawCandidateLimit);
31
+ const filter = buildPathSegmentsFilter(args.filterPaths);
32
+ const binaries = buildVectorBinaryParams(args.queryVector);
33
+ return {
34
+ params: {
35
+ ...(filter?.whereParams ?? {}),
36
+ $qbin_bit: new Bytes(binaries.bit),
37
+ $qbinf: new Bytes(binaries.float),
38
+ $candidateLimit: new Uint32(candidateLimit),
39
+ $safeTop: new Uint32(safeTop),
40
+ $uid: new Utf8(args.uid),
41
+ },
42
+ filterWhereSql: filter?.whereSql,
43
+ };
44
+ }
45
+ export async function searchPointsOneTableApproximate(args) {
46
+ assertVectorDimension(args.queryVector, args.dimension);
47
+ return await withSession(async (sql, signal) => {
48
+ const { fn, order } = mapDistanceToKnnFn(args.distance);
49
+ const { fn: bitFn, order: bitOrder } = mapDistanceToBitKnnFn(args.distance);
50
+ const { params, filterWhereSql } = buildApproxSearchParams({
51
+ queryVector: args.queryVector,
52
+ top: args.top,
53
+ uid: args.uid,
54
+ overfetchMultiplier: args.overfetchMultiplier,
55
+ filterPaths: args.filterPaths,
56
+ });
57
+ let payloadColumn;
58
+ if (args.withPayload) {
59
+ payloadColumn = sql.unsafe(", payload");
60
+ }
61
+ else {
62
+ payloadColumn = sql.unsafe("");
63
+ }
64
+ let filterClause;
65
+ if (filterWhereSql) {
66
+ filterClause = sql.unsafe(` AND ${filterWhereSql}`);
67
+ }
68
+ else {
69
+ filterClause = sql.unsafe("");
70
+ }
71
+ const baseQuery = sql `
72
+ $candidates = (
73
+ SELECT point_id
74
+ FROM ${sql.identifier(args.tableName)}
75
+ WHERE uid = $uid
76
+ AND embedding_quantized IS NOT NULL${filterClause}
77
+ ORDER BY ${sql.unsafe(bitFn)}(embedding_quantized, $qbin_bit) ${sql.unsafe(bitOrder)}
78
+ LIMIT $candidateLimit
79
+ );
80
+
81
+ SELECT
82
+ point_id${payloadColumn},
83
+ ${sql.unsafe(fn)}(embedding, $qbinf) AS score
84
+ FROM ${sql.identifier(args.tableName)}
85
+ WHERE uid = $uid
86
+ AND point_id IN $candidates${filterClause}
87
+ ORDER BY score ${sql.unsafe(order)}
88
+ LIMIT $safeTop;
89
+ `;
90
+ let q = attachQueryDiagnostics(baseQuery, {
91
+ operation: "searchPointsOneTableApproximate",
92
+ tableName: args.tableName,
93
+ uid: args.uid,
94
+ distance: args.distance,
95
+ withPayload: Boolean(args.withPayload),
96
+ overfetchMultiplier: args.overfetchMultiplier,
97
+ })
98
+ .idempotent(true)
99
+ .timeout(args.timeoutMs)
100
+ .signal(signal);
101
+ for (const [key, value] of Object.entries(params)) {
102
+ q = q.parameter(key, value);
103
+ }
104
+ const [rows] = await q;
105
+ return rows.map((r) => {
106
+ if (!r.point_id) {
107
+ throw new Error("point_id is missing in YDB search result");
108
+ }
109
+ const payload = args.withPayload
110
+ ? parsePayloadJson(r.payload)
111
+ : undefined;
112
+ return {
113
+ id: r.point_id,
114
+ score: Number(r.score),
115
+ ...(payload ? { payload } : {}),
116
+ };
117
+ });
118
+ });
119
+ }
@@ -0,0 +1,17 @@
1
+ import type { DistanceKind } from "../../../types";
2
+ import type { QdrantPayload } from "../../../qdrant/QdrantTypes.js";
3
+ export declare function searchPointsOneTableExact(args: {
4
+ tableName: string;
5
+ queryVector: number[];
6
+ top: number;
7
+ withPayload: boolean | undefined;
8
+ distance: DistanceKind;
9
+ dimension: number;
10
+ uid: string;
11
+ timeoutMs: number;
12
+ filterPaths?: Array<Array<string>>;
13
+ }): Promise<Array<{
14
+ id: string;
15
+ score: number;
16
+ payload?: QdrantPayload;
17
+ }>>;
@@ -0,0 +1,101 @@
1
+ import { withSession } from "../../../ydb/client.js";
2
+ import { buildVectorBinaryParams } from "../../../ydb/helpers.js";
3
+ import { Bytes, Uint32, Utf8 } from "@ydbjs/value/primitive";
4
+ import { mapDistanceToKnnFn } from "../../../utils/distance.js";
5
+ import { buildPathSegmentsFilter } from "../PathSegmentsFilter.js";
6
+ import { attachQueryDiagnostics } from "../../../ydb/QueryDiagnostics.js";
7
+ import { isRecord } from "../../../utils/typeGuards.js";
8
+ function assertVectorDimension(vector, dimension, messagePrefix = "Vector dimension mismatch") {
9
+ if (vector.length !== dimension) {
10
+ throw new Error(`${messagePrefix}: got ${vector.length}, expected ${dimension}`);
11
+ }
12
+ }
13
+ function parsePayloadJson(payloadText) {
14
+ if (isRecord(payloadText)) {
15
+ return payloadText;
16
+ }
17
+ if (typeof payloadText !== "string" || payloadText.length === 0) {
18
+ return undefined;
19
+ }
20
+ try {
21
+ return JSON.parse(payloadText);
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ function buildExactSearchParams(args) {
28
+ const filter = buildPathSegmentsFilter(args.filterPaths);
29
+ const binaries = buildVectorBinaryParams(args.queryVector);
30
+ return {
31
+ params: {
32
+ ...(filter?.whereParams ?? {}),
33
+ $qbinf: new Bytes(binaries.float),
34
+ $k: new Uint32(args.top),
35
+ $uid: new Utf8(args.uid),
36
+ },
37
+ filterWhereSql: filter?.whereSql,
38
+ };
39
+ }
40
+ export async function searchPointsOneTableExact(args) {
41
+ assertVectorDimension(args.queryVector, args.dimension);
42
+ return await withSession(async (sql, signal) => {
43
+ const { fn, order } = mapDistanceToKnnFn(args.distance);
44
+ const { params, filterWhereSql } = buildExactSearchParams({
45
+ queryVector: args.queryVector,
46
+ top: args.top,
47
+ uid: args.uid,
48
+ filterPaths: args.filterPaths,
49
+ });
50
+ let payloadColumn;
51
+ if (args.withPayload) {
52
+ payloadColumn = sql.unsafe(", payload");
53
+ }
54
+ else {
55
+ payloadColumn = sql.unsafe("");
56
+ }
57
+ let filterClause;
58
+ if (filterWhereSql) {
59
+ filterClause = sql.unsafe(` AND ${filterWhereSql}`);
60
+ }
61
+ else {
62
+ filterClause = sql.unsafe("");
63
+ }
64
+ const baseQuery = sql `
65
+ SELECT
66
+ point_id${payloadColumn},
67
+ ${sql.unsafe(fn)}(embedding, $qbinf) AS score
68
+ FROM ${sql.identifier(args.tableName)}
69
+ WHERE uid = $uid${filterClause}
70
+ ORDER BY score ${sql.unsafe(order)}
71
+ LIMIT $k;
72
+ `;
73
+ let q = attachQueryDiagnostics(baseQuery, {
74
+ operation: "searchPointsOneTableExact",
75
+ tableName: args.tableName,
76
+ uid: args.uid,
77
+ distance: args.distance,
78
+ withPayload: Boolean(args.withPayload),
79
+ })
80
+ .idempotent(true)
81
+ .timeout(args.timeoutMs)
82
+ .signal(signal);
83
+ for (const [key, value] of Object.entries(params)) {
84
+ q = q.parameter(key, value);
85
+ }
86
+ const [rows] = await q;
87
+ return rows.map((r) => {
88
+ if (!r.point_id) {
89
+ throw new Error("point_id is missing in YDB search result");
90
+ }
91
+ const payload = args.withPayload
92
+ ? parsePayloadJson(r.payload)
93
+ : undefined;
94
+ return {
95
+ id: r.point_id,
96
+ score: Number(r.score),
97
+ ...(payload ? { payload } : {}),
98
+ };
99
+ });
100
+ });
101
+ }
@@ -0,0 +1,8 @@
1
+ import type { DistanceKind } from "../../../types";
2
+ import { SearchMode } from "../../../config/env.js";
3
+ import type { QdrantPayload } from "../../../qdrant/QdrantTypes.js";
4
+ export declare function searchPointsOneTable(tableName: string, queryVector: number[], top: number, withPayload: boolean | undefined, distance: DistanceKind, dimension: number, uid: string, mode: SearchMode | undefined, overfetchMultiplier: number, filterPaths?: Array<Array<string>>): Promise<Array<{
5
+ id: string;
6
+ score: number;
7
+ payload?: QdrantPayload;
8
+ }>>;
@@ -0,0 +1,30 @@
1
+ import { SearchMode, SEARCH_OPERATION_TIMEOUT_MS, } from "../../../config/env.js";
2
+ import { searchPointsOneTableExact } from "./Exact.js";
3
+ import { searchPointsOneTableApproximate } from "./Approximate.js";
4
+ export async function searchPointsOneTable(tableName, queryVector, top, withPayload, distance, dimension, uid, mode, overfetchMultiplier, filterPaths) {
5
+ if (mode === SearchMode.Exact) {
6
+ return await searchPointsOneTableExact({
7
+ tableName,
8
+ queryVector,
9
+ top,
10
+ withPayload,
11
+ distance,
12
+ dimension,
13
+ uid,
14
+ timeoutMs: SEARCH_OPERATION_TIMEOUT_MS,
15
+ filterPaths,
16
+ });
17
+ }
18
+ return await searchPointsOneTableApproximate({
19
+ tableName,
20
+ queryVector,
21
+ top,
22
+ withPayload,
23
+ distance,
24
+ dimension,
25
+ uid,
26
+ overfetchMultiplier,
27
+ timeoutMs: SEARCH_OPERATION_TIMEOUT_MS,
28
+ filterPaths,
29
+ });
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { QdrantPointStructDense } from "../../qdrant/QdrantTypes.js";
2
+ export declare function upsertPointsOneTable(tableName: string, points: QdrantPointStructDense[], dimension: number, uid: string): Promise<number>;
@@ -0,0 +1,100 @@
1
+ import { getAbortErrorCause, isTimeoutAbortError } from "../../ydb/client.js";
2
+ import { buildVectorBinaryParams } from "../../ydb/helpers.js";
3
+ import { UPSERT_BATCH_SIZE } from "../../ydb/schema.js";
4
+ import { UPSERT_OPERATION_TIMEOUT_MS } from "../../config/env.js";
5
+ import { logger } from "../../logging/logger.js";
6
+ import { withRetry, isTransientYdbError } from "../../utils/retry.js";
7
+ import { bulkUpsertRowsOnce } from "../../ydb/bulkUpsert.js";
8
+ import { Bytes, JsonDocument, Utf8 } from "@ydbjs/value/primitive";
9
+ import { List } from "@ydbjs/value/list";
10
+ import { Struct } from "@ydbjs/value/struct";
11
+ function assertPointVectorsDimension(args) {
12
+ for (const p of args.points) {
13
+ const id = String(p.id);
14
+ if (p.vector.length !== args.dimension) {
15
+ const previewLength = Math.min(16, p.vector.length);
16
+ const vectorPreview = previewLength > 0 ? p.vector.slice(0, previewLength) : [];
17
+ logger.warn({
18
+ tableName: args.tableName,
19
+ uid: args.uid,
20
+ pointId: id,
21
+ vectorLen: p.vector.length,
22
+ expectedDimension: args.dimension,
23
+ vectorPreview,
24
+ }, "upsertPointsOneTable: vector dimension mismatch");
25
+ throw new Error(`Vector dimension mismatch for id=${id}: got ${p.vector.length}, expected ${args.dimension}`);
26
+ }
27
+ }
28
+ }
29
+ function buildUpsertQueryAndParams(args) {
30
+ const rows = args.batch.map((p) => {
31
+ const binaries = buildVectorBinaryParams(p.vector);
32
+ return {
33
+ uid: new Utf8(args.uid),
34
+ point_id: new Utf8(String(p.id)),
35
+ embedding: new Bytes(binaries.float),
36
+ embedding_quantized: new Bytes(binaries.bit),
37
+ payload: new JsonDocument(JSON.stringify(p.payload ?? {})),
38
+ };
39
+ });
40
+ const rowsValue = new List(...rows.map((row) => new Struct({
41
+ uid: row.uid,
42
+ point_id: row.point_id,
43
+ embedding: row.embedding,
44
+ embedding_quantized: row.embedding_quantized,
45
+ payload: row.payload,
46
+ })));
47
+ return {
48
+ rowsValue,
49
+ };
50
+ }
51
+ export async function upsertPointsOneTable(tableName, points, dimension, uid) {
52
+ assertPointVectorsDimension({ tableName, uid, points, dimension });
53
+ let upserted = 0;
54
+ for (let i = 0; i < points.length; i += UPSERT_BATCH_SIZE) {
55
+ const batch = points.slice(i, i + UPSERT_BATCH_SIZE);
56
+ const { rowsValue } = buildUpsertQueryAndParams({
57
+ tableName,
58
+ uid,
59
+ batch,
60
+ });
61
+ await withRetry(async () => {
62
+ const startedAtMs = Date.now();
63
+ try {
64
+ await bulkUpsertRowsOnce({
65
+ tableName,
66
+ rowsValue,
67
+ timeoutMs: UPSERT_OPERATION_TIMEOUT_MS,
68
+ });
69
+ }
70
+ catch (err) {
71
+ const durationMs = Date.now() - startedAtMs;
72
+ if (err instanceof Error && err.name === "AbortError") {
73
+ logger.warn({
74
+ tableName,
75
+ uid,
76
+ batchStart: i,
77
+ batchSize: batch.length,
78
+ timeoutMs: UPSERT_OPERATION_TIMEOUT_MS,
79
+ durationMs,
80
+ err,
81
+ errCause: getAbortErrorCause(err),
82
+ isTimeout: isTimeoutAbortError(err),
83
+ }, "upsertPointsOneTable: BulkUpsert aborted");
84
+ }
85
+ throw err;
86
+ }
87
+ }, {
88
+ isTransient: isTransientYdbError,
89
+ context: {
90
+ operation: "upsertPointsOneTable",
91
+ tableName,
92
+ uid,
93
+ batchStart: i,
94
+ batchSize: batch.length,
95
+ },
96
+ });
97
+ upserted += batch.length;
98
+ }
99
+ return upserted;
100
+ }
@@ -1,13 +1,3 @@
1
- import type { DistanceKind } from "../types";
2
- import { SearchMode } from "../config/env.js";
3
- export declare function upsertPointsOneTable(tableName: string, points: Array<{
4
- id: string | number;
5
- vector: number[];
6
- payload?: Record<string, unknown>;
7
- }>, dimension: number, uid: string): Promise<number>;
8
- export declare function searchPointsOneTable(tableName: string, queryVector: number[], top: number, withPayload: boolean | undefined, distance: DistanceKind, dimension: number, uid: string, mode: SearchMode | undefined, overfetchMultiplier: number): Promise<Array<{
9
- id: string;
10
- score: number;
11
- payload?: Record<string, unknown>;
12
- }>>;
13
- export declare function deletePointsOneTable(tableName: string, ids: Array<string | number>, uid: string): Promise<number>;
1
+ export { searchPointsOneTable } from "./pointsRepo.one-table/Search/index.js";
2
+ export { upsertPointsOneTable } from "./pointsRepo.one-table/Upsert.js";
3
+ export { deletePointsOneTable, deletePointsByPathSegmentsOneTable, } from "./pointsRepo.one-table/Delete.js";