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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Clifton Cunningham
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# drizzle-databend
|
|
2
|
+
|
|
3
|
+
A [Drizzle ORM](https://orm.drizzle.team/) driver for [Databend](https://databend.com/). Built on Drizzle's Postgres driver surface (`pg-core`) since Databend supports `$1` positional parameters and double-quote identifier quoting.
|
|
4
|
+
|
|
5
|
+
Uses [`databend-driver`](https://github.com/databendlabs/bendsql/tree/main/bindings/nodejs) (NAPI-RS bindings) as the underlying client.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun add drizzle-databend drizzle-orm databend-driver
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { drizzle } from 'drizzle-databend';
|
|
17
|
+
import { pgTable, integer, varchar, text } from 'drizzle-orm/pg-core';
|
|
18
|
+
import { eq } from 'drizzle-orm';
|
|
19
|
+
|
|
20
|
+
const users = pgTable('users', {
|
|
21
|
+
id: integer('id').notNull(),
|
|
22
|
+
name: varchar('name', { length: 256 }).notNull(),
|
|
23
|
+
email: text('email'),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const db = await drizzle('databend://databend:databend@localhost:8000/default?sslmode=disable');
|
|
27
|
+
|
|
28
|
+
// Insert
|
|
29
|
+
await db.insert(users).values({ id: 1, name: 'Alice', email: 'alice@example.com' });
|
|
30
|
+
|
|
31
|
+
// Select
|
|
32
|
+
const rows = await db.select().from(users).where(eq(users.name, 'Alice'));
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Databend-specific column types
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { databendVariant, databendArray, databendTuple, databendMap, databendTimestamp, databendDate } from 'drizzle-databend';
|
|
39
|
+
|
|
40
|
+
const events = pgTable('events', {
|
|
41
|
+
id: integer('id').notNull(),
|
|
42
|
+
payload: databendVariant('payload'), // VARIANT (semi-structured JSON)
|
|
43
|
+
tags: databendArray('tags', 'VARCHAR'), // ARRAY(VARCHAR)
|
|
44
|
+
coords: databendTuple('coords', ['FLOAT', 'FLOAT']), // TUPLE(FLOAT, FLOAT)
|
|
45
|
+
attrs: databendMap('attrs', 'VARCHAR', 'VARCHAR'), // MAP(VARCHAR, VARCHAR)
|
|
46
|
+
createdAt: databendTimestamp('created_at'), // TIMESTAMP
|
|
47
|
+
eventDate: databendDate('event_date'), // DATE
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Connection options
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// DSN string (async, auto-pools with 4 connections)
|
|
55
|
+
const db = await drizzle('databend://user:pass@host:8000/db?sslmode=disable');
|
|
56
|
+
|
|
57
|
+
// With config
|
|
58
|
+
const db = await drizzle('databend://...', { pool: { size: 8 }, logger: true });
|
|
59
|
+
|
|
60
|
+
// Config object
|
|
61
|
+
const db = await drizzle({ connection: 'databend://...', schema: mySchema });
|
|
62
|
+
|
|
63
|
+
// Explicit client (sync, no pooling)
|
|
64
|
+
import { Client } from 'databend-driver';
|
|
65
|
+
const conn = await new Client('databend://...').getConn();
|
|
66
|
+
const db = drizzle({ client: conn });
|
|
67
|
+
|
|
68
|
+
// Disable pooling
|
|
69
|
+
const db = await drizzle('databend://...', { pool: false });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Transactions
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
await db.transaction(async (tx) => {
|
|
76
|
+
await tx.insert(users).values({ id: 2, name: 'Bob', email: 'bob@example.com' });
|
|
77
|
+
await tx.update(users).set({ name: 'Robert' }).where(eq(users.id, 2));
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Note: Databend does not support savepoints. Nested transactions use a rollback-only fallback.
|
|
82
|
+
|
|
83
|
+
## Migrations
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { migrate } from 'drizzle-databend';
|
|
87
|
+
|
|
88
|
+
await migrate(db, { migrationsFolder: './drizzle' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
### Prerequisites
|
|
94
|
+
|
|
95
|
+
You need a running Databend instance. The quickest way is Docker:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
docker run -d \
|
|
99
|
+
--name databend \
|
|
100
|
+
--network host \
|
|
101
|
+
-e MINIO_ENABLED=true \
|
|
102
|
+
-e QUERY_DEFAULT_USER=databend \
|
|
103
|
+
-e QUERY_DEFAULT_PASSWORD=databend \
|
|
104
|
+
-v minio_data_dir:/var/lib/minio \
|
|
105
|
+
--restart unless-stopped \
|
|
106
|
+
datafuselabs/databend
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See the [Databend self-hosted quickstart](https://docs.databend.com/guides/self-hosted/quickstart) for more options.
|
|
110
|
+
|
|
111
|
+
Verify the connection:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
bendsql -udatabend -pdatabend
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Commands
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
bun install # Install dependencies
|
|
121
|
+
bun run build # Build (dist/index.mjs + type declarations)
|
|
122
|
+
bun test # Run integration tests (requires running Databend)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Seed data
|
|
126
|
+
|
|
127
|
+
Populate two sample tables (`users` and `events`) for manual testing:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
bun run scripts/seed.ts
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This creates 5 users and 10 events with VARIANT payloads and timestamps.
|
|
134
|
+
|
|
135
|
+
## Architecture
|
|
136
|
+
|
|
137
|
+
Built on the same pattern as [drizzle-duckdb](https://github.com/leonardovida/drizzle-duckdb):
|
|
138
|
+
|
|
139
|
+
- **`driver.ts`** -- `drizzle()` factory and `DatabendDatabase` extending `PgDatabase`
|
|
140
|
+
- **`session.ts`** -- `DatabendSession` and `DatabendPreparedQuery` for query execution
|
|
141
|
+
- **`dialect.ts`** -- `DatabendDialect` extending `PgDialect` with Databend-specific migrations and type mapping
|
|
142
|
+
- **`client.ts`** -- Low-level execution wrapping `databend-driver`'s `Connection` API
|
|
143
|
+
- **`pool.ts`** -- Connection pooling via `Client.getConn()`
|
|
144
|
+
- **`columns.ts`** -- Custom column types (VARIANT, ARRAY, TUPLE, MAP, TIMESTAMP, DATE)
|
|
145
|
+
- **`sql/result-mapper.ts`** -- Maps Databend results to Drizzle's expected format
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Connection } from 'databend-driver';
|
|
2
|
+
export interface DatabendConnectionPool {
|
|
3
|
+
acquire(): Promise<Connection>;
|
|
4
|
+
release(connection: Connection): void | Promise<void>;
|
|
5
|
+
close?(): Promise<void> | void;
|
|
6
|
+
}
|
|
7
|
+
export type DatabendClientLike = Connection | DatabendConnectionPool;
|
|
8
|
+
export type RowData = Record<string, unknown>;
|
|
9
|
+
export type ExecuteArraysResult = {
|
|
10
|
+
columns: string[];
|
|
11
|
+
rows: unknown[][];
|
|
12
|
+
};
|
|
13
|
+
export declare function isPool(client: DatabendClientLike): client is DatabendConnectionPool;
|
|
14
|
+
/**
|
|
15
|
+
* Convert Drizzle param array to a JSON value accepted by databend-driver's Params.
|
|
16
|
+
* Databend's Params is serde_json::Value, so we pass an array of JSON-serializable values.
|
|
17
|
+
*/
|
|
18
|
+
export declare function prepareParams(params: unknown[]): unknown[];
|
|
19
|
+
export declare function executeOnClient(client: DatabendClientLike, query: string, params: unknown[]): Promise<RowData[]>;
|
|
20
|
+
export declare function executeArraysOnClient(client: DatabendClientLike, query: string, params: unknown[]): Promise<ExecuteArraysResult>;
|
|
21
|
+
export declare function execOnClient(client: DatabendClientLike, query: string, params: unknown[]): Promise<number>;
|
|
22
|
+
export declare function closeClientConnection(connection: Connection): Promise<void>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Databend VARIANT column type.
|
|
3
|
+
* Stores semi-structured JSON-like data. Maps to `unknown` in TypeScript.
|
|
4
|
+
*/
|
|
5
|
+
export declare const databendVariant: <TData = unknown>(name: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
6
|
+
name: string;
|
|
7
|
+
dataType: "custom";
|
|
8
|
+
columnType: "PgCustomColumn";
|
|
9
|
+
data: TData;
|
|
10
|
+
driverParam: string | TData;
|
|
11
|
+
enumValues: undefined;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Databend ARRAY column type.
|
|
15
|
+
* Stores typed arrays. Maps to `T[]` in TypeScript.
|
|
16
|
+
*/
|
|
17
|
+
export declare const databendArray: <TData = unknown>(name: string, elementType: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
18
|
+
name: string;
|
|
19
|
+
dataType: "custom";
|
|
20
|
+
columnType: "PgCustomColumn";
|
|
21
|
+
data: TData[];
|
|
22
|
+
driverParam: string | TData[];
|
|
23
|
+
enumValues: undefined;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Databend TUPLE column type.
|
|
27
|
+
* Stores positional tuples. Maps to typed tuple in TypeScript.
|
|
28
|
+
*/
|
|
29
|
+
export declare const databendTuple: <TData extends unknown[]>(name: string, types: string[]) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
30
|
+
name: string;
|
|
31
|
+
dataType: "custom";
|
|
32
|
+
columnType: "PgCustomColumn";
|
|
33
|
+
data: TData;
|
|
34
|
+
driverParam: string | TData;
|
|
35
|
+
enumValues: undefined;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Databend MAP column type.
|
|
39
|
+
* Stores key-value maps. Maps to `Record<K, V>` in TypeScript.
|
|
40
|
+
*/
|
|
41
|
+
export declare const databendMap: <TData extends Record<string, any>>(name: string, keyType: string, valueType: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
42
|
+
name: string;
|
|
43
|
+
dataType: "custom";
|
|
44
|
+
columnType: "PgCustomColumn";
|
|
45
|
+
data: TData;
|
|
46
|
+
driverParam: string | TData;
|
|
47
|
+
enumValues: undefined;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Databend TIMESTAMP column type.
|
|
51
|
+
*/
|
|
52
|
+
export declare const databendTimestamp: (name: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
53
|
+
name: string;
|
|
54
|
+
dataType: "custom";
|
|
55
|
+
columnType: "PgCustomColumn";
|
|
56
|
+
data: string | Date;
|
|
57
|
+
driverParam: string | Date;
|
|
58
|
+
enumValues: undefined;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Databend DATE column type.
|
|
62
|
+
*/
|
|
63
|
+
export declare const databendDate: (name: string) => import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
64
|
+
name: string;
|
|
65
|
+
dataType: "custom";
|
|
66
|
+
columnType: "PgCustomColumn";
|
|
67
|
+
data: string | Date;
|
|
68
|
+
driverParam: string | Date;
|
|
69
|
+
enumValues: undefined;
|
|
70
|
+
}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { entityKind } from 'drizzle-orm/entity';
|
|
2
|
+
import type { MigrationConfig, MigrationMeta } from 'drizzle-orm/migrator';
|
|
3
|
+
import { PgDialect, PgSession } from 'drizzle-orm/pg-core';
|
|
4
|
+
import { type DriverValueEncoder, type QueryTypingsValue } from 'drizzle-orm';
|
|
5
|
+
export declare class DatabendDialect extends PgDialect {
|
|
6
|
+
static readonly [entityKind]: string;
|
|
7
|
+
areSavepointsUnsupported(): boolean;
|
|
8
|
+
migrate(migrations: MigrationMeta[], session: PgSession, config: MigrationConfig | string): Promise<void>;
|
|
9
|
+
prepareTyping(encoder: DriverValueEncoder<unknown, unknown>): QueryTypingsValue;
|
|
10
|
+
}
|
package/dist/driver.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Client } from 'databend-driver';
|
|
2
|
+
import { entityKind } from 'drizzle-orm/entity';
|
|
3
|
+
import type { Logger } from 'drizzle-orm/logger';
|
|
4
|
+
import { PgDatabase } from 'drizzle-orm/pg-core/db';
|
|
5
|
+
import { type ExtractTablesWithRelations, type RelationalSchemaConfig, type TablesRelationalConfig } from 'drizzle-orm/relations';
|
|
6
|
+
import { type DrizzleConfig } from 'drizzle-orm/utils';
|
|
7
|
+
import type { DatabendClientLike, DatabendQueryResultHKT, DatabendTransaction } from './session.ts';
|
|
8
|
+
import { DatabendSession } from './session.ts';
|
|
9
|
+
import { DatabendDialect } from './dialect.ts';
|
|
10
|
+
import { type DatabendPoolConfig } from './pool.ts';
|
|
11
|
+
export interface DatabendDriverOptions {
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
}
|
|
14
|
+
export declare class DatabendDriver {
|
|
15
|
+
private client;
|
|
16
|
+
private dialect;
|
|
17
|
+
private options;
|
|
18
|
+
static readonly [entityKind]: string;
|
|
19
|
+
constructor(client: DatabendClientLike, dialect: DatabendDialect, options?: DatabendDriverOptions);
|
|
20
|
+
createSession(schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined): DatabendSession<Record<string, unknown>, TablesRelationalConfig>;
|
|
21
|
+
}
|
|
22
|
+
export interface DatabendDrizzleConfig<TSchema extends Record<string, unknown> = Record<string, never>> extends DrizzleConfig<TSchema> {
|
|
23
|
+
/** Pool configuration. Use size config or false to disable. */
|
|
24
|
+
pool?: DatabendPoolConfig | false;
|
|
25
|
+
}
|
|
26
|
+
export interface DatabendDrizzleConfigWithConnection<TSchema extends Record<string, unknown> = Record<string, never>> extends DatabendDrizzleConfig<TSchema> {
|
|
27
|
+
/** Connection DSN string */
|
|
28
|
+
connection: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DatabendDrizzleConfigWithClient<TSchema extends Record<string, unknown> = Record<string, never>> extends DatabendDrizzleConfig<TSchema> {
|
|
31
|
+
/** Explicit client (connection or pool) */
|
|
32
|
+
client: DatabendClientLike;
|
|
33
|
+
}
|
|
34
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(dsn: string): Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
35
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(dsn: string, config: DatabendDrizzleConfig<TSchema>): Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
36
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(config: DatabendDrizzleConfigWithConnection<TSchema>): Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
37
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(config: DatabendDrizzleConfigWithClient<TSchema>): DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
38
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(client: DatabendClientLike, config?: DatabendDrizzleConfig<TSchema>): DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
39
|
+
export declare class DatabendDatabase<TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> extends PgDatabase<DatabendQueryResultHKT, TFullSchema, TSchema> {
|
|
40
|
+
readonly dialect: DatabendDialect;
|
|
41
|
+
readonly session: DatabendSession<TFullSchema, TSchema>;
|
|
42
|
+
static readonly [entityKind]: string;
|
|
43
|
+
/** The underlying connection or pool */
|
|
44
|
+
readonly $client: DatabendClientLike;
|
|
45
|
+
/** The Databend Client instance (when created from DSN) */
|
|
46
|
+
readonly $databendClient?: Client;
|
|
47
|
+
constructor(dialect: DatabendDialect, session: DatabendSession<TFullSchema, TSchema>, schema: RelationalSchemaConfig<TSchema> | undefined, client: DatabendClientLike, databendClient?: Client);
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
transaction<T>(transaction: (tx: DatabendTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T>;
|
|
50
|
+
}
|