@leonardovida-md/drizzle-neo-duckdb 1.0.3 → 1.1.1
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 +6 -1
- package/dist/dialect.d.ts +21 -0
- package/dist/driver.d.ts +33 -1
- package/dist/duckdb-introspect.mjs +610 -114
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.mjs +319 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +603 -117
- package/dist/introspect.d.ts +9 -0
- package/dist/pool.d.ts +30 -0
- package/dist/session.d.ts +7 -1
- package/dist/sql/query-rewriters.d.ts +1 -1
- package/dist/sql/result-mapper.d.ts +7 -0
- 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/bin/duckdb-introspect.ts +27 -0
- package/src/client.ts +54 -13
- package/src/columns.ts +10 -10
- package/src/dialect.ts +51 -3
- package/src/driver.ts +204 -7
- package/src/helpers.ts +18 -0
- package/src/index.ts +1 -0
- package/src/introspect.ts +47 -29
- package/src/migrator.ts +1 -1
- package/src/olap.ts +1 -0
- package/src/pool.ts +274 -0
- package/src/session.ts +134 -15
- package/src/sql/query-rewriters.ts +177 -116
- package/src/sql/result-mapper.ts +7 -7
- package/src/utils.ts +1 -1
- package/src/value-wrappers-core.ts +156 -0
- package/src/value-wrappers.ts +60 -219
package/dist/introspect.d.ts
CHANGED
|
@@ -61,5 +61,14 @@ export interface IntrospectResult {
|
|
|
61
61
|
relationsTs?: string;
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
export declare const DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb/helpers";
|
|
64
65
|
export declare function introspect(db: DuckDBDatabase, opts?: IntrospectOptions): Promise<IntrospectResult>;
|
|
66
|
+
export declare function buildDefault(defaultValue: string | null): string;
|
|
67
|
+
export declare function parseStructFields(inner: string): Array<{
|
|
68
|
+
name: string;
|
|
69
|
+
type: string;
|
|
70
|
+
}>;
|
|
71
|
+
export declare function parseMapValue(raw: string): string;
|
|
72
|
+
export declare function splitTopLevel(input: string, delimiter: string): string[];
|
|
73
|
+
export declare function toIdentifier(name: string): string;
|
|
65
74
|
export {};
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DuckDBInstance } from '@duckdb/node-api';
|
|
2
|
+
import { type DuckDBConnectionPool } from './client.ts';
|
|
3
|
+
/** Pool size presets for different MotherDuck instance types */
|
|
4
|
+
export type PoolPreset = 'pulse' | 'standard' | 'jumbo' | 'mega' | 'giga' | 'local' | 'memory';
|
|
5
|
+
/** Pool sizes optimized for each MotherDuck instance type */
|
|
6
|
+
export declare const POOL_PRESETS: Record<PoolPreset, number>;
|
|
7
|
+
export interface DuckDBPoolConfig {
|
|
8
|
+
/** Maximum concurrent connections. Defaults to 4. */
|
|
9
|
+
size?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve pool configuration to a concrete size.
|
|
13
|
+
* Returns false if pooling is disabled.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolvePoolSize(pool: DuckDBPoolConfig | PoolPreset | false | undefined): number | false;
|
|
16
|
+
export interface DuckDBConnectionPoolOptions {
|
|
17
|
+
/** Maximum concurrent connections. Defaults to 4. */
|
|
18
|
+
size?: number;
|
|
19
|
+
/** Timeout in milliseconds to wait for a connection. Defaults to 30000 (30s). */
|
|
20
|
+
acquireTimeout?: number;
|
|
21
|
+
/** Maximum number of requests waiting for a connection. Defaults to 100. */
|
|
22
|
+
maxWaitingRequests?: number;
|
|
23
|
+
/** Max time (ms) a connection may live before being recycled. */
|
|
24
|
+
maxLifetimeMs?: number;
|
|
25
|
+
/** Max idle time (ms) before an idle connection is discarded. */
|
|
26
|
+
idleTimeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function createDuckDBConnectionPool(instance: DuckDBInstance, options?: DuckDBConnectionPoolOptions): DuckDBConnectionPool & {
|
|
29
|
+
size: number;
|
|
30
|
+
};
|
package/dist/session.d.ts
CHANGED
|
@@ -44,12 +44,17 @@ export declare class DuckDBSession<TFullSchema extends Record<string, unknown> =
|
|
|
44
44
|
private rewriteArrays;
|
|
45
45
|
private rejectStringArrayLiterals;
|
|
46
46
|
private hasWarnedArrayLiteral;
|
|
47
|
+
private rollbackOnly;
|
|
47
48
|
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, schema: RelationalSchemaConfig<TSchema> | undefined, options?: DuckDBSessionOptions);
|
|
48
49
|
prepareQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(query: Query, fields: SelectedFieldsOrdered | undefined, name: string | undefined, isResponseInArrayMode: boolean, customResultMapper?: (rows: unknown[][]) => T['execute']): PgPreparedQuery<T>;
|
|
49
|
-
|
|
50
|
+
execute<T>(query: SQL): Promise<T>;
|
|
51
|
+
all<T = unknown>(query: SQL): Promise<T[]>;
|
|
52
|
+
transaction<T>(transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>, config?: PgTransactionConfig): Promise<T>;
|
|
50
53
|
private warnOnStringArrayLiteral;
|
|
51
54
|
executeBatches<T extends RowData = RowData>(query: SQL, options?: ExecuteInBatchesOptions): AsyncGenerator<GenericRowData<T>[], void, void>;
|
|
52
55
|
executeArrow(query: SQL): Promise<unknown>;
|
|
56
|
+
markRollbackOnly(): void;
|
|
57
|
+
isRollbackOnly(): boolean;
|
|
53
58
|
}
|
|
54
59
|
export declare class DuckDBTransaction<TFullSchema extends Record<string, unknown>, TSchema extends TablesRelationalConfig> extends PgTransaction<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
55
60
|
static readonly [entityKind]: string;
|
|
@@ -59,6 +64,7 @@ export declare class DuckDBTransaction<TFullSchema extends Record<string, unknow
|
|
|
59
64
|
executeBatches<T extends RowData = RowData>(query: SQL, options?: ExecuteInBatchesOptions): AsyncGenerator<GenericRowData<T>[], void, void>;
|
|
60
65
|
executeArrow(query: SQL): Promise<unknown>;
|
|
61
66
|
transaction<T>(transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T>;
|
|
67
|
+
private runNestedWithoutSavepoint;
|
|
62
68
|
}
|
|
63
69
|
export type GenericRowData<T extends RowData = RowData> = T;
|
|
64
70
|
export type GenericTableData<T = RowData> = T[];
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { type AnyColumn, type SelectedFieldsOrdered } from 'drizzle-orm';
|
|
2
|
+
export declare function normalizeInet(value: unknown): unknown;
|
|
3
|
+
export declare function normalizeTimestampString(value: unknown, withTimezone: boolean): string | unknown;
|
|
4
|
+
export declare function normalizeTimestamp(value: unknown, withTimezone: boolean): Date | unknown;
|
|
5
|
+
export declare function normalizeDateString(value: unknown): string | unknown;
|
|
6
|
+
export declare function normalizeDateValue(value: unknown): Date | unknown;
|
|
7
|
+
export declare function normalizeTime(value: unknown): string | unknown;
|
|
8
|
+
export declare function normalizeInterval(value: unknown): string | unknown;
|
|
2
9
|
export declare function mapResultRow<TResult>(columns: SelectedFieldsOrdered<AnyColumn>, row: unknown[], joinsNotNullableMap: Record<string, boolean> | undefined): TResult;
|
package/dist/utils.d.ts
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DuckDB wrapper value helpers that are safe for client-side bundles.
|
|
3
|
+
* These utilities only tag values; conversion to native bindings lives
|
|
4
|
+
* in value-wrappers.ts to avoid pulling @duckdb/node-api into browsers.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DUCKDB_VALUE_MARKER: unique symbol;
|
|
7
|
+
export type DuckDBValueKind = 'list' | 'array' | 'struct' | 'map' | 'timestamp' | 'blob' | 'json';
|
|
8
|
+
export interface DuckDBValueWrapper<TKind extends DuckDBValueKind = DuckDBValueKind, TData = unknown> {
|
|
9
|
+
readonly [DUCKDB_VALUE_MARKER]: true;
|
|
10
|
+
readonly kind: TKind;
|
|
11
|
+
readonly data: TData;
|
|
12
|
+
}
|
|
13
|
+
export interface ListValueWrapper extends DuckDBValueWrapper<'list', unknown[]> {
|
|
14
|
+
readonly elementType?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ArrayValueWrapper extends DuckDBValueWrapper<'array', unknown[]> {
|
|
17
|
+
readonly elementType?: string;
|
|
18
|
+
readonly fixedLength?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface StructValueWrapper extends DuckDBValueWrapper<'struct', Record<string, unknown>> {
|
|
21
|
+
readonly schema?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
export interface MapValueWrapper extends DuckDBValueWrapper<'map', Record<string, unknown>> {
|
|
24
|
+
readonly valueType?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface TimestampValueWrapper extends DuckDBValueWrapper<'timestamp', Date | string | number | bigint> {
|
|
27
|
+
readonly withTimezone: boolean;
|
|
28
|
+
readonly precision?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface BlobValueWrapper extends DuckDBValueWrapper<'blob', Buffer | Uint8Array> {
|
|
31
|
+
}
|
|
32
|
+
export interface JsonValueWrapper extends DuckDBValueWrapper<'json', unknown> {
|
|
33
|
+
}
|
|
34
|
+
export type AnyDuckDBValueWrapper = ListValueWrapper | ArrayValueWrapper | StructValueWrapper | MapValueWrapper | TimestampValueWrapper | BlobValueWrapper | JsonValueWrapper;
|
|
35
|
+
export declare function isDuckDBWrapper(value: unknown): value is AnyDuckDBValueWrapper;
|
|
36
|
+
export declare function wrapList(data: unknown[], elementType?: string): ListValueWrapper;
|
|
37
|
+
export declare function wrapArray(data: unknown[], elementType?: string, fixedLength?: number): ArrayValueWrapper;
|
|
38
|
+
export declare function wrapStruct(data: Record<string, unknown>, schema?: Record<string, string>): StructValueWrapper;
|
|
39
|
+
export declare function wrapMap(data: Record<string, unknown>, valueType?: string): MapValueWrapper;
|
|
40
|
+
export declare function wrapTimestamp(data: Date | string | number | bigint, withTimezone: boolean, precision?: number): TimestampValueWrapper;
|
|
41
|
+
export declare function wrapBlob(data: Buffer | Uint8Array): BlobValueWrapper;
|
|
42
|
+
export declare function wrapJson(data: unknown): JsonValueWrapper;
|
package/dist/value-wrappers.d.ts
CHANGED
|
@@ -1,104 +1,8 @@
|
|
|
1
1
|
import { type DuckDBValue } from '@duckdb/node-api';
|
|
2
|
-
|
|
3
|
-
* Symbol used to identify wrapped DuckDB values for native binding.
|
|
4
|
-
* Uses Symbol.for() to ensure cross-module compatibility.
|
|
5
|
-
*/
|
|
6
|
-
export declare const DUCKDB_VALUE_MARKER: unique symbol;
|
|
7
|
-
/**
|
|
8
|
-
* Type identifier for each wrapper kind.
|
|
9
|
-
*/
|
|
10
|
-
export type DuckDBValueKind = 'list' | 'array' | 'struct' | 'map' | 'timestamp' | 'blob' | 'json';
|
|
11
|
-
/**
|
|
12
|
-
* Base interface for all tagged DuckDB value wrappers.
|
|
13
|
-
*/
|
|
14
|
-
export interface DuckDBValueWrapper<TKind extends DuckDBValueKind = DuckDBValueKind, TData = unknown> {
|
|
15
|
-
readonly [DUCKDB_VALUE_MARKER]: true;
|
|
16
|
-
readonly kind: TKind;
|
|
17
|
-
readonly data: TData;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* List wrapper - maps to DuckDBListValue
|
|
21
|
-
*/
|
|
22
|
-
export interface ListValueWrapper extends DuckDBValueWrapper<'list', unknown[]> {
|
|
23
|
-
readonly elementType?: string;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Array wrapper (fixed size) - maps to DuckDBArrayValue
|
|
27
|
-
*/
|
|
28
|
-
export interface ArrayValueWrapper extends DuckDBValueWrapper<'array', unknown[]> {
|
|
29
|
-
readonly elementType?: string;
|
|
30
|
-
readonly fixedLength?: number;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Struct wrapper - maps to DuckDBStructValue
|
|
34
|
-
*/
|
|
35
|
-
export interface StructValueWrapper extends DuckDBValueWrapper<'struct', Record<string, unknown>> {
|
|
36
|
-
readonly schema?: Record<string, string>;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Map wrapper - maps to DuckDBMapValue
|
|
40
|
-
*/
|
|
41
|
-
export interface MapValueWrapper extends DuckDBValueWrapper<'map', Record<string, unknown>> {
|
|
42
|
-
readonly valueType?: string;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Timestamp wrapper - maps to DuckDBTimestampValue or DuckDBTimestampTZValue
|
|
46
|
-
*/
|
|
47
|
-
export interface TimestampValueWrapper extends DuckDBValueWrapper<'timestamp', Date | string> {
|
|
48
|
-
readonly withTimezone: boolean;
|
|
49
|
-
readonly precision?: number;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Blob wrapper - maps to DuckDBBlobValue
|
|
53
|
-
*/
|
|
54
|
-
export interface BlobValueWrapper extends DuckDBValueWrapper<'blob', Buffer | Uint8Array> {
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* JSON wrapper - delays JSON.stringify() to binding time.
|
|
58
|
-
* DuckDB stores JSON as VARCHAR internally.
|
|
59
|
-
*/
|
|
60
|
-
export interface JsonValueWrapper extends DuckDBValueWrapper<'json', unknown> {
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Union of all wrapper types for exhaustive type checking.
|
|
64
|
-
*/
|
|
65
|
-
export type AnyDuckDBValueWrapper = ListValueWrapper | ArrayValueWrapper | StructValueWrapper | MapValueWrapper | TimestampValueWrapper | BlobValueWrapper | JsonValueWrapper;
|
|
66
|
-
/**
|
|
67
|
-
* Type guard to check if a value is a tagged DuckDB wrapper.
|
|
68
|
-
* Optimized for fast detection in the hot path.
|
|
69
|
-
*/
|
|
70
|
-
export declare function isDuckDBWrapper(value: unknown): value is AnyDuckDBValueWrapper;
|
|
71
|
-
/**
|
|
72
|
-
* Create a list wrapper for variable-length lists.
|
|
73
|
-
*/
|
|
74
|
-
export declare function wrapList(data: unknown[], elementType?: string): ListValueWrapper;
|
|
75
|
-
/**
|
|
76
|
-
* Create an array wrapper for fixed-length arrays.
|
|
77
|
-
*/
|
|
78
|
-
export declare function wrapArray(data: unknown[], elementType?: string, fixedLength?: number): ArrayValueWrapper;
|
|
79
|
-
/**
|
|
80
|
-
* Create a struct wrapper for named field structures.
|
|
81
|
-
*/
|
|
82
|
-
export declare function wrapStruct(data: Record<string, unknown>, schema?: Record<string, string>): StructValueWrapper;
|
|
83
|
-
/**
|
|
84
|
-
* Create a map wrapper for key-value maps.
|
|
85
|
-
*/
|
|
86
|
-
export declare function wrapMap(data: Record<string, unknown>, valueType?: string): MapValueWrapper;
|
|
87
|
-
/**
|
|
88
|
-
* Create a timestamp wrapper.
|
|
89
|
-
*/
|
|
90
|
-
export declare function wrapTimestamp(data: Date | string, withTimezone: boolean, precision?: number): TimestampValueWrapper;
|
|
91
|
-
/**
|
|
92
|
-
* Create a blob wrapper for binary data.
|
|
93
|
-
*/
|
|
94
|
-
export declare function wrapBlob(data: Buffer | Uint8Array): BlobValueWrapper;
|
|
95
|
-
/**
|
|
96
|
-
* Create a JSON wrapper that delays JSON.stringify() to binding time.
|
|
97
|
-
* This ensures consistent handling with other wrapped types.
|
|
98
|
-
*/
|
|
99
|
-
export declare function wrapJson(data: unknown): JsonValueWrapper;
|
|
2
|
+
import { type AnyDuckDBValueWrapper } from './value-wrappers-core.ts';
|
|
100
3
|
/**
|
|
101
4
|
* Convert a wrapper to a DuckDB Node API value.
|
|
102
5
|
* Uses exhaustive switch for compile-time safety.
|
|
103
6
|
*/
|
|
104
7
|
export declare function wrapperToNodeApiValue(wrapper: AnyDuckDBValueWrapper, toValue: (v: unknown) => DuckDBValue): DuckDBValue;
|
|
8
|
+
export { DUCKDB_VALUE_MARKER, isDuckDBWrapper, wrapArray, wrapBlob, wrapJson, wrapList, wrapMap, wrapStruct, wrapTimestamp, type AnyDuckDBValueWrapper, type DuckDBValueWrapper, type ArrayValueWrapper, type BlobValueWrapper, type JsonValueWrapper, type ListValueWrapper, type MapValueWrapper, type StructValueWrapper, type TimestampValueWrapper, type DuckDBValueKind, } from './value-wrappers-core.ts';
|
package/package.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"module": "./dist/index.mjs",
|
|
4
4
|
"main": "./dist/index.mjs",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.1.1",
|
|
7
7
|
"description": "A drizzle ORM client for use with DuckDB. Based on drizzle's Postgres client.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "bun build --target=node ./src/index.ts --outfile=./dist/index.mjs --packages=external && bun build --target=node ./src/bin/duckdb-introspect.ts --outfile=./dist/duckdb-introspect.mjs --packages=external && bun run build:declarations",
|
|
10
|
+
"build": "bun build --target=node ./src/index.ts --outfile=./dist/index.mjs --packages=external && bun build --target=node ./src/helpers.ts --outfile=./dist/helpers.mjs --packages=external && bun build --target=node ./src/bin/duckdb-introspect.ts --outfile=./dist/duckdb-introspect.mjs --packages=external && bun run build:declarations",
|
|
11
11
|
"build:declarations": "tsc --emitDeclarationOnly --project tsconfig.types.json",
|
|
12
12
|
"test": "vitest",
|
|
13
13
|
"t": "vitest --watch --ui",
|
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"types": "./dist/index.d.ts",
|
|
49
49
|
"import": "./dist/index.mjs"
|
|
50
50
|
},
|
|
51
|
+
"./helpers": {
|
|
52
|
+
"types": "./dist/helpers.d.ts",
|
|
53
|
+
"import": "./dist/helpers.mjs"
|
|
54
|
+
},
|
|
51
55
|
"./package.json": "./package.json"
|
|
52
56
|
},
|
|
53
57
|
"sideEffects": false,
|
|
@@ -12,6 +12,7 @@ interface CliOptions {
|
|
|
12
12
|
allDatabases: boolean;
|
|
13
13
|
schemas?: string[];
|
|
14
14
|
outFile: string;
|
|
15
|
+
outMeta?: string;
|
|
15
16
|
includeViews: boolean;
|
|
16
17
|
useCustomTimeTypes: boolean;
|
|
17
18
|
importBasePath?: string;
|
|
@@ -20,6 +21,7 @@ interface CliOptions {
|
|
|
20
21
|
function parseArgs(argv: string[]): CliOptions {
|
|
21
22
|
const options: CliOptions = {
|
|
22
23
|
outFile: path.resolve(process.cwd(), 'drizzle/schema.ts'),
|
|
24
|
+
outMeta: undefined,
|
|
23
25
|
allDatabases: false,
|
|
24
26
|
includeViews: false,
|
|
25
27
|
useCustomTimeTypes: true,
|
|
@@ -52,6 +54,14 @@ function parseArgs(argv: string[]): CliOptions {
|
|
|
52
54
|
argv[++i] ?? 'drizzle/schema.ts'
|
|
53
55
|
);
|
|
54
56
|
break;
|
|
57
|
+
case '--out-json':
|
|
58
|
+
case '--outJson':
|
|
59
|
+
case '--json':
|
|
60
|
+
options.outMeta = path.resolve(
|
|
61
|
+
process.cwd(),
|
|
62
|
+
argv[++i] ?? 'drizzle/schema.meta.json'
|
|
63
|
+
);
|
|
64
|
+
break;
|
|
55
65
|
case '--include-views':
|
|
56
66
|
case '--includeViews':
|
|
57
67
|
options.includeViews = true;
|
|
@@ -88,6 +98,7 @@ Options:
|
|
|
88
98
|
--all-databases Introspect all attached databases (not just current)
|
|
89
99
|
--schema Comma separated schema list (defaults to all non-system schemas)
|
|
90
100
|
--out Output file (default: ./drizzle/schema.ts)
|
|
101
|
+
--json Optional JSON metadata output (default: ./drizzle/schema.meta.json)
|
|
91
102
|
--include-views Include views in the generated schema
|
|
92
103
|
--use-pg-time Use pg-core timestamp/date/time instead of DuckDB custom helpers
|
|
93
104
|
--import-base Override import path for duckdb helpers (default: package name)
|
|
@@ -136,8 +147,19 @@ async function main() {
|
|
|
136
147
|
|
|
137
148
|
await mkdir(path.dirname(options.outFile), { recursive: true });
|
|
138
149
|
await writeFile(options.outFile, result.files.schemaTs, 'utf8');
|
|
150
|
+
if (options.outMeta) {
|
|
151
|
+
await mkdir(path.dirname(options.outMeta), { recursive: true });
|
|
152
|
+
await writeFile(
|
|
153
|
+
options.outMeta,
|
|
154
|
+
JSON.stringify(result.files.metaJson, null, 2),
|
|
155
|
+
'utf8'
|
|
156
|
+
);
|
|
157
|
+
}
|
|
139
158
|
|
|
140
159
|
console.log(`Wrote schema to ${options.outFile}`);
|
|
160
|
+
if (options.outMeta) {
|
|
161
|
+
console.log(`Wrote metadata to ${options.outMeta}`);
|
|
162
|
+
}
|
|
141
163
|
} finally {
|
|
142
164
|
if (
|
|
143
165
|
'closeSync' in connection &&
|
|
@@ -145,6 +167,11 @@ async function main() {
|
|
|
145
167
|
) {
|
|
146
168
|
connection.closeSync();
|
|
147
169
|
}
|
|
170
|
+
if ('closeSync' in instance && typeof instance.closeSync === 'function') {
|
|
171
|
+
instance.closeSync();
|
|
172
|
+
} else if ('close' in instance && typeof instance.close === 'function') {
|
|
173
|
+
await instance.close();
|
|
174
|
+
}
|
|
148
175
|
}
|
|
149
176
|
}
|
|
150
177
|
|
package/src/client.ts
CHANGED
|
@@ -10,9 +10,21 @@ import {
|
|
|
10
10
|
type AnyDuckDBValueWrapper,
|
|
11
11
|
} from './value-wrappers.ts';
|
|
12
12
|
|
|
13
|
-
export type DuckDBClientLike = DuckDBConnection;
|
|
13
|
+
export type DuckDBClientLike = DuckDBConnection | DuckDBConnectionPool;
|
|
14
14
|
export type RowData = Record<string, unknown>;
|
|
15
15
|
|
|
16
|
+
export interface DuckDBConnectionPool {
|
|
17
|
+
acquire(): Promise<DuckDBConnection>;
|
|
18
|
+
release(connection: DuckDBConnection): void | Promise<void>;
|
|
19
|
+
close?(): Promise<void> | void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isPool(
|
|
23
|
+
client: DuckDBClientLike
|
|
24
|
+
): client is DuckDBConnectionPool {
|
|
25
|
+
return typeof (client as DuckDBConnectionPool).acquire === 'function';
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
export interface PrepareParamsOptions {
|
|
17
29
|
rejectStringArrayLiterals?: boolean;
|
|
18
30
|
warnOnStringArrayLiteral?: () => void;
|
|
@@ -32,25 +44,25 @@ function parsePgArrayLiteral(value: string): unknown {
|
|
|
32
44
|
}
|
|
33
45
|
}
|
|
34
46
|
|
|
35
|
-
let warnedArrayLiteral = false;
|
|
36
|
-
|
|
37
47
|
export function prepareParams(
|
|
38
48
|
params: unknown[],
|
|
39
49
|
options: PrepareParamsOptions = {}
|
|
40
50
|
): unknown[] {
|
|
41
51
|
return params.map((param) => {
|
|
42
|
-
if (typeof param === 'string'
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
if (typeof param === 'string') {
|
|
53
|
+
const trimmed = param.trim();
|
|
54
|
+
if (trimmed && isPgArrayLiteral(trimmed)) {
|
|
55
|
+
if (options.rejectStringArrayLiterals) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
'Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
if (options.warnOnStringArrayLiteral) {
|
|
62
|
+
options.warnOnStringArrayLiteral();
|
|
63
|
+
}
|
|
64
|
+
return parsePgArrayLiteral(trimmed);
|
|
52
65
|
}
|
|
53
|
-
return parsePgArrayLiteral(param);
|
|
54
66
|
}
|
|
55
67
|
return param;
|
|
56
68
|
});
|
|
@@ -138,6 +150,15 @@ export async function executeOnClient(
|
|
|
138
150
|
query: string,
|
|
139
151
|
params: unknown[]
|
|
140
152
|
): Promise<RowData[]> {
|
|
153
|
+
if (isPool(client)) {
|
|
154
|
+
const connection = await client.acquire();
|
|
155
|
+
try {
|
|
156
|
+
return await executeOnClient(connection, query, params);
|
|
157
|
+
} finally {
|
|
158
|
+
await client.release(connection);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
141
162
|
const values =
|
|
142
163
|
params.length > 0
|
|
143
164
|
? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
|
|
@@ -165,6 +186,16 @@ export async function* executeInBatches(
|
|
|
165
186
|
params: unknown[],
|
|
166
187
|
options: ExecuteInBatchesOptions = {}
|
|
167
188
|
): AsyncGenerator<RowData[], void, void> {
|
|
189
|
+
if (isPool(client)) {
|
|
190
|
+
const connection = await client.acquire();
|
|
191
|
+
try {
|
|
192
|
+
yield* executeInBatches(connection, query, params, options);
|
|
193
|
+
return;
|
|
194
|
+
} finally {
|
|
195
|
+
await client.release(connection);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
168
199
|
const rowsPerChunk =
|
|
169
200
|
options.rowsPerChunk && options.rowsPerChunk > 0
|
|
170
201
|
? options.rowsPerChunk
|
|
@@ -207,12 +238,22 @@ export async function executeArrowOnClient(
|
|
|
207
238
|
query: string,
|
|
208
239
|
params: unknown[]
|
|
209
240
|
): Promise<unknown> {
|
|
241
|
+
if (isPool(client)) {
|
|
242
|
+
const connection = await client.acquire();
|
|
243
|
+
try {
|
|
244
|
+
return await executeArrowOnClient(connection, query, params);
|
|
245
|
+
} finally {
|
|
246
|
+
await client.release(connection);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
210
250
|
const values =
|
|
211
251
|
params.length > 0
|
|
212
252
|
? (params.map((param) => toNodeApiValue(param)) as DuckDBValue[])
|
|
213
253
|
: undefined;
|
|
214
254
|
const result = await client.run(query, values);
|
|
215
255
|
|
|
256
|
+
// Runtime detection for Arrow API support (optional method, not in base type)
|
|
216
257
|
const maybeArrow =
|
|
217
258
|
(result as unknown as { toArrow?: () => Promise<unknown> }).toArrow ??
|
|
218
259
|
(result as unknown as { getArrowTable?: () => Promise<unknown> })
|
package/src/columns.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type MapValueWrapper,
|
|
13
13
|
type BlobValueWrapper,
|
|
14
14
|
type JsonValueWrapper,
|
|
15
|
-
} from './value-wrappers.ts';
|
|
15
|
+
} from './value-wrappers-core.ts';
|
|
16
16
|
|
|
17
17
|
type IntColType =
|
|
18
18
|
| 'SMALLINT'
|
|
@@ -63,7 +63,7 @@ type StructColType = `STRUCT (${string})`;
|
|
|
63
63
|
|
|
64
64
|
type Primitive = AnyColType | ListColType | ArrayColType | StructColType;
|
|
65
65
|
|
|
66
|
-
function coerceArrayString(value: string): unknown[] | undefined {
|
|
66
|
+
export function coerceArrayString(value: string): unknown[] | undefined {
|
|
67
67
|
const trimmed = value.trim();
|
|
68
68
|
if (!trimmed) {
|
|
69
69
|
return [];
|
|
@@ -86,7 +86,7 @@ function coerceArrayString(value: string): unknown[] | undefined {
|
|
|
86
86
|
return undefined;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function formatLiteral(value: unknown, typeHint?: string): string {
|
|
89
|
+
export function formatLiteral(value: unknown, typeHint?: string): string {
|
|
90
90
|
if (value === null || value === undefined) {
|
|
91
91
|
return 'NULL';
|
|
92
92
|
}
|
|
@@ -123,7 +123,7 @@ function formatLiteral(value: unknown, typeHint?: string): string {
|
|
|
123
123
|
return `'${escaped}'`;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
function buildListLiteral(values: unknown[], elementType?: string): SQL {
|
|
126
|
+
export function buildListLiteral(values: unknown[], elementType?: string): SQL {
|
|
127
127
|
if (values.length === 0) {
|
|
128
128
|
return sql`[]`;
|
|
129
129
|
}
|
|
@@ -135,7 +135,7 @@ function buildListLiteral(values: unknown[], elementType?: string): SQL {
|
|
|
135
135
|
return sql`list_value(${sql.join(chunks, sql.raw(', '))})`;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function buildStructLiteral(
|
|
138
|
+
export function buildStructLiteral(
|
|
139
139
|
value: Record<string, unknown>,
|
|
140
140
|
schema?: Record<string, Primitive>
|
|
141
141
|
): SQL {
|
|
@@ -154,7 +154,7 @@ function buildStructLiteral(
|
|
|
154
154
|
return sql`struct_pack(${sql.join(parts, sql.raw(', '))})`;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
function buildMapLiteral(
|
|
157
|
+
export function buildMapLiteral(
|
|
158
158
|
value: Record<string, unknown>,
|
|
159
159
|
valueType?: string
|
|
160
160
|
): SQL {
|
|
@@ -188,11 +188,11 @@ export const duckDbList = <TData = unknown>(
|
|
|
188
188
|
}
|
|
189
189
|
if (typeof value === 'string') {
|
|
190
190
|
const parsed = coerceArrayString(value);
|
|
191
|
-
if (parsed) {
|
|
191
|
+
if (parsed !== undefined) {
|
|
192
192
|
return parsed as TData[];
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
|
-
return
|
|
195
|
+
return value as unknown as TData[];
|
|
196
196
|
},
|
|
197
197
|
})(name);
|
|
198
198
|
|
|
@@ -219,11 +219,11 @@ export const duckDbArray = <TData = unknown>(
|
|
|
219
219
|
}
|
|
220
220
|
if (typeof value === 'string') {
|
|
221
221
|
const parsed = coerceArrayString(value);
|
|
222
|
-
if (parsed) {
|
|
222
|
+
if (parsed !== undefined) {
|
|
223
223
|
return parsed as TData[];
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
|
-
return
|
|
226
|
+
return value as unknown as TData[];
|
|
227
227
|
},
|
|
228
228
|
})(name);
|
|
229
229
|
|
package/src/dialect.ts
CHANGED
|
@@ -19,18 +19,64 @@ import {
|
|
|
19
19
|
type QueryTypingsValue,
|
|
20
20
|
} from 'drizzle-orm';
|
|
21
21
|
|
|
22
|
+
const enum SavepointSupport {
|
|
23
|
+
Unknown = 0,
|
|
24
|
+
Yes = 1,
|
|
25
|
+
No = 2,
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
export class DuckDBDialect extends PgDialect {
|
|
23
29
|
static readonly [entityKind]: string = 'DuckDBPgDialect';
|
|
30
|
+
// Track if PG JSON columns were detected during the current query preparation.
|
|
31
|
+
// Reset before each query via DuckDBSession to keep detection per-query.
|
|
24
32
|
private hasPgJsonColumn = false;
|
|
33
|
+
// Track savepoint support per-dialect instance to avoid cross-contamination
|
|
34
|
+
// when multiple database connections with different capabilities exist.
|
|
35
|
+
private savepointsSupported: SavepointSupport = SavepointSupport.Unknown;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reset the PG JSON detection flag. Should be called before preparing a new query.
|
|
39
|
+
*/
|
|
40
|
+
resetPgJsonFlag(): void {
|
|
41
|
+
this.hasPgJsonColumn = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Mark that a PG JSON/JSONB column was detected during query preparation.
|
|
46
|
+
*/
|
|
47
|
+
markPgJsonDetected(): void {
|
|
48
|
+
this.hasPgJsonColumn = true;
|
|
49
|
+
}
|
|
25
50
|
|
|
26
51
|
assertNoPgJsonColumns(): void {
|
|
27
52
|
if (this.hasPgJsonColumn) {
|
|
28
53
|
throw new Error(
|
|
29
|
-
|
|
54
|
+
"Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type."
|
|
30
55
|
);
|
|
31
56
|
}
|
|
32
57
|
}
|
|
33
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Check if savepoints are known to be unsupported for this dialect instance.
|
|
61
|
+
*/
|
|
62
|
+
areSavepointsUnsupported(): boolean {
|
|
63
|
+
return this.savepointsSupported === SavepointSupport.No;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Mark that savepoints are supported for this dialect instance.
|
|
68
|
+
*/
|
|
69
|
+
markSavepointsSupported(): void {
|
|
70
|
+
this.savepointsSupported = SavepointSupport.Yes;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Mark that savepoints are not supported for this dialect instance.
|
|
75
|
+
*/
|
|
76
|
+
markSavepointsUnsupported(): void {
|
|
77
|
+
this.savepointsSupported = SavepointSupport.No;
|
|
78
|
+
}
|
|
79
|
+
|
|
34
80
|
override async migrate(
|
|
35
81
|
migrations: MigrationMeta[],
|
|
36
82
|
session: PgSession,
|
|
@@ -117,8 +163,10 @@ export class DuckDBDialect extends PgDialect {
|
|
|
117
163
|
encoder: DriverValueEncoder<unknown, unknown>
|
|
118
164
|
): QueryTypingsValue {
|
|
119
165
|
if (is(encoder, PgJsonb) || is(encoder, PgJson)) {
|
|
120
|
-
this.
|
|
121
|
-
|
|
166
|
+
this.markPgJsonDetected();
|
|
167
|
+
throw new Error(
|
|
168
|
+
"Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB's native JSON type."
|
|
169
|
+
);
|
|
122
170
|
} else if (is(encoder, PgNumeric)) {
|
|
123
171
|
return 'decimal';
|
|
124
172
|
} else if (is(encoder, PgTime)) {
|