@@ -24,45 +24,130 @@ export function detectAndTransform(value: unknown): unknown {
2424 return value ;
2525}
2626
27- export function formatValue ( value : unknown ) : string {
27+ // Type-specific formatters
28+ function formatInterval ( value : unknown ) : string {
29+ // INTERVAL values are already converted to strings in the worker
30+ return String ( value ) ;
31+ }
32+
33+ function formatTime ( value : unknown ) : string {
34+ // TIME values are already converted to HH:MM:SS.ffffff in the worker
35+ return String ( value ) ;
36+ }
37+
38+ function formatTimestamp ( value : unknown ) : string {
39+ if ( value instanceof Date ) {
40+ return value . toISOString ( ) ;
41+ }
42+ // Fallback for timestamps that weren't converted
43+ if ( typeof value === "number" || typeof value === "bigint" ) {
44+ return new Date ( Number ( value ) ) . toISOString ( ) ;
45+ }
46+ return String ( value ) ;
47+ }
48+
49+ function formatHugeInt ( value : unknown ) : string {
50+ // Handle DuckDB hugeint/decimal - can be Uint32Array or object with keys "0", "1", "2", "3"
51+ let parts : { 0 : number ; 1 : number ; 2 : number ; 3 : number } | null = null ;
52+
53+ if ( value instanceof Uint32Array && value . length === 4 ) {
54+ // Convert Uint32Array to plain object structure
55+ parts = { 0 : value [ 0 ] , 1 : value [ 1 ] , 2 : value [ 2 ] , 3 : value [ 3 ] } ;
56+ } else if (
57+ typeof value === "object" &&
58+ value !== null &&
59+ "0" in value &&
60+ "1" in value &&
61+ "2" in value &&
62+ "3" in value
63+ ) {
64+ parts = value as { 0 : number ; 1 : number ; 2 : number ; 3 : number } ;
65+ }
66+
67+ if ( parts ) {
68+ // The first part (parts[0]) contains the lower 32 bits
69+ if ( parts [ 1 ] === 0 && parts [ 2 ] === 0 && parts [ 3 ] === 0 ) {
70+ return String ( parts [ 0 ] ) ;
71+ }
72+ // For very large values, construct a BigInt from the parts
73+ // DuckDB hugeint/decimal is stored as little-endian 32-bit chunks
74+ const bigIntValue =
75+ BigInt ( parts [ 0 ] ) +
76+ ( BigInt ( parts [ 1 ] ) << 32n ) +
77+ ( BigInt ( parts [ 2 ] ) << 64n ) +
78+ ( BigInt ( parts [ 3 ] ) << 96n ) ;
79+ return bigIntValue . toString ( ) ;
80+ }
81+
82+ // If it doesn't match the expected structure, just convert to string
83+ return String ( value ) ;
84+ }
85+
86+ function formatJson ( value : unknown ) : string {
87+ if ( typeof value === "object" && value !== null ) {
88+ return JSON . stringify ( value , null , 2 ) ;
89+ }
90+ return String ( value ) ;
91+ }
92+
93+ export function formatValue ( value : unknown , columnType ?: string ) : string {
2894 if ( value === null || value === undefined ) {
2995 return "" ;
3096 }
3197
32- if ( typeof value === "object" ) {
33- // Handle Date objects
98+ // If no type information provided, log warning and use best-effort formatting
99+ if ( ! columnType ) {
100+ console . warn ( "formatValue called without columnType for value:" , value ) ;
101+
102+ // Minimal fallback: handle Date and basic types
34103 if ( value instanceof Date ) {
35- // Format as ISO 8601
36104 return value . toISOString ( ) ;
37105 }
38-
39- // Handle DuckDB hugeint (128-bit integer) - represented as object with keys "0", "1", "2", "3"
40- if (
41- typeof value === "object" &&
42- value !== null &&
43- "0" in value &&
44- "1" in value &&
45- "2" in value &&
46- "3" in value
47- ) {
48- const parts = value as { 0 : number ; 1 : number ; 2 : number ; 3 : number } ;
49- // The first part (parts[0]) contains the lower 32 bits, which is usually the only non-zero part for reasonable values
50- // For very large numbers, we'd need proper BigInt handling, but for now just show the lower part
51- if ( parts [ 1 ] === 0 && parts [ 2 ] === 0 && parts [ 3 ] === 0 ) {
52- return String ( parts [ 0 ] ) ;
53- }
54- // For very large values, construct a BigInt from the parts
55- // DuckDB hugeint is stored as little-endian 32-bit chunks
56- const bigIntValue =
57- BigInt ( parts [ 0 ] ) +
58- ( BigInt ( parts [ 1 ] ) << 32n ) +
59- ( BigInt ( parts [ 2 ] ) << 64n ) +
60- ( BigInt ( parts [ 3 ] ) << 96n ) ;
61- return bigIntValue . toString ( ) ;
106+ if ( typeof value === "object" ) {
107+ return JSON . stringify ( value , null , 2 ) ;
62108 }
109+ return String ( value ) ;
110+ }
111+
112+ // Type-driven dispatch based on DuckDB type
113+ const typeUpper = columnType . toUpperCase ( ) ;
114+
115+ // Handle INTERVAL types
116+ if ( typeUpper . includes ( "INTERVAL" ) ) {
117+ return formatInterval ( value ) ;
118+ }
63119
120+ // Handle TIME types
121+ if ( typeUpper . includes ( "TIME" ) ) {
122+ return formatTime ( value ) ;
123+ }
124+
125+ // Handle TIMESTAMP types
126+ if ( typeUpper . includes ( "TIMESTAMP" ) || typeUpper . includes ( "DATE" ) ) {
127+ return formatTimestamp ( value ) ;
128+ }
129+
130+ // Handle HUGEINT (128-bit integers) and DECIMAL types
131+ if ( typeUpper . includes ( "HUGEINT" ) || typeUpper . includes ( "INT128" ) || typeUpper . includes ( "DECIMAL" ) ) {
132+ return formatHugeInt ( value ) ;
133+ }
134+
135+ // Handle JSON types
136+ if ( typeUpper . includes ( "JSON" ) || typeUpper . includes ( "STRUCT" ) || typeUpper . includes ( "MAP" ) ) {
137+ return formatJson ( value ) ;
138+ }
139+
140+ // Handle Date objects (might still appear for TIMESTAMP WITH TIME ZONE)
141+ if ( value instanceof Date ) {
142+ return value . toISOString ( ) ;
143+ }
144+
145+ // Handle objects that weren't caught by specific type handlers
146+ if ( typeof value === "object" ) {
147+ console . warn ( `Unexpected object value for type ${ columnType } :` , value ) ;
64148 return JSON . stringify ( value , null , 2 ) ;
65149 }
66150
151+ // Default: convert to string for primitive types (VARCHAR, INTEGER, DOUBLE, etc.)
67152 return String ( value ) ;
68153}
0 commit comments