ydb-qdrant 6.0.0 → 8.1.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.
Files changed (39) hide show
  1. package/dist/config/env.d.ts +0 -3
  2. package/dist/config/env.js +0 -17
  3. package/dist/package/api.d.ts +3 -0
  4. package/dist/qdrant/QdrantRestTypes.d.ts +35 -0
  5. package/dist/qdrant/QdrantRestTypes.js +1 -0
  6. package/dist/repositories/collectionsRepo.one-table.js +37 -63
  7. package/dist/repositories/collectionsRepo.shared.js +8 -2
  8. package/dist/repositories/pointsRepo.d.ts +5 -11
  9. package/dist/repositories/pointsRepo.js +6 -3
  10. package/dist/repositories/pointsRepo.one-table/Delete.d.ts +2 -0
  11. package/dist/repositories/pointsRepo.one-table/Delete.js +166 -0
  12. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.d.ts +14 -0
  13. package/dist/repositories/pointsRepo.one-table/PathSegmentsFilter.js +33 -0
  14. package/dist/repositories/pointsRepo.one-table/Search.d.ts +4 -0
  15. package/dist/repositories/pointsRepo.one-table/Search.js +208 -0
  16. package/dist/repositories/pointsRepo.one-table/Upsert.d.ts +2 -0
  17. package/dist/repositories/pointsRepo.one-table/Upsert.js +85 -0
  18. package/dist/repositories/pointsRepo.one-table.d.ts +3 -13
  19. package/dist/repositories/pointsRepo.one-table.js +3 -403
  20. package/dist/routes/points.js +17 -4
  21. package/dist/server.d.ts +1 -0
  22. package/dist/server.js +70 -2
  23. package/dist/services/CollectionService.d.ts +9 -0
  24. package/dist/services/CollectionService.js +9 -0
  25. package/dist/services/PointsService.d.ts +3 -10
  26. package/dist/services/PointsService.js +73 -3
  27. package/dist/types.d.ts +59 -5
  28. package/dist/types.js +27 -3
  29. package/dist/utils/normalization.d.ts +1 -0
  30. package/dist/utils/normalization.js +2 -1
  31. package/dist/utils/vectorBinary.js +94 -10
  32. package/dist/ydb/bootstrapMetaTable.d.ts +7 -0
  33. package/dist/ydb/bootstrapMetaTable.js +75 -0
  34. package/dist/ydb/client.d.ts +10 -3
  35. package/dist/ydb/client.js +26 -2
  36. package/dist/ydb/helpers.d.ts +0 -2
  37. package/dist/ydb/helpers.js +0 -7
  38. package/dist/ydb/schema.js +100 -66
  39. package/package.json +3 -6
@@ -1,65 +1,91 @@
1
1
  import { withSession, TableDescription, Column, Types, Ydb } from "./client.js";
2
2
  import { logger } from "../logging/logger.js";
3
- import { GLOBAL_POINTS_AUTOMIGRATE_ENABLED } from "../config/env.js";
4
3
  export const GLOBAL_POINTS_TABLE = "qdrant_all_points";
5
4
  // Shared YDB-related constants for repositories.
6
5
  export { UPSERT_BATCH_SIZE } from "../config/env.js";
6
+ let metaTableReady = false;
7
+ let metaTableReadyInFlight = null;
7
8
  let globalPointsTableReady = false;
9
+ let globalPointsTableReadyInFlight = null;
8
10
  function throwMigrationRequired(message) {
9
11
  logger.error(message);
10
12
  throw new Error(message);
11
13
  }
12
- export async function ensureMetaTable() {
13
- try {
14
- await withSession(async (s) => {
15
- // If table exists, describeTable will succeed
16
- try {
17
- const tableDescription = await s.describeTable("qdr__collections");
18
- const columns = tableDescription.columns ?? [];
19
- const hasLastAccessedAt = columns.some((col) => col.name === "last_accessed_at");
20
- if (!hasLastAccessedAt) {
21
- const alterDdl = `
22
- ALTER TABLE qdr__collections
23
- ADD COLUMN last_accessed_at Timestamp;
24
- `;
25
- // NOTE: ydb-sdk's public TableSession type does not surface executeSchemeQuery,
26
- // but the underlying implementation provides it. This cast relies on the
27
- // current ydb-sdk internals (tested with ydb-sdk v5.11.1) to run ALTER TABLE
28
- // as a scheme query. If the SDK changes its internal API, this may need to be
29
- // revisited or replaced with an officially supported migration mechanism.
30
- const rawSession = s;
31
- await rawSession.api.executeSchemeQuery({
32
- sessionId: rawSession.sessionId,
33
- yqlText: alterDdl,
34
- });
35
- logger.info("added last_accessed_at column to metadata table qdr__collections");
36
- }
37
- return;
38
- }
39
- catch {
40
- // create via schema API
41
- const desc = new TableDescription()
42
- .withColumns(new Column("collection", Types.UTF8), new Column("table_name", Types.UTF8), new Column("vector_dimension", Types.UINT32), new Column("distance", Types.UTF8), new Column("vector_type", Types.UTF8), new Column("created_at", Types.TIMESTAMP), new Column("last_accessed_at", Types.TIMESTAMP))
43
- .withPrimaryKey("collection");
44
- await s.createTable("qdr__collections", desc);
45
- logger.info("created metadata table qdr__collections");
46
- }
47
- });
14
+ function isTableNotFoundError(err) {
15
+ const msg = err instanceof Error ? err.message : String(err);
16
+ const ctorName = err instanceof Error
17
+ ? err.constructor?.name
18
+ : undefined;
19
+ const statusCodeMatch = /code\s+(\d{6})/i.exec(msg);
20
+ const statusCode = statusCodeMatch && statusCodeMatch[1]
21
+ ? Number(statusCodeMatch[1])
22
+ : undefined;
23
+ // ydb-sdk exposes dedicated error classes with server status codes.
24
+ // In practice, table-not-found can surface as:
25
+ // - NotFound (code 400140)
26
+ // - SchemeError (code 400070) with empty issues (observed in CI logs for describeTable)
27
+ if (ctorName === "NotFound" || statusCode === 400140) {
28
+ return true;
48
29
  }
49
- catch (err) {
50
- logger.warn({ err }, "ensureMetaTable: failed to verify or migrate qdr__collections; subsequent operations may fail if schema is incomplete");
30
+ if ((ctorName === "SchemeError" || statusCode === 400070) &&
31
+ /:\s*\[\s*\]\s*$/i.test(msg)) {
32
+ return true;
51
33
  }
34
+ return (/table.*not found/i.test(msg) ||
35
+ /path.*not found/i.test(msg) ||
36
+ /does not exist/i.test(msg));
52
37
  }
53
- export async function ensureGlobalPointsTable() {
54
- if (globalPointsTableReady) {
38
+ function isAlreadyExistsError(err) {
39
+ const msg = err instanceof Error ? err.message : String(err);
40
+ return /already exists/i.test(msg) || /path.*exists/i.test(msg);
41
+ }
42
+ async function ensureMetaTableOnce() {
43
+ await withSession(async (s) => {
44
+ let tableDescription = null;
45
+ try {
46
+ tableDescription = await s.describeTable("qdr__collections");
47
+ }
48
+ catch (err) {
49
+ if (isTableNotFoundError(err)) {
50
+ throwMigrationRequired("Metadata table qdr__collections does not exist; please create it before starting the service");
51
+ }
52
+ throw err;
53
+ }
54
+ // Table exists: validate required columns.
55
+ const columns = tableDescription.columns ?? [];
56
+ const hasLastAccessedAt = columns.some((col) => col.name === "last_accessed_at");
57
+ if (!hasLastAccessedAt) {
58
+ throwMigrationRequired("Metadata table qdr__collections is missing required column last_accessed_at; please recreate the table or apply a manual schema migration before starting the service");
59
+ }
60
+ });
61
+ metaTableReady = true;
62
+ }
63
+ export async function ensureMetaTable() {
64
+ if (metaTableReady) {
55
65
  return;
56
66
  }
67
+ if (metaTableReadyInFlight) {
68
+ await metaTableReadyInFlight;
69
+ return;
70
+ }
71
+ metaTableReadyInFlight = ensureMetaTableOnce();
72
+ try {
73
+ await metaTableReadyInFlight;
74
+ }
75
+ finally {
76
+ metaTableReadyInFlight = null;
77
+ }
78
+ }
79
+ async function ensureGlobalPointsTableOnce() {
57
80
  await withSession(async (s) => {
58
81
  let tableDescription = null;
59
82
  try {
60
83
  tableDescription = await s.describeTable(GLOBAL_POINTS_TABLE);
61
84
  }
62
- catch {
85
+ catch (err) {
86
+ if (!isTableNotFoundError(err)) {
87
+ throw err;
88
+ }
63
89
  // Table doesn't exist, create it with all columns using the new schema and
64
90
  // auto-partitioning enabled.
65
91
  const desc = new TableDescription()
@@ -70,35 +96,43 @@ export async function ensureGlobalPointsTable() {
70
96
  partitioningBySize: Ydb.FeatureFlag.Status.ENABLED,
71
97
  partitionSizeMb: 100,
72
98
  });
73
- await s.createTable(GLOBAL_POINTS_TABLE, desc);
74
- globalPointsTableReady = true;
75
- logger.info(`created global points table ${GLOBAL_POINTS_TABLE}`);
76
- return;
99
+ try {
100
+ await s.createTable(GLOBAL_POINTS_TABLE, desc);
101
+ logger.info(`created global points table ${GLOBAL_POINTS_TABLE}`);
102
+ return;
103
+ }
104
+ catch (createErr) {
105
+ // Race-safe: another concurrent caller might have created the table.
106
+ if (!isAlreadyExistsError(createErr)) {
107
+ throw createErr;
108
+ }
109
+ }
110
+ // If the table already exists (race), fall through to a fresh describe +
111
+ // schema validation.
112
+ tableDescription = await s.describeTable(GLOBAL_POINTS_TABLE);
77
113
  }
78
114
  // Table exists, require the new embedding_quantized column.
79
115
  const columns = tableDescription.columns ?? [];
80
116
  const hasEmbeddingQuantized = columns.some((col) => col.name === "embedding_quantized");
81
117
  if (!hasEmbeddingQuantized) {
82
- if (!GLOBAL_POINTS_AUTOMIGRATE_ENABLED) {
83
- throwMigrationRequired(`Global points table ${GLOBAL_POINTS_TABLE} is missing required column embedding_quantized; apply the migration (e.g., ALTER TABLE ${GLOBAL_POINTS_TABLE} RENAME COLUMN embedding_bit TO embedding_quantized) or set YDB_QDRANT_GLOBAL_POINTS_AUTOMIGRATE=true after backup to allow automatic migration.`);
84
- }
85
- const alterDdl = `
86
- ALTER TABLE ${GLOBAL_POINTS_TABLE}
87
- ADD COLUMN embedding_quantized String;
88
- `;
89
- // NOTE: Same rationale as in ensureMetaTable: executeSchemeQuery is not part of
90
- // the public TableSession TypeScript surface, so we reach into the underlying
91
- // ydb-sdk implementation (verified with ydb-sdk v5.11.1) to apply schema changes.
92
- // If future SDK versions alter this shape, this cast and migration path must be
93
- // updated accordingly.
94
- const rawSession = s;
95
- await rawSession.api.executeSchemeQuery({
96
- sessionId: rawSession.sessionId,
97
- yqlText: alterDdl,
98
- });
99
- logger.info(`added embedding_quantized column to existing table ${GLOBAL_POINTS_TABLE}`);
118
+ throwMigrationRequired(`Global points table ${GLOBAL_POINTS_TABLE} is missing required column embedding_quantized; please recreate the table or apply a manual schema migration before starting the service`);
100
119
  }
101
- // Mark table ready after schema checks/migrations succeed.
102
- globalPointsTableReady = true;
103
120
  });
104
121
  }
122
+ export async function ensureGlobalPointsTable() {
123
+ if (globalPointsTableReady) {
124
+ return;
125
+ }
126
+ if (globalPointsTableReadyInFlight) {
127
+ await globalPointsTableReadyInFlight;
128
+ return;
129
+ }
130
+ globalPointsTableReadyInFlight = ensureGlobalPointsTableOnce();
131
+ try {
132
+ await globalPointsTableReadyInFlight;
133
+ globalPointsTableReady = true;
134
+ }
135
+ finally {
136
+ globalPointsTableReadyInFlight = null;
137
+ }
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-qdrant",
3
- "version": "6.0.0",
3
+ "version": "8.1.0",
4
4
  "main": "dist/package/api.js",
5
5
  "types": "dist/package/api.d.ts",
6
6
  "exports": {
@@ -65,11 +65,8 @@
65
65
  "dependencies": {
66
66
  "@bufbuild/protobuf": "^2.10.0",
67
67
  "@grpc/grpc-js": "^1.14.0",
68
+ "@qdrant/js-client-rest": "^1.16.2",
68
69
  "@yandex-cloud/nodejs-sdk": "^2.9.0",
69
- "@ydbjs/api": "^6.0.4",
70
- "@ydbjs/core": "^6.0.4",
71
- "@ydbjs/query": "^6.0.4",
72
- "@ydbjs/value": "^6.0.4",
73
70
  "dotenv": "^17.2.3",
74
71
  "express": "^5.1.0",
75
72
  "nice-grpc": "^2.1.13",
@@ -91,4 +88,4 @@
91
88
  "typescript-eslint": "^8.47.0",
92
89
  "vitest": "^4.0.12"
93
90
  }
94
- }
91
+ }