From 2f2c4e188a765dbcedfee87a375165064aa6cec5 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Mon, 16 Jan 2023 15:36:16 +0200 Subject: [PATCH 1/2] add test --- src/statement/dataTypes.ts | 3 ++ test/integration/date.test.ts | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/integration/date.test.ts diff --git a/src/statement/dataTypes.ts b/src/statement/dataTypes.ts index 41bb3711..196d0ded 100644 --- a/src/statement/dataTypes.ts +++ b/src/statement/dataTypes.ts @@ -49,8 +49,11 @@ const getMappedType = (innerType: string) => { const COMPLEX_TYPE = /(nullable|array)\((.+)\)/; const DATE_TYPES = withNullableTypes([ + "pg_date", "date", "timestamp", + "timestamp_tz", + "timestamp_ntz", "datetime", "date32", "date_ext", diff --git a/test/integration/date.test.ts b/test/integration/date.test.ts new file mode 100644 index 00000000..ed1e5e82 --- /dev/null +++ b/test/integration/date.test.ts @@ -0,0 +1,67 @@ +import { Firebolt } from "../../src/index"; + +const connectionParams = { + auth: { + username: process.env.FIREBOLT_USERNAME as string, + password: process.env.FIREBOLT_PASSWORD as string + }, + database: process.env.FIREBOLT_DATABASE as string, + engineName: process.env.FIREBOLT_ENGINE_NAME as string +}; + +jest.setTimeout(100000); + +const dml = ` +CREATE FACT TABLE IF NOT EXISTS t1 +( +id INT NOT NULL UNIQUE, +description TEXT NULL, +pg_date pgdate NOT NULL , +time_wtz timestamptz NOT NULL , +time_ntz timestampntz NOT NULL , +done boolean NOT NULL default 1 +) +PRIMARY INDEX id; +`; + +const insertValues = ` +INSERT INTO t1 VALUES +(1, 'fitst value', '0001-01-01', '0001-01-01 00:00:00.000000 UTC', '0001-01-01 00:00:00.000000', TRUE), +(2, 'second value', '0301-05-08', '0342-01-12 15:16:00.000000 UTC', '0343-12-01 12:14:00.000000', TRUE), +(3, 'thirds value', '1500-12-10', '1479-01-01 00:00:00.000000 UTC', '1562-01-11 00:00:22.000000', TRUE), +(4, 'some value', '1890-12-10', '1889-06-01 00:21:12.000000 UTC', '1990-04-22 22:12:00.000000', TRUE), +(5, 'some second value', '1912-11-01', '1905-09-12 13:00:12.000000 UTC', '1919-01-01 02:22:41.123221', TRUE), +(6, 'just a value', '1980-01-23', '1977-11-01 11:11:00.000000 UTC', '1985-12-24 23:22:00.000000', TRUE), +(7, 'my value', '1989-06-24', '1989-06-24 06:24:24.000000 UTC', '1989-06-24 23:24:24.000000', TRUE), +(8, null, '1991-06-13', '1991-06-13 00:00:00.000000 UTC', '1991-06-13 23:59:00.000000', TRUE), +(9, null, '2022-05-15', '2022-05-15 14:28:00.000000 UTC', '2022-05-15 14:28:00.000000', TRUE), +(10, 'last value', '9999-12-31', '9999-01-01 00:00:00.000000 UTC', '9999-12-31 23:59:00.000000', TRUE) +`; + +describe("new date data format", () => { + afterAll(async () => { + try { + const firebolt = Firebolt({ + apiEndpoint: process.env.FIREBOLT_API_ENDPOINT as string + }); + const connection = await firebolt.connect(connectionParams); + await connection.execute(`drop table if exists t1`); + } catch (e) { + console.log(e); + } + }); + + it("works", async () => { + const firebolt = Firebolt({ + apiEndpoint: process.env.FIREBOLT_API_ENDPOINT as string + }); + + const connection = await firebolt.connect(connectionParams); + + await connection.execute(dml); + await connection.execute(insertValues); + const statement = await connection.execute(`select * from t1 limit 10`); + const { data, meta } = await statement.fetchResult(); + console.log(data, meta); + }); +}); From 1f80489c59cda171abb1961b33dcece06a8839be Mon Sep 17 00:00:00 2001 From: Vitalii Date: Mon, 16 Jan 2023 19:10:42 +0200 Subject: [PATCH 2/2] format datetimes --- src/formatter/index.ts | 71 ++++++++++++++++++++++++++--------- test/integration/date.test.ts | 4 +- test/unit/statement.test.ts | 63 ++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 20 deletions(-) diff --git a/src/formatter/index.ts b/src/formatter/index.ts index 26261e39..0e4e2a5d 100644 --- a/src/formatter/index.ts +++ b/src/formatter/index.ts @@ -32,10 +32,14 @@ const removeComments = (query: string) => { return query; }; -const zeroPad = (param: number, length: number) => { +const zeroPad = (param: number, length: number, direction = "left") => { let paded = param.toString(); while (paded.length < length) { - paded = "0" + paded; + if (direction === "left") { + paded = "0" + paded; + } else { + paded = paded + "0"; + } } return paded; @@ -49,6 +53,19 @@ export class Tuple { } } +export class PGDate extends Date {} + +export class TimestampTZ extends Date { + timeZone: string; + + constructor(value: number | string, { timeZone }: { timeZone: string }) { + super(value); + this.timeZone = timeZone; + } +} + +export class TimestampNTZ extends Date {} + export class QueryFormatter { private format(query: string, params: unknown[]) { params = [...params]; @@ -170,8 +187,8 @@ export class QueryFormatter { return sql; } - private escapeDate(param: Date) { - const dt = new Date(param); + private escapeDate(param: unknown) { + const dt = new Date(param as Date); if (isNaN(dt.getTime())) { return "NULL"; @@ -183,22 +200,40 @@ export class QueryFormatter { const hour = dt.getHours(); const minute = dt.getMinutes(); const second = dt.getSeconds(); + const millisecond = dt.getMilliseconds(); // YYYY-MM-DD HH:mm:ss.mmm - const str = - zeroPad(year, 4) + - "-" + - zeroPad(month, 2) + - "-" + - zeroPad(day, 2) + - " " + - zeroPad(hour, 2) + - ":" + - zeroPad(minute, 2) + - ":" + - zeroPad(second, 2); - //+ "." + zeroPad(millisecond, 3); + const yearMonthDay = + zeroPad(year, 4) + "-" + zeroPad(month, 2) + "-" + zeroPad(day, 2); + const hourMinuteSecond = + zeroPad(hour, 2) + ":" + zeroPad(minute, 2) + ":" + zeroPad(second, 2); + + if (param instanceof PGDate) { + return this.escapeString(yearMonthDay); + } + if (param instanceof TimestampTZ) { + const str = + yearMonthDay + + " " + + hourMinuteSecond + + "." + + zeroPad(millisecond, 6, "right") + + " " + + param.timeZone; + return this.escapeString(str); + } + + if (param instanceof TimestampNTZ) { + const str = + yearMonthDay + + " " + + hourMinuteSecond + + "." + + zeroPad(millisecond, 6, "right"); + return this.escapeString(str); + } + const str = yearMonthDay + " " + hourMinuteSecond; return this.escapeString(str); } @@ -208,7 +243,7 @@ export class QueryFormatter { } else if (BigNumber.isBigNumber(param)) { return param.toString(); } else if (Object.prototype.toString.call(param) === "[object Date]") { - return this.escapeDate(param as Date); + return this.escapeDate(param); } else if (Array.isArray(param)) { return this.escapeArray(param); } else if (Buffer.isBuffer(param)) { diff --git a/test/integration/date.test.ts b/test/integration/date.test.ts index ed1e5e82..a3e89428 100644 --- a/test/integration/date.test.ts +++ b/test/integration/date.test.ts @@ -62,6 +62,8 @@ describe("new date data format", () => { await connection.execute(insertValues); const statement = await connection.execute(`select * from t1 limit 10`); const { data, meta } = await statement.fetchResult(); - console.log(data, meta); + expect(meta[2].type).toEqual("pgdate"); + expect(meta[3].type).toEqual("timestamptz"); + expect(meta[4].type).toEqual("timestampntz"); }); }); diff --git a/test/unit/statement.test.ts b/test/unit/statement.test.ts index eb2f3f93..c9e7bb7e 100644 --- a/test/unit/statement.test.ts +++ b/test/unit/statement.test.ts @@ -1,5 +1,11 @@ import BigNumber from "bignumber.js"; -import { QueryFormatter, Tuple } from "../../src/formatter"; +import { + PGDate, + QueryFormatter, + TimestampTZ, + TimestampNTZ, + Tuple +} from "../../src/formatter"; describe("format query", () => { it("format", () => { @@ -143,4 +149,59 @@ describe("format query", () => { `"select foo, bar from baz where foo in ('some', 'other') and bar = 'str'"` ); }); + it("format pgdate", () => { + const queryFormatter = new QueryFormatter(); + const query = "select foo, bar from baz where foo < ? and bar = ?"; + const formattedQuery = queryFormatter.formatQuery(query, [ + new PGDate("2023-12-12"), + "str" + ]); + expect(formattedQuery).toMatchInlineSnapshot( + `"select foo, bar from baz where foo < '2023-12-12' and bar = 'str'"` + ); + }); + it("format timestampTZ", () => { + const queryFormatter = new QueryFormatter(); + const query = "insert into foo values(?, ?)"; + const formattedQuery = queryFormatter.formatQuery(query, [ + new TimestampTZ("2023-12-12 00:00:00", { timeZone: "UTC" }), + "str" + ]); + expect(formattedQuery).toMatchInlineSnapshot( + `"insert into foo values('2023-12-12 00:00:00.000000 UTC', 'str')"` + ); + }); + it("format timestampTZ with microseconds padding", () => { + const queryFormatter = new QueryFormatter(); + const query = "insert into foo values(?, ?)"; + const formattedQuery = queryFormatter.formatQuery(query, [ + new TimestampTZ("2023-12-12 00:00:00.123", { timeZone: "UTC" }), + "str" + ]); + expect(formattedQuery).toMatchInlineSnapshot( + `"insert into foo values('2023-12-12 00:00:00.123000 UTC', 'str')"` + ); + }); + it("format timestampNTZ", () => { + const queryFormatter = new QueryFormatter(); + const query = "insert into foo values(?, ?)"; + const formattedQuery = queryFormatter.formatQuery(query, [ + new TimestampNTZ("2023-12-12 00:00:00"), + "str" + ]); + expect(formattedQuery).toMatchInlineSnapshot( + `"insert into foo values('2023-12-12 00:00:00.000000', 'str')"` + ); + }); + it("format timestampNTZ with microseconds padding", () => { + const queryFormatter = new QueryFormatter(); + const query = "insert into foo values(?, ?)"; + const formattedQuery = queryFormatter.formatQuery(query, [ + new TimestampNTZ("2023-12-12 00:00:00.123"), + "str" + ]); + expect(formattedQuery).toMatchInlineSnapshot( + `"insert into foo values('2023-12-12 00:00:00.123000', 'str')"` + ); + }); });