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 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
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ export * from './driver.ts';
2
+ export * from './session.ts';
3
+ export * from './columns.ts';
4
+ export * from './migrator.ts';
5
+ export * from './client.ts';
6
+ export * from './pool.ts';