Skip to content

Commit 1a54cb4

Browse files
authored
Query compiler support for Date & typeHint (#5)
* Added Date object and typeHint support to query compiler. Adjust DATE, TIME & TIMESTAMP automatically to accepted format. Configured ESLint. * Fixed conflicts. * Restored .js imports.
1 parent 3d068ea commit 1a54cb4

9 files changed

Lines changed: 693 additions & 453 deletions

src/data-api-dialect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class DataApiDialect implements Dialect {
4646
throw new Error("Unknown mode " + this.#config.mode);
4747
}
4848

49-
createIntrospector(db: Kysely<any>): DatabaseIntrospector {
49+
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector {
5050
if (this.#config.mode === "postgres") return new PostgresIntrospector(db);
5151
if (this.#config.mode === "mysql") return new MysqlIntrospector(db);
5252

src/data-api-driver.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DatabaseConnection, QueryResult } from "kysely";
22
import { Driver } from "kysely";
33
import { CompiledQuery } from "kysely";
4-
import * as RDSDataService from "aws-sdk/clients/rdsdataservice.js";
4+
import RDSDataService, { SqlParametersList } from "aws-sdk/clients/rdsdataservice.js";
55

66
export type DataApiDriverConfig = {
77
client: RDSDataService;
@@ -17,7 +17,9 @@ export class DataApiDriver implements Driver {
1717
this.#config = config;
1818
}
1919

20-
async init(): Promise<void> {}
20+
async init(): Promise<void> {
21+
// do nothing
22+
}
2123

2224
async acquireConnection(): Promise<DatabaseConnection> {
2325
return new DataApiConnection(this.#config);
@@ -35,9 +37,13 @@ export class DataApiDriver implements Driver {
3537
await conn.rollbackTransaction();
3638
}
3739

38-
async releaseConnection(_connection: DatabaseConnection): Promise<void> {}
40+
async releaseConnection(_connection: DatabaseConnection): Promise<void> {
41+
// do nothing
42+
}
3943

40-
async destroy(): Promise<void> {}
44+
async destroy(): Promise<void> {
45+
// do nothing
46+
}
4147
}
4248

4349
class DataApiConnection implements DatabaseConnection {
@@ -90,7 +96,7 @@ class DataApiConnection implements DatabaseConnection {
9096
secretArn: this.#config.secretArn,
9197
resourceArn: this.#config.resourceArn,
9298
sql: compiledQuery.sql,
93-
parameters: compiledQuery.parameters as any,
99+
parameters: compiledQuery.parameters as SqlParametersList,
94100
database: this.#config.database,
95101
includeResultMetadata: true,
96102
})
@@ -114,8 +120,8 @@ class DataApiConnection implements DatabaseConnection {
114120
val.arrayValue ??
115121
val.doubleValue ??
116122
(val.isNull ? null : val.booleanValue),
117-
])
118-
) as unknown as O
123+
]),
124+
) as unknown as O,
119125
);
120126
const result: QueryResult<O> = {
121127
rows: rows || [],

src/data-api-query-compiler.ts

Lines changed: 93 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SqlParameter } from "aws-sdk/clients/rdsdataservice";
22
import { MysqlQueryCompiler, PostgresQueryCompiler } from "kysely";
33

44
export class PostgresDataApiQueryCompiler extends PostgresQueryCompiler {
5-
protected override appendValue(value: any) {
5+
protected override appendValue(value: unknown) {
66
const name = this.numParameters;
77
this.append(this.getCurrentParameterPlaceholder());
88
this.addParameter({
@@ -17,7 +17,7 @@ export class PostgresDataApiQueryCompiler extends PostgresQueryCompiler {
1717
}
1818

1919
export class MysqlDataApiQueryCompiler extends MysqlQueryCompiler {
20-
protected override appendValue(value: any) {
20+
protected override appendValue(value: unknown) {
2121
const name = this.numParameters;
2222
this.append(this.getCurrentParameterPlaceholder());
2323
this.addParameter({
@@ -31,74 +31,105 @@ export class MysqlDataApiQueryCompiler extends MysqlQueryCompiler {
3131
}
3232
}
3333

34-
function serialize(value: any): SqlParameter {
35-
if (value == null) return { value: { isNull: true } };
34+
function serialize(value: unknown): Pick<SqlParameter, "typeHint" | "value"> {
3635
switch (typeof value) {
37-
case "number":
38-
if (Number.isInteger(value))
39-
return {
40-
value: {
41-
longValue: value,
42-
},
43-
};
44-
else
45-
return {
46-
value: {
47-
doubleValue: value,
48-
}
49-
};
50-
case "bigint":
36+
case "bigint":
37+
return { value: { doubleValue: Number(value) } };
38+
case "boolean":
39+
return { value: { booleanValue: value } };
40+
case "number":
41+
if (Number.isInteger(value))
42+
return { value: { longValue: value } };
43+
else
44+
return { value: { doubleValue: value } };
45+
case "object":
46+
if (value == null)
47+
return { value: { isNull: true }};
48+
else if (Buffer.isBuffer(value))
49+
return { value: { blobValue: value } };
50+
else if (value instanceof Date)
5151
return {
52-
value: {
53-
doubleValue: Number(value),
54-
}
52+
typeHint: "TIMESTAMP",
53+
value: { stringValue: fixISOString(value.toISOString()) },
5554
};
56-
case "string":
57-
return {
58-
value: {
59-
stringValue: value,
60-
}
61-
};
62-
case "boolean":
63-
return {
64-
value: {
65-
booleanValue: value,
66-
}
67-
};
68-
case "object":
69-
if (Buffer.isBuffer(value))
70-
return {
71-
value: {
72-
blobValue: value,
73-
}
74-
};
55+
else if ((value as RSU)?.value && isValueObject((value as RSU).value as RSU)) {
56+
if (
57+
(value as RSU).typeHint && ((value as RSU).value as RSU).stringValue
58+
&& typeof ((value as RSU).value as RSU).stringValue === "string"
59+
)
60+
((value as RSU).value as RSU).stringValue = fixStringValue(
61+
(value as RSS).typeHint,
62+
((value as RSU).value as RSS).stringValue,
63+
);
64+
return value;
65+
}
66+
else
67+
break;
68+
case "string":
69+
return {
70+
value: { stringValue: value },
71+
};
72+
}
7573

76-
if (Array.isArray(value)) {
77-
return {
78-
value: {
79-
arrayValue: {
80-
stringValues: value,
81-
}
82-
}
83-
}
84-
}
74+
throw new QueryCompilerError("Could not serialize value");
75+
}
8576

86-
if (value instanceof Date)
87-
return {
88-
typeHint: "TIMESTAMP",
89-
value: { stringValue: serializeDate(value) },
90-
}
77+
function fixStringValue(typeHint: SqlParameter["typeHint"], value: string) {
78+
switch (typeHint) {
79+
case "DATE":
80+
return parseToISOString(value).slice(0, 10);
81+
case "TIME":
82+
if (value.match(/^\d{4}-\d{2}-\d{2}/)) {
83+
return parseToISOString(value).slice(11, 23);
84+
}
85+
return fixTimeString(value);
86+
case "TIMESTAMP":
87+
return fixISOString(parseToISOString(value));
88+
}
89+
return value;
90+
}
9191

92-
return {
93-
value: {
94-
stringValue: JSON.stringify(value),
95-
}
96-
}
92+
function fixTimeString(s: string) {
93+
const elements = (s || "00:00:00").split(":");
94+
while (elements.length < 3) {
95+
elements.push("00");
9796
}
97+
return elements.join(":").slice(0, 12);
98+
}
99+
100+
function fixISOString(s: string) {
101+
return s.replace("T", " ").slice(0, 23);
102+
}
98103

99-
throw new Error(`Unsupported type: ${value}`);
104+
function parseToISOString(s: string) {
105+
return new Date(Date.parse(s)).toISOString();
100106
}
101107

102-
function serializeDate(input: Date) {
103-
return input.toISOString().replace("T", " ").substring(0, 23);
108+
function isValueObject(value: Record<string, unknown>) {
109+
for (const key of primitiveKeys) {
110+
if (value[key]) {
111+
return true;
112+
}
113+
}
114+
if (value.arrayValue) {
115+
for (const key of arrayKeys) {
116+
if ((value.arrayValue as Record<string, unknown>)?.[key]) {
117+
return true;
118+
}
119+
}
120+
}
121+
return false;
104122
}
123+
124+
class QueryCompilerError extends Error {
125+
constructor(message: string) {
126+
super(message);
127+
this.name = QueryCompilerError.name;
128+
}
129+
}
130+
131+
const arrayKeys = ["booleanValues", "doubleValues", "longValues", "stringValues"];
132+
const primitiveKeys = ["blobValue", "booleanValue", "doubleValue", "isNull", "longValue", "stringValue"];
133+
134+
type RSS = Record<string, string>;
135+
type RSU = Record<string, unknown>;

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from "./data-api-dialect.js";
1+
export * from "./data-api-dialect";

0 commit comments

Comments
 (0)