@leonardovida-md/drizzle-neo-duckdb 1.0.3 → 1.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/README.md +20 -5
- package/dist/client.d.ts +7 -1
- package/dist/columns.d.ts +1 -1
- package/dist/driver.d.ts +33 -1
- package/dist/duckdb-introspect.mjs +211 -37
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.mjs +319 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +202 -36
- package/dist/introspect.d.ts +1 -0
- package/dist/pool.d.ts +22 -0
- package/dist/sql/query-rewriters.d.ts +0 -1
- package/dist/utils.d.ts +1 -1
- package/dist/value-wrappers-core.d.ts +42 -0
- package/dist/value-wrappers.d.ts +2 -98
- package/package.json +6 -2
- package/src/client.ts +43 -5
- package/src/columns.ts +1 -1
- package/src/driver.ts +191 -7
- package/src/helpers.ts +18 -0
- package/src/index.ts +1 -0
- package/src/introspect.ts +24 -23
- package/src/migrator.ts +1 -1
- package/src/olap.ts +1 -0
- package/src/pool.ts +104 -0
- package/src/session.ts +36 -10
- package/src/sql/query-rewriters.ts +2 -49
- package/src/utils.ts +1 -1
- package/src/value-wrappers-core.ts +156 -0
- package/src/value-wrappers.ts +44 -213
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/@leonardovida-md/drizzle-neo-duckdb)
|
|
8
8
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
9
9
|
|
|
10
|
-
[Documentation](https://leonardovida-
|
|
10
|
+
[Documentation](https://leonardovida.github.io/drizzle-neo-duckdb/) • [LLM Context](https://leonardovida.github.io/drizzle-neo-duckdb/llms.txt) • [Examples](./example) • [Contributing](#contributing)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -19,6 +19,8 @@ Works with local DuckDB files, in-memory databases, and [MotherDuck](https://mot
|
|
|
19
19
|
|
|
20
20
|
> **Status:** Experimental. Core query building, migrations, and type inference work well. Some DuckDB-specific types and edge cases are still being refined.
|
|
21
21
|
|
|
22
|
+
Docs tip: every docs page has a **Markdown (raw)** button for LLM-friendly source.
|
|
23
|
+
|
|
22
24
|
## Installation
|
|
23
25
|
|
|
24
26
|
```bash
|
|
@@ -188,7 +190,20 @@ const products = pgTable('products', {
|
|
|
188
190
|
});
|
|
189
191
|
```
|
|
190
192
|
|
|
191
|
-
See [Column Types Documentation](https://leonardovida
|
|
193
|
+
See [Column Types Documentation](https://leonardovida.github.io/drizzle-neo-duckdb/api/columns) for complete reference.
|
|
194
|
+
|
|
195
|
+
### Client-Safe Imports (schemas, drizzle-zod, trpc)
|
|
196
|
+
|
|
197
|
+
When generated schemas are consumed in client bundles, import the helpers from the browser-safe subpath to avoid pulling the native DuckDB bindings:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import {
|
|
201
|
+
duckDbJson,
|
|
202
|
+
duckDbList,
|
|
203
|
+
} from '@leonardovida-md/drizzle-neo-duckdb/helpers';
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The introspection CLI now emits this import path by default. You can still override `importBasePath` if you need the old root import.
|
|
192
207
|
|
|
193
208
|
## Querying
|
|
194
209
|
|
|
@@ -275,7 +290,7 @@ import { migrate } from '@leonardovida-md/drizzle-neo-duckdb';
|
|
|
275
290
|
await migrate(db, { migrationsFolder: './drizzle' });
|
|
276
291
|
```
|
|
277
292
|
|
|
278
|
-
Migration metadata is stored in `drizzle.__drizzle_migrations` by default. See [Migrations Documentation](https://leonardovida
|
|
293
|
+
Migration metadata is stored in `drizzle.__drizzle_migrations` by default. See [Migrations Documentation](https://leonardovida.github.io/drizzle-neo-duckdb/guide/migrations) for configuration options.
|
|
279
294
|
|
|
280
295
|
## Schema Introspection
|
|
281
296
|
|
|
@@ -300,7 +315,7 @@ const result = await introspect(db, {
|
|
|
300
315
|
console.log(result.files.schemaTs);
|
|
301
316
|
```
|
|
302
317
|
|
|
303
|
-
See [Introspection Documentation](https://leonardovida
|
|
318
|
+
See [Introspection Documentation](https://leonardovida.github.io/drizzle-neo-duckdb/guide/introspection) for all options.
|
|
304
319
|
|
|
305
320
|
## Configuration Options
|
|
306
321
|
|
|
@@ -333,7 +348,7 @@ This connector aims for compatibility with Drizzle's Postgres driver but has som
|
|
|
333
348
|
| Prepared statements | No statement caching |
|
|
334
349
|
| Streaming results | Materialized by default; use `executeBatches()` for chunks |
|
|
335
350
|
|
|
336
|
-
See [Limitations Documentation](https://leonardovida
|
|
351
|
+
See [Limitations Documentation](https://leonardovida.github.io/drizzle-neo-duckdb/guide/limitations) for details.
|
|
337
352
|
|
|
338
353
|
## Examples
|
|
339
354
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { type DuckDBConnection } from '@duckdb/node-api';
|
|
2
|
-
export type DuckDBClientLike = DuckDBConnection;
|
|
2
|
+
export type DuckDBClientLike = DuckDBConnection | DuckDBConnectionPool;
|
|
3
3
|
export type RowData = Record<string, unknown>;
|
|
4
|
+
export interface DuckDBConnectionPool {
|
|
5
|
+
acquire(): Promise<DuckDBConnection>;
|
|
6
|
+
release(connection: DuckDBConnection): void | Promise<void>;
|
|
7
|
+
close?(): Promise<void> | void;
|
|
8
|
+
}
|
|
9
|
+
export declare function isPool(client: DuckDBClientLike): client is DuckDBConnectionPool;
|
|
4
10
|
export interface PrepareParamsOptions {
|
|
5
11
|
rejectStringArrayLiterals?: boolean;
|
|
6
12
|
warnOnStringArrayLiteral?: () => void;
|
package/dist/columns.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SQL } from 'drizzle-orm';
|
|
2
2
|
import type { SQLWrapper } from 'drizzle-orm/sql/sql';
|
|
3
|
-
import { type ListValueWrapper, type ArrayValueWrapper, type MapValueWrapper, type BlobValueWrapper, type JsonValueWrapper } from './value-wrappers.ts';
|
|
3
|
+
import { type ListValueWrapper, type ArrayValueWrapper, type MapValueWrapper, type BlobValueWrapper, type JsonValueWrapper } from './value-wrappers-core.ts';
|
|
4
4
|
type IntColType = 'SMALLINT' | 'INTEGER' | 'BIGINT' | 'HUGEINT' | 'USMALLINT' | 'UINTEGER' | 'UBIGINT' | 'UHUGEINT' | 'INT' | 'INT16' | 'INT32' | 'INT64' | 'INT128' | 'LONG' | 'VARINT';
|
|
5
5
|
type FloatColType = 'FLOAT' | 'DOUBLE';
|
|
6
6
|
type StringColType = 'STRING' | 'VARCHAR' | 'TEXT';
|
package/dist/driver.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DuckDBInstance } from '@duckdb/node-api';
|
|
1
2
|
import { entityKind } from 'drizzle-orm/entity';
|
|
2
3
|
import type { Logger } from 'drizzle-orm/logger';
|
|
3
4
|
import { PgDatabase } from 'drizzle-orm/pg-core/db';
|
|
@@ -10,6 +11,7 @@ import { DuckDBSession } from './session.ts';
|
|
|
10
11
|
import { DuckDBDialect } from './dialect.ts';
|
|
11
12
|
import { DuckDBSelectBuilder } from './select-builder.ts';
|
|
12
13
|
import type { ExecuteInBatchesOptions, RowData } from './client.ts';
|
|
14
|
+
import { type DuckDBPoolConfig, type PoolPreset } from './pool.ts';
|
|
13
15
|
export interface PgDriverOptions {
|
|
14
16
|
logger?: Logger;
|
|
15
17
|
rewriteArrays?: boolean;
|
|
@@ -23,16 +25,46 @@ export declare class DuckDBDriver {
|
|
|
23
25
|
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, options?: PgDriverOptions);
|
|
24
26
|
createSession(schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined): DuckDBSession<Record<string, unknown>, TablesRelationalConfig>;
|
|
25
27
|
}
|
|
28
|
+
/** Connection configuration when using path-based connection */
|
|
29
|
+
export interface DuckDBConnectionConfig {
|
|
30
|
+
/** Database path: ':memory:', './file.duckdb', 'md:', 'md:database' */
|
|
31
|
+
path: string;
|
|
32
|
+
/** DuckDB instance options (e.g., motherduck_token) */
|
|
33
|
+
options?: Record<string, string>;
|
|
34
|
+
}
|
|
26
35
|
export interface DuckDBDrizzleConfig<TSchema extends Record<string, unknown> = Record<string, never>> extends DrizzleConfig<TSchema> {
|
|
27
36
|
rewriteArrays?: boolean;
|
|
28
37
|
rejectStringArrayLiterals?: boolean;
|
|
38
|
+
/** Pool configuration. Use preset name, size config, or false to disable. */
|
|
39
|
+
pool?: DuckDBPoolConfig | PoolPreset | false;
|
|
40
|
+
}
|
|
41
|
+
export interface DuckDBDrizzleConfigWithConnection<TSchema extends Record<string, unknown> = Record<string, never>> extends DuckDBDrizzleConfig<TSchema> {
|
|
42
|
+
/** Connection string or config object */
|
|
43
|
+
connection: string | DuckDBConnectionConfig;
|
|
44
|
+
}
|
|
45
|
+
export interface DuckDBDrizzleConfigWithClient<TSchema extends Record<string, unknown> = Record<string, never>> extends DuckDBDrizzleConfig<TSchema> {
|
|
46
|
+
/** Explicit client (connection or pool) */
|
|
47
|
+
client: DuckDBClientLike;
|
|
29
48
|
}
|
|
49
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(connectionString: string): Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
50
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(connectionString: string, config: DuckDBDrizzleConfig<TSchema>): Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
51
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(config: DuckDBDrizzleConfigWithConnection<TSchema>): Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
52
|
+
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(config: DuckDBDrizzleConfigWithClient<TSchema>): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
30
53
|
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(client: DuckDBClientLike, config?: DuckDBDrizzleConfig<TSchema>): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
31
54
|
export declare class DuckDBDatabase<TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> extends PgDatabase<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
32
55
|
readonly dialect: DuckDBDialect;
|
|
33
56
|
readonly session: DuckDBSession<TFullSchema, TSchema>;
|
|
34
57
|
static readonly [entityKind]: string;
|
|
35
|
-
|
|
58
|
+
/** The underlying connection or pool */
|
|
59
|
+
readonly $client: DuckDBClientLike;
|
|
60
|
+
/** The DuckDB instance (when created from connection string) */
|
|
61
|
+
readonly $instance?: DuckDBInstance;
|
|
62
|
+
constructor(dialect: DuckDBDialect, session: DuckDBSession<TFullSchema, TSchema>, schema: RelationalSchemaConfig<TSchema> | undefined, client: DuckDBClientLike, instance?: DuckDBInstance);
|
|
63
|
+
/**
|
|
64
|
+
* Close the database connection pool and instance.
|
|
65
|
+
* Should be called when shutting down the application.
|
|
66
|
+
*/
|
|
67
|
+
close(): Promise<void>;
|
|
36
68
|
select(): DuckDBSelectBuilder<undefined>;
|
|
37
69
|
select<TSelection extends SelectedFields>(fields: TSelection): DuckDBSelectBuilder<TSelection>;
|
|
38
70
|
executeBatches<T extends RowData = RowData>(query: SQL, options?: ExecuteInBatchesOptions): AsyncGenerator<T[], void, void>;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/duckdb-introspect.ts
|
|
4
|
-
import { DuckDBInstance } from "@duckdb/node-api";
|
|
4
|
+
import { DuckDBInstance as DuckDBInstance3 } from "@duckdb/node-api";
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
|
|
9
9
|
// src/driver.ts
|
|
10
|
+
import { DuckDBInstance as DuckDBInstance2 } from "@duckdb/node-api";
|
|
10
11
|
import { entityKind as entityKind3 } from "drizzle-orm/entity";
|
|
11
12
|
import { DefaultLogger } from "drizzle-orm/logger";
|
|
12
13
|
import { PgDatabase } from "drizzle-orm/pg-core/db";
|
|
@@ -23,10 +24,6 @@ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
|
|
|
23
24
|
import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
|
|
24
25
|
|
|
25
26
|
// src/sql/query-rewriters.ts
|
|
26
|
-
var tableIdPropSelectionRegex = new RegExp([
|
|
27
|
-
`("(.+)"\\."(.+)")`,
|
|
28
|
-
`(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`
|
|
29
|
-
].join(""), "i");
|
|
30
27
|
function adaptArrayOperators(query) {
|
|
31
28
|
const operators = [
|
|
32
29
|
{ token: "@>", fn: "array_has_all" },
|
|
@@ -43,6 +40,8 @@ function adaptArrayOperators(query) {
|
|
|
43
40
|
let inString = false;
|
|
44
41
|
for (;idx >= 0; idx--) {
|
|
45
42
|
const ch = source[idx];
|
|
43
|
+
if (ch === undefined)
|
|
44
|
+
break;
|
|
46
45
|
if (ch === "'" && source[idx - 1] !== "\\") {
|
|
47
46
|
inString = !inString;
|
|
48
47
|
}
|
|
@@ -70,6 +69,8 @@ function adaptArrayOperators(query) {
|
|
|
70
69
|
let inString = false;
|
|
71
70
|
for (;idx < source.length; idx++) {
|
|
72
71
|
const ch = source[idx];
|
|
72
|
+
if (ch === undefined)
|
|
73
|
+
break;
|
|
73
74
|
if (ch === "'" && source[idx - 1] !== "\\") {
|
|
74
75
|
inString = !inString;
|
|
75
76
|
}
|
|
@@ -325,7 +326,11 @@ import {
|
|
|
325
326
|
timestampValue,
|
|
326
327
|
timestampTZValue
|
|
327
328
|
} from "@duckdb/node-api";
|
|
329
|
+
|
|
330
|
+
// src/value-wrappers-core.ts
|
|
328
331
|
var DUCKDB_VALUE_MARKER = Symbol.for("drizzle-duckdb:value");
|
|
332
|
+
|
|
333
|
+
// src/value-wrappers.ts
|
|
329
334
|
function dateToMicros(value) {
|
|
330
335
|
if (value instanceof Date) {
|
|
331
336
|
return BigInt(value.getTime()) * 1000n;
|
|
@@ -383,6 +388,9 @@ function wrapperToNodeApiValue(wrapper, toValue) {
|
|
|
383
388
|
}
|
|
384
389
|
|
|
385
390
|
// src/client.ts
|
|
391
|
+
function isPool(client) {
|
|
392
|
+
return typeof client.acquire === "function";
|
|
393
|
+
}
|
|
386
394
|
function isPgArrayLiteral(value) {
|
|
387
395
|
return value.startsWith("{") && value.endsWith("}");
|
|
388
396
|
}
|
|
@@ -394,15 +402,13 @@ function parsePgArrayLiteral(value) {
|
|
|
394
402
|
return value;
|
|
395
403
|
}
|
|
396
404
|
}
|
|
397
|
-
var warnedArrayLiteral = false;
|
|
398
405
|
function prepareParams(params, options = {}) {
|
|
399
406
|
return params.map((param) => {
|
|
400
407
|
if (typeof param === "string" && isPgArrayLiteral(param)) {
|
|
401
408
|
if (options.rejectStringArrayLiterals) {
|
|
402
409
|
throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
|
|
403
410
|
}
|
|
404
|
-
if (
|
|
405
|
-
warnedArrayLiteral = true;
|
|
411
|
+
if (options.warnOnStringArrayLiteral) {
|
|
406
412
|
options.warnOnStringArrayLiteral();
|
|
407
413
|
}
|
|
408
414
|
return parsePgArrayLiteral(param);
|
|
@@ -445,7 +451,28 @@ function mapRowsToObjects(columns, rows) {
|
|
|
445
451
|
return obj;
|
|
446
452
|
});
|
|
447
453
|
}
|
|
454
|
+
async function closeClientConnection(connection) {
|
|
455
|
+
if ("close" in connection && typeof connection.close === "function") {
|
|
456
|
+
await connection.close();
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if ("closeSync" in connection && typeof connection.closeSync === "function") {
|
|
460
|
+
connection.closeSync();
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if ("disconnectSync" in connection && typeof connection.disconnectSync === "function") {
|
|
464
|
+
connection.disconnectSync();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
448
467
|
async function executeOnClient(client, query, params) {
|
|
468
|
+
if (isPool(client)) {
|
|
469
|
+
const connection = await client.acquire();
|
|
470
|
+
try {
|
|
471
|
+
return await executeOnClient(connection, query, params);
|
|
472
|
+
} finally {
|
|
473
|
+
await client.release(connection);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
449
476
|
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
450
477
|
const result = await client.run(query, values);
|
|
451
478
|
const rows = await result.getRowsJS();
|
|
@@ -454,6 +481,15 @@ async function executeOnClient(client, query, params) {
|
|
|
454
481
|
return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
|
|
455
482
|
}
|
|
456
483
|
async function* executeInBatches(client, query, params, options = {}) {
|
|
484
|
+
if (isPool(client)) {
|
|
485
|
+
const connection = await client.acquire();
|
|
486
|
+
try {
|
|
487
|
+
yield* executeInBatches(connection, query, params, options);
|
|
488
|
+
return;
|
|
489
|
+
} finally {
|
|
490
|
+
await client.release(connection);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
457
493
|
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
458
494
|
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
459
495
|
const result = await client.stream(query, values);
|
|
@@ -475,6 +511,14 @@ async function* executeInBatches(client, query, params, options = {}) {
|
|
|
475
511
|
}
|
|
476
512
|
}
|
|
477
513
|
async function executeArrowOnClient(client, query, params) {
|
|
514
|
+
if (isPool(client)) {
|
|
515
|
+
const connection = await client.acquire();
|
|
516
|
+
try {
|
|
517
|
+
return await executeArrowOnClient(connection, query, params);
|
|
518
|
+
} finally {
|
|
519
|
+
await client.release(connection);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
478
522
|
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
479
523
|
const result = await client.run(query, values);
|
|
480
524
|
const maybeArrow = result.toArrow ?? result.getArrowTable;
|
|
@@ -563,16 +607,30 @@ class DuckDBSession extends PgSession {
|
|
|
563
607
|
return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.rewriteArrays, this.rejectStringArrayLiterals, this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral);
|
|
564
608
|
}
|
|
565
609
|
async transaction(transaction) {
|
|
566
|
-
|
|
610
|
+
let pinnedConnection;
|
|
611
|
+
let pool;
|
|
612
|
+
let clientForTx = this.client;
|
|
613
|
+
if (isPool(this.client)) {
|
|
614
|
+
pool = this.client;
|
|
615
|
+
pinnedConnection = await pool.acquire();
|
|
616
|
+
clientForTx = pinnedConnection;
|
|
617
|
+
}
|
|
618
|
+
const session = new DuckDBSession(clientForTx, this.dialect, this.schema, this.options);
|
|
567
619
|
const tx = new DuckDBTransaction(this.dialect, session, this.schema);
|
|
568
|
-
await tx.execute(sql`BEGIN TRANSACTION;`);
|
|
569
620
|
try {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
621
|
+
await tx.execute(sql`BEGIN TRANSACTION;`);
|
|
622
|
+
try {
|
|
623
|
+
const result = await transaction(tx);
|
|
624
|
+
await tx.execute(sql`commit`);
|
|
625
|
+
return result;
|
|
626
|
+
} catch (error) {
|
|
627
|
+
await tx.execute(sql`rollback`);
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
} finally {
|
|
631
|
+
if (pinnedConnection && pool) {
|
|
632
|
+
await pool.release(pinnedConnection);
|
|
633
|
+
}
|
|
576
634
|
}
|
|
577
635
|
}
|
|
578
636
|
warnOnStringArrayLiteral = (query) => {
|
|
@@ -820,6 +878,72 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
|
|
|
820
878
|
}
|
|
821
879
|
}
|
|
822
880
|
|
|
881
|
+
// src/pool.ts
|
|
882
|
+
import { DuckDBConnection } from "@duckdb/node-api";
|
|
883
|
+
var POOL_PRESETS = {
|
|
884
|
+
pulse: 4,
|
|
885
|
+
standard: 6,
|
|
886
|
+
jumbo: 8,
|
|
887
|
+
mega: 12,
|
|
888
|
+
giga: 16,
|
|
889
|
+
local: 8,
|
|
890
|
+
memory: 4
|
|
891
|
+
};
|
|
892
|
+
function resolvePoolSize(pool) {
|
|
893
|
+
if (pool === false)
|
|
894
|
+
return false;
|
|
895
|
+
if (pool === undefined)
|
|
896
|
+
return 4;
|
|
897
|
+
if (typeof pool === "string")
|
|
898
|
+
return POOL_PRESETS[pool];
|
|
899
|
+
return pool.size ?? 4;
|
|
900
|
+
}
|
|
901
|
+
function createDuckDBConnectionPool(instance, options = {}) {
|
|
902
|
+
const size = options.size && options.size > 0 ? options.size : 4;
|
|
903
|
+
const idle = [];
|
|
904
|
+
const waiting = [];
|
|
905
|
+
let total = 0;
|
|
906
|
+
let closed = false;
|
|
907
|
+
const acquire = async () => {
|
|
908
|
+
if (closed) {
|
|
909
|
+
throw new Error("DuckDB connection pool is closed");
|
|
910
|
+
}
|
|
911
|
+
if (idle.length > 0) {
|
|
912
|
+
return idle.pop();
|
|
913
|
+
}
|
|
914
|
+
if (total < size) {
|
|
915
|
+
total += 1;
|
|
916
|
+
return await DuckDBConnection.create(instance);
|
|
917
|
+
}
|
|
918
|
+
return await new Promise((resolve) => {
|
|
919
|
+
waiting.push(resolve);
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
const release = async (connection) => {
|
|
923
|
+
if (closed) {
|
|
924
|
+
await closeClientConnection(connection);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const waiter = waiting.shift();
|
|
928
|
+
if (waiter) {
|
|
929
|
+
waiter(connection);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
idle.push(connection);
|
|
933
|
+
};
|
|
934
|
+
const close = async () => {
|
|
935
|
+
closed = true;
|
|
936
|
+
const toClose = idle.splice(0, idle.length);
|
|
937
|
+
await Promise.all(toClose.map((conn) => closeClientConnection(conn)));
|
|
938
|
+
};
|
|
939
|
+
return {
|
|
940
|
+
acquire,
|
|
941
|
+
release,
|
|
942
|
+
close,
|
|
943
|
+
size
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
823
947
|
// src/driver.ts
|
|
824
948
|
class DuckDBDriver {
|
|
825
949
|
client;
|
|
@@ -839,7 +963,14 @@ class DuckDBDriver {
|
|
|
839
963
|
});
|
|
840
964
|
}
|
|
841
965
|
}
|
|
842
|
-
function
|
|
966
|
+
function isConfigObject(data) {
|
|
967
|
+
if (typeof data !== "object" || data === null)
|
|
968
|
+
return false;
|
|
969
|
+
if (data.constructor?.name !== "Object")
|
|
970
|
+
return false;
|
|
971
|
+
return "connection" in data || "client" in data || "pool" in data || "schema" in data || "logger" in data;
|
|
972
|
+
}
|
|
973
|
+
function createFromClient(client, config = {}, instance) {
|
|
843
974
|
const dialect = new DuckDBDialect;
|
|
844
975
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
845
976
|
let schema;
|
|
@@ -857,17 +988,60 @@ function drizzle(client, config = {}) {
|
|
|
857
988
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals
|
|
858
989
|
});
|
|
859
990
|
const session = driver.createSession(schema);
|
|
860
|
-
|
|
991
|
+
const db = new DuckDBDatabase(dialect, session, schema, client, instance);
|
|
992
|
+
return db;
|
|
993
|
+
}
|
|
994
|
+
async function createFromConnectionString(path, instanceOptions, config = {}) {
|
|
995
|
+
const instance = await DuckDBInstance2.create(path, instanceOptions);
|
|
996
|
+
const poolSize = resolvePoolSize(config.pool);
|
|
997
|
+
if (poolSize === false) {
|
|
998
|
+
const connection = await instance.connect();
|
|
999
|
+
return createFromClient(connection, config, instance);
|
|
1000
|
+
}
|
|
1001
|
+
const pool = createDuckDBConnectionPool(instance, { size: poolSize });
|
|
1002
|
+
return createFromClient(pool, config, instance);
|
|
1003
|
+
}
|
|
1004
|
+
function drizzle(clientOrConfigOrPath, config) {
|
|
1005
|
+
if (typeof clientOrConfigOrPath === "string") {
|
|
1006
|
+
return createFromConnectionString(clientOrConfigOrPath, undefined, config);
|
|
1007
|
+
}
|
|
1008
|
+
if (isConfigObject(clientOrConfigOrPath)) {
|
|
1009
|
+
const configObj = clientOrConfigOrPath;
|
|
1010
|
+
if ("connection" in configObj) {
|
|
1011
|
+
const connConfig = configObj;
|
|
1012
|
+
const { connection, ...restConfig } = connConfig;
|
|
1013
|
+
if (typeof connection === "string") {
|
|
1014
|
+
return createFromConnectionString(connection, undefined, restConfig);
|
|
1015
|
+
}
|
|
1016
|
+
return createFromConnectionString(connection.path, connection.options, restConfig);
|
|
1017
|
+
}
|
|
1018
|
+
if ("client" in configObj) {
|
|
1019
|
+
const clientConfig = configObj;
|
|
1020
|
+
const { client: clientValue, ...restConfig } = clientConfig;
|
|
1021
|
+
return createFromClient(clientValue, restConfig);
|
|
1022
|
+
}
|
|
1023
|
+
throw new Error("Invalid drizzle config: either connection or client must be provided");
|
|
1024
|
+
}
|
|
1025
|
+
return createFromClient(clientOrConfigOrPath, config);
|
|
861
1026
|
}
|
|
862
1027
|
|
|
863
1028
|
class DuckDBDatabase extends PgDatabase {
|
|
864
1029
|
dialect;
|
|
865
1030
|
session;
|
|
866
1031
|
static [entityKind3] = "DuckDBDatabase";
|
|
867
|
-
|
|
1032
|
+
$client;
|
|
1033
|
+
$instance;
|
|
1034
|
+
constructor(dialect, session, schema, client, instance) {
|
|
868
1035
|
super(dialect, session, schema);
|
|
869
1036
|
this.dialect = dialect;
|
|
870
1037
|
this.session = session;
|
|
1038
|
+
this.$client = client;
|
|
1039
|
+
this.$instance = instance;
|
|
1040
|
+
}
|
|
1041
|
+
async close() {
|
|
1042
|
+
if (isPool(this.$client) && this.$client.close) {
|
|
1043
|
+
await this.$client.close();
|
|
1044
|
+
}
|
|
871
1045
|
}
|
|
872
1046
|
select(fields) {
|
|
873
1047
|
const selectedFields = fields ? aliasFields(fields) : undefined;
|
|
@@ -891,7 +1065,7 @@ class DuckDBDatabase extends PgDatabase {
|
|
|
891
1065
|
// src/introspect.ts
|
|
892
1066
|
import { sql as sql4 } from "drizzle-orm";
|
|
893
1067
|
var SYSTEM_SCHEMAS = new Set(["information_schema", "pg_catalog"]);
|
|
894
|
-
var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb";
|
|
1068
|
+
var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb/helpers";
|
|
895
1069
|
async function introspect(db, opts = {}) {
|
|
896
1070
|
const database = await resolveDatabase(db, opts.database, opts.allDatabases);
|
|
897
1071
|
const schemas = await resolveSchemas(db, database, opts.schemas);
|
|
@@ -1227,6 +1401,22 @@ function mapDuckDbType(column, imports, options) {
|
|
|
1227
1401
|
imports.pgCore.add("doublePrecision");
|
|
1228
1402
|
return { builder: `doublePrecision(${columnName(column.name)})` };
|
|
1229
1403
|
}
|
|
1404
|
+
const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
|
|
1405
|
+
if (arrayMatch) {
|
|
1406
|
+
imports.local.add("duckDbArray");
|
|
1407
|
+
const [, base, length] = arrayMatch;
|
|
1408
|
+
return {
|
|
1409
|
+
builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
const listMatch = /^(.*)\[\]$/.exec(upper);
|
|
1413
|
+
if (listMatch) {
|
|
1414
|
+
imports.local.add("duckDbList");
|
|
1415
|
+
const [, base] = listMatch;
|
|
1416
|
+
return {
|
|
1417
|
+
builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1230
1420
|
if (upper.startsWith("CHAR(") || upper === "CHAR") {
|
|
1231
1421
|
imports.pgCore.add("char");
|
|
1232
1422
|
const length = column.characterLength;
|
|
@@ -1267,22 +1457,6 @@ function mapDuckDbType(column, imports, options) {
|
|
|
1267
1457
|
imports.local.add("duckDbBlob");
|
|
1268
1458
|
return { builder: `duckDbBlob(${columnName(column.name)})` };
|
|
1269
1459
|
}
|
|
1270
|
-
const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
|
|
1271
|
-
if (arrayMatch) {
|
|
1272
|
-
imports.local.add("duckDbArray");
|
|
1273
|
-
const [, base, length] = arrayMatch;
|
|
1274
|
-
return {
|
|
1275
|
-
builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
const listMatch = /^(.*)\[\]$/.exec(upper);
|
|
1279
|
-
if (listMatch) {
|
|
1280
|
-
imports.local.add("duckDbList");
|
|
1281
|
-
const [, base] = listMatch;
|
|
1282
|
-
return {
|
|
1283
|
-
builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
1460
|
if (upper.startsWith("STRUCT")) {
|
|
1287
1461
|
imports.local.add("duckDbStruct");
|
|
1288
1462
|
const inner = upper.replace(/^STRUCT\s*\(/i, "").replace(/\)$/, "");
|
|
@@ -1531,7 +1705,7 @@ async function main() {
|
|
|
1531
1705
|
throw new Error("Missing required --url");
|
|
1532
1706
|
}
|
|
1533
1707
|
const instanceOptions = options.url.startsWith("md:") && process.env.MOTHERDUCK_TOKEN ? { motherduck_token: process.env.MOTHERDUCK_TOKEN } : undefined;
|
|
1534
|
-
const instance = await
|
|
1708
|
+
const instance = await DuckDBInstance3.create(options.url, instanceOptions);
|
|
1535
1709
|
const connection = await instance.connect();
|
|
1536
1710
|
const db = drizzle(connection);
|
|
1537
1711
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { duckDbList, duckDbArray, duckDbMap, duckDbStruct, duckDbJson, duckDbBlob, duckDbInet, duckDbInterval, duckDbTimestamp, duckDbDate, duckDbTime, duckDbArrayContains, duckDbArrayContained, duckDbArrayOverlaps, } from './columns.ts';
|