@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/schema.js ADDED
@@ -0,0 +1,387 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: Copyright The LanceDB Authors
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.inferSchema = inferSchema;
6
+ const apache_arrow_1 = require("apache-arrow");
7
+ const arrow_type_1 = require("./arrow_type");
8
+ const sanitize_1 = require("./sanitize");
9
+ /**
10
+ * Infer the Arrow schema represented by a set of records.
11
+ *
12
+ * This is the intentionally small interface to schema inference. The stateful
13
+ * details of combining partial type evidence are encapsulated below so callers
14
+ * only need to provide records, an optional schema, and inference options.
15
+ */
16
+ function inferSchema(data, schema, options) {
17
+ return new SchemaInferrer(schema, options).infer(data);
18
+ }
19
+ class SchemaInferrer {
20
+ providedSchema;
21
+ options;
22
+ fields = new FieldTree();
23
+ constructor(providedSchema, options) {
24
+ this.providedSchema = providedSchema;
25
+ this.options = options;
26
+ }
27
+ infer(data) {
28
+ for (const [row, record] of data.entries()) {
29
+ for (const [path, value] of recordPathsAndValues(record)) {
30
+ this.observe(path, value, row);
31
+ }
32
+ }
33
+ return this.providedSchema === undefined
34
+ ? new apache_arrow_1.Schema(fieldsFromTree(this.fields))
35
+ : new apache_arrow_1.Schema(matchingFields(this.providedSchema.fields, this.fields));
36
+ }
37
+ observe(path, value, row) {
38
+ const current = this.fields.get(path);
39
+ if (current === undefined) {
40
+ this.addField(path, value, row);
41
+ }
42
+ else if (this.providedSchema === undefined) {
43
+ this.updateInferredField(path, value, row, current);
44
+ }
45
+ }
46
+ addField(path, value, row) {
47
+ if (this.providedSchema !== undefined) {
48
+ this.addSchemaField(this.providedSchema, path, row);
49
+ return;
50
+ }
51
+ const evidence = this.inferType(value, path) ?? DeferredTypeEvidence.from(value, row);
52
+ if (evidence === undefined) {
53
+ throw typeInferenceError(path, row);
54
+ }
55
+ const conflict = this.fields.set(path, evidence, (existing) => existing instanceof DeferredTypeEvidence && existing.isOnlyNulls());
56
+ if (conflict !== undefined) {
57
+ throw branchConflictError(conflict, row, "Struct");
58
+ }
59
+ }
60
+ addSchemaField(schema, path, row) {
61
+ const field = fieldAtPath(schema, path);
62
+ if (field === undefined) {
63
+ throw new Error(`Found field not in schema: ${path.join(".")} at row ${row}`);
64
+ }
65
+ const conflict = this.fields.set(path, field.type);
66
+ if (conflict !== undefined) {
67
+ throw branchConflictError(conflict, row, "Struct");
68
+ }
69
+ }
70
+ updateInferredField(path, value, row, current) {
71
+ const newType = this.inferType(value, path);
72
+ const deferred = DeferredTypeEvidence.from(value, row);
73
+ if (current instanceof FieldTree) {
74
+ if (deferred?.isOnlyNulls()) {
75
+ return;
76
+ }
77
+ throw schemaInferenceError(path, row, "Struct", describeEvidence(newType ?? deferred));
78
+ }
79
+ if (current instanceof DeferredTypeEvidence) {
80
+ this.resolveDeferredField(path, row, current, newType, deferred);
81
+ return;
82
+ }
83
+ if (newType !== undefined) {
84
+ if (!inferredTypesEqual(current, newType)) {
85
+ throw schemaInferenceError(path, row, describeEvidence(current), describeEvidence(newType));
86
+ }
87
+ return;
88
+ }
89
+ if (deferred === undefined || !deferred.matches(current)) {
90
+ throw schemaInferenceError(path, row, describeEvidence(current), describeEvidence(deferred));
91
+ }
92
+ }
93
+ resolveDeferredField(path, row, current, newType, deferred) {
94
+ if (newType !== undefined) {
95
+ if (!current.matches(newType)) {
96
+ throw schemaInferenceError(path, row, current.describe(), describeEvidence(newType));
97
+ }
98
+ this.fields.set(path, newType);
99
+ return;
100
+ }
101
+ if (deferred !== undefined) {
102
+ this.fields.set(path, current.merge(deferred));
103
+ return;
104
+ }
105
+ throw schemaInferenceError(path, row, current.describe(), describeEvidence(newType));
106
+ }
107
+ inferType(value, path) {
108
+ if (typeof value === "bigint") {
109
+ return new apache_arrow_1.Int64();
110
+ }
111
+ if (typeof value === "number") {
112
+ return new apache_arrow_1.Float64();
113
+ }
114
+ if (typeof value === "string") {
115
+ return this.options.dictionaryEncodeStrings
116
+ ? new apache_arrow_1.Dictionary(new apache_arrow_1.Utf8(), new apache_arrow_1.Int32())
117
+ : new apache_arrow_1.Utf8();
118
+ }
119
+ if (typeof value === "boolean") {
120
+ return new apache_arrow_1.Bool();
121
+ }
122
+ if (value instanceof Buffer) {
123
+ return new apache_arrow_1.Binary();
124
+ }
125
+ if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
126
+ const typedArray = (0, arrow_type_1.typedArrayToArrowType)(value);
127
+ return typedArray === undefined
128
+ ? undefined
129
+ : new apache_arrow_1.FixedSizeList(typedArray.length, new apache_arrow_1.Field("item", typedArray.elementType, true));
130
+ }
131
+ if (!Array.isArray(value) || value.length === 0) {
132
+ return undefined;
133
+ }
134
+ const configuredVector = path.length === 1 ? this.options.vectorColumns[path[0]] : undefined;
135
+ if (configuredVector !== undefined) {
136
+ return new apache_arrow_1.FixedSizeList(value.length, new apache_arrow_1.Field("item", (0, sanitize_1.sanitizeType)(configuredVector.type), true));
137
+ }
138
+ const itemType = this.inferArrayItemType(value, path);
139
+ if (itemType === undefined) {
140
+ return undefined;
141
+ }
142
+ return nameSuggestsVectorColumn(path[path.length - 1])
143
+ ? new apache_arrow_1.FixedSizeList(value.length, new apache_arrow_1.Field("item", new apache_arrow_1.Float32(), true))
144
+ : new apache_arrow_1.List(new apache_arrow_1.Field("item", itemType, true));
145
+ }
146
+ inferArrayItemType(values, path) {
147
+ let itemType;
148
+ const deferredItems = [];
149
+ for (const value of values) {
150
+ const candidate = this.inferType(value, path);
151
+ if (candidate === undefined) {
152
+ if (!isDeferredValue(value)) {
153
+ return undefined;
154
+ }
155
+ deferredItems.push(value);
156
+ }
157
+ else if (itemType === undefined) {
158
+ itemType = candidate;
159
+ }
160
+ else if (!inferredTypesEqual(itemType, candidate)) {
161
+ return undefined;
162
+ }
163
+ }
164
+ if (itemType === undefined) {
165
+ return undefined;
166
+ }
167
+ return deferredItems.every((value) => deferredValueMatchesType(value, itemType))
168
+ ? itemType
169
+ : undefined;
170
+ }
171
+ }
172
+ /** Nulls and empty/all-null lists that do not determine a type by themselves. */
173
+ class DeferredTypeEvidence {
174
+ values;
175
+ constructor(values) {
176
+ this.values = values;
177
+ }
178
+ static from(value, row) {
179
+ return isDeferredValue(value)
180
+ ? new DeferredTypeEvidence([{ value, row }])
181
+ : undefined;
182
+ }
183
+ isOnlyNulls() {
184
+ return this.values.every(({ value }) => value == null);
185
+ }
186
+ matches(type) {
187
+ return this.values.every(({ value }) => deferredValueMatchesType(value, type));
188
+ }
189
+ merge(other) {
190
+ return new DeferredTypeEvidence([...this.values, ...other.values]);
191
+ }
192
+ describe() {
193
+ const list = this.values.find(({ value }) => Array.isArray(value));
194
+ return list === undefined
195
+ ? "null"
196
+ : `List[${list.value.length}]`;
197
+ }
198
+ firstRow() {
199
+ return this.values[0].row;
200
+ }
201
+ }
202
+ /** Nested field state, kept separate from Arrow's eventual Struct types. */
203
+ class FieldTree {
204
+ children = new Map();
205
+ get(path) {
206
+ let current = this;
207
+ for (const part of path) {
208
+ if (!(current instanceof FieldTree)) {
209
+ return undefined;
210
+ }
211
+ const child = current.children.get(part);
212
+ if (child === undefined) {
213
+ return undefined;
214
+ }
215
+ current = child;
216
+ }
217
+ return current;
218
+ }
219
+ set(path, value, canReplaceLeaf = () => false) {
220
+ let branch = this;
221
+ for (const [index, part] of path.slice(0, -1).entries()) {
222
+ const child = branch.children.get(part);
223
+ if (child === undefined || (isLeaf(child) && canReplaceLeaf(child))) {
224
+ const nextBranch = new FieldTree();
225
+ branch.children.set(part, nextBranch);
226
+ branch = nextBranch;
227
+ }
228
+ else if (child instanceof FieldTree) {
229
+ branch = child;
230
+ }
231
+ else {
232
+ return { path: path.slice(0, index + 1), value: child };
233
+ }
234
+ }
235
+ const name = path[path.length - 1];
236
+ const current = branch.children.get(name);
237
+ if (current instanceof FieldTree) {
238
+ return { path, value: current };
239
+ }
240
+ branch.children.set(name, value);
241
+ return undefined;
242
+ }
243
+ entries() {
244
+ return this.children.entries();
245
+ }
246
+ has(name) {
247
+ return this.children.has(name);
248
+ }
249
+ }
250
+ function isLeaf(value) {
251
+ return !(value instanceof FieldTree);
252
+ }
253
+ function fieldsFromTree(tree, path = []) {
254
+ const fields = [];
255
+ for (const [name, value] of tree.entries()) {
256
+ if (value instanceof FieldTree) {
257
+ fields.push(new apache_arrow_1.Field(name, new apache_arrow_1.Struct(fieldsFromTree(value, [...path, name])), true));
258
+ }
259
+ else if (value instanceof DeferredTypeEvidence) {
260
+ throw typeInferenceError([...path, name], value.firstRow());
261
+ }
262
+ else {
263
+ fields.push(new apache_arrow_1.Field(name, value, true));
264
+ }
265
+ }
266
+ return fields;
267
+ }
268
+ function matchingFields(fields, tree) {
269
+ const matches = [];
270
+ for (const field of fields) {
271
+ if (!tree.has(field.name)) {
272
+ continue;
273
+ }
274
+ const value = tree.get([field.name]);
275
+ if (value instanceof FieldTree) {
276
+ const struct = field.type;
277
+ matches.push(new apache_arrow_1.Field(field.name, new apache_arrow_1.Struct(matchingFields(struct.children, value)), field.nullable, field.metadata));
278
+ }
279
+ else {
280
+ matches.push(field);
281
+ }
282
+ }
283
+ return matches;
284
+ }
285
+ function* recordPathsAndValues(record, path = []) {
286
+ for (const [name, value] of Object.entries(record)) {
287
+ if (isRecord(value)) {
288
+ yield* recordPathsAndValues(value, [...path, name]);
289
+ }
290
+ else if (value !== undefined) {
291
+ yield [[...path, name], value];
292
+ }
293
+ }
294
+ }
295
+ function isRecord(value) {
296
+ return (typeof value === "object" &&
297
+ value !== null &&
298
+ !Array.isArray(value) &&
299
+ !(value instanceof RegExp) &&
300
+ !(value instanceof Date) &&
301
+ !(value instanceof Set) &&
302
+ !(value instanceof Map) &&
303
+ !(value instanceof Buffer) &&
304
+ !ArrayBuffer.isView(value));
305
+ }
306
+ function fieldAtPath(schema, path) {
307
+ let fields = schema.fields;
308
+ let field;
309
+ for (const [index, name] of path.entries()) {
310
+ field = fields.find((candidate) => candidate.name === name);
311
+ if (field === undefined || index === path.length - 1) {
312
+ return field;
313
+ }
314
+ if (!apache_arrow_1.DataType.isStruct(field.type)) {
315
+ return undefined;
316
+ }
317
+ fields = field.type.children;
318
+ }
319
+ return field;
320
+ }
321
+ function isDeferredValue(value) {
322
+ return (value == null || (Array.isArray(value) && value.every(isDeferredValue)));
323
+ }
324
+ function deferredValueMatchesType(value, type) {
325
+ if (value == null) {
326
+ return true;
327
+ }
328
+ if (!Array.isArray(value)) {
329
+ return false;
330
+ }
331
+ if (apache_arrow_1.DataType.isList(type)) {
332
+ return value.every((item) => deferredValueMatchesType(item, type.valueType));
333
+ }
334
+ if (apache_arrow_1.DataType.isFixedSizeList(type)) {
335
+ return (value.length === type.listSize &&
336
+ value.every((item) => deferredValueMatchesType(item, type.valueType)));
337
+ }
338
+ return false;
339
+ }
340
+ function inferredTypesEqual(current, candidate) {
341
+ if (apache_arrow_1.DataType.isDictionary(current)) {
342
+ return (apache_arrow_1.DataType.isDictionary(candidate) &&
343
+ current.isOrdered === candidate.isOrdered &&
344
+ inferredTypesEqual(current.indices, candidate.indices) &&
345
+ inferredTypesEqual(current.dictionary, candidate.dictionary));
346
+ }
347
+ if (apache_arrow_1.DataType.isList(current)) {
348
+ return (apache_arrow_1.DataType.isList(candidate) &&
349
+ current.valueField.name === candidate.valueField.name &&
350
+ current.valueField.nullable === candidate.valueField.nullable &&
351
+ inferredTypesEqual(current.valueType, candidate.valueType));
352
+ }
353
+ if (apache_arrow_1.DataType.isFixedSizeList(current)) {
354
+ return (apache_arrow_1.DataType.isFixedSizeList(candidate) &&
355
+ current.listSize === candidate.listSize &&
356
+ current.valueField.name === candidate.valueField.name &&
357
+ current.valueField.nullable === candidate.valueField.nullable &&
358
+ inferredTypesEqual(current.valueType, candidate.valueType));
359
+ }
360
+ return apache_arrow_1.util.compareTypes(current, candidate);
361
+ }
362
+ function describeEvidence(evidence) {
363
+ if (evidence === undefined) {
364
+ return "an unsupported value";
365
+ }
366
+ return evidence instanceof DeferredTypeEvidence
367
+ ? evidence.describe()
368
+ : evidence.toString();
369
+ }
370
+ function branchConflictError(conflict, row, candidate) {
371
+ return schemaInferenceError(conflict.path, row, conflict.value instanceof FieldTree
372
+ ? "Struct"
373
+ : describeEvidence(conflict.value), candidate);
374
+ }
375
+ function schemaInferenceError(path, row, currentType, newType) {
376
+ return new Error(`Failed to infer schema for data. Previously inferred type ${currentType} ` +
377
+ `but found ${newType} for field ${path.join(".")} at row ${row}. ` +
378
+ "Consider providing an explicit schema.");
379
+ }
380
+ function typeInferenceError(path, row) {
381
+ return new Error(`Failed to infer data type for field ${path.join(".")} at row ${row}. ` +
382
+ "Consider providing an explicit schema.");
383
+ }
384
+ function nameSuggestsVectorColumn(name) {
385
+ const normalized = name.toLowerCase();
386
+ return normalized.includes("vector") || normalized.includes("embedding");
387
+ }