@lancedb/lancedb 0.5.2 → 0.7.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/Cargo.toml +3 -3
- package/biome.json +19 -3
- package/dist/arrow.d.ts +41 -8
- package/dist/arrow.js +4 -4
- package/dist/connection.d.ts +49 -29
- package/dist/connection.js +21 -73
- package/dist/embedding/embedding_function.d.ts +9 -1
- package/dist/embedding/embedding_function.js +6 -0
- package/dist/embedding/openai.d.ts +6 -5
- package/dist/embedding/openai.js +4 -2
- package/dist/embedding/registry.d.ts +6 -11
- package/dist/index.d.ts +51 -3
- package/dist/index.js +28 -4
- package/dist/merge.d.ts +54 -0
- package/dist/merge.js +64 -0
- package/dist/native.d.ts +29 -3
- package/dist/native.js +26 -9
- package/dist/query.d.ts +33 -10
- package/dist/query.js +100 -13
- package/dist/remote/client.d.ts +28 -0
- package/dist/remote/client.js +172 -0
- package/dist/remote/connection.d.ts +25 -0
- package/dist/remote/connection.js +110 -0
- package/dist/remote/index.d.ts +3 -0
- package/dist/remote/index.js +9 -0
- package/dist/remote/table.d.ts +42 -0
- package/dist/remote/table.js +179 -0
- package/dist/sanitize.d.ts +3 -2
- package/dist/sanitize.js +55 -1
- package/dist/table.d.ts +105 -30
- package/dist/table.js +94 -237
- package/dist/util.d.ts +14 -0
- package/dist/util.js +65 -0
- package/examples/ann_indexes.ts +49 -0
- package/examples/basic.ts +149 -0
- package/examples/embedding.ts +83 -0
- package/examples/filtering.ts +34 -0
- package/examples/jsconfig.json +27 -0
- package/examples/package-lock.json +79 -0
- package/examples/package.json +18 -0
- package/examples/search.ts +37 -0
- package/lancedb/arrow.ts +80 -23
- package/lancedb/connection.ts +107 -92
- package/lancedb/embedding/embedding_function.ts +12 -1
- package/lancedb/embedding/openai.ts +11 -6
- package/lancedb/embedding/registry.ts +34 -22
- package/lancedb/index.ts +101 -2
- package/lancedb/merge.ts +70 -0
- package/lancedb/query.ts +114 -28
- package/lancedb/remote/client.ts +221 -0
- package/lancedb/remote/connection.ts +201 -0
- package/lancedb/remote/index.ts +3 -0
- package/lancedb/remote/table.ts +226 -0
- package/lancedb/sanitize.ts +73 -1
- package/lancedb/table.ts +320 -132
- package/lancedb/util.ts +69 -0
- package/native.d.ts +208 -0
- package/nodejs-artifacts/arrow.d.ts +41 -8
- package/nodejs-artifacts/arrow.js +4 -4
- package/nodejs-artifacts/connection.d.ts +49 -29
- package/nodejs-artifacts/connection.js +21 -73
- package/nodejs-artifacts/embedding/embedding_function.d.ts +9 -1
- package/nodejs-artifacts/embedding/embedding_function.js +6 -0
- package/nodejs-artifacts/embedding/openai.d.ts +6 -5
- package/nodejs-artifacts/embedding/openai.js +4 -2
- package/nodejs-artifacts/embedding/registry.d.ts +6 -11
- package/nodejs-artifacts/index.d.ts +51 -3
- package/nodejs-artifacts/index.js +28 -4
- package/nodejs-artifacts/merge.d.ts +54 -0
- package/nodejs-artifacts/merge.js +64 -0
- package/nodejs-artifacts/native.d.ts +29 -3
- package/nodejs-artifacts/native.js +26 -9
- package/nodejs-artifacts/query.d.ts +33 -10
- package/nodejs-artifacts/query.js +100 -13
- package/nodejs-artifacts/remote/client.d.ts +28 -0
- package/nodejs-artifacts/remote/client.js +172 -0
- package/nodejs-artifacts/remote/connection.d.ts +25 -0
- package/nodejs-artifacts/remote/connection.js +110 -0
- package/nodejs-artifacts/remote/index.d.ts +3 -0
- package/nodejs-artifacts/remote/index.js +9 -0
- package/nodejs-artifacts/remote/table.d.ts +42 -0
- package/nodejs-artifacts/remote/table.js +179 -0
- package/nodejs-artifacts/sanitize.d.ts +3 -2
- package/nodejs-artifacts/sanitize.js +55 -1
- package/nodejs-artifacts/table.d.ts +105 -30
- package/nodejs-artifacts/table.js +94 -237
- package/nodejs-artifacts/util.d.ts +14 -0
- package/nodejs-artifacts/util.js +65 -0
- package/package.json +25 -11
package/lancedb/query.ts
CHANGED
|
@@ -89,15 +89,26 @@ export interface QueryExecutionOptions {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/** Common methods supported by all query types */
|
|
92
|
-
export class QueryBase<
|
|
93
|
-
|
|
94
|
-
QueryType,
|
|
95
|
-
> implements AsyncIterable<RecordBatch>
|
|
92
|
+
export class QueryBase<NativeQueryType extends NativeQuery | NativeVectorQuery>
|
|
93
|
+
implements AsyncIterable<RecordBatch>
|
|
96
94
|
{
|
|
97
|
-
protected constructor(
|
|
95
|
+
protected constructor(
|
|
96
|
+
protected inner: NativeQueryType | Promise<NativeQueryType>,
|
|
97
|
+
) {
|
|
98
98
|
// intentionally empty
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// call a function on the inner (either a promise or the actual object)
|
|
102
|
+
protected doCall(fn: (inner: NativeQueryType) => void) {
|
|
103
|
+
if (this.inner instanceof Promise) {
|
|
104
|
+
this.inner = this.inner.then((inner) => {
|
|
105
|
+
fn(inner);
|
|
106
|
+
return inner;
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
fn(this.inner);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
101
112
|
/**
|
|
102
113
|
* A filter statement to be applied to this query.
|
|
103
114
|
*
|
|
@@ -110,9 +121,17 @@ export class QueryBase<
|
|
|
110
121
|
* Filtering performance can often be improved by creating a scalar index
|
|
111
122
|
* on the filter column(s).
|
|
112
123
|
*/
|
|
113
|
-
where(predicate: string):
|
|
114
|
-
this.inner.onlyIf(predicate);
|
|
115
|
-
return this
|
|
124
|
+
where(predicate: string): this {
|
|
125
|
+
this.doCall((inner: NativeQueryType) => inner.onlyIf(predicate));
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A filter statement to be applied to this query.
|
|
130
|
+
* @alias where
|
|
131
|
+
* @deprecated Use `where` instead
|
|
132
|
+
*/
|
|
133
|
+
filter(predicate: string): this {
|
|
134
|
+
return this.where(predicate);
|
|
116
135
|
}
|
|
117
136
|
|
|
118
137
|
/**
|
|
@@ -147,7 +166,7 @@ export class QueryBase<
|
|
|
147
166
|
*/
|
|
148
167
|
select(
|
|
149
168
|
columns: string[] | Map<string, string> | Record<string, string> | string,
|
|
150
|
-
):
|
|
169
|
+
): this {
|
|
151
170
|
let columnTuples: [string, string][];
|
|
152
171
|
if (typeof columns === "string") {
|
|
153
172
|
columns = [columns];
|
|
@@ -159,8 +178,10 @@ export class QueryBase<
|
|
|
159
178
|
} else {
|
|
160
179
|
columnTuples = Object.entries(columns);
|
|
161
180
|
}
|
|
162
|
-
this.inner
|
|
163
|
-
|
|
181
|
+
this.doCall((inner: NativeQueryType) => {
|
|
182
|
+
inner.select(columnTuples);
|
|
183
|
+
});
|
|
184
|
+
return this;
|
|
164
185
|
}
|
|
165
186
|
|
|
166
187
|
/**
|
|
@@ -169,15 +190,19 @@ export class QueryBase<
|
|
|
169
190
|
* By default, a plain search has no limit. If this method is not
|
|
170
191
|
* called then every valid row from the table will be returned.
|
|
171
192
|
*/
|
|
172
|
-
limit(limit: number):
|
|
173
|
-
this.inner.limit(limit);
|
|
174
|
-
return this
|
|
193
|
+
limit(limit: number): this {
|
|
194
|
+
this.doCall((inner: NativeQueryType) => inner.limit(limit));
|
|
195
|
+
return this;
|
|
175
196
|
}
|
|
176
197
|
|
|
177
198
|
protected nativeExecute(
|
|
178
199
|
options?: Partial<QueryExecutionOptions>,
|
|
179
200
|
): Promise<NativeBatchIterator> {
|
|
180
|
-
|
|
201
|
+
if (this.inner instanceof Promise) {
|
|
202
|
+
return this.inner.then((inner) => inner.execute(options?.maxBatchLength));
|
|
203
|
+
} else {
|
|
204
|
+
return this.inner.execute(options?.maxBatchLength);
|
|
205
|
+
}
|
|
181
206
|
}
|
|
182
207
|
|
|
183
208
|
/**
|
|
@@ -206,7 +231,13 @@ export class QueryBase<
|
|
|
206
231
|
/** Collect the results as an Arrow @see {@link ArrowTable}. */
|
|
207
232
|
async toArrow(options?: Partial<QueryExecutionOptions>): Promise<ArrowTable> {
|
|
208
233
|
const batches = [];
|
|
209
|
-
|
|
234
|
+
let inner;
|
|
235
|
+
if (this.inner instanceof Promise) {
|
|
236
|
+
inner = await this.inner;
|
|
237
|
+
} else {
|
|
238
|
+
inner = this.inner;
|
|
239
|
+
}
|
|
240
|
+
for await (const batch of new RecordBatchIterable(inner, options)) {
|
|
210
241
|
batches.push(batch);
|
|
211
242
|
}
|
|
212
243
|
return new ArrowTable(batches);
|
|
@@ -218,6 +249,28 @@ export class QueryBase<
|
|
|
218
249
|
const tbl = await this.toArrow(options);
|
|
219
250
|
return tbl.toArray();
|
|
220
251
|
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generates an explanation of the query execution plan.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* import * as lancedb from "@lancedb/lancedb"
|
|
258
|
+
* const db = await lancedb.connect("./.lancedb");
|
|
259
|
+
* const table = await db.createTable("my_table", [
|
|
260
|
+
* { vector: [1.1, 0.9], id: "1" },
|
|
261
|
+
* ]);
|
|
262
|
+
* const plan = await table.query().nearestTo([0.5, 0.2]).explainPlan();
|
|
263
|
+
*
|
|
264
|
+
* @param verbose - If true, provides a more detailed explanation. Defaults to false.
|
|
265
|
+
* @returns A Promise that resolves to a string containing the query execution plan explanation.
|
|
266
|
+
*/
|
|
267
|
+
async explainPlan(verbose = false): Promise<string> {
|
|
268
|
+
if (this.inner instanceof Promise) {
|
|
269
|
+
return this.inner.then((inner) => inner.explainPlan(verbose));
|
|
270
|
+
} else {
|
|
271
|
+
return this.inner.explainPlan(verbose);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
221
274
|
}
|
|
222
275
|
|
|
223
276
|
/**
|
|
@@ -232,8 +285,8 @@ export interface ExecutableQuery {}
|
|
|
232
285
|
*
|
|
233
286
|
* This builder can be reused to execute the query many times.
|
|
234
287
|
*/
|
|
235
|
-
export class VectorQuery extends QueryBase<NativeVectorQuery
|
|
236
|
-
constructor(inner: NativeVectorQuery) {
|
|
288
|
+
export class VectorQuery extends QueryBase<NativeVectorQuery> {
|
|
289
|
+
constructor(inner: NativeVectorQuery | Promise<NativeVectorQuery>) {
|
|
237
290
|
super(inner);
|
|
238
291
|
}
|
|
239
292
|
|
|
@@ -260,7 +313,8 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
260
313
|
* you the desired recall.
|
|
261
314
|
*/
|
|
262
315
|
nprobes(nprobes: number): VectorQuery {
|
|
263
|
-
|
|
316
|
+
super.doCall((inner) => inner.nprobes(nprobes));
|
|
317
|
+
|
|
264
318
|
return this;
|
|
265
319
|
}
|
|
266
320
|
|
|
@@ -274,7 +328,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
274
328
|
* whose data type is a fixed-size-list of floats.
|
|
275
329
|
*/
|
|
276
330
|
column(column: string): VectorQuery {
|
|
277
|
-
|
|
331
|
+
super.doCall((inner) => inner.column(column));
|
|
278
332
|
return this;
|
|
279
333
|
}
|
|
280
334
|
|
|
@@ -292,8 +346,10 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
292
346
|
*
|
|
293
347
|
* By default "l2" is used.
|
|
294
348
|
*/
|
|
295
|
-
distanceType(
|
|
296
|
-
|
|
349
|
+
distanceType(
|
|
350
|
+
distanceType: Required<IvfPqOptions>["distanceType"],
|
|
351
|
+
): VectorQuery {
|
|
352
|
+
super.doCall((inner) => inner.distanceType(distanceType));
|
|
297
353
|
return this;
|
|
298
354
|
}
|
|
299
355
|
|
|
@@ -327,7 +383,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
327
383
|
* distance between the query vector and the actual uncompressed vector.
|
|
328
384
|
*/
|
|
329
385
|
refineFactor(refineFactor: number): VectorQuery {
|
|
330
|
-
|
|
386
|
+
super.doCall((inner) => inner.refineFactor(refineFactor));
|
|
331
387
|
return this;
|
|
332
388
|
}
|
|
333
389
|
|
|
@@ -352,7 +408,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
352
408
|
* factor can often help restore some of the results lost by post filtering.
|
|
353
409
|
*/
|
|
354
410
|
postfilter(): VectorQuery {
|
|
355
|
-
|
|
411
|
+
super.doCall((inner) => inner.postfilter());
|
|
356
412
|
return this;
|
|
357
413
|
}
|
|
358
414
|
|
|
@@ -366,13 +422,13 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
366
422
|
* calculate your recall to select an appropriate value for nprobes.
|
|
367
423
|
*/
|
|
368
424
|
bypassVectorIndex(): VectorQuery {
|
|
369
|
-
|
|
425
|
+
super.doCall((inner) => inner.bypassVectorIndex());
|
|
370
426
|
return this;
|
|
371
427
|
}
|
|
372
428
|
}
|
|
373
429
|
|
|
374
430
|
/** A builder for LanceDB queries. */
|
|
375
|
-
export class Query extends QueryBase<NativeQuery
|
|
431
|
+
export class Query extends QueryBase<NativeQuery> {
|
|
376
432
|
constructor(tbl: NativeTable) {
|
|
377
433
|
super(tbl.query());
|
|
378
434
|
}
|
|
@@ -415,7 +471,37 @@ export class Query extends QueryBase<NativeQuery, Query> {
|
|
|
415
471
|
* a default `limit` of 10 will be used. @see {@link Query#limit}
|
|
416
472
|
*/
|
|
417
473
|
nearestTo(vector: IntoVector): VectorQuery {
|
|
418
|
-
|
|
419
|
-
|
|
474
|
+
if (this.inner instanceof Promise) {
|
|
475
|
+
const nativeQuery = this.inner.then(async (inner) => {
|
|
476
|
+
if (vector instanceof Promise) {
|
|
477
|
+
const arr = await vector.then((v) => Float32Array.from(v));
|
|
478
|
+
return inner.nearestTo(arr);
|
|
479
|
+
} else {
|
|
480
|
+
return inner.nearestTo(Float32Array.from(vector));
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return new VectorQuery(nativeQuery);
|
|
484
|
+
}
|
|
485
|
+
if (vector instanceof Promise) {
|
|
486
|
+
const res = (async () => {
|
|
487
|
+
try {
|
|
488
|
+
const v = await vector;
|
|
489
|
+
const arr = Float32Array.from(v);
|
|
490
|
+
//
|
|
491
|
+
// biome-ignore lint/suspicious/noExplicitAny: we need to get the `inner`, but js has no package scoping
|
|
492
|
+
const value: any = this.nearestTo(arr);
|
|
493
|
+
const inner = value.inner as
|
|
494
|
+
| NativeVectorQuery
|
|
495
|
+
| Promise<NativeVectorQuery>;
|
|
496
|
+
return inner;
|
|
497
|
+
} catch (e) {
|
|
498
|
+
return Promise.reject(e);
|
|
499
|
+
}
|
|
500
|
+
})();
|
|
501
|
+
return new VectorQuery(res);
|
|
502
|
+
} else {
|
|
503
|
+
const vectorQuery = this.inner.nearestTo(Float32Array.from(vector));
|
|
504
|
+
return new VectorQuery(vectorQuery);
|
|
505
|
+
}
|
|
420
506
|
}
|
|
421
507
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Copyright 2023 LanceDB Developers.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import axios, {
|
|
16
|
+
AxiosError,
|
|
17
|
+
type AxiosResponse,
|
|
18
|
+
type ResponseType,
|
|
19
|
+
} from "axios";
|
|
20
|
+
import { Table as ArrowTable } from "../arrow";
|
|
21
|
+
import { tableFromIPC } from "../arrow";
|
|
22
|
+
import { VectorQuery } from "../query";
|
|
23
|
+
|
|
24
|
+
export class RestfulLanceDBClient {
|
|
25
|
+
#dbName: string;
|
|
26
|
+
#region: string;
|
|
27
|
+
#apiKey: string;
|
|
28
|
+
#hostOverride?: string;
|
|
29
|
+
#closed: boolean = false;
|
|
30
|
+
#connectionTimeout: number = 12 * 1000; // 12 seconds;
|
|
31
|
+
#readTimeout: number = 30 * 1000; // 30 seconds;
|
|
32
|
+
#session?: import("axios").AxiosInstance;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
dbName: string,
|
|
36
|
+
apiKey: string,
|
|
37
|
+
region: string,
|
|
38
|
+
hostOverride?: string,
|
|
39
|
+
connectionTimeout?: number,
|
|
40
|
+
readTimeout?: number,
|
|
41
|
+
) {
|
|
42
|
+
this.#dbName = dbName;
|
|
43
|
+
this.#apiKey = apiKey;
|
|
44
|
+
this.#region = region;
|
|
45
|
+
this.#hostOverride = hostOverride ?? this.#hostOverride;
|
|
46
|
+
this.#connectionTimeout = connectionTimeout ?? this.#connectionTimeout;
|
|
47
|
+
this.#readTimeout = readTimeout ?? this.#readTimeout;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// todo: cache the session.
|
|
51
|
+
get session(): import("axios").AxiosInstance {
|
|
52
|
+
if (this.#session !== undefined) {
|
|
53
|
+
return this.#session;
|
|
54
|
+
} else {
|
|
55
|
+
return axios.create({
|
|
56
|
+
baseURL: this.url,
|
|
57
|
+
headers: {
|
|
58
|
+
// biome-ignore lint: external API
|
|
59
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
60
|
+
},
|
|
61
|
+
transformResponse: decodeErrorData,
|
|
62
|
+
timeout: this.#connectionTimeout,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get url(): string {
|
|
68
|
+
return (
|
|
69
|
+
this.#hostOverride ??
|
|
70
|
+
`https://${this.#dbName}.${this.#region}.api.lancedb.com`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get headers(): { [key: string]: string } {
|
|
75
|
+
const headers: { [key: string]: string } = {
|
|
76
|
+
"x-api-key": this.#apiKey,
|
|
77
|
+
"x-request-id": "na",
|
|
78
|
+
};
|
|
79
|
+
if (this.#region == "local") {
|
|
80
|
+
headers["Host"] = `${this.#dbName}.${this.#region}.api.lancedb.com`;
|
|
81
|
+
}
|
|
82
|
+
if (this.#hostOverride) {
|
|
83
|
+
headers["x-lancedb-database"] = this.#dbName;
|
|
84
|
+
}
|
|
85
|
+
return headers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
isOpen(): boolean {
|
|
89
|
+
return !this.#closed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private checkNotClosed(): void {
|
|
93
|
+
if (this.#closed) {
|
|
94
|
+
throw new Error("Connection is closed");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
close(): void {
|
|
99
|
+
this.#session = undefined;
|
|
100
|
+
this.#closed = true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
104
|
+
async get(uri: string, params?: Record<string, any>): Promise<any> {
|
|
105
|
+
this.checkNotClosed();
|
|
106
|
+
uri = new URL(uri, this.url).toString();
|
|
107
|
+
let response;
|
|
108
|
+
try {
|
|
109
|
+
response = await this.session.get(uri, {
|
|
110
|
+
headers: this.headers,
|
|
111
|
+
params,
|
|
112
|
+
});
|
|
113
|
+
} catch (e) {
|
|
114
|
+
if (e instanceof AxiosError) {
|
|
115
|
+
response = e.response;
|
|
116
|
+
} else {
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
RestfulLanceDBClient.checkStatus(response!);
|
|
122
|
+
return response!.data;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// biome-ignore lint/suspicious/noExplicitAny: api response
|
|
126
|
+
async post(uri: string, body?: any): Promise<any>;
|
|
127
|
+
async post(
|
|
128
|
+
uri: string,
|
|
129
|
+
// biome-ignore lint/suspicious/noExplicitAny: api request
|
|
130
|
+
body: any,
|
|
131
|
+
additional: {
|
|
132
|
+
config?: { responseType: "arraybuffer" };
|
|
133
|
+
headers?: Record<string, string>;
|
|
134
|
+
params?: Record<string, string>;
|
|
135
|
+
},
|
|
136
|
+
): Promise<Buffer>;
|
|
137
|
+
async post(
|
|
138
|
+
uri: string,
|
|
139
|
+
// biome-ignore lint/suspicious/noExplicitAny: api request
|
|
140
|
+
body?: any,
|
|
141
|
+
additional?: {
|
|
142
|
+
config?: { responseType: ResponseType };
|
|
143
|
+
headers?: Record<string, string>;
|
|
144
|
+
params?: Record<string, string>;
|
|
145
|
+
},
|
|
146
|
+
// biome-ignore lint/suspicious/noExplicitAny: api response
|
|
147
|
+
): Promise<any> {
|
|
148
|
+
this.checkNotClosed();
|
|
149
|
+
uri = new URL(uri, this.url).toString();
|
|
150
|
+
additional = Object.assign(
|
|
151
|
+
{ config: { responseType: "json" } },
|
|
152
|
+
additional,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const headers = { ...this.headers, ...additional.headers };
|
|
156
|
+
|
|
157
|
+
if (!headers["Content-Type"]) {
|
|
158
|
+
headers["Content-Type"] = "application/json";
|
|
159
|
+
}
|
|
160
|
+
let response;
|
|
161
|
+
try {
|
|
162
|
+
response = await this.session.post(uri, body, {
|
|
163
|
+
headers,
|
|
164
|
+
responseType: additional!.config!.responseType,
|
|
165
|
+
params: new Map(Object.entries(additional.params ?? {})),
|
|
166
|
+
});
|
|
167
|
+
} catch (e) {
|
|
168
|
+
if (e instanceof AxiosError) {
|
|
169
|
+
response = e.response;
|
|
170
|
+
} else {
|
|
171
|
+
throw e;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
RestfulLanceDBClient.checkStatus(response!);
|
|
175
|
+
if (additional!.config!.responseType === "arraybuffer") {
|
|
176
|
+
return response!.data;
|
|
177
|
+
} else {
|
|
178
|
+
return JSON.parse(response!.data);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async listTables(limit = 10, pageToken = ""): Promise<string[]> {
|
|
183
|
+
const json = await this.get("/v1/table", { limit, pageToken });
|
|
184
|
+
return json.tables;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async query(tableName: string, query: VectorQuery): Promise<ArrowTable> {
|
|
188
|
+
const tbl = await this.post(`/v1/table/${tableName}/query`, query, {
|
|
189
|
+
config: {
|
|
190
|
+
responseType: "arraybuffer",
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
return tableFromIPC(tbl);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static checkStatus(response: AxiosResponse): void {
|
|
197
|
+
if (response.status === 404) {
|
|
198
|
+
throw new Error(`Not found: ${response.data}`);
|
|
199
|
+
} else if (response.status >= 400 && response.status < 500) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Bad Request: ${response.status}, error: ${response.data}`,
|
|
202
|
+
);
|
|
203
|
+
} else if (response.status >= 500 && response.status < 600) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Internal Server Error: ${response.status}, error: ${response.data}`,
|
|
206
|
+
);
|
|
207
|
+
} else if (response.status !== 200) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Unknown Error: ${response.status}, error: ${response.data}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function decodeErrorData(data: unknown) {
|
|
216
|
+
if (Buffer.isBuffer(data)) {
|
|
217
|
+
const decoded = data.toString("utf-8");
|
|
218
|
+
return decoded;
|
|
219
|
+
}
|
|
220
|
+
return data;
|
|
221
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Schema } from "apache-arrow";
|
|
2
|
+
import {
|
|
3
|
+
Data,
|
|
4
|
+
SchemaLike,
|
|
5
|
+
fromTableToStreamBuffer,
|
|
6
|
+
makeEmptyTable,
|
|
7
|
+
} from "../arrow";
|
|
8
|
+
import {
|
|
9
|
+
Connection,
|
|
10
|
+
CreateTableOptions,
|
|
11
|
+
OpenTableOptions,
|
|
12
|
+
TableNamesOptions,
|
|
13
|
+
} from "../connection";
|
|
14
|
+
import { Table } from "../table";
|
|
15
|
+
import { TTLCache } from "../util";
|
|
16
|
+
import { RestfulLanceDBClient } from "./client";
|
|
17
|
+
import { RemoteTable } from "./table";
|
|
18
|
+
|
|
19
|
+
export interface RemoteConnectionOptions {
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
region?: string;
|
|
22
|
+
hostOverride?: string;
|
|
23
|
+
connectionTimeout?: number;
|
|
24
|
+
readTimeout?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class RemoteConnection extends Connection {
|
|
28
|
+
#dbName: string;
|
|
29
|
+
#apiKey: string;
|
|
30
|
+
#region: string;
|
|
31
|
+
#client: RestfulLanceDBClient;
|
|
32
|
+
#tableCache = new TTLCache(300_000);
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
url: string,
|
|
36
|
+
{
|
|
37
|
+
apiKey,
|
|
38
|
+
region,
|
|
39
|
+
hostOverride,
|
|
40
|
+
connectionTimeout,
|
|
41
|
+
readTimeout,
|
|
42
|
+
}: RemoteConnectionOptions,
|
|
43
|
+
) {
|
|
44
|
+
super();
|
|
45
|
+
apiKey = apiKey ?? process.env.LANCEDB_API_KEY;
|
|
46
|
+
region = region ?? process.env.LANCEDB_REGION;
|
|
47
|
+
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
throw new Error("apiKey is required when connecting to LanceDB Cloud");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!region) {
|
|
53
|
+
throw new Error("region is required when connecting to LanceDB Cloud");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const parsed = new URL(url);
|
|
57
|
+
if (parsed.protocol !== "db:") {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`invalid protocol: ${parsed.protocol}, only accepts db://`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.#dbName = parsed.hostname;
|
|
64
|
+
this.#apiKey = apiKey;
|
|
65
|
+
this.#region = region;
|
|
66
|
+
this.#client = new RestfulLanceDBClient(
|
|
67
|
+
this.#dbName,
|
|
68
|
+
this.#apiKey,
|
|
69
|
+
this.#region,
|
|
70
|
+
hostOverride,
|
|
71
|
+
connectionTimeout,
|
|
72
|
+
readTimeout,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
isOpen(): boolean {
|
|
77
|
+
return this.#client.isOpen();
|
|
78
|
+
}
|
|
79
|
+
close(): void {
|
|
80
|
+
return this.#client.close();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
display(): string {
|
|
84
|
+
return `RemoteConnection(${this.#dbName})`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async tableNames(options?: Partial<TableNamesOptions>): Promise<string[]> {
|
|
88
|
+
const response = await this.#client.get("/v1/table/", {
|
|
89
|
+
limit: options?.limit ?? 10,
|
|
90
|
+
// biome-ignore lint/style/useNamingConvention: <explanation>
|
|
91
|
+
page_token: options?.startAfter ?? "",
|
|
92
|
+
});
|
|
93
|
+
const body = await response.body();
|
|
94
|
+
for (const table of body.tables) {
|
|
95
|
+
this.#tableCache.set(table, true);
|
|
96
|
+
}
|
|
97
|
+
return body.tables;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async openTable(
|
|
101
|
+
name: string,
|
|
102
|
+
_options?: Partial<OpenTableOptions> | undefined,
|
|
103
|
+
): Promise<Table> {
|
|
104
|
+
if (this.#tableCache.get(name) === undefined) {
|
|
105
|
+
await this.#client.post(
|
|
106
|
+
`/v1/table/${encodeURIComponent(name)}/describe/`,
|
|
107
|
+
);
|
|
108
|
+
this.#tableCache.set(name, true);
|
|
109
|
+
}
|
|
110
|
+
return new RemoteTable(this.#client, name, this.#dbName);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async createTable(
|
|
114
|
+
nameOrOptions:
|
|
115
|
+
| string
|
|
116
|
+
| ({ name: string; data: Data } & Partial<CreateTableOptions>),
|
|
117
|
+
data?: Data,
|
|
118
|
+
options?: Partial<CreateTableOptions> | undefined,
|
|
119
|
+
): Promise<Table> {
|
|
120
|
+
if (typeof nameOrOptions !== "string" && "name" in nameOrOptions) {
|
|
121
|
+
const { name, data, ...options } = nameOrOptions;
|
|
122
|
+
return this.createTable(name, data, options);
|
|
123
|
+
}
|
|
124
|
+
if (data === undefined) {
|
|
125
|
+
throw new Error("data is required");
|
|
126
|
+
}
|
|
127
|
+
if (options?.mode) {
|
|
128
|
+
console.warn(
|
|
129
|
+
"option 'mode' is not supported in LanceDB Cloud",
|
|
130
|
+
"LanceDB Cloud only supports the default 'create' mode.",
|
|
131
|
+
"If the table already exists, an error will be thrown.",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (options?.embeddingFunction) {
|
|
135
|
+
console.warn(
|
|
136
|
+
"embedding_functions is not yet supported on LanceDB Cloud.",
|
|
137
|
+
"Please vote https://github.com/lancedb/lancedb/issues/626 ",
|
|
138
|
+
"for this feature.",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { buf } = await Table.parseTableData(
|
|
143
|
+
data,
|
|
144
|
+
options,
|
|
145
|
+
true /** streaming */,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
await this.#client.post(
|
|
149
|
+
`/v1/table/${encodeURIComponent(nameOrOptions)}/create/`,
|
|
150
|
+
buf,
|
|
151
|
+
{
|
|
152
|
+
config: {
|
|
153
|
+
responseType: "arraybuffer",
|
|
154
|
+
},
|
|
155
|
+
headers: { "Content-Type": "application/vnd.apache.arrow.stream" },
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
this.#tableCache.set(nameOrOptions, true);
|
|
159
|
+
return new RemoteTable(this.#client, nameOrOptions, this.#dbName);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async createEmptyTable(
|
|
163
|
+
name: string,
|
|
164
|
+
schema: SchemaLike,
|
|
165
|
+
options?: Partial<CreateTableOptions> | undefined,
|
|
166
|
+
): Promise<Table> {
|
|
167
|
+
if (options?.mode) {
|
|
168
|
+
console.warn(`mode is not supported on LanceDB Cloud`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (options?.embeddingFunction) {
|
|
172
|
+
console.warn(
|
|
173
|
+
"embeddingFunction is not yet supported on LanceDB Cloud.",
|
|
174
|
+
"Please vote https://github.com/lancedb/lancedb/issues/626 ",
|
|
175
|
+
"for this feature.",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const emptyTable = makeEmptyTable(schema);
|
|
179
|
+
const buf = await fromTableToStreamBuffer(emptyTable);
|
|
180
|
+
|
|
181
|
+
await this.#client.post(
|
|
182
|
+
`/v1/table/${encodeURIComponent(name)}/create/`,
|
|
183
|
+
buf,
|
|
184
|
+
{
|
|
185
|
+
config: {
|
|
186
|
+
responseType: "arraybuffer",
|
|
187
|
+
},
|
|
188
|
+
headers: { "Content-Type": "application/vnd.apache.arrow.stream" },
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
this.#tableCache.set(name, true);
|
|
193
|
+
return new RemoteTable(this.#client, name, this.#dbName);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async dropTable(name: string): Promise<void> {
|
|
197
|
+
await this.#client.post(`/v1/table/${encodeURIComponent(name)}/drop/`);
|
|
198
|
+
|
|
199
|
+
this.#tableCache.delete(name);
|
|
200
|
+
}
|
|
201
|
+
}
|