@functional-systems/lambdadb 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/commonjs/compat/qdrant/client.d.ts +212 -0
- package/dist/commonjs/compat/qdrant/client.d.ts.map +1 -0
- package/dist/commonjs/compat/qdrant/client.js +618 -0
- package/dist/commonjs/compat/qdrant/client.js.map +1 -0
- package/dist/commonjs/compat/qdrant/conversions.d.ts +40 -0
- package/dist/commonjs/compat/qdrant/conversions.d.ts.map +1 -0
- package/dist/commonjs/compat/qdrant/conversions.js +399 -0
- package/dist/commonjs/compat/qdrant/conversions.js.map +1 -0
- package/dist/commonjs/compat/qdrant/errors.d.ts +10 -0
- package/dist/commonjs/compat/qdrant/errors.d.ts.map +1 -0
- package/dist/commonjs/compat/qdrant/errors.js +25 -0
- package/dist/commonjs/compat/qdrant/errors.js.map +1 -0
- package/dist/commonjs/compat/qdrant/filters.d.ts +10 -0
- package/dist/commonjs/compat/qdrant/filters.d.ts.map +1 -0
- package/dist/commonjs/compat/qdrant/filters.js +172 -0
- package/dist/commonjs/compat/qdrant/filters.js.map +1 -0
- package/dist/commonjs/compat/qdrant/models.d.ts +198 -0
- package/dist/commonjs/compat/qdrant/models.d.ts.map +1 -0
- package/dist/commonjs/compat/qdrant/models.js +205 -0
- package/dist/commonjs/compat/qdrant/models.js.map +1 -0
- package/dist/commonjs/compat/qdrant.d.ts +4 -0
- package/dist/commonjs/compat/qdrant.d.ts.map +1 -0
- package/dist/commonjs/compat/qdrant.js +45 -0
- package/dist/commonjs/compat/qdrant.js.map +1 -0
- package/dist/esm/compat/qdrant/client.d.ts +212 -0
- package/dist/esm/compat/qdrant/client.d.ts.map +1 -0
- package/dist/esm/compat/qdrant/client.js +581 -0
- package/dist/esm/compat/qdrant/client.js.map +1 -0
- package/dist/esm/compat/qdrant/conversions.d.ts +40 -0
- package/dist/esm/compat/qdrant/conversions.d.ts.map +1 -0
- package/dist/esm/compat/qdrant/conversions.js +349 -0
- package/dist/esm/compat/qdrant/conversions.js.map +1 -0
- package/dist/esm/compat/qdrant/errors.d.ts +10 -0
- package/dist/esm/compat/qdrant/errors.d.ts.map +1 -0
- package/dist/esm/compat/qdrant/errors.js +19 -0
- package/dist/esm/compat/qdrant/errors.js.map +1 -0
- package/dist/esm/compat/qdrant/filters.d.ts +10 -0
- package/dist/esm/compat/qdrant/filters.d.ts.map +1 -0
- package/dist/esm/compat/qdrant/filters.js +169 -0
- package/dist/esm/compat/qdrant/filters.js.map +1 -0
- package/dist/esm/compat/qdrant/models.d.ts +198 -0
- package/dist/esm/compat/qdrant/models.d.ts.map +1 -0
- package/dist/esm/compat/qdrant/models.js +185 -0
- package/dist/esm/compat/qdrant/models.js.map +1 -0
- package/dist/esm/compat/qdrant.d.ts +4 -0
- package/dist/esm/compat/qdrant.d.ts.map +1 -0
- package/dist/esm/compat/qdrant.js +4 -0
- package/dist/esm/compat/qdrant.js.map +1 -0
- package/jsr.json +3 -1
- package/package.json +32 -1
- package/src/compat/qdrant/client.ts +955 -0
- package/src/compat/qdrant/conversions.ts +440 -0
- package/src/compat/qdrant/errors.ts +20 -0
- package/src/compat/qdrant/filters.ts +213 -0
- package/src/compat/qdrant/models.ts +317 -0
- package/src/compat/qdrant.ts +3 -0
- package/test/integration/qdrant-compat-external.test.mjs +351 -0
- package/test/integration/qdrant-compat-live.test.mjs +122 -0
- package/test/qdrant-compat.test.mjs +455 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
QdrantCompatClient,
|
|
6
|
+
UnsupportedQdrantFeatureError,
|
|
7
|
+
models,
|
|
8
|
+
} from "../../dist/esm/compat/qdrant.js";
|
|
9
|
+
|
|
10
|
+
const shouldRun = process.env.LAMBDADB_RUN_LIVE_TESTS === "1";
|
|
11
|
+
const projectApiKey = process.env.LAMBDADB_PROJECT_API_KEY;
|
|
12
|
+
const projectName = process.env.LAMBDADB_PROJECT_NAME || "playground";
|
|
13
|
+
const baseUrl = process.env.LAMBDADB_BASE_URL || "https://api.lambdadb.ai";
|
|
14
|
+
|
|
15
|
+
test("qdrant compatibility live smoke", {
|
|
16
|
+
skip: shouldRun && projectApiKey
|
|
17
|
+
? false
|
|
18
|
+
: "Set LAMBDADB_RUN_LIVE_TESTS=1 and LAMBDADB_PROJECT_API_KEY to run live tests",
|
|
19
|
+
}, async () => {
|
|
20
|
+
const client = new QdrantCompatClient({
|
|
21
|
+
projectApiKey,
|
|
22
|
+
projectName,
|
|
23
|
+
baseUrl,
|
|
24
|
+
});
|
|
25
|
+
const collectionName = `qdrant-live-${Date.now()}`;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await client.createCollection(collectionName, {
|
|
29
|
+
vectorsConfig: new models.VectorParams({
|
|
30
|
+
size: 3,
|
|
31
|
+
distance: models.Distance.COSINE,
|
|
32
|
+
}),
|
|
33
|
+
payloadSchema: {
|
|
34
|
+
tenant: models.PayloadSchemaType.KEYWORD,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
assert.equal(await client.collectionExists(collectionName), true);
|
|
39
|
+
|
|
40
|
+
const upsertResult = await client.upsert(collectionName, {
|
|
41
|
+
points: [
|
|
42
|
+
new models.PointStruct({
|
|
43
|
+
id: 1,
|
|
44
|
+
vector: [1.0, 0.0, 0.0],
|
|
45
|
+
payload: { tenant: "acme", title: "alpha" },
|
|
46
|
+
}),
|
|
47
|
+
new models.PointStruct({
|
|
48
|
+
id: 2,
|
|
49
|
+
vector: [0.0, 1.0, 0.0],
|
|
50
|
+
payload: { tenant: "acme", title: "beta" },
|
|
51
|
+
}),
|
|
52
|
+
new models.PointStruct({
|
|
53
|
+
id: 3,
|
|
54
|
+
vector: [0.0, 0.0, 1.0],
|
|
55
|
+
payload: { tenant: "other", title: "gamma" },
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
assert.equal(upsertResult.status, models.UpdateStatus.COMPLETED);
|
|
60
|
+
|
|
61
|
+
const queryResult = await client.queryPoints(collectionName, {
|
|
62
|
+
query: [1.0, 0.0, 0.0],
|
|
63
|
+
queryFilter: new models.Filter({
|
|
64
|
+
must: [
|
|
65
|
+
new models.FieldCondition({
|
|
66
|
+
key: "tenant",
|
|
67
|
+
match: new models.MatchValue({ value: "acme" }),
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
}),
|
|
71
|
+
limit: 2,
|
|
72
|
+
});
|
|
73
|
+
assert.ok(queryResult.points.length > 0);
|
|
74
|
+
assert.equal(queryResult.points[0].payload?.tenant, "acme");
|
|
75
|
+
|
|
76
|
+
const records = await client.retrieve(collectionName, { ids: [1] });
|
|
77
|
+
assert.equal(records[0]?.id, 1);
|
|
78
|
+
assert.equal(records[0]?.payload?.tenant, "acme");
|
|
79
|
+
|
|
80
|
+
const deleteResult = await client.delete(collectionName, {
|
|
81
|
+
pointsSelector: [1],
|
|
82
|
+
});
|
|
83
|
+
assert.equal(deleteResult.status, models.UpdateStatus.COMPLETED);
|
|
84
|
+
|
|
85
|
+
const deleteByFilterResult = await client.delete(collectionName, {
|
|
86
|
+
pointsSelector: {
|
|
87
|
+
filter: new models.Filter({
|
|
88
|
+
must: [
|
|
89
|
+
new models.FieldCondition({
|
|
90
|
+
key: "tenant",
|
|
91
|
+
match: new models.MatchValue({ value: "other" }),
|
|
92
|
+
}),
|
|
93
|
+
],
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
assert.equal(deleteByFilterResult.status, models.UpdateStatus.COMPLETED);
|
|
98
|
+
|
|
99
|
+
const deletedByFilterRecords = await client.retrieve(collectionName, { ids: [3] });
|
|
100
|
+
assert.equal(deletedByFilterRecords.length, 0);
|
|
101
|
+
|
|
102
|
+
await assert.rejects(
|
|
103
|
+
client.scroll(collectionName, {
|
|
104
|
+
scrollFilter: new models.Filter({
|
|
105
|
+
must: [
|
|
106
|
+
new models.FieldCondition({
|
|
107
|
+
key: "tenant",
|
|
108
|
+
match: new models.MatchValue({ value: "acme" }),
|
|
109
|
+
}),
|
|
110
|
+
],
|
|
111
|
+
}),
|
|
112
|
+
}),
|
|
113
|
+
UnsupportedQdrantFeatureError,
|
|
114
|
+
);
|
|
115
|
+
} finally {
|
|
116
|
+
try {
|
|
117
|
+
await client.deleteCollection(collectionName);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error?.name !== "ResourceNotFoundError") throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
QdrantClient,
|
|
6
|
+
QdrantCompatClient,
|
|
7
|
+
UnsupportedQdrantFeatureError,
|
|
8
|
+
models,
|
|
9
|
+
} from "../dist/esm/compat/qdrant.js";
|
|
10
|
+
import { PointStruct } from "../dist/esm/compat/qdrant/models.js";
|
|
11
|
+
|
|
12
|
+
class FakeDocs {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.upserts = [];
|
|
15
|
+
this.deletes = [];
|
|
16
|
+
this.fetches = [];
|
|
17
|
+
this.lists = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async upsert(body) {
|
|
21
|
+
this.upserts.push(body);
|
|
22
|
+
return { message: "ok" };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async fetch(body) {
|
|
26
|
+
this.fetches.push(body);
|
|
27
|
+
return {
|
|
28
|
+
docs: [
|
|
29
|
+
{
|
|
30
|
+
collection: "docs",
|
|
31
|
+
doc: {
|
|
32
|
+
id: "1",
|
|
33
|
+
_qdrant_id: 1,
|
|
34
|
+
_qdrant_vector: [0.1, 0.2],
|
|
35
|
+
tenant: "acme",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async delete(body) {
|
|
43
|
+
this.deletes.push(body);
|
|
44
|
+
return { message: "ok" };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async list(body = {}) {
|
|
48
|
+
this.lists.push(body);
|
|
49
|
+
return {
|
|
50
|
+
docs: [
|
|
51
|
+
{
|
|
52
|
+
collection: "docs",
|
|
53
|
+
doc: { id: "1", _qdrant_id: 1, tenant: "acme" },
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
nextPageToken: undefined,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class FakeCollection {
|
|
62
|
+
constructor(owner, name) {
|
|
63
|
+
this.owner = owner;
|
|
64
|
+
this.name = name;
|
|
65
|
+
this.docs = new FakeDocs();
|
|
66
|
+
this.queries = [];
|
|
67
|
+
this.updates = [];
|
|
68
|
+
this.deletes = [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async get() {
|
|
72
|
+
this.owner.gets.push({ collectionName: this.name });
|
|
73
|
+
return {
|
|
74
|
+
collection: {
|
|
75
|
+
collectionName: this.name,
|
|
76
|
+
indexConfigs: this.owner.indexConfigs,
|
|
77
|
+
numDocs: this.owner.numDocs,
|
|
78
|
+
collectionStatus: "ACTIVE",
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async update(body) {
|
|
84
|
+
this.updates.push(body);
|
|
85
|
+
this.owner.indexConfigs = body.indexConfigs;
|
|
86
|
+
return { collection: { collectionName: this.name, indexConfigs: body.indexConfigs } };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async delete() {
|
|
90
|
+
this.deletes.push({});
|
|
91
|
+
return { message: "ok" };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async query(body) {
|
|
95
|
+
this.queries.push(body);
|
|
96
|
+
return {
|
|
97
|
+
docs: [
|
|
98
|
+
{
|
|
99
|
+
collection: this.name,
|
|
100
|
+
score: 0.9,
|
|
101
|
+
doc: {
|
|
102
|
+
id: "1",
|
|
103
|
+
_qdrant_id: 1,
|
|
104
|
+
_qdrant_vector: [0.1, 0.2],
|
|
105
|
+
tenant: "acme",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class FakeLambdaDB {
|
|
114
|
+
constructor() {
|
|
115
|
+
this.created = [];
|
|
116
|
+
this.gets = [];
|
|
117
|
+
this.indexConfigs = {};
|
|
118
|
+
this.numDocs = 0;
|
|
119
|
+
this.collectionsByName = new Map();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async createCollection(body) {
|
|
123
|
+
this.created.push(body);
|
|
124
|
+
this.indexConfigs = body.indexConfigs ?? {};
|
|
125
|
+
return { collection: { collectionName: body.collectionName } };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
collection(name) {
|
|
129
|
+
if (!this.collectionsByName.has(name)) {
|
|
130
|
+
this.collectionsByName.set(name, new FakeCollection(this, name));
|
|
131
|
+
}
|
|
132
|
+
return this.collectionsByName.get(name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
test("qdrant compatibility exports public client and models", () => {
|
|
137
|
+
assert.equal(QdrantClient, QdrantCompatClient);
|
|
138
|
+
assert.equal(models.PointStruct, PointStruct);
|
|
139
|
+
|
|
140
|
+
const point = new models.PointStruct({
|
|
141
|
+
id: 1,
|
|
142
|
+
vector: [0.1, 0.2],
|
|
143
|
+
payload: { tenant: "acme" },
|
|
144
|
+
});
|
|
145
|
+
assert.equal(point.id, 1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("createCollection maps vector params and payload schema", async () => {
|
|
149
|
+
const fake = new FakeLambdaDB();
|
|
150
|
+
const client = new QdrantCompatClient(fake);
|
|
151
|
+
|
|
152
|
+
assert.equal(
|
|
153
|
+
await client.createCollection("docs", {
|
|
154
|
+
vectorsConfig: new models.VectorParams({
|
|
155
|
+
size: 3,
|
|
156
|
+
distance: models.Distance.DOT,
|
|
157
|
+
}),
|
|
158
|
+
payloadSchema: {
|
|
159
|
+
tenant: models.PayloadSchemaType.KEYWORD,
|
|
160
|
+
views: models.PayloadSchemaType.INTEGER,
|
|
161
|
+
score: models.PayloadSchemaType.FLOAT,
|
|
162
|
+
active: models.PayloadSchemaType.BOOL,
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
true,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
assert.deepEqual(fake.created, [
|
|
169
|
+
{
|
|
170
|
+
collectionName: "docs",
|
|
171
|
+
indexConfigs: {
|
|
172
|
+
_qdrant_vector: {
|
|
173
|
+
type: "vector",
|
|
174
|
+
dimensions: 3,
|
|
175
|
+
similarity: "dot_product",
|
|
176
|
+
},
|
|
177
|
+
tenant: { type: "keyword" },
|
|
178
|
+
views: { type: "long" },
|
|
179
|
+
score: { type: "double" },
|
|
180
|
+
active: { type: "boolean" },
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("createPayloadIndex merges existing configs and rejects non-empty backfill", async () => {
|
|
187
|
+
const fake = new FakeLambdaDB();
|
|
188
|
+
fake.indexConfigs = {
|
|
189
|
+
_qdrant_vector: {
|
|
190
|
+
type: "vector",
|
|
191
|
+
dimensions: 3,
|
|
192
|
+
similarity: "cosine",
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const client = new QdrantCompatClient(fake);
|
|
196
|
+
|
|
197
|
+
assert.equal(
|
|
198
|
+
await client.createPayloadIndex(
|
|
199
|
+
"docs",
|
|
200
|
+
"tenant",
|
|
201
|
+
models.PayloadSchemaType.KEYWORD,
|
|
202
|
+
),
|
|
203
|
+
true,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
assert.deepEqual(fake.collection("docs").updates, [
|
|
207
|
+
{
|
|
208
|
+
indexConfigs: {
|
|
209
|
+
_qdrant_vector: {
|
|
210
|
+
type: "vector",
|
|
211
|
+
dimensions: 3,
|
|
212
|
+
similarity: "cosine",
|
|
213
|
+
},
|
|
214
|
+
tenant: { type: "keyword" },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
fake.numDocs = 3;
|
|
220
|
+
await assert.rejects(
|
|
221
|
+
client.createPayloadIndex("docs", "views", models.PayloadSchemaType.INTEGER),
|
|
222
|
+
UnsupportedQdrantFeatureError,
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("upsert maps qdrant points to LambdaDB documents", async () => {
|
|
227
|
+
const fake = new FakeLambdaDB();
|
|
228
|
+
const client = new QdrantCompatClient(fake);
|
|
229
|
+
|
|
230
|
+
const result = await client.upsert("docs", {
|
|
231
|
+
points: [
|
|
232
|
+
new models.PointStruct({
|
|
233
|
+
id: 1,
|
|
234
|
+
vector: [0.1, 0.2],
|
|
235
|
+
payload: { tenant: "acme" },
|
|
236
|
+
}),
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
assert.equal(result.status, models.UpdateStatus.COMPLETED);
|
|
241
|
+
assert.deepEqual(fake.collection("docs").docs.upserts, [
|
|
242
|
+
{
|
|
243
|
+
docs: [
|
|
244
|
+
{
|
|
245
|
+
id: "1",
|
|
246
|
+
_qdrant_id: 1,
|
|
247
|
+
_qdrant_vector: [0.1, 0.2],
|
|
248
|
+
tenant: "acme",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
await assert.rejects(
|
|
255
|
+
client.upsert("docs", {
|
|
256
|
+
points: [{ id: 1, vector: [0.1], payload: { _qdrant_vector: [0.2] } }],
|
|
257
|
+
}),
|
|
258
|
+
/reserved prefix/,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
await assert.rejects(
|
|
262
|
+
client.upsert("docs", {
|
|
263
|
+
points: [
|
|
264
|
+
{
|
|
265
|
+
id: 1,
|
|
266
|
+
vector: {
|
|
267
|
+
sparse: new models.SparseVector({ indices: [1], values: [0.9] }),
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
}),
|
|
272
|
+
UnsupportedQdrantFeatureError,
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("queryPoints maps dense vectors, filters, and responses", async () => {
|
|
277
|
+
const fake = new FakeLambdaDB();
|
|
278
|
+
const client = new QdrantCompatClient(fake);
|
|
279
|
+
|
|
280
|
+
const response = await client.queryPoints("docs", {
|
|
281
|
+
query: [0.1, 0.2],
|
|
282
|
+
queryFilter: new models.Filter({
|
|
283
|
+
must: [
|
|
284
|
+
new models.FieldCondition({
|
|
285
|
+
key: "tenant",
|
|
286
|
+
match: new models.MatchValue({ value: "acme" }),
|
|
287
|
+
}),
|
|
288
|
+
],
|
|
289
|
+
mustNot: [
|
|
290
|
+
new models.FieldCondition({
|
|
291
|
+
key: "status",
|
|
292
|
+
match: new models.MatchValue({ value: "deleted" }),
|
|
293
|
+
}),
|
|
294
|
+
],
|
|
295
|
+
}),
|
|
296
|
+
limit: 5,
|
|
297
|
+
withVectors: true,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
assert.equal(response.points[0].id, 1);
|
|
301
|
+
assert.equal(response.points[0].score, 0.9);
|
|
302
|
+
assert.deepEqual(response.points[0].payload, { tenant: "acme" });
|
|
303
|
+
assert.deepEqual(response.points[0].vector, [0.1, 0.2]);
|
|
304
|
+
assert.deepEqual(fake.collection("docs").queries, [
|
|
305
|
+
{
|
|
306
|
+
query: {
|
|
307
|
+
knn: {
|
|
308
|
+
field: "_qdrant_vector",
|
|
309
|
+
k: 5,
|
|
310
|
+
queryVector: [0.1, 0.2],
|
|
311
|
+
filter: {
|
|
312
|
+
bool: [
|
|
313
|
+
{ queryString: { query: "tenant:acme" }, occur: "filter" },
|
|
314
|
+
{ queryString: { query: "status:deleted" }, occur: "must_not" },
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
size: 5,
|
|
320
|
+
consistentRead: true,
|
|
321
|
+
includeVectors: true,
|
|
322
|
+
},
|
|
323
|
+
]);
|
|
324
|
+
|
|
325
|
+
await assert.rejects(
|
|
326
|
+
client.queryPoints("docs", { query: [0.1], offset: 1 }),
|
|
327
|
+
UnsupportedQdrantFeatureError,
|
|
328
|
+
);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("payload and vector selectors map to LambdaDB fields and qdrant responses", async () => {
|
|
332
|
+
const fake = new FakeLambdaDB();
|
|
333
|
+
const collection = fake.collection("docs");
|
|
334
|
+
collection.query = async function query(body) {
|
|
335
|
+
this.queries.push(body);
|
|
336
|
+
return {
|
|
337
|
+
docs: [
|
|
338
|
+
{
|
|
339
|
+
collection: this.name,
|
|
340
|
+
score: 0.9,
|
|
341
|
+
doc: {
|
|
342
|
+
id: "1",
|
|
343
|
+
_qdrant_id: 1,
|
|
344
|
+
_qdrant_vector: [0.1, 0.2],
|
|
345
|
+
_qdrant_vector_title: [0.3, 0.4],
|
|
346
|
+
tenant: "acme",
|
|
347
|
+
hidden: "value",
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
const client = new QdrantCompatClient(fake);
|
|
354
|
+
|
|
355
|
+
const response = await client.queryPoints("docs", {
|
|
356
|
+
query: [0.1, 0.2],
|
|
357
|
+
limit: 1,
|
|
358
|
+
withPayload: ["tenant"],
|
|
359
|
+
withVectors: ["title"],
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
assert.deepEqual(response.points[0].payload, { tenant: "acme" });
|
|
363
|
+
assert.deepEqual(response.points[0].vector, { title: [0.3, 0.4] });
|
|
364
|
+
assert.deepEqual(collection.queries[0].fields, {
|
|
365
|
+
include: ["_qdrant_id", "tenant", "_qdrant_vector_title"],
|
|
366
|
+
});
|
|
367
|
+
assert.equal(collection.queries[0].includeVectors, true);
|
|
368
|
+
|
|
369
|
+
const records = await client.retrieve("docs", {
|
|
370
|
+
ids: [1],
|
|
371
|
+
withPayload: ["tenant"],
|
|
372
|
+
withVectors: false,
|
|
373
|
+
});
|
|
374
|
+
assert.deepEqual(records[0].payload, { tenant: "acme" });
|
|
375
|
+
assert.equal(records[0].vector, undefined);
|
|
376
|
+
assert.deepEqual(collection.docs.fetches.at(-1), {
|
|
377
|
+
ids: ["1"],
|
|
378
|
+
consistentRead: true,
|
|
379
|
+
includeVectors: false,
|
|
380
|
+
fields: { include: ["_qdrant_id", "tenant"] },
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("retrieve, delete, scroll, count, and getCollection return qdrant-style objects", async () => {
|
|
385
|
+
const fake = new FakeLambdaDB();
|
|
386
|
+
fake.indexConfigs = {
|
|
387
|
+
_qdrant_vector: {
|
|
388
|
+
type: "vector",
|
|
389
|
+
dimensions: 2,
|
|
390
|
+
similarity: "cosine",
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
fake.numDocs = 12;
|
|
394
|
+
const client = new QdrantCompatClient(fake);
|
|
395
|
+
|
|
396
|
+
const collection = await client.getCollection("docs");
|
|
397
|
+
assert.equal(collection.config.params.vectors.size, 2);
|
|
398
|
+
assert.equal(collection.config.params.vectors.distance, models.Distance.COSINE);
|
|
399
|
+
|
|
400
|
+
const records = await client.retrieve("docs", { ids: [1], withVectors: true });
|
|
401
|
+
assert.equal(records[0].id, 1);
|
|
402
|
+
assert.deepEqual(records[0].payload, { tenant: "acme" });
|
|
403
|
+
assert.deepEqual(records[0].vector, [0.1, 0.2]);
|
|
404
|
+
assert.deepEqual(fake.collection("docs").docs.fetches, [
|
|
405
|
+
{ ids: ["1"], consistentRead: true, includeVectors: true },
|
|
406
|
+
]);
|
|
407
|
+
|
|
408
|
+
const deleteResult = await client.delete("docs", { pointsSelector: [1] });
|
|
409
|
+
assert.equal(deleteResult.status, models.UpdateStatus.COMPLETED);
|
|
410
|
+
assert.deepEqual(fake.collection("docs").docs.deletes, [{ ids: ["1"] }]);
|
|
411
|
+
|
|
412
|
+
await client.delete("docs", {
|
|
413
|
+
filter: new models.Filter({
|
|
414
|
+
must: [
|
|
415
|
+
new models.FieldCondition({
|
|
416
|
+
key: "tenant",
|
|
417
|
+
match: new models.MatchValue({ value: "acme" }),
|
|
418
|
+
}),
|
|
419
|
+
],
|
|
420
|
+
}),
|
|
421
|
+
});
|
|
422
|
+
await client.delete("docs", {
|
|
423
|
+
pointsSelector: {
|
|
424
|
+
filter: {
|
|
425
|
+
must: [
|
|
426
|
+
{
|
|
427
|
+
key: "status",
|
|
428
|
+
match: { value: "archived" },
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
assert.deepEqual(fake.collection("docs").docs.deletes, [
|
|
435
|
+
{ ids: ["1"] },
|
|
436
|
+
{
|
|
437
|
+
filter: {
|
|
438
|
+
queryString: { query: "tenant:acme" },
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
filter: {
|
|
443
|
+
queryString: { query: "status:archived" },
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
]);
|
|
447
|
+
|
|
448
|
+
const [scrollRecords, nextOffset] = await client.scroll("docs", { limit: 3 });
|
|
449
|
+
assert.equal(scrollRecords[0].id, 1);
|
|
450
|
+
assert.equal(nextOffset, undefined);
|
|
451
|
+
assert.deepEqual(fake.collection("docs").docs.lists, [{ size: 3 }]);
|
|
452
|
+
|
|
453
|
+
const count = await client.count("docs");
|
|
454
|
+
assert.equal(count.count, 12);
|
|
455
|
+
});
|