Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 53 additions & 18 deletions src/formatter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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];
Expand Down Expand Up @@ -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";
Expand All @@ -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);
}

Expand All @@ -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)) {
Expand Down
3 changes: 3 additions & 0 deletions src/statement/dataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
69 changes: 69 additions & 0 deletions test/integration/date.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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();
expect(meta[2].type).toEqual("pgdate");
expect(meta[3].type).toEqual("timestamptz");
expect(meta[4].type).toEqual("timestampntz");
});
});
63 changes: 62 additions & 1 deletion test/unit/statement.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down Expand Up @@ -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')"`
);
});
});