drizzle-databend 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/client.d.ts +22 -0
- package/dist/columns.d.ts +70 -0
- package/dist/dialect.d.ts +10 -0
- package/dist/driver.d.ts +50 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +887 -0
- package/dist/migrator.d.ts +4 -0
- package/dist/pool.d.ts +21 -0
- package/dist/session.d.ts +55 -0
- package/dist/sql/result-mapper.d.ts +7 -0
- package/dist/sql/selection.d.ts +2 -0
- package/package.json +53 -0
- package/src/client.ts +140 -0
- package/src/columns.ts +164 -0
- package/src/dialect.ts +109 -0
- package/src/driver.ts +268 -0
- package/src/index.ts +6 -0
- package/src/migrator.ts +22 -0
- package/src/pool.ts +233 -0
- package/src/session.ts +311 -0
- package/src/sql/result-mapper.ts +234 -0
- package/src/sql/selection.ts +60 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MigrationConfig } from 'drizzle-orm/migrator';
|
|
2
|
+
import type { DatabendDatabase } from './driver.ts';
|
|
3
|
+
export type DatabendMigrationConfig = MigrationConfig | string;
|
|
4
|
+
export declare function migrate<TSchema extends Record<string, unknown>>(db: DatabendDatabase<TSchema>, config: DatabendMigrationConfig): Promise<void>;
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Client } from 'databend-driver';
|
|
2
|
+
import { type DatabendConnectionPool } from './client.ts';
|
|
3
|
+
export interface DatabendPoolConfig {
|
|
4
|
+
/** Maximum concurrent connections. Defaults to 4. */
|
|
5
|
+
size?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DatabendConnectionPoolOptions {
|
|
8
|
+
/** Maximum concurrent connections. Defaults to 4. */
|
|
9
|
+
size?: number;
|
|
10
|
+
/** Timeout in milliseconds to wait for a connection. Defaults to 30000 (30s). */
|
|
11
|
+
acquireTimeout?: number;
|
|
12
|
+
/** Maximum number of requests waiting for a connection. Defaults to 100. */
|
|
13
|
+
maxWaitingRequests?: number;
|
|
14
|
+
/** Max time (ms) a connection may live before being recycled. */
|
|
15
|
+
maxLifetimeMs?: number;
|
|
16
|
+
/** Max idle time (ms) before an idle connection is discarded. */
|
|
17
|
+
idleTimeoutMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function createDatabendConnectionPool(client: Client, options?: DatabendConnectionPoolOptions): DatabendConnectionPool & {
|
|
20
|
+
size: number;
|
|
21
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { entityKind } from 'drizzle-orm/entity';
|
|
2
|
+
import type { Logger } from 'drizzle-orm/logger';
|
|
3
|
+
import { PgTransaction } from 'drizzle-orm/pg-core';
|
|
4
|
+
import type { SelectedFieldsOrdered } from 'drizzle-orm/pg-core/query-builders/select.types';
|
|
5
|
+
import type { PgTransactionConfig, PreparedQueryConfig, PgQueryResultHKT } from 'drizzle-orm/pg-core/session';
|
|
6
|
+
import { PgPreparedQuery, PgSession } from 'drizzle-orm/pg-core/session';
|
|
7
|
+
import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations';
|
|
8
|
+
import { type Query, SQL } from 'drizzle-orm/sql/sql';
|
|
9
|
+
import type { Assume } from 'drizzle-orm/utils';
|
|
10
|
+
import type { DatabendDialect } from './dialect.ts';
|
|
11
|
+
import type { DatabendClientLike, RowData } from './client.ts';
|
|
12
|
+
export type { DatabendClientLike, RowData } from './client.ts';
|
|
13
|
+
export declare class DatabendPreparedQuery<T extends PreparedQueryConfig> extends PgPreparedQuery<T> {
|
|
14
|
+
private client;
|
|
15
|
+
private queryString;
|
|
16
|
+
private params;
|
|
17
|
+
private logger;
|
|
18
|
+
private fields;
|
|
19
|
+
private _isResponseInArrayMode;
|
|
20
|
+
private customResultMapper;
|
|
21
|
+
static readonly [entityKind]: string;
|
|
22
|
+
constructor(client: DatabendClientLike, queryString: string, params: unknown[], logger: Logger, fields: SelectedFieldsOrdered | undefined, _isResponseInArrayMode: boolean, customResultMapper: ((rows: unknown[][]) => T['execute']) | undefined);
|
|
23
|
+
execute(placeholderValues?: Record<string, unknown> | undefined): Promise<T['execute']>;
|
|
24
|
+
all(placeholderValues?: Record<string, unknown> | undefined): Promise<T['all']>;
|
|
25
|
+
isResponseInArrayMode(): boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface DatabendSessionOptions {
|
|
28
|
+
logger?: Logger;
|
|
29
|
+
}
|
|
30
|
+
export declare class DatabendSession<TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = Record<string, never>> extends PgSession<DatabendQueryResultHKT, TFullSchema, TSchema> {
|
|
31
|
+
private client;
|
|
32
|
+
private schema;
|
|
33
|
+
private options;
|
|
34
|
+
static readonly [entityKind]: string;
|
|
35
|
+
protected dialect: DatabendDialect;
|
|
36
|
+
private logger;
|
|
37
|
+
private rollbackOnly;
|
|
38
|
+
constructor(client: DatabendClientLike, dialect: DatabendDialect, schema: RelationalSchemaConfig<TSchema> | undefined, options?: DatabendSessionOptions);
|
|
39
|
+
prepareQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(query: Query, fields: SelectedFieldsOrdered | undefined, name: string | undefined, isResponseInArrayMode: boolean, customResultMapper?: (rows: unknown[][]) => T['execute']): PgPreparedQuery<T>;
|
|
40
|
+
transaction<T>(transaction: (tx: DatabendTransaction<TFullSchema, TSchema>) => Promise<T>, config?: PgTransactionConfig): Promise<T>;
|
|
41
|
+
markRollbackOnly(): void;
|
|
42
|
+
isRollbackOnly(): boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare class DatabendTransaction<TFullSchema extends Record<string, unknown>, TSchema extends TablesRelationalConfig> extends PgTransaction<DatabendQueryResultHKT, TFullSchema, TSchema> {
|
|
45
|
+
static readonly [entityKind]: string;
|
|
46
|
+
rollback(): never;
|
|
47
|
+
getTransactionConfigSQL(config: PgTransactionConfig): SQL;
|
|
48
|
+
setTransaction(config: PgTransactionConfig): Promise<void>;
|
|
49
|
+
transaction<T>(transaction: (tx: DatabendTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T>;
|
|
50
|
+
}
|
|
51
|
+
export type GenericRowData<T extends RowData = RowData> = T;
|
|
52
|
+
export type GenericTableData<T = RowData> = T[];
|
|
53
|
+
export interface DatabendQueryResultHKT extends PgQueryResultHKT {
|
|
54
|
+
type: GenericTableData<Assume<this['row'], RowData>>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AnyColumn, type SelectedFieldsOrdered } from 'drizzle-orm';
|
|
2
|
+
export declare function normalizeTimestampString(value: unknown, withTimezone: boolean): string | unknown;
|
|
3
|
+
export declare function normalizeTimestamp(value: unknown, withTimezone: boolean): Date | unknown;
|
|
4
|
+
export declare function normalizeDateString(value: unknown): string | unknown;
|
|
5
|
+
export declare function normalizeDateValue(value: unknown): Date | unknown;
|
|
6
|
+
export declare function normalizeTime(value: unknown): string | unknown;
|
|
7
|
+
export declare function mapResultRow<TResult>(columns: SelectedFieldsOrdered<AnyColumn>, row: unknown[], joinsNotNullableMap: Record<string, boolean> | undefined): TResult;
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "drizzle-databend",
|
|
3
|
+
"module": "./dist/index.mjs",
|
|
4
|
+
"main": "./dist/index.mjs",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"description": "A drizzle ORM driver for use with Databend. Based on drizzle's Postgres driver surface.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "bun build --target=node ./src/index.ts --outfile=./dist/index.mjs --packages=external && bun run build:declarations",
|
|
11
|
+
"build:declarations": "tsc --emitDeclarationOnly --project tsconfig.types.json",
|
|
12
|
+
"test": "vitest"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"databend-driver": ">=0.33.0",
|
|
16
|
+
"drizzle-orm": "^0.40.0"
|
|
17
|
+
},
|
|
18
|
+
"peerDependenciesMeta": {
|
|
19
|
+
"databend-driver": {
|
|
20
|
+
"optional": true
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"databend-driver": "^0.33.6",
|
|
25
|
+
"@types/bun": "^1.2.5",
|
|
26
|
+
"drizzle-orm": "0.40.0",
|
|
27
|
+
"typescript": "^5.8.2",
|
|
28
|
+
"vitest": "^1.6.0"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.mjs",
|
|
34
|
+
"default": "./dist/index.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.17"
|
|
41
|
+
},
|
|
42
|
+
"packageManager": "bun@1.3.6",
|
|
43
|
+
"keywords": [
|
|
44
|
+
"drizzle",
|
|
45
|
+
"databend"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"files": [
|
|
49
|
+
"src/**/*.ts",
|
|
50
|
+
"dist/*.mjs",
|
|
51
|
+
"dist/**/*.d.ts"
|
|
52
|
+
]
|
|
53
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { Connection } from 'databend-driver';
|
|
2
|
+
|
|
3
|
+
export interface DatabendConnectionPool {
|
|
4
|
+
acquire(): Promise<Connection>;
|
|
5
|
+
release(connection: Connection): void | Promise<void>;
|
|
6
|
+
close?(): Promise<void> | void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type DatabendClientLike = Connection | DatabendConnectionPool;
|
|
10
|
+
export type RowData = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
export type ExecuteArraysResult = { columns: string[]; rows: unknown[][] };
|
|
13
|
+
|
|
14
|
+
export function isPool(
|
|
15
|
+
client: DatabendClientLike
|
|
16
|
+
): client is DatabendConnectionPool {
|
|
17
|
+
return typeof (client as DatabendConnectionPool).acquire === 'function';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert Drizzle param array to a JSON value accepted by databend-driver's Params.
|
|
22
|
+
* Databend's Params is serde_json::Value, so we pass an array of JSON-serializable values.
|
|
23
|
+
*/
|
|
24
|
+
export function prepareParams(params: unknown[]): unknown[] {
|
|
25
|
+
return params.map((param) => {
|
|
26
|
+
if (param === undefined) return null;
|
|
27
|
+
if (param instanceof Date) return param.toISOString();
|
|
28
|
+
if (typeof param === 'bigint') return param.toString();
|
|
29
|
+
return param;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deduplicateColumns(columns: string[]): string[] {
|
|
34
|
+
const counts = new Map<string, number>();
|
|
35
|
+
let hasDuplicates = false;
|
|
36
|
+
|
|
37
|
+
for (const column of columns) {
|
|
38
|
+
const next = (counts.get(column) ?? 0) + 1;
|
|
39
|
+
counts.set(column, next);
|
|
40
|
+
if (next > 1) {
|
|
41
|
+
hasDuplicates = true;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!hasDuplicates) {
|
|
47
|
+
return columns;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
counts.clear();
|
|
51
|
+
return columns.map((column) => {
|
|
52
|
+
const count = counts.get(column) ?? 0;
|
|
53
|
+
counts.set(column, count + 1);
|
|
54
|
+
return count === 0 ? column : `${column}_${count}`;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function executeOnClient(
|
|
59
|
+
client: DatabendClientLike,
|
|
60
|
+
query: string,
|
|
61
|
+
params: unknown[]
|
|
62
|
+
): Promise<RowData[]> {
|
|
63
|
+
if (isPool(client)) {
|
|
64
|
+
const connection = await client.acquire();
|
|
65
|
+
try {
|
|
66
|
+
return await executeOnClient(connection, query, params);
|
|
67
|
+
} finally {
|
|
68
|
+
await client.release(connection);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const prepared = prepareParams(params);
|
|
73
|
+
const paramValue = prepared.length > 0 ? prepared : undefined;
|
|
74
|
+
const rows = await client.queryAll(query, paramValue);
|
|
75
|
+
|
|
76
|
+
if (!rows || rows.length === 0) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return rows.map((r) => r.data());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function executeArraysOnClient(
|
|
84
|
+
client: DatabendClientLike,
|
|
85
|
+
query: string,
|
|
86
|
+
params: unknown[]
|
|
87
|
+
): Promise<ExecuteArraysResult> {
|
|
88
|
+
if (isPool(client)) {
|
|
89
|
+
const connection = await client.acquire();
|
|
90
|
+
try {
|
|
91
|
+
return await executeArraysOnClient(connection, query, params);
|
|
92
|
+
} finally {
|
|
93
|
+
await client.release(connection);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const prepared = prepareParams(params);
|
|
98
|
+
const paramValue = prepared.length > 0 ? prepared : undefined;
|
|
99
|
+
const iter = await client.queryIter(query, paramValue);
|
|
100
|
+
const schema = iter.schema();
|
|
101
|
+
const fields = schema.fields();
|
|
102
|
+
const columns = deduplicateColumns(fields.map((f) => f.name));
|
|
103
|
+
|
|
104
|
+
const rows: unknown[][] = [];
|
|
105
|
+
while (true) {
|
|
106
|
+
const row = await iter.next();
|
|
107
|
+
if (row === null) break;
|
|
108
|
+
if (row instanceof Error) throw row;
|
|
109
|
+
rows.push(row.values());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { columns, rows };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function execOnClient(
|
|
116
|
+
client: DatabendClientLike,
|
|
117
|
+
query: string,
|
|
118
|
+
params: unknown[]
|
|
119
|
+
): Promise<number> {
|
|
120
|
+
if (isPool(client)) {
|
|
121
|
+
const connection = await client.acquire();
|
|
122
|
+
try {
|
|
123
|
+
return await execOnClient(connection, query, params);
|
|
124
|
+
} finally {
|
|
125
|
+
await client.release(connection);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const prepared = prepareParams(params);
|
|
130
|
+
const paramValue = prepared.length > 0 ? prepared : undefined;
|
|
131
|
+
return await client.exec(query, paramValue);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function closeClientConnection(
|
|
135
|
+
connection: Connection
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
if ('close' in connection && typeof connection.close === 'function') {
|
|
138
|
+
await connection.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/columns.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { customType } from 'drizzle-orm/pg-core';
|
|
2
|
+
import type { SQL } from 'drizzle-orm';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Databend VARIANT column type.
|
|
6
|
+
* Stores semi-structured JSON-like data. Maps to `unknown` in TypeScript.
|
|
7
|
+
*/
|
|
8
|
+
export const databendVariant = <TData = unknown>(name: string) =>
|
|
9
|
+
customType<{ data: TData; driverData: string | TData }>({
|
|
10
|
+
dataType() {
|
|
11
|
+
return 'VARIANT';
|
|
12
|
+
},
|
|
13
|
+
toDriver(value: TData): string {
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
},
|
|
19
|
+
fromDriver(value: string | TData): TData {
|
|
20
|
+
if (typeof value === 'string') {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value) as TData;
|
|
23
|
+
} catch {
|
|
24
|
+
return value as unknown as TData;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return value as TData;
|
|
28
|
+
},
|
|
29
|
+
})(name);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Databend ARRAY column type.
|
|
33
|
+
* Stores typed arrays. Maps to `T[]` in TypeScript.
|
|
34
|
+
*/
|
|
35
|
+
export const databendArray = <TData = unknown>(
|
|
36
|
+
name: string,
|
|
37
|
+
elementType: string
|
|
38
|
+
) =>
|
|
39
|
+
customType<{ data: TData[]; driverData: TData[] | string }>({
|
|
40
|
+
dataType() {
|
|
41
|
+
return `ARRAY(${elementType})`;
|
|
42
|
+
},
|
|
43
|
+
toDriver(value: TData[]): TData[] {
|
|
44
|
+
return value;
|
|
45
|
+
},
|
|
46
|
+
fromDriver(value: TData[] | string): TData[] {
|
|
47
|
+
if (typeof value === 'string') {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(value) as TData[];
|
|
50
|
+
} catch {
|
|
51
|
+
return [] as TData[];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
},
|
|
56
|
+
})(name);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Databend TUPLE column type.
|
|
60
|
+
* Stores positional tuples. Maps to typed tuple in TypeScript.
|
|
61
|
+
*/
|
|
62
|
+
export const databendTuple = <TData extends unknown[]>(
|
|
63
|
+
name: string,
|
|
64
|
+
types: string[]
|
|
65
|
+
) =>
|
|
66
|
+
customType<{ data: TData; driverData: TData | string }>({
|
|
67
|
+
dataType() {
|
|
68
|
+
return `TUPLE(${types.join(', ')})`;
|
|
69
|
+
},
|
|
70
|
+
toDriver(value: TData): TData {
|
|
71
|
+
return value;
|
|
72
|
+
},
|
|
73
|
+
fromDriver(value: TData | string): TData {
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(value) as TData;
|
|
77
|
+
} catch {
|
|
78
|
+
return value as unknown as TData;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
},
|
|
83
|
+
})(name);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Databend MAP column type.
|
|
87
|
+
* Stores key-value maps. Maps to `Record<K, V>` in TypeScript.
|
|
88
|
+
*/
|
|
89
|
+
export const databendMap = <TData extends Record<string, any>>(
|
|
90
|
+
name: string,
|
|
91
|
+
keyType: string,
|
|
92
|
+
valueType: string
|
|
93
|
+
) =>
|
|
94
|
+
customType<{ data: TData; driverData: TData | string }>({
|
|
95
|
+
dataType() {
|
|
96
|
+
return `MAP(${keyType}, ${valueType})`;
|
|
97
|
+
},
|
|
98
|
+
toDriver(value: TData): TData {
|
|
99
|
+
return value;
|
|
100
|
+
},
|
|
101
|
+
fromDriver(value: TData | string): TData {
|
|
102
|
+
if (typeof value === 'string') {
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(value) as TData;
|
|
105
|
+
} catch {
|
|
106
|
+
return value as unknown as TData;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
},
|
|
111
|
+
})(name);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Databend TIMESTAMP column type.
|
|
115
|
+
*/
|
|
116
|
+
export const databendTimestamp = (name: string) =>
|
|
117
|
+
customType<{
|
|
118
|
+
data: Date | string;
|
|
119
|
+
driverData: string | Date;
|
|
120
|
+
}>({
|
|
121
|
+
dataType() {
|
|
122
|
+
return 'TIMESTAMP';
|
|
123
|
+
},
|
|
124
|
+
toDriver(value: Date | string): string {
|
|
125
|
+
if (value instanceof Date) {
|
|
126
|
+
return value.toISOString();
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
},
|
|
130
|
+
fromDriver(value: string | Date): Date {
|
|
131
|
+
if (value instanceof Date) {
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
const str = String(value);
|
|
135
|
+
const hasOffset =
|
|
136
|
+
str.endsWith('Z') || /[+-]\d{2}:?\d{2}$/.test(str);
|
|
137
|
+
const normalized = hasOffset
|
|
138
|
+
? str.replace(' ', 'T')
|
|
139
|
+
: `${str.replace(' ', 'T')}Z`;
|
|
140
|
+
return new Date(normalized);
|
|
141
|
+
},
|
|
142
|
+
})(name);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Databend DATE column type.
|
|
146
|
+
*/
|
|
147
|
+
export const databendDate = (name: string) =>
|
|
148
|
+
customType<{ data: string | Date; driverData: string | Date }>({
|
|
149
|
+
dataType() {
|
|
150
|
+
return 'DATE';
|
|
151
|
+
},
|
|
152
|
+
toDriver(value: string | Date): string {
|
|
153
|
+
if (value instanceof Date) {
|
|
154
|
+
return value.toISOString().slice(0, 10);
|
|
155
|
+
}
|
|
156
|
+
return value;
|
|
157
|
+
},
|
|
158
|
+
fromDriver(value: string | Date): string {
|
|
159
|
+
if (value instanceof Date) {
|
|
160
|
+
return value.toISOString().slice(0, 10);
|
|
161
|
+
}
|
|
162
|
+
return value.slice(0, 10);
|
|
163
|
+
},
|
|
164
|
+
})(name);
|
package/src/dialect.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { entityKind, is } from 'drizzle-orm/entity';
|
|
2
|
+
import type { MigrationConfig, MigrationMeta } from 'drizzle-orm/migrator';
|
|
3
|
+
import {
|
|
4
|
+
PgDate,
|
|
5
|
+
PgDateString,
|
|
6
|
+
PgDialect,
|
|
7
|
+
PgNumeric,
|
|
8
|
+
PgSession,
|
|
9
|
+
PgTime,
|
|
10
|
+
PgTimestamp,
|
|
11
|
+
PgTimestampString,
|
|
12
|
+
PgUUID,
|
|
13
|
+
} from 'drizzle-orm/pg-core';
|
|
14
|
+
import {
|
|
15
|
+
sql,
|
|
16
|
+
type DriverValueEncoder,
|
|
17
|
+
type QueryTypingsValue,
|
|
18
|
+
} from 'drizzle-orm';
|
|
19
|
+
|
|
20
|
+
export class DatabendDialect extends PgDialect {
|
|
21
|
+
static readonly [entityKind]: string = 'DatabendPgDialect';
|
|
22
|
+
|
|
23
|
+
// Databend does not support savepoints
|
|
24
|
+
areSavepointsUnsupported(): boolean {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override async migrate(
|
|
29
|
+
migrations: MigrationMeta[],
|
|
30
|
+
session: PgSession,
|
|
31
|
+
config: MigrationConfig | string
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const migrationConfig: MigrationConfig =
|
|
34
|
+
typeof config === 'string' ? { migrationsFolder: config } : config;
|
|
35
|
+
|
|
36
|
+
const migrationsSchema = migrationConfig.migrationsSchema ?? 'default';
|
|
37
|
+
const migrationsTable =
|
|
38
|
+
migrationConfig.migrationsTable ?? '__drizzle_migrations';
|
|
39
|
+
|
|
40
|
+
// Databend has no sequences. Use explicit ID via COALESCE(MAX(id), 0) + 1.
|
|
41
|
+
const migrationTableCreate = sql`
|
|
42
|
+
CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsSchema)}.${sql.identifier(
|
|
43
|
+
migrationsTable
|
|
44
|
+
)} (
|
|
45
|
+
id INT NOT NULL,
|
|
46
|
+
hash VARCHAR NOT NULL,
|
|
47
|
+
created_at BIGINT
|
|
48
|
+
)
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
await session.execute(migrationTableCreate);
|
|
52
|
+
|
|
53
|
+
const dbMigrations = await session.all<{
|
|
54
|
+
id: number;
|
|
55
|
+
hash: string;
|
|
56
|
+
created_at: string;
|
|
57
|
+
}>(
|
|
58
|
+
sql`SELECT id, hash, created_at FROM ${sql.identifier(
|
|
59
|
+
migrationsSchema
|
|
60
|
+
)}.${sql.identifier(migrationsTable)} ORDER BY created_at DESC LIMIT 1`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const lastDbMigration = dbMigrations[0];
|
|
64
|
+
|
|
65
|
+
await session.transaction(async (tx) => {
|
|
66
|
+
for await (const migration of migrations) {
|
|
67
|
+
if (
|
|
68
|
+
!lastDbMigration ||
|
|
69
|
+
Number(lastDbMigration.created_at) < migration.folderMillis
|
|
70
|
+
) {
|
|
71
|
+
for (const stmt of migration.sql) {
|
|
72
|
+
await tx.execute(sql.raw(stmt));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await tx.execute(
|
|
76
|
+
sql`INSERT INTO ${sql.identifier(
|
|
77
|
+
migrationsSchema
|
|
78
|
+
)}.${sql.identifier(migrationsTable)} (id, hash, created_at)
|
|
79
|
+
VALUES (
|
|
80
|
+
(SELECT COALESCE(MAX(id), 0) + 1 FROM ${sql.identifier(
|
|
81
|
+
migrationsSchema
|
|
82
|
+
)}.${sql.identifier(migrationsTable)}),
|
|
83
|
+
${migration.hash},
|
|
84
|
+
${migration.folderMillis}
|
|
85
|
+
)`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override prepareTyping(
|
|
93
|
+
encoder: DriverValueEncoder<unknown, unknown>
|
|
94
|
+
): QueryTypingsValue {
|
|
95
|
+
if (is(encoder, PgNumeric)) {
|
|
96
|
+
return 'decimal';
|
|
97
|
+
} else if (is(encoder, PgTime)) {
|
|
98
|
+
return 'time';
|
|
99
|
+
} else if (is(encoder, PgTimestamp) || is(encoder, PgTimestampString)) {
|
|
100
|
+
return 'timestamp';
|
|
101
|
+
} else if (is(encoder, PgDate) || is(encoder, PgDateString)) {
|
|
102
|
+
return 'date';
|
|
103
|
+
} else if (is(encoder, PgUUID)) {
|
|
104
|
+
return 'uuid';
|
|
105
|
+
} else {
|
|
106
|
+
return 'none';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|