Skip to content

Commit ca95dcd

Browse files
committed
fix interval rendering
1 parent c1f2d45 commit ca95dcd

File tree

2 files changed

+98
-3
lines changed

2 files changed

+98
-3
lines changed

src/crdb/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@ export function formatValue(value: unknown): string {
3636
return value.toISOString();
3737
}
3838

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();
62+
}
63+
3964
return JSON.stringify(value, null, 2);
4065
}
4166

src/workers/db.worker.ts

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,7 +1386,6 @@ async function executeQuery(
13861386
try {
13871387
const rewrittenSql = rewriteQuery(sql);
13881388
const result = await conn.query(rewrittenSql);
1389-
const data = result.toArray();
13901389

13911390
// Get column types from the result schema
13921391
const schema = result.schema.fields;
@@ -1395,14 +1394,85 @@ async function executeQuery(
13951394
columnTypes.set(field.name, field.type.toString());
13961395
});
13971396

1397+
// Extract interval columns before toArray() loses the data
1398+
const intervalColumns = new Map<string, any>();
1399+
schema.forEach((field, idx) => {
1400+
if (field.type.toString().toUpperCase().includes('INTERVAL')) {
1401+
intervalColumns.set(field.name, result.getChildAt(idx));
1402+
}
1403+
});
1404+
1405+
const data = result.toArray();
1406+
13981407
// Sanitize data and convert types in one pass
1399-
const sanitizedData = data.map((row) => {
1408+
const sanitizedData = data.map((row, rowIndex) => {
14001409
const sanitizedRow: Record<string, unknown> = {};
14011410
for (const [key, value] of Object.entries(row)) {
14021411
const columnType = columnTypes.get(key);
14031412

1413+
// Handle INTERVAL columns - get from raw Arrow data
1414+
if (columnType && columnType.toUpperCase().includes('INTERVAL')) {
1415+
const intervalColumn = intervalColumns.get(key);
1416+
if (intervalColumn) {
1417+
// Access raw values array directly since .get() loses data
1418+
const data = intervalColumn.data[0];
1419+
const values = data.values;
1420+
1421+
// stride=3 means layout is: [months, days, nanos_low] repeated, with nanos_high separate
1422+
// Actually for MONTH_DAY_NANO it should be 4 int32s per interval
1423+
// Let me check the actual memory layout
1424+
const stride = 4; // Force 4 for MONTH_DAY_NANO (months, days, nanos as int64)
1425+
const offset = rowIndex * stride;
1426+
1427+
console.log('Stride info:', 'column.stride:', intervalColumn.stride, 'values.length:', values.length, 'rowIndex:', rowIndex, 'calculated offset:', offset);
1428+
console.log('Values at offset:', values.slice(offset, offset + 4));
1429+
1430+
// MONTH_DAY_NANO intervals: [int32 months, int32 days, int64 nanos as two int32s]
1431+
const months = values[offset] || 0;
1432+
const days = values[offset + 1] || 0;
1433+
// Reconstruct 64-bit nanoseconds from two 32-bit parts (signed!)
1434+
const nanosLow = values[offset + 2] || 0;
1435+
const nanosHigh = values[offset + 3] || 0;
1436+
1437+
console.log('Parsed values:', {months, days, nanosLow, nanosHigh});
1438+
1439+
// Convert to unsigned for low part, keep high part signed
1440+
const nanos = (BigInt(nanosHigh) << 32n) | BigInt(nanosLow >>> 0);
1441+
1442+
// Convert to human-readable string
1443+
const components: string[] = [];
1444+
1445+
if (months !== 0) {
1446+
const years = Math.floor(Math.abs(months) / 12);
1447+
const remainingMonths = Math.abs(months) % 12;
1448+
if (years > 0) components.push(`${months < 0 ? '-' : ''}${years} year${years !== 1 ? 's' : ''}`);
1449+
if (remainingMonths > 0) components.push(`${months < 0 && years === 0 ? '-' : ''}${remainingMonths} mon${remainingMonths !== 1 ? 's' : ''}`);
1450+
}
1451+
if (days !== 0) {
1452+
components.push(`${days < 0 ? '-' : ''}${Math.abs(days)} day${Math.abs(days) !== 1 ? 's' : ''}`);
1453+
}
1454+
if (nanos !== 0n) {
1455+
// Convert nanoseconds to seconds
1456+
const totalNanos = nanos < 0n ? -nanos : nanos;
1457+
const totalSeconds = Number(totalNanos) / 1000000000;
1458+
const hours = Math.floor(totalSeconds / 3600);
1459+
const minutes = Math.floor((totalSeconds % 3600) / 60);
1460+
const seconds = totalSeconds % 60;
1461+
1462+
const timeParts: string[] = [];
1463+
if (hours > 0) timeParts.push(`${hours}:`);
1464+
timeParts.push(`${String(minutes).padStart(hours > 0 ? 2 : 1, '0')}:`);
1465+
timeParts.push(String(seconds.toFixed(6)).padStart(9, '0'));
1466+
components.push(`${nanos < 0n ? '-' : ''}${timeParts.join('')}`);
1467+
}
1468+
1469+
sanitizedRow[key] = components.length > 0 ? components.join(' ') : '00:00:00';
1470+
} else {
1471+
sanitizedRow[key] = value;
1472+
}
1473+
}
14041474
// Handle TIME columns (microseconds since midnight as bigint)
1405-
if (columnType && (columnType.toUpperCase().includes('TIME') || columnType === 'Time64[us]')) {
1475+
else if (columnType && (columnType.toUpperCase().includes('TIME') || columnType === 'Time64[us]')) {
14061476
if (value !== null && value !== undefined && (typeof value === 'bigint' || typeof value === 'number')) {
14071477
// Convert microseconds to HH:MM:SS.ffffff
14081478
const totalMicros = typeof value === 'bigint' ? Number(value) : value;

0 commit comments

Comments
 (0)