11// © Copyright 2025-2026, Query.Farm LLC - https://query.farm
22// SPDX-License-Identifier: Apache-2.0
33
4- import { RecordBatch , Struct , makeData , type Schema } from "@query-farm/apache-arrow" ;
4+ import {
5+ type DataType ,
6+ RecordBatch ,
7+ Struct ,
8+ Type ,
9+ makeData ,
10+ type Schema ,
11+ vectorFromArray ,
12+ } from "@query-farm/apache-arrow" ;
13+
14+ /** Return true when the source type's values can be losslessly read and
15+ * re-encoded into the target type (e.g., int32 → float64). */
16+ function needsValueCast ( src : DataType , dst : DataType ) : boolean {
17+ if ( src . typeId === dst . typeId ) return false ;
18+ // Same broad family (e.g. Float → Float64) — clone is sufficient.
19+ if ( src . constructor === dst . constructor ) return false ;
20+ return true ;
21+ }
22+
23+ /** Check if a type is a numeric type we can cast between.
24+ * Uses typeId instead of instanceof because IPC-deserialized types
25+ * may be generic (e.g., Int_ instead of Int64). */
26+ function isNumeric ( t : DataType ) : boolean {
27+ return t . typeId === Type . Int || t . typeId === Type . Float ;
28+ }
529
630/**
731 * Rebuild a batch's data to match the given schema's field types.
@@ -12,13 +36,54 @@ import { RecordBatch, Struct, makeData, type Schema } from "@query-farm/apache-a
1236 * match the writer's schema. Cloning each child Data with the schema's field
1337 * type fixes the type metadata while preserving the underlying buffers.
1438 *
15- * This is also used to cast compatible input types (e.g., decimal→double,
16- * int32→int64) when the input batch schema doesn't exactly match the method's
17- * declared input schema.
39+ * This is also used to cast compatible input types (e.g., int32→float64,
40+ * float32→float64) when the input batch schema doesn't exactly match the
41+ * method's declared input schema. When the underlying buffer layout differs
42+ * (e.g., 4-byte int32 vs 8-byte float64), we read the values and build a
43+ * new vector with the target type.
1844 */
1945export function conformBatchToSchema ( batch : RecordBatch , schema : Schema ) : RecordBatch {
2046 if ( batch . numRows === 0 ) return batch ;
21- const children = schema . fields . map ( ( f , i ) => batch . data . children [ i ] . clone ( f . type ) ) ;
47+
48+ // Validate field count and names match before attempting any cast.
49+ if ( batch . schema . fields . length !== schema . fields . length ) {
50+ throw new TypeError (
51+ `Field count mismatch: expected ${ schema . fields . length } , got ${ batch . schema . fields . length } ` ,
52+ ) ;
53+ }
54+ for ( let i = 0 ; i < schema . fields . length ; i ++ ) {
55+ if ( batch . schema . fields [ i ] . name !== schema . fields [ i ] . name ) {
56+ throw new TypeError (
57+ `Field name mismatch at index ${ i } : expected '${ schema . fields [ i ] . name } ', got '${ batch . schema . fields [ i ] . name } '` ,
58+ ) ;
59+ }
60+ }
61+
62+ const children = schema . fields . map ( ( f , i ) => {
63+ const srcChild = batch . data . children [ i ] ;
64+ const srcType = srcChild . type ;
65+ const dstType = f . type ;
66+
67+ if ( ! needsValueCast ( srcType , dstType ) ) {
68+ return srcChild . clone ( dstType ) ;
69+ }
70+
71+ // Numeric → numeric: read values and rebuild with target type.
72+ if ( isNumeric ( srcType ) && isNumeric ( dstType ) ) {
73+ // Read source values via the batch's column vector.
74+ const col = batch . getChildAt ( i ) ! ;
75+ const values : number [ ] = [ ] ;
76+ for ( let r = 0 ; r < batch . numRows ; r ++ ) {
77+ const v = col . get ( r ) ;
78+ values . push ( typeof v === "bigint" ? Number ( v ) : ( v as number ) ) ;
79+ }
80+ return vectorFromArray ( values , dstType ) . data [ 0 ] ;
81+ }
82+
83+ // Fallback: clone type metadata (works for same-layout types).
84+ return srcChild . clone ( dstType ) ;
85+ } ) ;
86+
2287 const structType = new Struct ( schema . fields ) ;
2388 const data = makeData ( {
2489 type : structType ,
0 commit comments