diff --git a/api/src/conversion/dateTimeStringConversion.ts b/api/src/conversion/dateTimeStringConversion.ts index fdab733..6505142 100644 --- a/api/src/conversion/dateTimeStringConversion.ts +++ b/api/src/conversion/dateTimeStringConversion.ts @@ -3,7 +3,7 @@ const MILLISECONDS_PER_DAY_NUM = 86400000; // 1000 * 60 * 60 * 24 const MICROSECONDS_PER_SECOND = 1000000n; const MICROSECONDS_PER_MILLISECOND = 1000n; -const NANOSECONDS_PER_SECOND = 1000000000n +const NANOSECONDS_PER_SECOND = 1000000000n; const SECONDS_PER_MINUTE = 60n; const MINUTES_PER_HOUR = 60n; const MICROSECONDS_PER_DAY = 86400000000n; // 24 * 60 * 60 * 1000000 @@ -15,7 +15,7 @@ const POSITIVE_INFINITY_TIMESTAMP = 9223372036854775807n; // 2^63-1 export function getDuckDBDateStringFromYearMonthDay( year: number, month: number, - dayOfMonth: number, + dayOfMonth: number ): string { const yearStr = String(Math.abs(year)).padStart(4, '0'); const monthStr = String(month).padStart(2, '0'); @@ -48,11 +48,70 @@ export function getDuckDBDateStringFromDays(days: number): string { return getDuckDBDateStringFromYearMonthDay(year, month, dayOfMonth); } +export function getTimezoneOffsetString( + timezoneOffsetInMinutes?: number +): string | undefined { + if (timezoneOffsetInMinutes === undefined) { + return undefined; + } + const negative = timezoneOffsetInMinutes < 0; + const positiveMinutes = Math.abs(timezoneOffsetInMinutes); + const minutesPart = positiveMinutes % 60; + const hoursPart = Math.floor(positiveMinutes / 60); + const minutesStr = + minutesPart !== 0 ? String(minutesPart).padStart(2, '0') : ''; + const hoursStr = String(hoursPart).padStart(2, '0'); + return `${negative ? '-' : '+'}${hoursStr}${ + minutesStr ? `:${minutesStr}` : '' + }`; +} + +export function getAbsoluteOffsetStringFromParts( + hoursPart: number, + minutesPart: number, + secondsPart: number +): string { + const hoursStr = String(hoursPart).padStart(2, '0'); + const minutesStr = + minutesPart !== 0 || secondsPart !== 0 + ? String(minutesPart).padStart(2, '0') + : ''; + const secondsStr = + secondsPart !== 0 ? String(secondsPart).padStart(2, '0') : ''; + let result = hoursStr; + if (minutesStr) { + result += `:${minutesStr}`; + if (secondsStr) { + result += `:${secondsStr}`; + } + } + return result; +} + +export function getOffsetStringFromAbsoluteSeconds( + absoluteOffsetInSeconds: number +): string { + const secondsPart = absoluteOffsetInSeconds % 60; + const minutes = Math.floor(absoluteOffsetInSeconds / 60); + const minutesPart = minutes % 60; + const hoursPart = Math.floor(minutes / 60); + return getAbsoluteOffsetStringFromParts(hoursPart, minutesPart, secondsPart); +} + +export function getOffsetStringFromSeconds(offsetInSeconds: number): string { + const negative = offsetInSeconds < 0; + const absoluteOffsetInSeconds = negative ? -offsetInSeconds : offsetInSeconds; + const absoluteString = getOffsetStringFromAbsoluteSeconds( + absoluteOffsetInSeconds + ); + return `${negative ? '-' : '+'}${absoluteString}`; +} + export function getDuckDBTimeStringFromParts( hoursPart: bigint, minutesPart: bigint, secondsPart: bigint, - microsecondsPart: bigint, + microsecondsPart: bigint ): string { const hoursStr = String(hoursPart).padStart(2, '0'); const minutesStr = String(minutesPart).padStart(2, '0'); @@ -69,7 +128,7 @@ export function getDuckDBTimeStringFromPartsNS( hoursPart: bigint, minutesPart: bigint, secondsPart: bigint, - nanosecondsPart: bigint, + nanosecondsPart: bigint ): string { const hoursStr = String(hoursPart).padStart(2, '0'); const minutesStr = String(minutesPart).padStart(2, '0'); @@ -83,7 +142,7 @@ export function getDuckDBTimeStringFromPartsNS( } export function getDuckDBTimeStringFromPositiveMicroseconds( - positiveMicroseconds: bigint, + positiveMicroseconds: bigint ): string { const microsecondsPart = positiveMicroseconds % MICROSECONDS_PER_SECOND; const seconds = positiveMicroseconds / MICROSECONDS_PER_SECOND; @@ -95,12 +154,12 @@ export function getDuckDBTimeStringFromPositiveMicroseconds( hoursPart, minutesPart, secondsPart, - microsecondsPart, + microsecondsPart ); } export function getDuckDBTimeStringFromPositiveNanoseconds( - positiveNanoseconds: bigint, + positiveNanoseconds: bigint ): string { const nanosecondsPart = positiveNanoseconds % NANOSECONDS_PER_SECOND; const seconds = positiveNanoseconds / NANOSECONDS_PER_SECOND; @@ -112,12 +171,12 @@ export function getDuckDBTimeStringFromPositiveNanoseconds( hoursPart, minutesPart, secondsPart, - nanosecondsPart, + nanosecondsPart ); } export function getDuckDBTimeStringFromMicrosecondsInDay( - microsecondsInDay: bigint, + microsecondsInDay: bigint ): string { const positiveMicroseconds = microsecondsInDay < 0 @@ -127,7 +186,7 @@ export function getDuckDBTimeStringFromMicrosecondsInDay( } export function getDuckDBTimeStringFromNanosecondsInDay( - nanosecondsInDay: bigint, + nanosecondsInDay: bigint ): string { const positiveNanoseconds = nanosecondsInDay < 0 @@ -137,7 +196,7 @@ export function getDuckDBTimeStringFromNanosecondsInDay( } export function getDuckDBTimeStringFromMicroseconds( - microseconds: bigint, + microseconds: bigint ): string { const negative = microseconds < 0; const positiveMicroseconds = negative ? -microseconds : microseconds; @@ -149,32 +208,29 @@ export function getDuckDBTimeStringFromMicroseconds( export function getDuckDBTimestampStringFromDaysAndMicroseconds( days: bigint, microsecondsInDay: bigint, - timezone?: string | null, + timezonePart?: string ): string { // This conversion of BigInt to Number is safe, because the largest absolute value that `days` can has is 106751991, // which fits without loss of precision in a JS Number. (106751991 = (2^63-1) / MICROSECONDS_PER_DAY) const dateStr = getDuckDBDateStringFromDays(Number(days)); const timeStr = getDuckDBTimeStringFromMicrosecondsInDay(microsecondsInDay); - const timezoneStr = timezone ? ` ${timezone}` : ''; - return `${dateStr} ${timeStr}${timezoneStr}`; + return `${dateStr} ${timeStr}${timezonePart ?? ''}`; } export function getDuckDBTimestampStringFromDaysAndNanoseconds( days: bigint, - nanosecondsInDay: bigint, - timezone?: string | null, + nanosecondsInDay: bigint ): string { // This conversion of BigInt to Number is safe, because the largest absolute value that `days` can has is 106751 // which fits without loss of precision in a JS Number. (106751 = (2^63-1) / NANOSECONDS_PER_DAY) const dateStr = getDuckDBDateStringFromDays(Number(days)); const timeStr = getDuckDBTimeStringFromNanosecondsInDay(nanosecondsInDay); - const timezoneStr = timezone ? ` ${timezone}` : ''; - return `${dateStr} ${timeStr}${timezoneStr}`; + return `${dateStr} ${timeStr}`; } export function getDuckDBTimestampStringFromMicroseconds( microseconds: bigint, - timezone?: string | null, + timezoneOffsetInMinutes?: number ): string { // Note that -infinity and infinity are only representable in TIMESTAMP (and TIMESTAMPTZ), not the other timestamp // variants. This is by-design and matches DuckDB. @@ -184,8 +240,15 @@ export function getDuckDBTimestampStringFromMicroseconds( if (microseconds === POSITIVE_INFINITY_TIMESTAMP) { return 'infinity'; } - let days = microseconds / MICROSECONDS_PER_DAY; - let microsecondsPart = microseconds % MICROSECONDS_PER_DAY; + const offsetMicroseconds = + timezoneOffsetInMinutes !== undefined + ? microseconds + + BigInt(timezoneOffsetInMinutes) * + MICROSECONDS_PER_SECOND * + SECONDS_PER_MINUTE + : microseconds; + let days = offsetMicroseconds / MICROSECONDS_PER_DAY; + let microsecondsPart = offsetMicroseconds % MICROSECONDS_PER_DAY; if (microsecondsPart < 0) { days--; microsecondsPart += MICROSECONDS_PER_DAY; @@ -193,33 +256,26 @@ export function getDuckDBTimestampStringFromMicroseconds( return getDuckDBTimestampStringFromDaysAndMicroseconds( days, microsecondsPart, - timezone, + getTimezoneOffsetString(timezoneOffsetInMinutes) ); } -export function getDuckDBTimestampStringFromSeconds( - seconds: bigint, - timezone?: string | null, -): string { +export function getDuckDBTimestampStringFromSeconds(seconds: bigint): string { return getDuckDBTimestampStringFromMicroseconds( - seconds * MICROSECONDS_PER_SECOND, - timezone, + seconds * MICROSECONDS_PER_SECOND ); } export function getDuckDBTimestampStringFromMilliseconds( - milliseconds: bigint, - timezone?: string | null, + milliseconds: bigint ): string { return getDuckDBTimestampStringFromMicroseconds( - milliseconds * MICROSECONDS_PER_MILLISECOND, - timezone, + milliseconds * MICROSECONDS_PER_MILLISECOND ); } export function getDuckDBTimestampStringFromNanoseconds( - nanoseconds: bigint, - timezone?: string | null, + nanoseconds: bigint ): string { let days = nanoseconds / NANOSECONDS_PER_DAY; let nanosecondsPart = nanoseconds % NANOSECONDS_PER_DAY; @@ -227,11 +283,7 @@ export function getDuckDBTimestampStringFromNanoseconds( days--; nanosecondsPart += NANOSECONDS_PER_DAY; } - return getDuckDBTimestampStringFromDaysAndNanoseconds( - days, - nanosecondsPart, - timezone, - ); + return getDuckDBTimestampStringFromDaysAndNanoseconds(days, nanosecondsPart); } // Assumes baseUnit can be pluralized by adding an 's'. @@ -242,7 +294,7 @@ function numberAndUnit(value: number, baseUnit: string): string { export function getDuckDBIntervalString( months: number, days: number, - microseconds: bigint, + microseconds: bigint ): string { const parts: string[] = []; if (months !== 0) { diff --git a/api/src/values/DuckDBTimeTZValue.ts b/api/src/values/DuckDBTimeTZValue.ts index 000bbee..2279d91 100644 --- a/api/src/values/DuckDBTimeTZValue.ts +++ b/api/src/values/DuckDBTimeTZValue.ts @@ -1,20 +1,23 @@ import duckdb, { TimeTZ, TimeTZParts } from '@duckdb/node-bindings'; -import { getDuckDBTimeStringFromMicrosecondsInDay } from '../conversion/dateTimeStringConversion'; +import { + getDuckDBTimeStringFromMicrosecondsInDay, + getOffsetStringFromSeconds +} from '../conversion/dateTimeStringConversion'; export type { TimeTZParts }; export class DuckDBTimeTZValue implements TimeTZ { /** - * 40 bits for micros, then 24 bits for encoded offset in seconds. - * - * Max absolute unencoded offset = 15:59:59 = 60 * (60 * 15 + 59) + 59 = 57599. - * - * Encoded offset is unencoded offset inverted then shifted (by +57599) to unsigned. - * - * Max unencoded offset = 57599 -> -57599 -> 0 encoded. - * - * Min unencoded offset = -57599 -> 57599 -> 115198 encoded. - */ + * 40 bits for micros, then 24 bits for encoded offset in seconds. + * + * Max absolute unencoded offset = 15:59:59 = 60 * (60 * 15 + 59) + 59 = 57599. + * + * Encoded offset is unencoded offset inverted then shifted (by +57599) to unsigned. + * + * Max unencoded offset = 57599 -> -57599 -> 0 encoded. + * + * Min unencoded offset = -57599 -> 57599 -> 115198 encoded. + */ public readonly bits: bigint; /** Ranges from 0 to 86400000000 (= 24 * 60 * 60 * 1000 * 1000) */ @@ -30,8 +33,9 @@ export class DuckDBTimeTZValue implements TimeTZ { } public toString(): string { - // TODO: display offset - return getDuckDBTimeStringFromMicrosecondsInDay(this.micros); + return `${getDuckDBTimeStringFromMicrosecondsInDay( + this.micros + )}${getOffsetStringFromSeconds(this.offset)}`; } public toParts(): TimeTZParts { @@ -39,30 +43,52 @@ export class DuckDBTimeTZValue implements TimeTZ { } public static TimeBits = 40; - public static OffsetBits = 24; - public static MaxOffset = 16 * 60 * 60 - 1; // ±15:59:59 = 57599 seconds + public static OffsetBits = 24; + public static MaxOffset = 16 * 60 * 60 - 1; // ±15:59:59 = 57599 seconds public static MinOffset = -DuckDBTimeTZValue.MaxOffset; public static MaxMicros = 24n * 60n * 60n * 1000n * 1000n; // 86400000000 public static MinMicros = 0n; public static fromBits(bits: bigint): DuckDBTimeTZValue { - const micros = BigInt.asUintN(DuckDBTimeTZValue.TimeBits, bits >> BigInt(DuckDBTimeTZValue.OffsetBits)); - const offset = DuckDBTimeTZValue.MaxOffset - Number(BigInt.asUintN(DuckDBTimeTZValue.OffsetBits, bits)); + const micros = BigInt.asUintN( + DuckDBTimeTZValue.TimeBits, + bits >> BigInt(DuckDBTimeTZValue.OffsetBits) + ); + const offset = + DuckDBTimeTZValue.MaxOffset - + Number(BigInt.asUintN(DuckDBTimeTZValue.OffsetBits, bits)); return new DuckDBTimeTZValue(bits, micros, offset); } - public static fromMicrosAndOffset(micros: bigint, offset: number): DuckDBTimeTZValue { - const bits = BigInt.asUintN(DuckDBTimeTZValue.TimeBits, micros) << BigInt(DuckDBTimeTZValue.OffsetBits) - | BigInt.asUintN(DuckDBTimeTZValue.OffsetBits, BigInt(DuckDBTimeTZValue.MaxOffset - offset)); + public static fromMicrosAndOffset( + micros: bigint, + offset: number + ): DuckDBTimeTZValue { + const bits = + (BigInt.asUintN(DuckDBTimeTZValue.TimeBits, micros) << + BigInt(DuckDBTimeTZValue.OffsetBits)) | + BigInt.asUintN( + DuckDBTimeTZValue.OffsetBits, + BigInt(DuckDBTimeTZValue.MaxOffset - offset) + ); return new DuckDBTimeTZValue(bits, micros, offset); } public static fromParts(parts: TimeTZParts): DuckDBTimeTZValue { - return DuckDBTimeTZValue.fromMicrosAndOffset(duckdb.to_time(parts.time).micros, parts.offset); + return DuckDBTimeTZValue.fromMicrosAndOffset( + duckdb.to_time(parts.time).micros, + parts.offset + ); } - public static readonly Max = DuckDBTimeTZValue.fromMicrosAndOffset(DuckDBTimeTZValue.MaxMicros, DuckDBTimeTZValue.MinOffset); - public static readonly Min = DuckDBTimeTZValue.fromMicrosAndOffset(DuckDBTimeTZValue.MinMicros, DuckDBTimeTZValue.MaxOffset); + public static readonly Max = DuckDBTimeTZValue.fromMicrosAndOffset( + DuckDBTimeTZValue.MaxMicros, + DuckDBTimeTZValue.MinOffset + ); + public static readonly Min = DuckDBTimeTZValue.fromMicrosAndOffset( + DuckDBTimeTZValue.MinMicros, + DuckDBTimeTZValue.MaxOffset + ); } export function timeTZValue(micros: bigint, offset: number): DuckDBTimeTZValue { diff --git a/api/src/values/DuckDBTimestampTZValue.ts b/api/src/values/DuckDBTimestampTZValue.ts index 77551ad..26e5e75 100644 --- a/api/src/values/DuckDBTimestampTZValue.ts +++ b/api/src/values/DuckDBTimestampTZValue.ts @@ -3,6 +3,9 @@ import { getDuckDBTimestampStringFromMicroseconds } from '../conversion/dateTime import { DuckDBTimestampValue } from './DuckDBTimestampValue'; export class DuckDBTimestampTZValue implements Timestamp { + public static timezoneOffsetInMinutes: number = + -new Date().getTimezoneOffset(); + public readonly micros: bigint; public constructor(micros: bigint) { @@ -10,8 +13,10 @@ export class DuckDBTimestampTZValue implements Timestamp { } public toString(): string { - // TODO: adjust micros for local timezone offset, and pass in timezone string - return getDuckDBTimestampStringFromMicroseconds(this.micros); + return getDuckDBTimestampStringFromMicroseconds( + this.micros, + DuckDBTimestampTZValue.timezoneOffsetInMinutes + ); } public toParts(): TimestampParts { @@ -23,10 +28,18 @@ export class DuckDBTimestampTZValue implements Timestamp { } public static readonly Epoch = new DuckDBTimestampTZValue(0n); - public static readonly Max = new DuckDBTimestampTZValue(DuckDBTimestampValue.Max.micros); - public static readonly Min = new DuckDBTimestampTZValue(DuckDBTimestampValue.Min.micros); - public static readonly PosInf = new DuckDBTimestampTZValue(DuckDBTimestampValue.PosInf.micros); - public static readonly NegInf = new DuckDBTimestampTZValue(DuckDBTimestampValue.NegInf.micros); + public static readonly Max = new DuckDBTimestampTZValue( + DuckDBTimestampValue.Max.micros + ); + public static readonly Min = new DuckDBTimestampTZValue( + DuckDBTimestampValue.Min.micros + ); + public static readonly PosInf = new DuckDBTimestampTZValue( + DuckDBTimestampValue.PosInf.micros + ); + public static readonly NegInf = new DuckDBTimestampTZValue( + DuckDBTimestampValue.NegInf.micros + ); } export function timestampTZValue(micros: bigint): DuckDBTimestampTZValue { diff --git a/api/test/api.test.ts b/api/test/api.test.ts index 3df606c..acfd647 100644 --- a/api/test/api.test.ts +++ b/api/test/api.test.ts @@ -1,4 +1,4 @@ -import { assert, describe, test } from 'vitest'; +import { assert, beforeAll, describe, test } from 'vitest'; import { ANY, ARRAY, @@ -111,13 +111,13 @@ import { } from '../src'; import { ColumnNameAndType, - testAllTypesColumnTypes, - testAllTypesColumns, - testAllTypesColumnsJson, - testAllTypesColumnsNamesAndTypes, - testAllTypesColumnsObjectJson, - testAllTypesRowObjectsJson, - testAllTypesRowsJson, + createTestAllTypesColumnTypes, + createTestAllTypesColumns, + createTestAllTypesColumnsJson, + createTestAllTypesColumnsNamesAndTypes, + createTestAllTypesColumnsObjectJson, + createTestAllTypesRowObjectsJson, + createTestAllTypesRowsJson, } from './util/testAllTypes'; async function sleep(ms: number): Promise { @@ -228,6 +228,10 @@ function bigints(start: bigint, end: bigint) { } describe('api', () => { + beforeAll(() => { + // Set this to a constant so tests don't depend on the local timezone. + DuckDBTimestampTZValue.timezoneOffsetInMinutes = -330; + }); test('should expose version', () => { const ver = version(); assert.ok(ver.startsWith('v'), `version starts with 'v'`); @@ -635,7 +639,7 @@ describe('api', () => { const result = await connection.run( 'from test_all_types(use_large_enum=true)' ); - assertColumns(result, testAllTypesColumnsNamesAndTypes); + assertColumns(result, createTestAllTypesColumnsNamesAndTypes()); const chunk = await result.fetchChunk(); assert.isDefined(chunk); @@ -643,6 +647,8 @@ describe('api', () => { assert.strictEqual(chunk.columnCount, 54); assert.strictEqual(chunk.rowCount, 3); + const testAllTypesColumns = createTestAllTypesColumns(); + assertValues(chunk, 0, DuckDBBooleanVector, testAllTypesColumns[0]); assertValues(chunk, 1, DuckDBTinyIntVector, testAllTypesColumns[1]); assertValues(chunk, 2, DuckDBSmallIntVector, testAllTypesColumns[2]); @@ -926,11 +932,15 @@ describe('api', () => { assert.equal(TIMESTAMP_S.min.toString(), '290309-12-22 (BC) 00:00:00'); // timestamp tz - assert.equal(TIMESTAMPTZ.epoch.toString(), '1970-01-01 00:00:00'); - // assert.equal(TIMESTAMPTZ.max.toString(), '294247-01-09 20:00:54.775806-08'); // in PST - assert.equal(TIMESTAMPTZ.max.toString(), '294247-01-10 04:00:54.775806'); // TODO TZ - // assert.equal(TIMESTAMPTZ.min.toString(), '290309-12-21 (BC) 16:00:00-08'); // in PST - assert.equal(TIMESTAMPTZ.min.toString(), '290309-12-22 (BC) 00:00:00'); // TODO TZ + assert.equal(TIMESTAMPTZ.epoch.toString(), '1969-12-31 18:30:00-05:30'); + assert.equal( + TIMESTAMPTZ.max.toString(), + '294247-01-09 22:30:54.775806-05:30' + ); + assert.equal( + TIMESTAMPTZ.min.toString(), + '290309-12-21 (BC) 18:30:00-05:30' + ); assert.equal(TIMESTAMPTZ.posInf.toString(), 'infinity'); assert.equal(TIMESTAMPTZ.negInf.toString(), '-infinity'); @@ -942,11 +952,16 @@ describe('api', () => { assert.equal(TIMESTAMP.negInf.toString(), '-infinity'); // time tz - assert.equal(timeTZValue(0n, 0).toString(), '00:00:00'); - // assert.equal(TIMETZ.max.toString(), '24:00:00-15:59:59'); - assert.equal(TIMETZ.max.toString(), '24:00:00'); // TODO TZ - // assert.equal(TIMETZ.min.toString(), '00:00:00+15:59:59'); - assert.equal(TIMETZ.min.toString(), '00:00:00'); // TODO TZ + assert.equal(timeTZValue(0n, 0).toString(), '00:00:00+00'); + assert.equal( + timeTZValue( + (((12n * 60n + 34n) * 60n + 56n) * 1000n + 789n) * 1000n, + -((7 * 60 + 9) * 60) + ).toString(), + '12:34:56.789-07:09' + ); + assert.equal(TIMETZ.max.toString(), '24:00:00-15:59:59'); + assert.equal(TIMETZ.min.toString(), '00:00:00+15:59:59'); // time assert.equal(TIME.max.toString(), '24:00:00'); @@ -1052,28 +1067,28 @@ describe('api', () => { await withConnection(async (connection) => { const reader = await connection.runAndReadAll(`from test_all_types()`); const columnsJson = reader.getColumnsJson(); - assert.deepEqual(columnsJson, testAllTypesColumnsJson); + assert.deepEqual(columnsJson, createTestAllTypesColumnsJson()); }); }); test('columns object json', async () => { await withConnection(async (connection) => { const reader = await connection.runAndReadAll(`from test_all_types()`); const columnsJson = reader.getColumnsObjectJson(); - assert.deepEqual(columnsJson, testAllTypesColumnsObjectJson); + assert.deepEqual(columnsJson, createTestAllTypesColumnsObjectJson()); }); }); test('rows json', async () => { await withConnection(async (connection) => { const reader = await connection.runAndReadAll(`from test_all_types()`); const rowsJson = reader.getRowsJson(); - assert.deepEqual(rowsJson, testAllTypesRowsJson); + assert.deepEqual(rowsJson, createTestAllTypesRowsJson()); }); }); test('row objects json', async () => { await withConnection(async (connection) => { const reader = await connection.runAndReadAll(`from test_all_types()`); const rowObjectsJson = reader.getRowObjectsJson(); - assert.deepEqual(rowObjectsJson, testAllTypesRowObjectsJson); + assert.deepEqual(rowObjectsJson, createTestAllTypesRowObjectsJson()); }); }); test('result reader', async () => { @@ -1339,7 +1354,7 @@ describe('api', () => { } }); }); - test('create and append data chunk , modify nested list vector', async () => { + test('create and append data chunk, modify nested list vector', async () => { await withConnection(async (connection) => { const originalValues = [ listValue([listValue([110, 111]), listValue([]), listValue([130])]), @@ -1517,9 +1532,10 @@ describe('api', () => { }); test('create and append data chunk with all types', async () => { await withConnection(async (connection) => { - const types = [...testAllTypesColumnTypes]; - const columns = [...testAllTypesColumns]; - const columnNamesAndTypes = [...testAllTypesColumnsNamesAndTypes]; + const types = createTestAllTypesColumnTypes() as DuckDBType[]; + const columns = createTestAllTypesColumns() as (readonly DuckDBValue[])[]; + const columnNamesAndTypes = + createTestAllTypesColumnsNamesAndTypes() as ColumnNameAndType[]; // workaround until VARINT is fixed (in 1.2.0) types[11] = BOOLEAN; diff --git a/api/test/util/testAllTypes.ts b/api/test/util/testAllTypes.ts index 91475af..ecc20ad 100644 --- a/api/test/util/testAllTypes.ts +++ b/api/test/util/testAllTypes.ts @@ -84,611 +84,642 @@ function col( return { name, type, rows, json }; } -export const testAllTypesData: ColumnNameTypeAndRows[] = [ - col('bool', BOOLEAN, [false, true, null], [false, true, null]), - col( - 'tinyint', - TINYINT, - [TINYINT.min, TINYINT.max, null], - [TINYINT.min, TINYINT.max, null] - ), - col( - 'smallint', - SMALLINT, - [SMALLINT.min, SMALLINT.max, null], - [SMALLINT.min, SMALLINT.max, null] - ), - col( - 'int', - INTEGER, - [INTEGER.min, INTEGER.max, null], - [INTEGER.min, INTEGER.max, null] - ), - col( - 'bigint', - BIGINT, - [BIGINT.min, BIGINT.max, null], - [String(BIGINT.min), String(BIGINT.max), null] - ), - col( - 'hugeint', - HUGEINT, - [HUGEINT.min, HUGEINT.max, null], - [String(HUGEINT.min), String(HUGEINT.max), null] - ), - col( - 'uhugeint', - UHUGEINT, - [UHUGEINT.min, UHUGEINT.max, null], - [String(UHUGEINT.min), String(UHUGEINT.max), null] - ), - col( - 'utinyint', - UTINYINT, - [UTINYINT.min, UTINYINT.max, null], - [UTINYINT.min, UTINYINT.max, null] - ), - col( - 'usmallint', - USMALLINT, - [USMALLINT.min, USMALLINT.max, null], - [USMALLINT.min, USMALLINT.max, null] - ), - col( - 'uint', - UINTEGER, - [UINTEGER.min, UINTEGER.max, null], - [UINTEGER.min, UINTEGER.max, null] - ), - col( - 'ubigint', - UBIGINT, - [UBIGINT.min, UBIGINT.max, null], - [String(UBIGINT.min), String(UBIGINT.max), null] - ), - col( - 'varint', - VARINT, - [VARINT.min, VARINT.max, null], - [String(VARINT.min), String(VARINT.max), null] - ), - col( - 'date', - DATE, - [DATE.min, DATE.max, null], - [String(DATE.min), String(DATE.max), null] - ), - col( - 'time', - TIME, - [TIME.min, TIME.max, null], - [String(TIME.min), String(TIME.max), null] - ), - col( - 'timestamp', - TIMESTAMP, - [TIMESTAMP.min, TIMESTAMP.max, null], - [String(TIMESTAMP.min), String(TIMESTAMP.max), null] - ), - col( - 'timestamp_s', - TIMESTAMP_S, - [TIMESTAMP_S.min, TIMESTAMP_S.max, null], - [String(TIMESTAMP_S.min), String(TIMESTAMP_S.max), null] - ), - col( - 'timestamp_ms', - TIMESTAMP_MS, - [TIMESTAMP_MS.min, TIMESTAMP_MS.max, null], - [String(TIMESTAMP_MS.min), String(TIMESTAMP_MS.max), null] - ), - col( - 'timestamp_ns', - TIMESTAMP_NS, - [TIMESTAMP_NS.min, TIMESTAMP_NS.max, null], - [String(TIMESTAMP_NS.min), String(TIMESTAMP_NS.max), null] - ), - col( - 'time_tz', - TIMETZ, - [TIMETZ.min, TIMETZ.max, null], - [String(TIMETZ.min), String(TIMETZ.max), null] - ), - col( - 'timestamp_tz', - TIMESTAMPTZ, - [TIMESTAMPTZ.min, TIMESTAMPTZ.max, null], - [String(TIMESTAMPTZ.min), String(TIMESTAMPTZ.max), null] - ), - col( - 'float', - FLOAT, - [FLOAT.min, FLOAT.max, null], - [FLOAT.min, FLOAT.max, null] - ), - col( - 'double', - DOUBLE, - [DOUBLE.min, DOUBLE.max, null], - [DOUBLE.min, DOUBLE.max, null] - ), - col( - 'dec_4_1', - DECIMAL(4, 1), - [decimalValue(-9999n, 4, 1), decimalValue(9999n, 4, 1), null], - ['-999.9', '999.9', null] - ), - col( - 'dec_9_4', - DECIMAL(9, 4), - [decimalValue(-999999999n, 9, 4), decimalValue(999999999n, 9, 4), null], - ['-99999.9999', '99999.9999', null] - ), - col( - 'dec_18_6', - DECIMAL(18, 6), - [decimalValue(-BI_18_9s, 18, 6), decimalValue(BI_18_9s, 18, 6), null], - ['-999999999999.999999', '999999999999.999999', null] - ), - col( - 'dec38_10', - DECIMAL(38, 10), - [decimalValue(-BI_38_9s, 38, 10), decimalValue(BI_38_9s, 38, 10), null], - [ - '-9999999999999999999999999999.9999999999', - '9999999999999999999999999999.9999999999', - null, - ] - ), - col( - 'uuid', - UUID, - [UUID.min, UUID.max, null], - [String(UUID.min), String(UUID.max), null] - ), - col( - 'interval', - INTERVAL, - [intervalValue(0, 0, 0n), intervalValue(999, 999, 999999999n), null], - [ - { months: 0, days: 0, micros: '0' }, - { months: 999, days: 999, micros: '999999999' }, - null, - ] - ), - col( - 'varchar', - VARCHAR, - ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null], - ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null] - ), - col( - 'blob', - BLOB, - [ - blobValue('thisisalongblob\x00withnullbytes'), - blobValue('\x00\x00\x00a'), - null, - ], - ['thisisalongblob\\x00withnullbytes', '\\x00\\x00\\x00a', null] - ), - col( - 'bit', - BIT, - [bitValue('0010001001011100010101011010111'), bitValue('10101'), null], - ['0010001001011100010101011010111', '10101', null] - ), - col( - 'small_enum', - ENUM8(smallEnumValues), - [smallEnumValues[0], smallEnumValues[smallEnumValues.length - 1], null], - [smallEnumValues[0], smallEnumValues[smallEnumValues.length - 1], null] - ), - col( - 'medium_enum', - ENUM16(mediumEnumValues), - [mediumEnumValues[0], mediumEnumValues[mediumEnumValues.length - 1], null], - [mediumEnumValues[0], mediumEnumValues[mediumEnumValues.length - 1], null] - ), - col( - 'large_enum', - ENUM32(largeEnumValues), - [largeEnumValues[0], largeEnumValues[largeEnumValues.length - 1], null], - [largeEnumValues[0], largeEnumValues[largeEnumValues.length - 1], null] - ), - col( - 'int_array', - LIST(INTEGER), - [listValue([]), listValue([42, 999, null, null, -42]), null], - [[], [42, 999, null, null, -42], null] - ), - col( - 'double_array', - LIST(DOUBLE), - [ - listValue([]), - listValue([42.0, NaN, Infinity, -Infinity, null, -42.0]), - null, - ], - [[], [42, 'NaN', 'Infinity', '-Infinity', null, -42], null] - ), - col( - 'date_array', - LIST(DATE), - [ - listValue([]), - // 19124 days from the epoch is 2022-05-12 - listValue([DATE.epoch, DATE.posInf, DATE.negInf, null, dateValue(19124)]), - null, - ], - [ - [], - ['1970-01-01', '5881580-07-11', '5877642-06-24 (BC)', null, '2022-05-12'], - null, - ] - ), - col( - 'timestamp_array', - LIST(TIMESTAMP), - [ - listValue([]), - listValue([ - TIMESTAMP.epoch, - TIMESTAMP.posInf, - TIMESTAMP.negInf, - null, - // 1652372625 seconds from the epoch is 2022-05-12 16:23:45 - timestampValue(1652372625n * 1000n * 1000n), - ]), - null, - ], - [ - [], - [ - '1970-01-01 00:00:00', - 'infinity', - '-infinity', - null, - '2022-05-12 16:23:45', +export function createTestAllTypesData(): ColumnNameTypeAndRows[] { + return [ + col('bool', BOOLEAN, [false, true, null], [false, true, null]), + col( + 'tinyint', + TINYINT, + [TINYINT.min, TINYINT.max, null], + [TINYINT.min, TINYINT.max, null] + ), + col( + 'smallint', + SMALLINT, + [SMALLINT.min, SMALLINT.max, null], + [SMALLINT.min, SMALLINT.max, null] + ), + col( + 'int', + INTEGER, + [INTEGER.min, INTEGER.max, null], + [INTEGER.min, INTEGER.max, null] + ), + col( + 'bigint', + BIGINT, + [BIGINT.min, BIGINT.max, null], + [String(BIGINT.min), String(BIGINT.max), null] + ), + col( + 'hugeint', + HUGEINT, + [HUGEINT.min, HUGEINT.max, null], + [String(HUGEINT.min), String(HUGEINT.max), null] + ), + col( + 'uhugeint', + UHUGEINT, + [UHUGEINT.min, UHUGEINT.max, null], + [String(UHUGEINT.min), String(UHUGEINT.max), null] + ), + col( + 'utinyint', + UTINYINT, + [UTINYINT.min, UTINYINT.max, null], + [UTINYINT.min, UTINYINT.max, null] + ), + col( + 'usmallint', + USMALLINT, + [USMALLINT.min, USMALLINT.max, null], + [USMALLINT.min, USMALLINT.max, null] + ), + col( + 'uint', + UINTEGER, + [UINTEGER.min, UINTEGER.max, null], + [UINTEGER.min, UINTEGER.max, null] + ), + col( + 'ubigint', + UBIGINT, + [UBIGINT.min, UBIGINT.max, null], + [String(UBIGINT.min), String(UBIGINT.max), null] + ), + col( + 'varint', + VARINT, + [VARINT.min, VARINT.max, null], + [String(VARINT.min), String(VARINT.max), null] + ), + col( + 'date', + DATE, + [DATE.min, DATE.max, null], + [String(DATE.min), String(DATE.max), null] + ), + col( + 'time', + TIME, + [TIME.min, TIME.max, null], + [String(TIME.min), String(TIME.max), null] + ), + col( + 'timestamp', + TIMESTAMP, + [TIMESTAMP.min, TIMESTAMP.max, null], + [String(TIMESTAMP.min), String(TIMESTAMP.max), null] + ), + col( + 'timestamp_s', + TIMESTAMP_S, + [TIMESTAMP_S.min, TIMESTAMP_S.max, null], + [String(TIMESTAMP_S.min), String(TIMESTAMP_S.max), null] + ), + col( + 'timestamp_ms', + TIMESTAMP_MS, + [TIMESTAMP_MS.min, TIMESTAMP_MS.max, null], + [String(TIMESTAMP_MS.min), String(TIMESTAMP_MS.max), null] + ), + col( + 'timestamp_ns', + TIMESTAMP_NS, + [TIMESTAMP_NS.min, TIMESTAMP_NS.max, null], + [String(TIMESTAMP_NS.min), String(TIMESTAMP_NS.max), null] + ), + col( + 'time_tz', + TIMETZ, + [TIMETZ.min, TIMETZ.max, null], + [String(TIMETZ.min), String(TIMETZ.max), null] + ), + col( + 'timestamp_tz', + TIMESTAMPTZ, + [TIMESTAMPTZ.min, TIMESTAMPTZ.max, null], + [String(TIMESTAMPTZ.min), String(TIMESTAMPTZ.max), null] + ), + col( + 'float', + FLOAT, + [FLOAT.min, FLOAT.max, null], + [FLOAT.min, FLOAT.max, null] + ), + col( + 'double', + DOUBLE, + [DOUBLE.min, DOUBLE.max, null], + [DOUBLE.min, DOUBLE.max, null] + ), + col( + 'dec_4_1', + DECIMAL(4, 1), + [decimalValue(-9999n, 4, 1), decimalValue(9999n, 4, 1), null], + ['-999.9', '999.9', null] + ), + col( + 'dec_9_4', + DECIMAL(9, 4), + [decimalValue(-999999999n, 9, 4), decimalValue(999999999n, 9, 4), null], + ['-99999.9999', '99999.9999', null] + ), + col( + 'dec_18_6', + DECIMAL(18, 6), + [decimalValue(-BI_18_9s, 18, 6), decimalValue(BI_18_9s, 18, 6), null], + ['-999999999999.999999', '999999999999.999999', null] + ), + col( + 'dec38_10', + DECIMAL(38, 10), + [decimalValue(-BI_38_9s, 38, 10), decimalValue(BI_38_9s, 38, 10), null], + [ + '-9999999999999999999999999999.9999999999', + '9999999999999999999999999999.9999999999', + null, + ] + ), + col( + 'uuid', + UUID, + [UUID.min, UUID.max, null], + [String(UUID.min), String(UUID.max), null] + ), + col( + 'interval', + INTERVAL, + [intervalValue(0, 0, 0n), intervalValue(999, 999, 999999999n), null], + [ + { months: 0, days: 0, micros: '0' }, + { months: 999, days: 999, micros: '999999999' }, + null, + ] + ), + col( + 'varchar', + VARCHAR, + ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null], + ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null] + ), + col( + 'blob', + BLOB, + [ + blobValue('thisisalongblob\x00withnullbytes'), + blobValue('\x00\x00\x00a'), + null, ], - null, - ] - ), - col( - 'timestamptz_array', - LIST(TIMESTAMPTZ), - [ - listValue([]), - listValue([ - TIMESTAMPTZ.epoch, - TIMESTAMPTZ.posInf, - TIMESTAMPTZ.negInf, - null, - // 1652397825 = 1652372625 + 25200, 25200 = 7 * 60 * 60 = 7 hours in seconds - // This 7 hour difference is hard coded into test_all_types (value is 2022-05-12 16:23:45-07) - timestampTZValue(1652397825n * 1000n * 1000n), - ]), - null, - ], - [ - [], - [ - '1970-01-01 00:00:00', // TODO: timestamptz timezone offset - 'infinity', - '-infinity', - null, - '2022-05-12 23:23:45', + ['thisisalongblob\\x00withnullbytes', '\\x00\\x00\\x00a', null] + ), + col( + 'bit', + BIT, + [bitValue('0010001001011100010101011010111'), bitValue('10101'), null], + ['0010001001011100010101011010111', '10101', null] + ), + col( + 'small_enum', + ENUM8(smallEnumValues), + [smallEnumValues[0], smallEnumValues[smallEnumValues.length - 1], null], + [smallEnumValues[0], smallEnumValues[smallEnumValues.length - 1], null] + ), + col( + 'medium_enum', + ENUM16(mediumEnumValues), + [ + mediumEnumValues[0], + mediumEnumValues[mediumEnumValues.length - 1], + null, ], - null, - ] - ), - col( - 'varchar_array', - LIST(VARCHAR), - [ - listValue([]), - // Note that the string 'goose' in varchar_array does NOT have an embedded null character. - listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), - null, - ], - [[], ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], null] - ), - col( - 'nested_int_array', - LIST(LIST(INTEGER)), - [ - listValue([]), - listValue([ + [mediumEnumValues[0], mediumEnumValues[mediumEnumValues.length - 1], null] + ), + col( + 'large_enum', + ENUM32(largeEnumValues), + [largeEnumValues[0], largeEnumValues[largeEnumValues.length - 1], null], + [largeEnumValues[0], largeEnumValues[largeEnumValues.length - 1], null] + ), + col( + 'int_array', + LIST(INTEGER), + [listValue([]), listValue([42, 999, null, null, -42]), null], + [[], [42, 999, null, null, -42], null] + ), + col( + 'double_array', + LIST(DOUBLE), + [ listValue([]), - listValue([42, 999, null, null, -42]), + listValue([42.0, NaN, Infinity, -Infinity, null, -42.0]), null, + ], + [[], [42, 'NaN', 'Infinity', '-Infinity', null, -42], null] + ), + col( + 'date_array', + LIST(DATE), + [ listValue([]), - listValue([42, 999, null, null, -42]), - ]), - null, - ], - [ - [], - [[], [42, 999, null, null, -42], null, [], [42, 999, null, null, -42]], - null, - ] - ), - col( - 'struct', - STRUCT({ 'a': INTEGER, 'b': VARCHAR }), - [ - structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), - null, - ], - [{ 'a': null, 'b': null }, { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, null] - ), - col( - 'struct_of_arrays', - STRUCT({ 'a': LIST(INTEGER), 'b': LIST(VARCHAR) }), - [ - structValue({ 'a': null, 'b': null }), - structValue({ - 'a': listValue([42, 999, null, null, -42]), - 'b': listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), - }), - null, - ], - [ - { 'a': null, 'b': null }, - { - 'a': [42, 999, null, null, -42], - 'b': ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], - }, - null, - ] - ), - col( - 'array_of_structs', - LIST(STRUCT({ 'a': INTEGER, 'b': VARCHAR })), - [ - listValue([]), - listValue([ - structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + // 19124 days from the epoch is 2022-05-12 + listValue([ + DATE.epoch, + DATE.posInf, + DATE.negInf, + null, + dateValue(19124), + ]), null, - ]), - null, - ], - [ - [], - [{ 'a': null, 'b': null }, { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, null], - null, - ] - ), - col( - 'map', - MAP(VARCHAR, VARCHAR), - [ - mapValue([]), - mapValue([ - { key: 'key1', value: '🦆🦆🦆🦆🦆🦆' }, - { key: 'key2', value: 'goose' }, - ]), - null, - ], - [ - [], - [ - { 'key': 'key1', 'value': '🦆🦆🦆🦆🦆🦆' }, - { 'key': 'key2', 'value': 'goose' }, ], - null, - ] - ), - col( - 'union', - UNION({ 'name': VARCHAR, 'age': SMALLINT }), - [unionValue('name', 'Frank'), unionValue('age', 5), null], - [{ 'tag': 'name', 'value': 'Frank' }, { 'tag': 'age', 'value': 5 }, null] - ), - col( - 'fixed_int_array', - ARRAY(INTEGER, 3), - [arrayValue([null, 2, 3]), arrayValue([4, 5, 6]), null], - [[null, 2, 3], [4, 5, 6], null] - ), - col( - 'fixed_varchar_array', - ARRAY(VARCHAR, 3), - [arrayValue(['a', null, 'c']), arrayValue(['d', 'e', 'f']), null], - [['a', null, 'c'], ['d', 'e', 'f'], null] - ), - col( - 'fixed_nested_int_array', - ARRAY(ARRAY(INTEGER, 3), 3), - [ - arrayValue([arrayValue([null, 2, 3]), null, arrayValue([null, 2, 3])]), - arrayValue([ - arrayValue([4, 5, 6]), - arrayValue([null, 2, 3]), - arrayValue([4, 5, 6]), - ]), - null, - ], - [ - [[null, 2, 3], null, [null, 2, 3]], - [ - [4, 5, 6], - [null, 2, 3], - [4, 5, 6], + [ + [], + [ + '1970-01-01', + '5881580-07-11', + '5877642-06-24 (BC)', + null, + '2022-05-12', + ], + null, + ] + ), + col( + 'timestamp_array', + LIST(TIMESTAMP), + [ + listValue([]), + listValue([ + TIMESTAMP.epoch, + TIMESTAMP.posInf, + TIMESTAMP.negInf, + null, + // 1652372625 seconds from the epoch is 2022-05-12 16:23:45 + timestampValue(1652372625n * 1000n * 1000n), + ]), + null, ], - null, - ] - ), - col( - 'fixed_nested_varchar_array', - ARRAY(ARRAY(VARCHAR, 3), 3), - [ - arrayValue([ - arrayValue(['a', null, 'c']), - null, - arrayValue(['a', null, 'c']), - ]), - arrayValue([ - arrayValue(['d', 'e', 'f']), - arrayValue(['a', null, 'c']), - arrayValue(['d', 'e', 'f']), - ]), - null, - ], - [ - [['a', null, 'c'], null, ['a', null, 'c']], - [ - ['d', 'e', 'f'], - ['a', null, 'c'], - ['d', 'e', 'f'], + [ + [], + [ + '1970-01-01 00:00:00', + 'infinity', + '-infinity', + null, + '2022-05-12 16:23:45', + ], + null, + ] + ), + col( + 'timestamptz_array', + LIST(TIMESTAMPTZ), + [ + listValue([]), + listValue([ + TIMESTAMPTZ.epoch, + TIMESTAMPTZ.posInf, + TIMESTAMPTZ.negInf, + null, + // 1652397825 = 1652372625 + 25200, 25200 = 7 * 60 * 60 = 7 hours in seconds + // This 7 hour difference is hard coded into test_all_types (value is 2022-05-12 16:23:45-07) + timestampTZValue(1652397825n * 1000n * 1000n), + ]), + null, ], - null, - ] - ), - col( - 'fixed_struct_array', - ARRAY(STRUCT({ 'a': INTEGER, 'b': VARCHAR }), 3), - [ - arrayValue([ - structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + [ + [], + [ + String(TIMESTAMPTZ.epoch), + 'infinity', + '-infinity', + null, + String(timestampTZValue(1652397825n * 1000n * 1000n)), + ], + null, + ] + ), + col( + 'varchar_array', + LIST(VARCHAR), + [ + listValue([]), + // Note that the string 'goose' in varchar_array does NOT have an embedded null character. + listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), + null, + ], + [[], ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], null] + ), + col( + 'nested_int_array', + LIST(LIST(INTEGER)), + [ + listValue([]), + listValue([ + listValue([]), + listValue([42, 999, null, null, -42]), + null, + listValue([]), + listValue([42, 999, null, null, -42]), + ]), + null, + ], + [ + [], + [[], [42, 999, null, null, -42], null, [], [42, 999, null, null, -42]], + null, + ] + ), + col( + 'struct', + STRUCT({ 'a': INTEGER, 'b': VARCHAR }), + [ structValue({ 'a': null, 'b': null }), - ]), - arrayValue([ structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + null, + ], + [{ 'a': null, 'b': null }, { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, null] + ), + col( + 'struct_of_arrays', + STRUCT({ 'a': LIST(INTEGER), 'b': LIST(VARCHAR) }), + [ structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), - ]), - null, - ], - [ + structValue({ + 'a': listValue([42, 999, null, null, -42]), + 'b': listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), + }), + null, + ], [ { 'a': null, 'b': null }, - { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, - { 'a': null, 'b': null }, + { + 'a': [42, 999, null, null, -42], + 'b': ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], + }, + null, + ] + ), + col( + 'array_of_structs', + LIST(STRUCT({ 'a': INTEGER, 'b': VARCHAR })), + [ + listValue([]), + listValue([ + structValue({ 'a': null, 'b': null }), + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + null, + ]), + null, ], [ - { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, - { 'a': null, 'b': null }, - { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, + [], + [{ 'a': null, 'b': null }, { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, null], + null, + ] + ), + col( + 'map', + MAP(VARCHAR, VARCHAR), + [ + mapValue([]), + mapValue([ + { key: 'key1', value: '🦆🦆🦆🦆🦆🦆' }, + { key: 'key2', value: 'goose' }, + ]), + null, ], - null, - ] - ), - col( - 'struct_of_fixed_array', - STRUCT({ 'a': ARRAY(INTEGER, 3), 'b': ARRAY(VARCHAR, 3) }), - [ - structValue({ - 'a': arrayValue([null, 2, 3]), - 'b': arrayValue(['a', null, 'c']), - }), - structValue({ - 'a': arrayValue([4, 5, 6]), - 'b': arrayValue(['d', 'e', 'f']), - }), - null, - ], - [ - { 'a': [null, 2, 3], 'b': ['a', null, 'c'] }, - { 'a': [4, 5, 6], 'b': ['d', 'e', 'f'] }, - null, - ] - ), - col( - 'fixed_array_of_int_list', - ARRAY(LIST(INTEGER), 3), - [ - arrayValue([ - listValue([]), - listValue([42, 999, null, null, -42]), - listValue([]), - ]), - arrayValue([ - listValue([42, 999, null, null, -42]), - listValue([]), - listValue([42, 999, null, null, -42]), - ]), - null, - ], - [ - [[], [42, 999, null, null, -42], []], - [[42, 999, null, null, -42], [], [42, 999, null, null, -42]], - null, - ] - ), - col( - 'list_of_fixed_int_array', - LIST(ARRAY(INTEGER, 3)), - [ - listValue([ - arrayValue([null, 2, 3]), - arrayValue([4, 5, 6]), - arrayValue([null, 2, 3]), - ]), - listValue([ - arrayValue([4, 5, 6]), - arrayValue([null, 2, 3]), - arrayValue([4, 5, 6]), - ]), - null, - ], - [ - [ - [null, 2, 3], - [4, 5, 6], - [null, 2, 3], + [ + [], + [ + { 'key': 'key1', 'value': '🦆🦆🦆🦆🦆🦆' }, + { 'key': 'key2', 'value': 'goose' }, + ], + null, + ] + ), + col( + 'union', + UNION({ 'name': VARCHAR, 'age': SMALLINT }), + [unionValue('name', 'Frank'), unionValue('age', 5), null], + [{ 'tag': 'name', 'value': 'Frank' }, { 'tag': 'age', 'value': 5 }, null] + ), + col( + 'fixed_int_array', + ARRAY(INTEGER, 3), + [arrayValue([null, 2, 3]), arrayValue([4, 5, 6]), null], + [[null, 2, 3], [4, 5, 6], null] + ), + col( + 'fixed_varchar_array', + ARRAY(VARCHAR, 3), + [arrayValue(['a', null, 'c']), arrayValue(['d', 'e', 'f']), null], + [['a', null, 'c'], ['d', 'e', 'f'], null] + ), + col( + 'fixed_nested_int_array', + ARRAY(ARRAY(INTEGER, 3), 3), + [ + arrayValue([arrayValue([null, 2, 3]), null, arrayValue([null, 2, 3])]), + arrayValue([ + arrayValue([4, 5, 6]), + arrayValue([null, 2, 3]), + arrayValue([4, 5, 6]), + ]), + null, ], [ - [4, 5, 6], - [null, 2, 3], - [4, 5, 6], + [[null, 2, 3], null, [null, 2, 3]], + [ + [4, 5, 6], + [null, 2, 3], + [4, 5, 6], + ], + null, + ] + ), + col( + 'fixed_nested_varchar_array', + ARRAY(ARRAY(VARCHAR, 3), 3), + [ + arrayValue([ + arrayValue(['a', null, 'c']), + null, + arrayValue(['a', null, 'c']), + ]), + arrayValue([ + arrayValue(['d', 'e', 'f']), + arrayValue(['a', null, 'c']), + arrayValue(['d', 'e', 'f']), + ]), + null, + ], + [ + [['a', null, 'c'], null, ['a', null, 'c']], + [ + ['d', 'e', 'f'], + ['a', null, 'c'], + ['d', 'e', 'f'], + ], + null, + ] + ), + col( + 'fixed_struct_array', + ARRAY(STRUCT({ 'a': INTEGER, 'b': VARCHAR }), 3), + [ + arrayValue([ + structValue({ 'a': null, 'b': null }), + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + structValue({ 'a': null, 'b': null }), + ]), + arrayValue([ + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + structValue({ 'a': null, 'b': null }), + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + ]), + null, ], - null, - ] - ), -]; + [ + [ + { 'a': null, 'b': null }, + { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, + { 'a': null, 'b': null }, + ], + [ + { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, + { 'a': null, 'b': null }, + { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, + ], + null, + ] + ), + col( + 'struct_of_fixed_array', + STRUCT({ 'a': ARRAY(INTEGER, 3), 'b': ARRAY(VARCHAR, 3) }), + [ + structValue({ + 'a': arrayValue([null, 2, 3]), + 'b': arrayValue(['a', null, 'c']), + }), + structValue({ + 'a': arrayValue([4, 5, 6]), + 'b': arrayValue(['d', 'e', 'f']), + }), + null, + ], + [ + { 'a': [null, 2, 3], 'b': ['a', null, 'c'] }, + { 'a': [4, 5, 6], 'b': ['d', 'e', 'f'] }, + null, + ] + ), + col( + 'fixed_array_of_int_list', + ARRAY(LIST(INTEGER), 3), + [ + arrayValue([ + listValue([]), + listValue([42, 999, null, null, -42]), + listValue([]), + ]), + arrayValue([ + listValue([42, 999, null, null, -42]), + listValue([]), + listValue([42, 999, null, null, -42]), + ]), + null, + ], + [ + [[], [42, 999, null, null, -42], []], + [[42, 999, null, null, -42], [], [42, 999, null, null, -42]], + null, + ] + ), + col( + 'list_of_fixed_int_array', + LIST(ARRAY(INTEGER, 3)), + [ + listValue([ + arrayValue([null, 2, 3]), + arrayValue([4, 5, 6]), + arrayValue([null, 2, 3]), + ]), + listValue([ + arrayValue([4, 5, 6]), + arrayValue([null, 2, 3]), + arrayValue([4, 5, 6]), + ]), + null, + ], + [ + [ + [null, 2, 3], + [4, 5, 6], + [null, 2, 3], + ], + [ + [4, 5, 6], + [null, 2, 3], + [4, 5, 6], + ], + null, + ] + ), + ]; +} -export const testAllTypesColumnNames: readonly string[] = testAllTypesData.map( - ({ name }) => name -); +export function createTestAllTypesColumnNames(): readonly string[] { + return createTestAllTypesData().map(({ name }) => name); +} -export const testAllTypesColumnTypes: readonly DuckDBType[] = - testAllTypesData.map(({ type }) => type); +export function createTestAllTypesColumnTypes(): readonly DuckDBType[] { + return createTestAllTypesData().map(({ type }) => type); +} -export const testAllTypesColumnsNamesAndTypes: readonly ColumnNameAndType[] = - testAllTypesData.map(({ name, type }) => ({ name, type })); +export function createTestAllTypesColumnsNamesAndTypes(): readonly ColumnNameAndType[] { + return createTestAllTypesData().map(({ name, type }) => ({ name, type })); +} -export const testAllTypesColumns: readonly (readonly DuckDBValue[])[] = - testAllTypesData.map(({ rows }) => rows); +export function createTestAllTypesColumns(): readonly (readonly DuckDBValue[])[] { + return createTestAllTypesData().map(({ rows }) => rows); +} -export const testAllTypesColumnsJson: readonly (readonly Json[])[] = - testAllTypesData.map(({ json }) => json); +export function createTestAllTypesColumnsJson(): readonly (readonly Json[])[] { + return createTestAllTypesData().map(({ json }) => json); +} -export const testAllTypesColumnsObjectJson: Record = - (function () { - const columnsObject: Record = {}; - for (const columnData of testAllTypesData) { - columnsObject[columnData.name] = columnData.json; - } - return columnsObject; - })(); +export function createTestAllTypesColumnsObjectJson(): Record< + string, + readonly Json[] +> { + const columnsObject: Record = {}; + const data = createTestAllTypesData(); + for (const columnData of data) { + columnsObject[columnData.name] = columnData.json; + } + return columnsObject; +} -export const testAllTypesRowsJson: readonly (readonly Json[])[] = [ - testAllTypesData.map(({ json }) => json[0]), - testAllTypesData.map(({ json }) => json[1]), - testAllTypesData.map(({ json }) => json[2]), -]; +export function createTestAllTypesRowsJson(): readonly (readonly Json[])[] { + const data = createTestAllTypesData(); + return [ + data.map(({ json }) => json[0]), + data.map(({ json }) => json[1]), + data.map(({ json }) => json[2]), + ]; +} -export const testAllTypesRowObjectsJson: readonly Record[] = - (function () { - const rowObjects: Record[] = [{}, {}, {}]; - for (const columnData of testAllTypesData) { - rowObjects[0][columnData.name] = columnData.json[0]; - rowObjects[1][columnData.name] = columnData.json[1]; - rowObjects[2][columnData.name] = columnData.json[2]; - } - return rowObjects; - })(); +export function createTestAllTypesRowObjectsJson(): readonly Record< + string, + Json +>[] { + const rowObjects: Record[] = [{}, {}, {}]; + const data = createTestAllTypesData(); + for (const columnData of data) { + rowObjects[0][columnData.name] = columnData.json[0]; + rowObjects[1][columnData.name] = columnData.json[1]; + rowObjects[2][columnData.name] = columnData.json[2]; + } + return rowObjects; +}