@lancedb/lancedb 0.37.1 → 0.38.0-beta.12

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/dist/query.js CHANGED
@@ -2,8 +2,9 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  // SPDX-FileCopyrightText: Copyright The LanceDB Authors
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.BooleanQuery = exports.MultiMatchQuery = exports.BoostQuery = exports.PhraseQuery = exports.MatchQuery = exports.Occur = exports.Operator = exports.FullTextQueryType = exports.Query = exports.TakeQuery = exports.VectorQuery = exports.StandardQueryBase = exports.QueryBase = void 0;
5
+ exports.BooleanQuery = exports.MultiMatchQuery = exports.BoostQuery = exports.PhraseQuery = exports.MatchQuery = exports.Occur = exports.Operator = exports.FullTextQueryType = exports.Query = exports.AutoQuery = exports.TakeQuery = exports.VectorQuery = exports.StandardQueryBase = exports.QueryBase = void 0;
6
6
  exports.RecordBatchIterator = RecordBatchIterator;
7
+ exports.createAutoQuery = createAutoQuery;
7
8
  exports.instanceOfFullTextQuery = instanceOfFullTextQuery;
8
9
  const arrow_1 = require("./arrow");
9
10
  const native_1 = require("./native");
@@ -32,6 +33,22 @@ class RecordBatchIterable {
32
33
  return RecordBatchIterator(this.inner.execute(this.options?.maxBatchLength, this.options?.timeoutMs));
33
34
  }
34
35
  }
36
+ function nearestToNative(inner, vector) {
37
+ const raw = Array.isArray(vector) ? null : (0, arrow_1.extractVectorBuffer)(vector);
38
+ if (raw) {
39
+ return inner.nearestToRaw(raw.data, raw.dtype);
40
+ }
41
+ return inner.nearestTo(Float32Array.from(vector));
42
+ }
43
+ function addQueryVectorToNative(inner, vector) {
44
+ const raw = Array.isArray(vector) ? null : (0, arrow_1.extractVectorBuffer)(vector);
45
+ if (raw) {
46
+ inner.addQueryVectorRaw(raw.data, raw.dtype);
47
+ }
48
+ else {
49
+ inner.addQueryVector(Float32Array.from(vector));
50
+ }
51
+ }
35
52
  /** Common methods supported by all query types
36
53
  *
37
54
  * @see {@link Query}
@@ -45,8 +62,9 @@ class QueryBase {
45
62
  * @hidden
46
63
  */
47
64
  constructor(inner) {
48
- this.inner = inner;
49
- // intentionally empty
65
+ if (inner !== undefined) {
66
+ this.inner = inner;
67
+ }
50
68
  }
51
69
  // call a function on the inner (either a promise or the actual object)
52
70
  /**
@@ -63,6 +81,14 @@ class QueryBase {
63
81
  fn(this.inner);
64
82
  }
65
83
  }
84
+ /**
85
+ * Return the native query used by the next terminal operation.
86
+ *
87
+ * @hidden
88
+ */
89
+ async getInner() {
90
+ return this.inner;
91
+ }
66
92
  /**
67
93
  * Return only the specified columns.
68
94
  *
@@ -132,13 +158,9 @@ class QueryBase {
132
158
  /**
133
159
  * @hidden
134
160
  */
135
- nativeExecute(options) {
136
- if (this.inner instanceof Promise) {
137
- return this.inner.then((inner) => inner.execute(options?.maxBatchLength, options?.timeoutMs));
138
- }
139
- else {
140
- return this.inner.execute(options?.maxBatchLength, options?.timeoutMs);
141
- }
161
+ async nativeExecute(options) {
162
+ const inner = await this.getInner();
163
+ return inner.execute(options?.maxBatchLength, options?.timeoutMs);
142
164
  }
143
165
  /**
144
166
  * Execute the query and return the results as an @see {@link AsyncIterator}
@@ -164,13 +186,7 @@ class QueryBase {
164
186
  /** Collect the results as an Arrow @see {@link ArrowTable}. */
165
187
  async toArrow(options) {
166
188
  const batches = [];
167
- let inner;
168
- if (this.inner instanceof Promise) {
169
- inner = await this.inner;
170
- }
171
- else {
172
- inner = this.inner;
173
- }
189
+ const inner = await this.getInner();
174
190
  for await (const batch of new RecordBatchIterable(inner, options)) {
175
191
  batches.push(batch);
176
192
  }
@@ -197,12 +213,8 @@ class QueryBase {
197
213
  * @returns A Promise that resolves to a string containing the query execution plan explanation.
198
214
  */
199
215
  async explainPlan(verbose = false) {
200
- if (this.inner instanceof Promise) {
201
- return this.inner.then((inner) => inner.explainPlan(verbose));
202
- }
203
- else {
204
- return this.inner.explainPlan(verbose);
205
- }
216
+ const inner = await this.getInner();
217
+ return inner.explainPlan(verbose);
206
218
  }
207
219
  /**
208
220
  * Executes the query and returns the physical query plan annotated with runtime metrics.
@@ -237,12 +249,8 @@ class QueryBase {
237
249
  */
238
250
  async analyzePlan(distributedMetrics) {
239
251
  const distributedMetricsMode = distributedMetrics ?? "aggregate";
240
- if (this.inner instanceof Promise) {
241
- return this.inner.then((inner) => inner.analyzePlan(distributedMetricsMode));
242
- }
243
- else {
244
- return this.inner.analyzePlan(distributedMetricsMode);
245
- }
252
+ const inner = await this.getInner();
253
+ return inner.analyzePlan(distributedMetricsMode);
246
254
  }
247
255
  /**
248
256
  * Returns the schema of the output that will be returned by this query.
@@ -253,13 +261,8 @@ class QueryBase {
253
261
  * @returns An Arrow Schema describing the output columns.
254
262
  */
255
263
  async outputSchema() {
256
- let schemaBuffer;
257
- if (this.inner instanceof Promise) {
258
- schemaBuffer = await this.inner.then((inner) => inner.outputSchema());
259
- }
260
- else {
261
- schemaBuffer = await this.inner.outputSchema();
262
- }
264
+ const inner = await this.getInner();
265
+ const schemaBuffer = await inner.outputSchema();
263
266
  const schema = (0, arrow_1.tableFromIPC)(schemaBuffer).schema;
264
267
  return schema;
265
268
  }
@@ -403,6 +406,12 @@ class VectorQuery extends StandardQueryBase {
403
406
  constructor(inner) {
404
407
  super(inner);
405
408
  }
409
+ /**
410
+ * @hidden
411
+ */
412
+ doVectorCall(fn) {
413
+ super.doCall(fn);
414
+ }
406
415
  /**
407
416
  * Set the number of partitions to search (probe)
408
417
  *
@@ -430,7 +439,7 @@ class VectorQuery extends StandardQueryBase {
430
439
  * the minimum and maximum to the same value.
431
440
  */
432
441
  nprobes(nprobes) {
433
- super.doCall((inner) => inner.nprobes(nprobes));
442
+ this.doVectorCall((inner) => inner.nprobes(nprobes));
434
443
  return this;
435
444
  }
436
445
  /**
@@ -442,7 +451,7 @@ class VectorQuery extends StandardQueryBase {
442
451
  * but will also increase latency.
443
452
  */
444
453
  minimumNprobes(minimumNprobes) {
445
- super.doCall((inner) => inner.minimumNprobes(minimumNprobes));
454
+ this.doVectorCall((inner) => inner.minimumNprobes(minimumNprobes));
446
455
  return this;
447
456
  }
448
457
  /**
@@ -455,7 +464,7 @@ class VectorQuery extends StandardQueryBase {
455
464
  * potential false negatives.
456
465
  */
457
466
  maximumNprobes(maximumNprobes) {
458
- super.doCall((inner) => inner.maximumNprobes(maximumNprobes));
467
+ this.doVectorCall((inner) => inner.maximumNprobes(maximumNprobes));
459
468
  return this;
460
469
  }
461
470
  /*
@@ -467,7 +476,7 @@ class VectorQuery extends StandardQueryBase {
467
476
  * `undefined` means no lower or upper bound.
468
477
  */
469
478
  distanceRange(lowerBound, upperBound) {
470
- super.doCall((inner) => inner.distanceRange(lowerBound, upperBound));
479
+ this.doVectorCall((inner) => inner.distanceRange(lowerBound, upperBound));
471
480
  return this;
472
481
  }
473
482
  /**
@@ -480,7 +489,7 @@ class VectorQuery extends StandardQueryBase {
480
489
  * also increase the latency of your query. The default value is 1.5*limit.
481
490
  */
482
491
  ef(ef) {
483
- super.doCall((inner) => inner.ef(ef));
492
+ this.doVectorCall((inner) => inner.ef(ef));
484
493
  return this;
485
494
  }
486
495
  /**
@@ -493,7 +502,7 @@ class VectorQuery extends StandardQueryBase {
493
502
  * whose data type is a fixed-size-list of floats.
494
503
  */
495
504
  column(column) {
496
- super.doCall((inner) => inner.column(column));
505
+ this.doVectorCall((inner) => inner.column(column));
497
506
  return this;
498
507
  }
499
508
  /**
@@ -511,7 +520,7 @@ class VectorQuery extends StandardQueryBase {
511
520
  * By default "l2" is used.
512
521
  */
513
522
  distanceType(distanceType) {
514
- super.doCall((inner) => inner.distanceType(distanceType));
523
+ this.doVectorCall((inner) => inner.distanceType(distanceType));
515
524
  return this;
516
525
  }
517
526
  /**
@@ -544,7 +553,7 @@ class VectorQuery extends StandardQueryBase {
544
553
  * distance between the query vector and the actual uncompressed vector.
545
554
  */
546
555
  refineFactor(refineFactor) {
547
- super.doCall((inner) => inner.refineFactor(refineFactor));
556
+ this.doVectorCall((inner) => inner.refineFactor(refineFactor));
548
557
  return this;
549
558
  }
550
559
  /**
@@ -568,7 +577,7 @@ class VectorQuery extends StandardQueryBase {
568
577
  * factor can often help restore some of the results lost by post filtering.
569
578
  */
570
579
  postfilter() {
571
- super.doCall((inner) => inner.postfilter());
580
+ this.doVectorCall((inner) => inner.postfilter());
572
581
  return this;
573
582
  }
574
583
  /**
@@ -581,50 +590,43 @@ class VectorQuery extends StandardQueryBase {
581
590
  * calculate your recall to select an appropriate value for nprobes.
582
591
  */
583
592
  bypassVectorIndex() {
584
- super.doCall((inner) => inner.bypassVectorIndex());
593
+ this.doVectorCall((inner) => inner.bypassVectorIndex());
585
594
  return this;
586
595
  }
587
596
  /*
588
597
  * Add a query vector to the search
589
598
  *
590
599
  * This method can be called multiple times to add multiple query vectors
591
- * to the search. If multiple query vectors are added, then they will be searched
592
- * in parallel, and the results will be concatenated. A column called `query_index`
593
- * will be added to indicate the index of the query vector that produced the result.
594
- *
595
- * Performance wise, this is equivalent to running multiple queries concurrently.
600
+ * to the search. A column called `query_index` will be added to indicate the index
601
+ * of the query vector that produced the result. Flat searches share one table scan
602
+ * across the query vectors, avoiding the scan and memory amplification of running
603
+ * multiple queries concurrently. Indexed searches may still perform per-vector
604
+ * index work.
596
605
  */
597
606
  addQueryVector(vector) {
598
607
  if (vector instanceof Promise) {
608
+ // Observe the promise as soon as it is accepted. The existing native
609
+ // query may still be pending, and delaying observation until it resolves
610
+ // can otherwise surface a fast rejection as unhandled.
611
+ const settledVector = vector.then((value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason }));
599
612
  const res = (async () => {
600
- try {
601
- const v = await vector;
602
- // biome-ignore lint/suspicious/noExplicitAny: we need to get the `inner`, but js has no package scoping
603
- const value = this.addQueryVector(v);
604
- const inner = value.inner;
605
- return inner;
606
- }
607
- catch (e) {
608
- return Promise.reject(e);
613
+ const inner = await this.getInner();
614
+ const outcome = await settledVector;
615
+ if (outcome.status === "rejected") {
616
+ throw outcome.reason;
609
617
  }
618
+ addQueryVectorToNative(inner, outcome.value);
619
+ return inner;
610
620
  })();
611
621
  return new VectorQuery(res);
612
622
  }
613
623
  else {
614
- super.doCall((inner) => {
615
- const raw = Array.isArray(vector) ? null : (0, arrow_1.extractVectorBuffer)(vector);
616
- if (raw) {
617
- inner.addQueryVectorRaw(raw.data, raw.dtype);
618
- }
619
- else {
620
- inner.addQueryVector(Float32Array.from(vector));
621
- }
622
- });
624
+ this.doVectorCall((inner) => addQueryVectorToNative(inner, vector));
623
625
  return this;
624
626
  }
625
627
  }
626
628
  rerank(reranker) {
627
- super.doCall((inner) => inner.rerank(async (args) => {
629
+ this.doVectorCall((inner) => inner.rerank(async (args) => {
628
630
  const vecResults = await (0, arrow_1.fromBufferToRecordBatch)(args.vecResults);
629
631
  const ftsResults = await (0, arrow_1.fromBufferToRecordBatch)(args.ftsResults);
630
632
  const result = await reranker.rerankHybrid(args.query, vecResults, ftsResults);
@@ -635,6 +637,51 @@ class VectorQuery extends StandardQueryBase {
635
637
  }
636
638
  }
637
639
  exports.VectorQuery = VectorQuery;
640
+ /**
641
+ * Create a string query whose vector/FTS routing is resolved against the active
642
+ * table schema when the query executes.
643
+ *
644
+ * @hidden
645
+ */
646
+ function createAutoQuery(table, query, columns, getVector) {
647
+ let cachedPreparation;
648
+ const snapshotRoute = async () => {
649
+ const snapshot = await table.querySnapshot();
650
+ const schema = (0, arrow_1.tableFromIPC)(await snapshot.schema()).schema;
651
+ return {
652
+ table: snapshot,
653
+ embeddingMetadata: schema.metadata.get("embedding_functions"),
654
+ };
655
+ };
656
+ const createInner = async () => {
657
+ const route = await snapshotRoute();
658
+ if (route.embeddingMetadata === undefined) {
659
+ const inner = route.table.query();
660
+ inner.fullTextSearch({ query, columns });
661
+ return inner;
662
+ }
663
+ const metadata = route.embeddingMetadata;
664
+ if (cachedPreparation?.metadata !== metadata) {
665
+ cachedPreparation = {
666
+ metadata,
667
+ vector: Promise.resolve().then(() => getVector(metadata)),
668
+ };
669
+ }
670
+ const preparation = cachedPreparation;
671
+ let vector;
672
+ try {
673
+ vector = await preparation.vector;
674
+ }
675
+ catch (error) {
676
+ if (cachedPreparation === preparation) {
677
+ cachedPreparation = undefined;
678
+ }
679
+ throw error;
680
+ }
681
+ return nearestToNative(route.table.query(), vector);
682
+ };
683
+ return new AutoQuery(createInner);
684
+ }
638
685
  /**
639
686
  * A query that returns a subset of the rows in the table.
640
687
  *
@@ -659,6 +706,38 @@ class TakeQuery extends QueryBase {
659
706
  }
660
707
  }
661
708
  exports.TakeQuery = TakeQuery;
709
+ /**
710
+ * A builder for automatic string searches.
711
+ *
712
+ * Automatic search determines whether to use full-text or vector search from
713
+ * the table revision selected for each execution. This builder exposes the
714
+ * common operations supported by both query families.
715
+ *
716
+ * @hideconstructor
717
+ */
718
+ class AutoQuery extends StandardQueryBase {
719
+ createInner;
720
+ calls = [];
721
+ /** @hidden */
722
+ constructor(createInner) {
723
+ super();
724
+ this.createInner = createInner;
725
+ }
726
+ /** @hidden */
727
+ doCall(fn) {
728
+ this.calls.push(fn);
729
+ }
730
+ /** @hidden */
731
+ async getInner() {
732
+ const calls = [...this.calls];
733
+ const inner = await this.createInner();
734
+ for (const call of calls) {
735
+ call(inner);
736
+ }
737
+ return inner;
738
+ }
739
+ }
740
+ exports.AutoQuery = AutoQuery;
662
741
  /** A builder for LanceDB queries.
663
742
  *
664
743
  * @see {@link Table#query}, {@link Table#search}
@@ -710,41 +789,15 @@ class Query extends StandardQueryBase {
710
789
  * a default `limit` of 10 will be used. @see {@link Query#limit}
711
790
  */
712
791
  nearestTo(vector) {
713
- const callNearestTo = (inner, resolved) => {
714
- const raw = Array.isArray(resolved)
715
- ? null
716
- : (0, arrow_1.extractVectorBuffer)(resolved);
717
- if (raw) {
718
- return inner.nearestToRaw(raw.data, raw.dtype);
719
- }
720
- return inner.nearestTo(Float32Array.from(resolved));
721
- };
722
- if (this.inner instanceof Promise) {
723
- const nativeQuery = this.inner.then(async (inner) => {
724
- const resolved = vector instanceof Promise ? await vector : vector;
725
- return callNearestTo(inner, resolved);
726
- });
792
+ const inner = this.inner;
793
+ if (inner instanceof Promise) {
794
+ const nativeQuery = inner.then(async (resolvedInner) => nearestToNative(resolvedInner, await vector));
727
795
  return new VectorQuery(nativeQuery);
728
796
  }
729
797
  if (vector instanceof Promise) {
730
- const res = (async () => {
731
- try {
732
- const v = await vector;
733
- // biome-ignore lint/suspicious/noExplicitAny: we need to get the `inner`, but js has no package scoping
734
- const value = this.nearestTo(v);
735
- const inner = value.inner;
736
- return inner;
737
- }
738
- catch (e) {
739
- return Promise.reject(e);
740
- }
741
- })();
742
- return new VectorQuery(res);
743
- }
744
- else {
745
- const vectorQuery = callNearestTo(this.inner, vector);
746
- return new VectorQuery(vectorQuery);
798
+ return new VectorQuery(vector.then((resolvedVector) => nearestToNative(inner, resolvedVector)));
747
799
  }
800
+ return new VectorQuery(nearestToNative(inner, vector));
748
801
  }
749
802
  nearestToText(query, columns) {
750
803
  this.doCall((inner) => {
package/dist/sanitize.js CHANGED
@@ -45,15 +45,21 @@ function sanitizeMetadata(metadataLike) {
45
45
  if (metadataLike === undefined || metadataLike === null) {
46
46
  return undefined;
47
47
  }
48
- if (!(metadataLike instanceof Map)) {
48
+ let entries;
49
+ try {
50
+ entries = Map.prototype.entries.call(metadataLike);
51
+ }
52
+ catch {
49
53
  throw Error("Expected metadata, if present, to be a Map<string, string>");
50
54
  }
51
- for (const item of metadataLike) {
52
- if (typeof item[0] !== "string" || typeof item[1] !== "string") {
55
+ const metadata = new Map();
56
+ for (const [key, value] of entries) {
57
+ if (typeof key !== "string" || typeof value !== "string") {
53
58
  throw Error("Expected metadata, if present, to be a Map<string, string> but it had non-string keys or values");
54
59
  }
60
+ metadata.set(key, value);
55
61
  }
56
- return metadataLike;
62
+ return metadata;
57
63
  }
58
64
  function sanitizeInt(typeLike) {
59
65
  if (!("bitWidth" in typeLike) ||
@@ -0,0 +1,16 @@
1
+ import { Schema } from "apache-arrow";
2
+ type InferenceOptions = {
3
+ dictionaryEncodeStrings: boolean;
4
+ vectorColumns: Record<string, {
5
+ type: unknown;
6
+ }>;
7
+ };
8
+ /**
9
+ * Infer the Arrow schema represented by a set of records.
10
+ *
11
+ * This is the intentionally small interface to schema inference. The stateful
12
+ * details of combining partial type evidence are encapsulated below so callers
13
+ * only need to provide records, an optional schema, and inference options.
14
+ */
15
+ export declare function inferSchema(data: Array<Record<string, unknown>>, schema: Schema | undefined, options: InferenceOptions): Schema;
16
+ export {};