ydb-qdrant 8.1.0 → 9.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -18
- package/dist/SmokeTest.js +2 -2
- package/dist/compute/ComputePool.d.ts +5 -0
- package/dist/compute/ComputePool.js +64 -0
- package/dist/compute/ComputeWorker.d.ts +36 -0
- package/dist/compute/ComputeWorker.js +84 -0
- package/dist/config/env.d.ts +24 -7
- package/dist/config/env.js +65 -35
- package/dist/index.d.ts +2 -0
- package/dist/index.js +92 -2
- package/dist/logging/DeployLogFormatter.d.ts +2 -0
- package/dist/logging/DeployLogFormatter.js +131 -0
- package/dist/logging/logger.js +13 -1
- package/dist/logging/requestContext.d.ts +17 -0
- package/dist/logging/requestContext.js +43 -0
- package/dist/middleware/requestLogger.js +134 -6
- package/dist/middleware/upsertBodyPhase.d.ts +6 -0
- package/dist/middleware/upsertBodyPhase.js +184 -0
- package/dist/middleware/upsertRequestTimeout.d.ts +16 -0
- package/dist/middleware/upsertRequestTimeout.js +158 -0
- package/dist/package/api.d.ts +20 -12
- package/dist/package/api.js +57 -28
- package/dist/qdrant/QdrantRestTypes.d.ts +4 -0
- package/dist/qdrant/Requests.d.ts +97 -0
- package/dist/qdrant/Requests.js +72 -0
- package/dist/repositories/collectionsRepo.d.ts +18 -2
- package/dist/repositories/collectionsRepo.js +103 -7
- package/dist/repositories/collectionsRepo.one-table.d.ts +4 -3
- package/dist/repositories/collectionsRepo.one-table.js +99 -36
- package/dist/repositories/collectionsRepo.shared.d.ts +2 -2
- package/dist/repositories/collectionsRepo.shared.js +9 -4
- package/dist/repositories/pointsRepo.d.ts +6 -4
- package/dist/repositories/pointsRepo.js +8 -7
- package/dist/repositories/pointsRepo.one-table/Delete.d.ts +2 -2
- package/dist/repositories/pointsRepo.one-table/Delete.js +157 -60
- package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.d.ts +7 -5
- package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.js +44 -13
- package/dist/repositories/pointsRepo.one-table/Retrieve.d.ts +6 -0
- package/dist/repositories/pointsRepo.one-table/Retrieve.js +69 -0
- package/dist/repositories/pointsRepo.one-table/Search.d.ts +2 -3
- package/dist/repositories/pointsRepo.one-table/Search.js +102 -124
- package/dist/repositories/pointsRepo.one-table/Upsert.d.ts +2 -2
- package/dist/repositories/pointsRepo.one-table/Upsert.js +244 -48
- package/dist/repositories/pointsRepo.one-table.d.ts +1 -0
- package/dist/repositories/pointsRepo.one-table.js +1 -0
- package/dist/routes/collections.js +45 -36
- package/dist/routes/points.js +145 -56
- package/dist/server.js +42 -6
- package/dist/services/CollectionService.d.ts +7 -5
- package/dist/services/CollectionService.js +12 -9
- package/dist/services/CollectionService.one-table.js +1 -2
- package/dist/services/CollectionService.shared.d.ts +6 -5
- package/dist/services/CollectionService.shared.js +28 -12
- package/dist/services/PointsService.d.ts +8 -0
- package/dist/services/PointsService.js +132 -15
- package/dist/types.d.ts +4 -94
- package/dist/types.js +1 -54
- package/dist/utils/EnvParsers.d.ts +5 -0
- package/dist/utils/EnvParsers.js +30 -0
- package/dist/utils/PayloadSign.d.ts +4 -0
- package/dist/utils/PayloadSign.js +18 -0
- package/dist/utils/distance.d.ts +1 -12
- package/dist/utils/distance.js +0 -21
- package/dist/utils/pathPrefix.d.ts +3 -0
- package/dist/utils/pathPrefix.js +47 -0
- package/dist/utils/prefixExpansion.d.ts +1 -0
- package/dist/utils/prefixExpansion.js +11 -0
- package/dist/utils/qdrantResponse.d.ts +13 -0
- package/dist/utils/qdrantResponse.js +12 -0
- package/dist/utils/requestIdentity.d.ts +8 -0
- package/dist/utils/requestIdentity.js +52 -0
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.js +55 -11
- package/dist/utils/tenant.d.ts +12 -6
- package/dist/utils/tenant.js +41 -32
- package/dist/utils/vectorBinary.d.ts +0 -1
- package/dist/utils/vectorBinary.js +0 -98
- package/dist/utils/ydbErrors.d.ts +1 -0
- package/dist/utils/ydbErrors.js +14 -0
- package/dist/ydb/bootstrapMetaTable.js +14 -2
- package/dist/ydb/client.d.ts +10 -2
- package/dist/ydb/client.js +83 -24
- package/dist/ydb/helpers.d.ts +0 -1
- package/dist/ydb/helpers.js +1 -2
- package/dist/ydb/schema.d.ts +2 -0
- package/dist/ydb/schema.js +84 -7
- package/package.json +10 -5
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { TypedValues, Types, withSession, Ydb as YdbRuntime, createBulkUpsertSettingsWithTimeout, } from "../../ydb/client.js";
|
|
1
|
+
import { TypedValues, Types, withSession, Ydb as YdbRuntime, createBulkUpsertSettingsWithTimeout, createExecuteQuerySettingsWithTimeout, } from "../../ydb/client.js";
|
|
2
2
|
import { buildVectorBinaryParams } from "../../ydb/helpers.js";
|
|
3
|
-
import { withRetry,
|
|
4
|
-
import { UPSERT_BATCH_SIZE } from "../../ydb/schema.js";
|
|
3
|
+
import { withRetry, isTransientYdbErrorInAcquiredSession, } from "../../utils/retry.js";
|
|
4
|
+
import { POINTS_BY_FILE_LOOKUP_TABLE, UPSERT_BATCH_SIZE, ensurePointsByFileTable, } from "../../ydb/schema.js";
|
|
5
5
|
import { UPSERT_OPERATION_TIMEOUT_MS } from "../../config/env.js";
|
|
6
6
|
import { logger } from "../../logging/logger.js";
|
|
7
|
+
import { elapsedMsSince, getMonotonicTimeNs, } from "../../logging/requestContext.js";
|
|
8
|
+
import { computePayloadSign } from "../../utils/PayloadSign.js";
|
|
9
|
+
import { extractPathPrefix } from "../../utils/pathPrefix.js";
|
|
10
|
+
import { expandPathPrefixes } from "../../utils/prefixExpansion.js";
|
|
11
|
+
import { isComputePoolEnabled, isComputePoolQueueAtLimitError, runPrepareUpsertBatch, } from "../../compute/ComputePool.js";
|
|
7
12
|
function assertPointVectorsDimension(args) {
|
|
8
13
|
for (const p of args.points) {
|
|
9
14
|
const id = String(p.id);
|
|
@@ -12,7 +17,7 @@ function assertPointVectorsDimension(args) {
|
|
|
12
17
|
const vectorPreview = previewLength > 0 ? p.vector.slice(0, previewLength) : [];
|
|
13
18
|
logger.warn({
|
|
14
19
|
tableName: args.tableName,
|
|
15
|
-
|
|
20
|
+
collection: args.collection,
|
|
16
21
|
pointId: id,
|
|
17
22
|
vectorLen: p.vector.length,
|
|
18
23
|
expectedDimension: args.dimension,
|
|
@@ -22,64 +27,255 @@ function assertPointVectorsDimension(args) {
|
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
function createBulkUpsertRowType() {
|
|
31
|
+
return Types.struct({
|
|
32
|
+
collection: Types.UTF8,
|
|
28
33
|
point_id: Types.UTF8,
|
|
29
34
|
embedding: Types.BYTES,
|
|
30
|
-
embedding_quantized: Types.BYTES,
|
|
31
35
|
payload: Types.JSON_DOCUMENT,
|
|
36
|
+
payload_sign: Types.UTF8,
|
|
37
|
+
path_prefix: Types.optional(Types.UTF8),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function asBytesBuffer(value) {
|
|
41
|
+
// In worker_threads structured clone, Buffer arrives as Uint8Array.
|
|
42
|
+
// Wrap it into Buffer without copying again.
|
|
43
|
+
return Buffer.isBuffer(value)
|
|
44
|
+
? value
|
|
45
|
+
: Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
46
|
+
}
|
|
47
|
+
function createPointsByFileLookupRowType() {
|
|
48
|
+
return Types.struct({
|
|
49
|
+
collection: Types.UTF8,
|
|
50
|
+
file_path: Types.UTF8,
|
|
51
|
+
point_id: Types.UTF8,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function buildPointsByFileLookupRows(args) {
|
|
55
|
+
return args.batch.flatMap((point) => {
|
|
56
|
+
const payloadObj = point.payload ?? {};
|
|
57
|
+
const filePath = extractPathPrefix(payloadObj);
|
|
58
|
+
if (!filePath) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return expandPathPrefixes(filePath).map((prefix) => ({
|
|
62
|
+
collection: args.collection,
|
|
63
|
+
file_path: prefix,
|
|
64
|
+
point_id: String(point.id),
|
|
65
|
+
}));
|
|
32
66
|
});
|
|
33
|
-
|
|
67
|
+
}
|
|
68
|
+
async function syncPointsByFileLookupRows(args) {
|
|
69
|
+
const rows = buildPointsByFileLookupRows(args);
|
|
70
|
+
if (rows.length === 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const rowsValue = TypedValues.list(createPointsByFileLookupRowType(), rows);
|
|
74
|
+
const upsertLookupYql = `
|
|
75
|
+
DECLARE $rows AS List<Struct<
|
|
76
|
+
collection: Utf8,
|
|
77
|
+
file_path: Utf8,
|
|
78
|
+
point_id: Utf8
|
|
79
|
+
>>;
|
|
80
|
+
|
|
81
|
+
UPSERT INTO ${POINTS_BY_FILE_LOOKUP_TABLE}
|
|
82
|
+
SELECT collection, file_path, point_id FROM AS_TABLE($rows);
|
|
83
|
+
`;
|
|
84
|
+
await withRetry(async () => {
|
|
85
|
+
const settings = createExecuteQuerySettingsWithTimeout({
|
|
86
|
+
timeoutMs: UPSERT_OPERATION_TIMEOUT_MS,
|
|
87
|
+
});
|
|
88
|
+
await args.session.executeQuery(upsertLookupYql, {
|
|
89
|
+
$rows: rowsValue,
|
|
90
|
+
}, undefined, settings);
|
|
91
|
+
}, {
|
|
92
|
+
isTransient: isTransientYdbErrorInAcquiredSession,
|
|
93
|
+
context: {
|
|
94
|
+
operation: "syncPointsByFileLookupRows",
|
|
95
|
+
collection: args.collection,
|
|
96
|
+
batchSize: args.batch.length,
|
|
97
|
+
lookupRowsCount: rows.length,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function buildBulkUpsertRowsValue(args) {
|
|
102
|
+
const apiKey = args.apiKey.trim();
|
|
103
|
+
return TypedValues.list(createBulkUpsertRowType(), args.batch.map((p) => {
|
|
34
104
|
const binaries = buildVectorBinaryParams(p.vector);
|
|
35
|
-
|
|
36
|
-
|
|
105
|
+
const payloadObj = p.payload ?? {};
|
|
106
|
+
const row = {
|
|
107
|
+
collection: args.collection,
|
|
37
108
|
point_id: String(p.id),
|
|
38
109
|
embedding: binaries.float,
|
|
39
|
-
|
|
40
|
-
|
|
110
|
+
payload: JSON.stringify(payloadObj),
|
|
111
|
+
payload_sign: computePayloadSign({ apiKey, payload: payloadObj }),
|
|
112
|
+
path_prefix: extractPathPrefix(payloadObj),
|
|
41
113
|
};
|
|
114
|
+
return row;
|
|
42
115
|
}));
|
|
43
116
|
}
|
|
44
|
-
|
|
117
|
+
async function buildBulkUpsertRowsValueWithWorkers(args) {
|
|
118
|
+
const prepared = await runPrepareUpsertBatch({
|
|
119
|
+
collection: args.collection,
|
|
120
|
+
apiKey: args.apiKey,
|
|
121
|
+
batch: args.batch,
|
|
122
|
+
});
|
|
123
|
+
return TypedValues.list(createBulkUpsertRowType(), prepared.map((row) => {
|
|
124
|
+
const mapped = {
|
|
125
|
+
collection: row.collection,
|
|
126
|
+
point_id: row.point_id,
|
|
127
|
+
embedding: asBytesBuffer(row.embedding),
|
|
128
|
+
payload: row.payload,
|
|
129
|
+
payload_sign: row.payload_sign,
|
|
130
|
+
path_prefix: row.path_prefix,
|
|
131
|
+
};
|
|
132
|
+
return mapped;
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
export async function upsertPointsOneTable(tableName, points, dimension, collection, apiKey) {
|
|
45
136
|
if (!tableName) {
|
|
46
137
|
throw new Error("bulkUpsert: tableName is empty");
|
|
47
138
|
}
|
|
48
|
-
assertPointVectorsDimension({ tableName,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
139
|
+
assertPointVectorsDimension({ tableName, collection, points, dimension });
|
|
140
|
+
await ensurePointsByFileTable();
|
|
141
|
+
const repoStartNs = getMonotonicTimeNs();
|
|
142
|
+
let batchCount = 0;
|
|
143
|
+
let currentBatchIndex = 0;
|
|
144
|
+
let prepareRowsMsTotal = 0;
|
|
145
|
+
let lookupSyncMsTotal = 0;
|
|
146
|
+
let bulkUpsertMsTotal = 0;
|
|
147
|
+
let usedWorkersAny = false;
|
|
148
|
+
try {
|
|
149
|
+
await withSession(async (s) => {
|
|
150
|
+
const bulkSettings = createBulkUpsertSettingsWithTimeout({
|
|
151
|
+
timeoutMs: UPSERT_OPERATION_TIMEOUT_MS,
|
|
59
152
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
153
|
+
for (let i = 0; i < points.length; i += UPSERT_BATCH_SIZE) {
|
|
154
|
+
const batch = points.slice(i, i + UPSERT_BATCH_SIZE);
|
|
155
|
+
currentBatchIndex = Math.floor(i / UPSERT_BATCH_SIZE) + 1;
|
|
156
|
+
const batchStartNs = getMonotonicTimeNs();
|
|
157
|
+
const workersEnabled = isComputePoolEnabled();
|
|
158
|
+
let usedWorkers = false;
|
|
159
|
+
let rowsValue;
|
|
160
|
+
const prepareRowsStartNs = getMonotonicTimeNs();
|
|
161
|
+
if (workersEnabled) {
|
|
162
|
+
try {
|
|
163
|
+
usedWorkers = true;
|
|
164
|
+
rowsValue = await buildBulkUpsertRowsValueWithWorkers({
|
|
165
|
+
collection,
|
|
166
|
+
batch,
|
|
167
|
+
apiKey,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
if (!isComputePoolQueueAtLimitError(err)) {
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
// Backpressure: fall back to inline compute to avoid failing the request.
|
|
175
|
+
usedWorkers = false;
|
|
176
|
+
rowsValue = buildBulkUpsertRowsValue({
|
|
177
|
+
collection,
|
|
178
|
+
batch,
|
|
179
|
+
apiKey,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
rowsValue = buildBulkUpsertRowsValue({
|
|
185
|
+
collection,
|
|
186
|
+
batch,
|
|
187
|
+
apiKey,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const prepareRowsMs = elapsedMsSince(prepareRowsStartNs);
|
|
191
|
+
prepareRowsMsTotal += prepareRowsMs;
|
|
192
|
+
usedWorkersAny = usedWorkersAny || usedWorkers;
|
|
193
|
+
if (logger.isLevelEnabled("debug")) {
|
|
194
|
+
logger.debug({
|
|
195
|
+
tableName,
|
|
196
|
+
mode: usedWorkers
|
|
197
|
+
? "one_table_bulk_upsert_worker_serialization"
|
|
198
|
+
: "one_table_bulk_upsert_client_side_serialization",
|
|
199
|
+
batchSize: batch.length,
|
|
200
|
+
params: {
|
|
201
|
+
rows: batch.map((p) => ({
|
|
202
|
+
collection,
|
|
203
|
+
point_id: String(p.id),
|
|
204
|
+
vectorLength: p.vector.length,
|
|
205
|
+
vectorPreview: p.vector.slice(0, 3),
|
|
206
|
+
payload: p.payload ?? {},
|
|
207
|
+
})),
|
|
208
|
+
},
|
|
209
|
+
}, "one_table upsert: executing BulkUpsert");
|
|
210
|
+
}
|
|
211
|
+
const lookupSyncStartNs = getMonotonicTimeNs();
|
|
212
|
+
await syncPointsByFileLookupRows({
|
|
213
|
+
session: s,
|
|
214
|
+
collection,
|
|
215
|
+
batch,
|
|
216
|
+
});
|
|
217
|
+
const lookupSyncMs = elapsedMsSince(lookupSyncStartNs);
|
|
218
|
+
lookupSyncMsTotal += lookupSyncMs;
|
|
219
|
+
const typedRows = YdbRuntime.TypedValue.create(rowsValue);
|
|
220
|
+
const bulkUpsertStartNs = getMonotonicTimeNs();
|
|
221
|
+
await withRetry(() => s.bulkUpsert(tableName, typedRows, bulkSettings), {
|
|
222
|
+
isTransient: isTransientYdbErrorInAcquiredSession,
|
|
223
|
+
context: {
|
|
224
|
+
tableName,
|
|
225
|
+
batchSize: batch.length,
|
|
226
|
+
mode: "bulkUpsert",
|
|
73
227
|
},
|
|
74
|
-
}
|
|
228
|
+
});
|
|
229
|
+
const bulkUpsertMs = elapsedMsSince(bulkUpsertStartNs);
|
|
230
|
+
bulkUpsertMsTotal += bulkUpsertMs;
|
|
231
|
+
batchCount = currentBatchIndex;
|
|
232
|
+
if (logger.isLevelEnabled("debug")) {
|
|
233
|
+
logger.debug({
|
|
234
|
+
phase: "upsertRepoBatch",
|
|
235
|
+
tableName,
|
|
236
|
+
collection,
|
|
237
|
+
batchIndex: currentBatchIndex,
|
|
238
|
+
batchSize: batch.length,
|
|
239
|
+
prepareRowsMs,
|
|
240
|
+
lookupSyncMs,
|
|
241
|
+
bulkUpsertMs,
|
|
242
|
+
batchTotalMs: elapsedMsSince(batchStartNs),
|
|
243
|
+
usedWorkers,
|
|
244
|
+
}, "upsert: repo batch");
|
|
245
|
+
}
|
|
75
246
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
logger.error({
|
|
251
|
+
err: err instanceof Error ? err : new Error(String(err)),
|
|
252
|
+
phase: "upsertRepo",
|
|
253
|
+
tableName,
|
|
254
|
+
collection,
|
|
255
|
+
repoTotalMs: elapsedMsSince(repoStartNs),
|
|
256
|
+
batchCount,
|
|
257
|
+
failedBatchIndex: currentBatchIndex > batchCount ? currentBatchIndex : undefined,
|
|
258
|
+
pointCount: points.length,
|
|
259
|
+
prepareRowsMsTotal,
|
|
260
|
+
lookupSyncMsTotal,
|
|
261
|
+
bulkUpsertMsTotal,
|
|
262
|
+
usedWorkersAny,
|
|
263
|
+
}, "upsert: failed");
|
|
264
|
+
throw err;
|
|
265
|
+
}
|
|
266
|
+
logger.info({
|
|
267
|
+
phase: "upsertRepo",
|
|
268
|
+
tableName,
|
|
269
|
+
collection,
|
|
270
|
+
repoTotalMs: elapsedMsSince(repoStartNs),
|
|
271
|
+
batchCount,
|
|
272
|
+
pointCount: points.length,
|
|
273
|
+
prepareRowsMsTotal,
|
|
274
|
+
lookupSyncMsTotal,
|
|
275
|
+
bulkUpsertMsTotal,
|
|
276
|
+
usedWorkersAny,
|
|
277
|
+
}, "upsert: repo summary");
|
|
278
|
+
// BulkUpsert is idempotent for the same (collection, point_id) keys; we report the
|
|
279
|
+
// number of points accepted by the API rather than rows actually changed.
|
|
280
|
+
return points.length;
|
|
85
281
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { searchPointsOneTable } from "./pointsRepo.one-table/Search.js";
|
|
2
2
|
export { upsertPointsOneTable } from "./pointsRepo.one-table/Upsert.js";
|
|
3
3
|
export { deletePointsOneTable, deletePointsByPathSegmentsOneTable, } from "./pointsRepo.one-table/Delete.js";
|
|
4
|
+
export { retrievePointsByIdsOneTable } from "./pointsRepo.one-table/Retrieve.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { searchPointsOneTable } from "./pointsRepo.one-table/Search.js";
|
|
2
2
|
export { upsertPointsOneTable } from "./pointsRepo.one-table/Upsert.js";
|
|
3
3
|
export { deletePointsOneTable, deletePointsByPathSegmentsOneTable, } from "./pointsRepo.one-table/Delete.js";
|
|
4
|
+
export { retrievePointsByIdsOneTable } from "./pointsRepo.one-table/Retrieve.js";
|
|
@@ -2,39 +2,55 @@ import { Router } from "express";
|
|
|
2
2
|
import { putCollectionIndex, createCollection, getCollection, deleteCollection, } from "../services/CollectionService.js";
|
|
3
3
|
import { QdrantServiceError } from "../services/errors.js";
|
|
4
4
|
import { logger } from "../logging/logger.js";
|
|
5
|
+
import { qdrantResponse } from "../utils/qdrantResponse.js";
|
|
6
|
+
import { isAnonymousIdentityError, resolveRequestNamespaceUserUid, resolveRequestSigningKey, } from "../utils/requestIdentity.js";
|
|
5
7
|
export const collectionsRouter = Router();
|
|
8
|
+
function buildCollectionContext(req) {
|
|
9
|
+
const userUid = resolveRequestNamespaceUserUid(req);
|
|
10
|
+
return {
|
|
11
|
+
userUid,
|
|
12
|
+
collection: String(req.params.collection),
|
|
13
|
+
apiKey: resolveRequestSigningKey(req, userUid),
|
|
14
|
+
userAgent: req.header("User-Agent") ?? undefined,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function sendKnownRouteError(res, err) {
|
|
18
|
+
if (err instanceof QdrantServiceError) {
|
|
19
|
+
res.status(err.statusCode).json(err.payload);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (isAnonymousIdentityError(err)) {
|
|
23
|
+
res.status(400).json({ status: "error", error: err.message });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
// Fix #8: PUT index → UpdateResult shape
|
|
6
29
|
collectionsRouter.put("/:collection/index", async (req, res) => {
|
|
30
|
+
const start = process.hrtime();
|
|
7
31
|
try {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
collection: String(req.params.collection),
|
|
11
|
-
apiKey: req.header("api-key") ?? undefined,
|
|
12
|
-
userAgent: req.header("User-Agent") ?? undefined,
|
|
13
|
-
});
|
|
14
|
-
res.json({ status: "ok", result });
|
|
32
|
+
await putCollectionIndex(buildCollectionContext(req));
|
|
33
|
+
res.json(qdrantResponse({ operation_id: 0, status: "completed" }, start));
|
|
15
34
|
}
|
|
16
35
|
catch (err) {
|
|
17
|
-
if (err
|
|
18
|
-
return
|
|
36
|
+
if (sendKnownRouteError(res, err)) {
|
|
37
|
+
return;
|
|
19
38
|
}
|
|
20
39
|
logger.error({ err }, "build index failed");
|
|
21
40
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
22
41
|
res.status(500).json({ status: "error", error: errorMessage });
|
|
23
42
|
}
|
|
24
43
|
});
|
|
44
|
+
// Fix #4: createCollection → result: true
|
|
25
45
|
collectionsRouter.put("/:collection", async (req, res) => {
|
|
46
|
+
const start = process.hrtime();
|
|
26
47
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
collection: String(req.params.collection),
|
|
30
|
-
apiKey: req.header("api-key") ?? undefined,
|
|
31
|
-
userAgent: req.header("User-Agent") ?? undefined,
|
|
32
|
-
}, req.body);
|
|
33
|
-
res.json({ status: "ok", result });
|
|
48
|
+
await createCollection(buildCollectionContext(req), req.body);
|
|
49
|
+
res.json(qdrantResponse(true, start));
|
|
34
50
|
}
|
|
35
51
|
catch (err) {
|
|
36
|
-
if (err
|
|
37
|
-
return
|
|
52
|
+
if (sendKnownRouteError(res, err)) {
|
|
53
|
+
return;
|
|
38
54
|
}
|
|
39
55
|
logger.error({ err }, "create collection failed");
|
|
40
56
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -42,37 +58,30 @@ collectionsRouter.put("/:collection", async (req, res) => {
|
|
|
42
58
|
}
|
|
43
59
|
});
|
|
44
60
|
collectionsRouter.get("/:collection", async (req, res) => {
|
|
61
|
+
const start = process.hrtime();
|
|
45
62
|
try {
|
|
46
|
-
const result = await getCollection(
|
|
47
|
-
|
|
48
|
-
collection: String(req.params.collection),
|
|
49
|
-
apiKey: req.header("api-key") ?? undefined,
|
|
50
|
-
userAgent: req.header("User-Agent") ?? undefined,
|
|
51
|
-
});
|
|
52
|
-
res.json({ status: "ok", result });
|
|
63
|
+
const result = await getCollection(buildCollectionContext(req));
|
|
64
|
+
res.json(qdrantResponse(result, start));
|
|
53
65
|
}
|
|
54
66
|
catch (err) {
|
|
55
|
-
if (err
|
|
56
|
-
return
|
|
67
|
+
if (sendKnownRouteError(res, err)) {
|
|
68
|
+
return;
|
|
57
69
|
}
|
|
58
70
|
logger.error({ err }, "get collection failed");
|
|
59
71
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
60
72
|
res.status(500).json({ status: "error", error: errorMessage });
|
|
61
73
|
}
|
|
62
74
|
});
|
|
75
|
+
// Fix #5: deleteCollection → result: true
|
|
63
76
|
collectionsRouter.delete("/:collection", async (req, res) => {
|
|
77
|
+
const start = process.hrtime();
|
|
64
78
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
collection: String(req.params.collection),
|
|
68
|
-
apiKey: req.header("api-key") ?? undefined,
|
|
69
|
-
userAgent: req.header("User-Agent") ?? undefined,
|
|
70
|
-
});
|
|
71
|
-
res.json({ status: "ok", result });
|
|
79
|
+
await deleteCollection(buildCollectionContext(req));
|
|
80
|
+
res.json(qdrantResponse(true, start));
|
|
72
81
|
}
|
|
73
82
|
catch (err) {
|
|
74
|
-
if (err
|
|
75
|
-
return
|
|
83
|
+
if (sendKnownRouteError(res, err)) {
|
|
84
|
+
return;
|
|
76
85
|
}
|
|
77
86
|
logger.error({ err }, "delete collection failed");
|
|
78
87
|
const errorMessage = err instanceof Error ? err.message : String(err);
|