This is an automated email from the ASF dual-hosted git repository.
kou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-js.git
The following commit(s) were added to refs/heads/main by this push:
new 582398e fix: implement Symbol.hasInstance for cross-library
instanceof checks (#377)
582398e is described below
commit 582398ef7ed93127d6093e8c2656f98b8370b21f
Author: Divyanshu Singh <[email protected]>
AuthorDate: Wed Feb 18 07:41:23 2026 +0530
fix: implement Symbol.hasInstance for cross-library instanceof checks (#377)
What's Changed
Fixed the instanceof check issue that occurs when multiple versions or
instances of the Arrow library are loaded in the same application.
Previously, checks like [value instanceof Schema] would fail if the
value came from a different Arrow library instance (e.g., when a library
like LanceDB uses a different Arrow version than the user's code).
Now instanceof works reliably across different Arrow library instances
by using global symbols for type identification.
Also added helper functions like [isArrowSchema()],[isArrowTable()],
etc. for explicit type checking.
Closes #61.
---------
Co-authored-by: Divyanshu singh <[email protected]>
---
src/Arrow.dom.ts | 9 +-
src/Arrow.ts | 13 ++
src/data.ts | 22 +++
src/recordbatch.ts | 22 +++
src/schema.ts | 43 +++++
src/table.ts | 24 ++-
src/type.ts | 20 +++
src/util/typecheck.ts | 93 +++++++++++
src/vector.ts | 22 +++
test/unit/instanceof-tests.ts | 374 ++++++++++++++++++++++++++++++++++++++++++
10 files changed, 640 insertions(+), 2 deletions(-)
diff --git a/src/Arrow.dom.ts b/src/Arrow.dom.ts
index 30feeb8..777b859 100644
--- a/src/Arrow.dom.ts
+++ b/src/Arrow.dom.ts
@@ -77,7 +77,14 @@ export {
RecordBatch,
util,
Builder, makeBuilder, builderThroughIterable, builderThroughAsyncIterable,
- compressionRegistry, CompressionType
+ compressionRegistry, CompressionType,
+ isArrowSchema,
+ isArrowField,
+ isArrowDataType,
+ isArrowData,
+ isArrowVector,
+ isArrowRecordBatch,
+ isArrowTable
} from './Arrow.js';
export {
diff --git a/src/Arrow.ts b/src/Arrow.ts
index 2049583..848156d 100644
--- a/src/Arrow.ts
+++ b/src/Arrow.ts
@@ -102,6 +102,16 @@ export { Message } from './ipc/metadata/message.js';
export { RecordBatch } from './recordbatch.js';
export type { ArrowJSONLike, FileHandle, Readable, Writable, ReadableWritable,
ReadableDOMStreamOptions } from './io/interfaces.js';
+export {
+ isArrowSchema,
+ isArrowField,
+ isArrowDataType,
+ isArrowData,
+ isArrowVector,
+ isArrowRecordBatch,
+ isArrowTable,
+} from './util/typecheck.js';
+
import * as util_bn_ from './util/bn.js';
import * as util_int_ from './util/int.js';
import * as util_bit_ from './util/bit.js';
@@ -113,6 +123,8 @@ import * as util_pretty_ from './util/pretty.js';
import * as util_interval_ from './util/interval.js';
export type * from './util/interval.js';
+import * as util_typecheck_ from './util/typecheck.js';
+
import { compareSchemas, compareFields, compareTypes } from
'./visitor/typecomparator.js';
/** @ignore */
@@ -125,6 +137,7 @@ export const util = {
...util_vector_,
...util_pretty_,
...util_interval_,
+ ...util_typecheck_,
compareSchemas,
compareFields,
compareTypes,
diff --git a/src/data.ts b/src/data.ts
index b5edff8..46080e6 100644
--- a/src/data.ts
+++ b/src/data.ts
@@ -41,6 +41,9 @@ export interface Buffers<T extends DataType> {
[BufferType.TYPE]: T['TArray'];
}
+/** @ignore */
+const kDataSymbol = Symbol.for('apache-arrow/Data');
+
/** @ignore */
export interface Data<T extends DataType = DataType> {
readonly TType: T['TType'];
@@ -53,6 +56,17 @@ export interface Data<T extends DataType = DataType> {
*/
export class Data<T extends DataType = DataType> {
+ /**
+ * Check if an object is an instance of Data.
+ * This works across different instances of the Arrow library.
+ */
+ /** @nocollapse */ static isData(x: any): x is Data {
+ return x?.[kDataSymbol] === true;
+ }
+
+ /** @internal */
+ declare public readonly [kDataSymbol]: true;
+
declare public readonly type: T;
declare public readonly length: number;
declare public readonly offset: number;
@@ -291,6 +305,14 @@ export class Data<T extends DataType = DataType> {
}
(Data.prototype as any).children = Object.freeze([]);
+(Data.prototype as any)[kDataSymbol] = true;
+
+Object.defineProperty(Data, Symbol.hasInstance, {
+ value: function isDataInstance(instance: any): instance is Data {
+ return Function.prototype[Symbol.hasInstance].call(this, instance)
+ || (this === Data && Data.isData(instance));
+ },
+});
import {
Dictionary,
diff --git a/src/recordbatch.ts b/src/recordbatch.ts
index 33dbe9e..cb4dbf9 100644
--- a/src/recordbatch.ts
+++ b/src/recordbatch.ts
@@ -27,6 +27,9 @@ import { instance as setVisitor } from './visitor/set.js';
import { instance as indexOfVisitor } from './visitor/indexof.js';
import { instance as iteratorVisitor } from './visitor/iterator.js';
+/** @ignore */
+const kRecordBatchSymbol = Symbol.for('apache-arrow/RecordBatch');
+
/** @ignore */
export interface RecordBatch<T extends TypeMap = any> {
///
@@ -46,6 +49,17 @@ export interface RecordBatch<T extends TypeMap = any> {
/** @ignore */
export class RecordBatch<T extends TypeMap = any> {
+ /**
+ * Check if an object is an instance of RecordBatch.
+ * This works across different instances of the Arrow library.
+ */
+ /** @nocollapse */ static isRecordBatch(x: any): x is RecordBatch {
+ return x?.[kRecordBatchSymbol] === true;
+ }
+
+ /** @internal */
+ declare public readonly [kRecordBatchSymbol]: true;
+
constructor(columns: { [P in keyof T]: Data<T[P]> });
constructor(schema: Schema<T>, data?: Data<Struct<T>>);
constructor(...args: any[]) {
@@ -280,10 +294,18 @@ export class RecordBatch<T extends TypeMap = any> {
protected static [Symbol.toStringTag] = ((proto: RecordBatch) => {
(proto as any)._nullCount = -1;
(proto as any)[Symbol.isConcatSpreadable] = true;
+ (proto as any)[kRecordBatchSymbol] = true;
return 'RecordBatch';
})(RecordBatch.prototype);
}
+Object.defineProperty(RecordBatch, Symbol.hasInstance, {
+ value: function isRecordBatchInstance(instance: any): instance is
RecordBatch {
+ return Function.prototype[Symbol.hasInstance].call(this, instance)
+ || (this === RecordBatch && RecordBatch.isRecordBatch(instance));
+ },
+});
+
/** @ignore */
function ensureSameLengthData<T extends TypeMap = any>(
diff --git a/src/schema.ts b/src/schema.ts
index 2eb33b7..5916793 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -18,8 +18,24 @@
import { MetadataVersion } from './enum.js';
import { DataType, TypeMap } from './type.js';
+/** @ignore */
+const kSchemaSymbol = Symbol.for('apache-arrow/Schema');
+/** @ignore */
+const kFieldSymbol = Symbol.for('apache-arrow/Field');
+
export class Schema<T extends TypeMap = any> {
+ /**
+ * Check if an object is an instance of Schema.
+ * This works across different instances of the Arrow library.
+ */
+ /** @nocollapse */ static isSchema(x: any): x is Schema {
+ return x?.[kSchemaSymbol] === true;
+ }
+
+ /** @internal */
+ declare public readonly [kSchemaSymbol]: true;
+
public readonly fields: Field<T[keyof T]>[];
public readonly metadata: Map<string, string>;
public readonly dictionaries: Map<number, DataType>;
@@ -102,9 +118,28 @@ export class Schema<T extends TypeMap = any> {
(Schema.prototype as any).fields = <any>null;
(Schema.prototype as any).metadata = <any>null;
(Schema.prototype as any).dictionaries = <any>null;
+(Schema.prototype as any)[kSchemaSymbol] = true;
+
+Object.defineProperty(Schema, Symbol.hasInstance, {
+ value: function isSchemaInstance(instance: any): instance is Schema {
+ return Function.prototype[Symbol.hasInstance].call(this, instance)
+ || (this === Schema && Schema.isSchema(instance));
+ },
+});
export class Field<T extends DataType = any> {
+ /**
+ * Check if an object is an instance of Field.
+ * This works across different instances of the Arrow library.
+ */
+ /** @nocollapse */ static isField(x: any): x is Field {
+ return x?.[kFieldSymbol] === true;
+ }
+
+ /** @internal */
+ declare public readonly [kFieldSymbol]: true;
+
public static new<T extends DataType = any>(props: { name: string |
number; type: T; nullable?: boolean; metadata?: Map<string, string> | null }):
Field<T>;
public static new<T extends DataType = any>(name: string | number |
Field<T>, type: T, nullable?: boolean, metadata?: Map<string, string> | null):
Field<T>;
/** @nocollapse */
@@ -151,6 +186,14 @@ export class Field<T extends DataType = any> {
(Field.prototype as any).name = null;
(Field.prototype as any).nullable = null;
(Field.prototype as any).metadata = null;
+(Field.prototype as any)[kFieldSymbol] = true;
+
+Object.defineProperty(Field, Symbol.hasInstance, {
+ value: function isFieldInstance(instance: any): instance is Field {
+ return Function.prototype[Symbol.hasInstance].call(this, instance)
+ || (this === Field && Field.isField(instance));
+ },
+});
/** @ignore */
function mergeMaps<TKey, TVal>(m1?: Map<TKey, TVal> | null, m2?: Map<TKey,
TVal> | null): Map<TKey, TVal> {
diff --git a/src/table.ts b/src/table.ts
index 2aab2b7..2caeb75 100644
--- a/src/table.ts
+++ b/src/table.ts
@@ -44,6 +44,9 @@ import { clampRange, wrapIndex } from './util/vector.js';
import { ArrayDataType, BigIntArray, TypedArray, TypedArrayDataType } from
'./interfaces.js';
import { RecordBatch, _InternalEmptyPlaceholderRecordBatch } from
'./recordbatch.js';
+/** @ignore */
+const kTableSymbol = Symbol.for('apache-arrow/Table');
+
/** @ignore */
export interface Table<T extends TypeMap = any> {
///
@@ -67,6 +70,17 @@ export interface Table<T extends TypeMap = any> {
*/
export class Table<T extends TypeMap = any> {
+ /**
+ * Check if an object is an instance of Table.
+ * This works across different instances of the Arrow library.
+ */
+ /** @nocollapse */ static isTable(x: any): x is Table {
+ return x?.[kTableSymbol] === true;
+ }
+
+ /** @internal */
+ declare public readonly [kTableSymbol]: true;
+
constructor();
constructor(batches: Iterable<RecordBatch<T>>);
constructor(...batches: readonly RecordBatch<T>[]);
@@ -104,7 +118,7 @@ export class Table<T extends TypeMap = any> {
return x.batches;
} else if (x instanceof Data) {
if (x.type instanceof Struct) {
- return [new RecordBatch(new Schema(x.type.children),
x)];
+ return [new RecordBatch(new Schema(x.type.children), x
as Data<Struct<any>>)];
}
} else if (Array.isArray(x)) {
return x.flatMap(v => unwrap(v));
@@ -387,6 +401,7 @@ export class Table<T extends TypeMap = any> {
(proto as any)._offsets = new Uint32Array([0]);
(proto as any)._nullCount = -1;
(proto as any)[Symbol.isConcatSpreadable] = true;
+ (proto as any)[kTableSymbol] = true;
(proto as any)['isValid'] = wrapChunkedCall1(isChunkedValid);
(proto as any)['get'] =
wrapChunkedCall1(getVisitor.getVisitFn(Type.Struct));
(proto as any)['set'] =
wrapChunkedCall2(setVisitor.getVisitFn(Type.Struct));
@@ -395,6 +410,13 @@ export class Table<T extends TypeMap = any> {
})(Table.prototype);
}
+Object.defineProperty(Table, Symbol.hasInstance, {
+ value: function isTableInstance(instance: any): instance is Table {
+ return Function.prototype[Symbol.hasInstance].call(this, instance)
+ || (this === Table && Table.isTable(instance));
+ },
+});
+
type VectorsMap<T extends TypeMap> = { [P in keyof T]: Vector<T[P]> };
diff --git a/src/type.ts b/src/type.ts
index f1fc3fc..7bb9a07 100644
--- a/src/type.ts
+++ b/src/type.ts
@@ -35,6 +35,9 @@ export type IntBitWidth = 8 | 16 | 32 | 64;
/** @ignore */
export type IsSigned = { 'true': true; 'false': false };
+/** @ignore */
+const kDataTypeSymbol = Symbol.for('apache-arrow/DataType');
+
export interface DataType<TType extends Type = Type, TChildren extends TypeMap
= any> {
readonly TType: TType;
readonly TArray: any;
@@ -52,6 +55,22 @@ export interface DataType<TType extends Type = Type,
TChildren extends TypeMap =
*/
export abstract class DataType<TType extends Type = Type, TChildren extends
TypeMap = any> {
+ /**
+ * Check if an object is an instance of DataType.
+ * This works across different instances of the Arrow library.
+ *
+ * Note: We intentionally do NOT implement Symbol.hasInstance here because
+ * it would break instanceof checks for subclasses like Struct,
Dictionary, etc.
+ * Use DataType.isDataType() for cross-library type checking instead.
+ * @nocollapse
+ */
+ static isDataType(x: any): x is DataType {
+ return x?.[kDataTypeSymbol] === true;
+ }
+
+ /** @internal */
+ declare public readonly [kDataTypeSymbol]: true;
+
declare public [Symbol.toStringTag]: string;
/** @nocollapse */ static isNull(x: any): x is Null { return x?.typeId ===
Type.Null; }
@@ -93,6 +112,7 @@ export abstract class DataType<TType extends Type = Type,
TChildren extends Type
(<any>proto).children = null;
(<any>proto).ArrayType = Array;
(<any>proto).OffsetArrayType = Int32Array;
+ (<any>proto)[kDataTypeSymbol] = true;
return proto[Symbol.toStringTag] = 'DataType';
})(DataType.prototype);
}
diff --git a/src/util/typecheck.ts b/src/util/typecheck.ts
new file mode 100644
index 0000000..7e77c8a
--- /dev/null
+++ b/src/util/typecheck.ts
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/**
+ * Type guards for Apache Arrow types that work across different instances
+ * of the Arrow library. These functions use Symbol.for() based markers
+ * to identify Arrow types, making them reliable when multiple versions
+ * or instances of the library are loaded.
+ *
+ * @example
+ * ```ts
+ * import { isArrowSchema, isArrowTable } from 'apache-arrow';
+ *
+ * // Works even with different Arrow library instances
+ * if (isArrowSchema(maybeSchema)) {
+ * console.log('This is a Schema from any Arrow version');
+ * }
+ *
+ * if (isArrowTable(maybeTable)) {
+ * console.log('This is a Table from any Arrow version');
+ * }
+ * ```
+ */
+
+import { Schema, Field } from '../schema.js';
+import { DataType } from '../type.js';
+import { Data } from '../data.js';
+import { Vector } from '../vector.js';
+import { RecordBatch } from '../recordbatch.js';
+import { Table } from '../table.js';
+
+/**
+ * Check if a value is an Arrow Schema from any version of the library.
+ */
+export function isArrowSchema(x: any): x is Schema {
+ return Schema.isSchema(x);
+}
+
+/**
+ * Check if a value is an Arrow Field from any version of the library.
+ */
+export function isArrowField(x: any): x is Field {
+ return Field.isField(x);
+}
+
+/**
+ * Check if a value is an Arrow DataType from any version of the library.
+ */
+export function isArrowDataType(x: any): x is DataType {
+ return DataType.isDataType(x);
+}
+
+/**
+ * Check if a value is an Arrow Data from any version of the library.
+ */
+export function isArrowData(x: any): x is Data {
+ return Data.isData(x);
+}
+
+/**
+ * Check if a value is an Arrow Vector from any version of the library.
+ */
+export function isArrowVector(x: any): x is Vector {
+ return Vector.isVector(x);
+}
+
+/**
+ * Check if a value is an Arrow RecordBatch from any version of the library.
+ */
+export function isArrowRecordBatch(x: any): x is RecordBatch {
+ return RecordBatch.isRecordBatch(x);
+}
+
+/**
+ * Check if a value is an Arrow Table from any version of the library.
+ */
+export function isArrowTable(x: any): x is Table {
+ return Table.isTable(x);
+}
diff --git a/src/vector.ts b/src/vector.ts
index aeaa1c1..197c0cd 100644
--- a/src/vector.ts
+++ b/src/vector.ts
@@ -40,6 +40,9 @@ import { instance as iteratorVisitor } from
'./visitor/iterator.js';
// @ts-ignore
import type { vectorFromArray } from './factories.js';
+/** @ignore */
+const kVectorSymbol = Symbol.for('apache-arrow/Vector');
+
export interface Vector<T extends DataType = any> {
///
// Virtual properties for the TypeScript compiler.
@@ -63,6 +66,17 @@ const vectorPrototypesByTypeId = {} as { [typeId: number]:
any };
*/
export class Vector<T extends DataType = any> {
+ /**
+ * Check if an object is an instance of Vector.
+ * This works across different instances of the Arrow library.
+ */
+ /** @nocollapse */ static isVector(x: any): x is Vector {
+ return x?.[kVectorSymbol] === true;
+ }
+
+ /** @internal */
+ declare public readonly [kVectorSymbol]: true;
+
constructor(input: readonly (Data<T> | Vector<T>)[]) {
const data: Data<T>[] = input[0] instanceof Vector
? (input as Vector<T>[]).flatMap(x => x.data)
@@ -356,6 +370,7 @@ export class Vector<T extends DataType = any> {
(proto as any).numChildren = 0;
(proto as any)._offsets = new Uint32Array([0]);
(proto as any)[Symbol.isConcatSpreadable] = true;
+ (proto as any)[kVectorSymbol] = true;
const typeIds: Type[] = Object.keys(Type)
.map((T: any) => Type[T] as any)
@@ -379,6 +394,13 @@ export class Vector<T extends DataType = any> {
})(Vector.prototype);
}
+Object.defineProperty(Vector, Symbol.hasInstance, {
+ value: function isVectorInstance(instance: any): instance is Vector {
+ return Function.prototype[Symbol.hasInstance].call(this, instance)
+ || (this === Vector && Vector.isVector(instance));
+ },
+});
+
class MemoizedVector<T extends DataType = any> extends Vector<T> {
public constructor(vector: Vector<T>) {
diff --git a/test/unit/instanceof-tests.ts b/test/unit/instanceof-tests.ts
new file mode 100644
index 0000000..0ef1fd7
--- /dev/null
+++ b/test/unit/instanceof-tests.ts
@@ -0,0 +1,374 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import {
+ Schema, Field, DataType, Data, Vector, RecordBatch, Table,
+ Int32, Utf8, Float64,
+ makeData, vectorFromArray,
+ isArrowSchema, isArrowField, isArrowDataType, isArrowData,
+ isArrowVector, isArrowRecordBatch, isArrowTable
+} from 'apache-arrow';
+
+/**
+ * Tests for Symbol.hasInstance implementation that enables instanceof
+ * to work across different instances of the Arrow library.
+ *
+ * @see https://github.com/apache/arrow/issues/61
+ */
+describe('Cross-library instanceof support', () => {
+
+ describe('Schema', () => {
+ const schema = new Schema([
+ new Field('id', new Int32()),
+ new Field('name', new Utf8())
+ ]);
+
+ test('instanceof works with Schema', () => {
+ expect(schema instanceof Schema).toBe(true);
+ });
+
+ test('Schema.isSchema() returns true for Schema instances', () => {
+ expect(Schema.isSchema(schema)).toBe(true);
+ });
+
+ test('isArrowSchema() returns true for Schema instances', () => {
+ expect(isArrowSchema(schema)).toBe(true);
+ });
+
+ test('Schema.isSchema() returns false for non-Schema values', () => {
+ expect(Schema.isSchema(null)).toBe(false);
+ expect(Schema.isSchema()).toBe(false);
+ expect(Schema.isSchema({})).toBe(false);
+ expect(Schema.isSchema({ fields: [] })).toBe(false);
+ expect(Schema.isSchema('Schema')).toBe(false);
+ });
+
+ test('isArrowSchema() returns false for non-Schema values', () => {
+ expect(isArrowSchema(null)).toBe(false);
+ expect(isArrowSchema()).toBe(false);
+ expect(isArrowSchema({})).toBe(false);
+ });
+ });
+
+ describe('Field', () => {
+ const field = new Field('test', new Int32());
+
+ test('instanceof works with Field', () => {
+ expect(field instanceof Field).toBe(true);
+ });
+
+ test('Field.isField() returns true for Field instances', () => {
+ expect(Field.isField(field)).toBe(true);
+ });
+
+ test('isArrowField() returns true for Field instances', () => {
+ expect(isArrowField(field)).toBe(true);
+ });
+
+ test('Field.isField() returns false for non-Field values', () => {
+ expect(Field.isField(null)).toBe(false);
+ expect(Field.isField()).toBe(false);
+ expect(Field.isField({})).toBe(false);
+ expect(Field.isField({ name: 'test', type: new Int32()
})).toBe(false);
+ });
+
+ test('isArrowField() returns false for non-Field values', () => {
+ expect(isArrowField(null)).toBe(false);
+ expect(isArrowField()).toBe(false);
+ expect(isArrowField({})).toBe(false);
+ });
+ });
+
+ describe('DataType', () => {
+ const dataType = new Int32();
+
+ test('instanceof works with DataType', () => {
+ expect(dataType instanceof DataType).toBe(true);
+ });
+
+ test('DataType.isDataType() returns true for DataType instances', ()
=> {
+ expect(DataType.isDataType(dataType)).toBe(true);
+ expect(DataType.isDataType(new Utf8())).toBe(true);
+ expect(DataType.isDataType(new Float64())).toBe(true);
+ });
+
+ test('isArrowDataType() returns true for DataType instances', () => {
+ expect(isArrowDataType(dataType)).toBe(true);
+ });
+
+ test('DataType.isDataType() returns false for non-DataType values', ()
=> {
+ expect(DataType.isDataType(null)).toBe(false);
+ expect(DataType.isDataType()).toBe(false);
+ expect(DataType.isDataType({})).toBe(false);
+ expect(DataType.isDataType({ typeId: 0 })).toBe(false);
+ });
+
+ test('isArrowDataType() returns false for non-DataType values', () => {
+ expect(isArrowDataType(null)).toBe(false);
+ expect(isArrowDataType()).toBe(false);
+ expect(isArrowDataType({})).toBe(false);
+ });
+ });
+
+ describe('Data', () => {
+ const data = makeData({ type: new Int32(), length: 5 });
+
+ test('instanceof works with Data', () => {
+ expect(data instanceof Data).toBe(true);
+ });
+
+ test('Data.isData() returns true for Data instances', () => {
+ expect(Data.isData(data)).toBe(true);
+ });
+
+ test('isArrowData() returns true for Data instances', () => {
+ expect(isArrowData(data)).toBe(true);
+ });
+
+ test('Data.isData() returns false for non-Data values', () => {
+ expect(Data.isData(null)).toBe(false);
+ expect(Data.isData()).toBe(false);
+ expect(Data.isData({})).toBe(false);
+ expect(Data.isData({ type: new Int32(), length: 5 })).toBe(false);
+ });
+
+ test('isArrowData() returns false for non-Data values', () => {
+ expect(isArrowData(null)).toBe(false);
+ expect(isArrowData()).toBe(false);
+ expect(isArrowData({})).toBe(false);
+ });
+ });
+
+ describe('Vector', () => {
+ const vector = vectorFromArray([1, 2, 3, 4, 5]);
+
+ test('instanceof works with Vector', () => {
+ expect(vector instanceof Vector).toBe(true);
+ });
+
+ test('Vector.isVector() returns true for Vector instances', () => {
+ expect(Vector.isVector(vector)).toBe(true);
+ });
+
+ test('isArrowVector() returns true for Vector instances', () => {
+ expect(isArrowVector(vector)).toBe(true);
+ });
+
+ test('Vector.isVector() returns false for non-Vector values', () => {
+ expect(Vector.isVector(null)).toBe(false);
+ expect(Vector.isVector()).toBe(false);
+ expect(Vector.isVector({})).toBe(false);
+ expect(Vector.isVector([1, 2, 3])).toBe(false);
+ });
+
+ test('isArrowVector() returns false for non-Vector values', () => {
+ expect(isArrowVector(null)).toBe(false);
+ expect(isArrowVector()).toBe(false);
+ expect(isArrowVector({})).toBe(false);
+ });
+ });
+
+ describe('RecordBatch', () => {
+ const schema = new Schema([
+ new Field('id', new Int32()),
+ new Field('value', new Float64())
+ ]);
+ const batch = new RecordBatch({
+ id: vectorFromArray([1, 2, 3]).data[0],
+ value: vectorFromArray([1.1, 2.2, 3.3]).data[0]
+ });
+
+ test('instanceof works with RecordBatch', () => {
+ expect(batch instanceof RecordBatch).toBe(true);
+ });
+
+ test('RecordBatch.isRecordBatch() returns true for RecordBatch
instances', () => {
+ expect(RecordBatch.isRecordBatch(batch)).toBe(true);
+ });
+
+ test('isArrowRecordBatch() returns true for RecordBatch instances', ()
=> {
+ expect(isArrowRecordBatch(batch)).toBe(true);
+ });
+
+ test('RecordBatch.isRecordBatch() returns false for non-RecordBatch
values', () => {
+ expect(RecordBatch.isRecordBatch(null)).toBe(false);
+ expect(RecordBatch.isRecordBatch()).toBe(false);
+ expect(RecordBatch.isRecordBatch({})).toBe(false);
+ expect(RecordBatch.isRecordBatch({ schema, numRows: 3
})).toBe(false);
+ });
+
+ test('isArrowRecordBatch() returns false for non-RecordBatch values',
() => {
+ expect(isArrowRecordBatch(null)).toBe(false);
+ expect(isArrowRecordBatch()).toBe(false);
+ expect(isArrowRecordBatch({})).toBe(false);
+ });
+ });
+
+ describe('Table', () => {
+ const table = new Table({
+ id: vectorFromArray([1, 2, 3]),
+ name: vectorFromArray(['a', 'b', 'c'])
+ });
+
+ test('instanceof works with Table', () => {
+ expect(table instanceof Table).toBe(true);
+ });
+
+ test('Table.isTable() returns true for Table instances', () => {
+ expect(Table.isTable(table)).toBe(true);
+ });
+
+ test('isArrowTable() returns true for Table instances', () => {
+ expect(isArrowTable(table)).toBe(true);
+ });
+
+ test('Table.isTable() returns false for non-Table values', () => {
+ expect(Table.isTable(null)).toBe(false);
+ expect(Table.isTable()).toBe(false);
+ expect(Table.isTable({})).toBe(false);
+ expect(Table.isTable({ schema: new Schema([]), batches: []
})).toBe(false);
+ });
+
+ test('isArrowTable() returns false for non-Table values', () => {
+ expect(isArrowTable(null)).toBe(false);
+ expect(isArrowTable()).toBe(false);
+ expect(isArrowTable({})).toBe(false);
+ });
+ });
+
+ describe('Symbol.for markers', () => {
+ test('Schema has the correct symbol marker', () => {
+ const schema = new Schema([]);
+ const marker = Symbol.for('apache-arrow/Schema');
+ expect((schema as any)[marker]).toBe(true);
+ });
+
+ test('Field has the correct symbol marker', () => {
+ const field = new Field('test', new Int32());
+ const marker = Symbol.for('apache-arrow/Field');
+ expect((field as any)[marker]).toBe(true);
+ });
+
+ test('DataType has the correct symbol marker', () => {
+ const dataType = new Int32();
+ const marker = Symbol.for('apache-arrow/DataType');
+ expect((dataType as any)[marker]).toBe(true);
+ });
+
+ test('Data has the correct symbol marker', () => {
+ const data = makeData({ type: new Int32(), length: 5 });
+ const marker = Symbol.for('apache-arrow/Data');
+ expect((data as any)[marker]).toBe(true);
+ });
+
+ test('Vector has the correct symbol marker', () => {
+ const vector = vectorFromArray([1, 2, 3]);
+ const marker = Symbol.for('apache-arrow/Vector');
+ expect((vector as any)[marker]).toBe(true);
+ });
+
+ test('RecordBatch has the correct symbol marker', () => {
+ const batch = new RecordBatch({
+ id: vectorFromArray([1, 2, 3]).data[0]
+ });
+ const marker = Symbol.for('apache-arrow/RecordBatch');
+ expect((batch as any)[marker]).toBe(true);
+ });
+
+ test('Table has the correct symbol marker', () => {
+ const table = new Table({ id: vectorFromArray([1, 2, 3]) });
+ const marker = Symbol.for('apache-arrow/Table');
+ expect((table as any)[marker]).toBe(true);
+ });
+ });
+
+ describe('Cross-instance detection simulation', () => {
+ /**
+ * Simulates what happens when an object comes from a different
+ * Arrow library instance. We create a plain object with the
+ * Symbol.for marker to simulate this scenario.
+ */
+ test('Schema marker is detected on foreign objects', () => {
+ const foreignSchema = {
+ [Symbol.for('apache-arrow/Schema')]: true,
+ fields: [],
+ metadata: new Map()
+ };
+ expect(Schema.isSchema(foreignSchema)).toBe(true);
+ expect(isArrowSchema(foreignSchema)).toBe(true);
+ });
+
+ test('Field marker is detected on foreign objects', () => {
+ const foreignField = {
+ [Symbol.for('apache-arrow/Field')]: true,
+ name: 'test',
+ type: new Int32()
+ };
+ expect(Field.isField(foreignField)).toBe(true);
+ expect(isArrowField(foreignField)).toBe(true);
+ });
+
+ test('DataType marker is detected on foreign objects', () => {
+ const foreignDataType = {
+ [Symbol.for('apache-arrow/DataType')]: true,
+ typeId: 8 // Int32
+ };
+ expect(DataType.isDataType(foreignDataType)).toBe(true);
+ expect(isArrowDataType(foreignDataType)).toBe(true);
+ });
+
+ test('Data marker is detected on foreign objects', () => {
+ const foreignData = {
+ [Symbol.for('apache-arrow/Data')]: true,
+ type: new Int32(),
+ length: 5
+ };
+ expect(Data.isData(foreignData)).toBe(true);
+ expect(isArrowData(foreignData)).toBe(true);
+ });
+
+ test('Vector marker is detected on foreign objects', () => {
+ const foreignVector = {
+ [Symbol.for('apache-arrow/Vector')]: true,
+ data: [],
+ type: new Int32()
+ };
+ expect(Vector.isVector(foreignVector)).toBe(true);
+ expect(isArrowVector(foreignVector)).toBe(true);
+ });
+
+ test('RecordBatch marker is detected on foreign objects', () => {
+ const foreignBatch = {
+ [Symbol.for('apache-arrow/RecordBatch')]: true,
+ schema: new Schema([]),
+ numRows: 0
+ };
+ expect(RecordBatch.isRecordBatch(foreignBatch)).toBe(true);
+ expect(isArrowRecordBatch(foreignBatch)).toBe(true);
+ });
+
+ test('Table marker is detected on foreign objects', () => {
+ const foreignTable = {
+ [Symbol.for('apache-arrow/Table')]: true,
+ schema: new Schema([]),
+ batches: []
+ };
+ expect(Table.isTable(foreignTable)).toBe(true);
+ expect(isArrowTable(foreignTable)).toBe(true);
+ });
+ });
+});