@leonardovida-md/drizzle-neo-duckdb 1.1.0 → 1.1.2
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 +44 -85
- package/dist/client.d.ts +15 -1
- package/dist/columns.d.ts +8 -2
- package/dist/dialect.d.ts +21 -0
- package/dist/driver.d.ts +7 -3
- package/dist/duckdb-introspect.mjs +667 -133
- package/dist/helpers.mjs +35 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +695 -137
- package/dist/introspect.d.ts +8 -0
- package/dist/options.d.ts +10 -0
- package/dist/pool.d.ts +8 -0
- package/dist/session.d.ts +18 -6
- package/dist/sql/query-rewriters.d.ts +1 -0
- package/dist/sql/result-mapper.d.ts +7 -0
- package/dist/value-wrappers-core.d.ts +2 -2
- package/package.json +1 -1
- package/src/bin/duckdb-introspect.ts +27 -0
- package/src/client.ts +301 -38
- package/src/columns.ts +60 -13
- package/src/dialect.ts +51 -3
- package/src/driver.ts +45 -7
- package/src/index.ts +1 -0
- package/src/introspect.ts +23 -6
- package/src/options.ts +40 -0
- package/src/pool.ts +182 -12
- package/src/session.ts +206 -31
- package/src/sql/query-rewriters.ts +191 -75
- package/src/sql/result-mapper.ts +7 -7
- package/src/value-wrappers-core.ts +2 -2
- package/src/value-wrappers.ts +13 -3
package/src/columns.ts
CHANGED
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
wrapMap,
|
|
8
8
|
wrapBlob,
|
|
9
9
|
wrapJson,
|
|
10
|
+
wrapTimestamp,
|
|
10
11
|
type ListValueWrapper,
|
|
11
12
|
type ArrayValueWrapper,
|
|
12
13
|
type MapValueWrapper,
|
|
13
14
|
type BlobValueWrapper,
|
|
14
15
|
type JsonValueWrapper,
|
|
16
|
+
type TimestampValueWrapper,
|
|
15
17
|
} from './value-wrappers-core.ts';
|
|
16
18
|
|
|
17
19
|
type IntColType =
|
|
@@ -63,7 +65,7 @@ type StructColType = `STRUCT (${string})`;
|
|
|
63
65
|
|
|
64
66
|
type Primitive = AnyColType | ListColType | ArrayColType | StructColType;
|
|
65
67
|
|
|
66
|
-
function coerceArrayString(value: string): unknown[] | undefined {
|
|
68
|
+
export function coerceArrayString(value: string): unknown[] | undefined {
|
|
67
69
|
const trimmed = value.trim();
|
|
68
70
|
if (!trimmed) {
|
|
69
71
|
return [];
|
|
@@ -86,7 +88,7 @@ function coerceArrayString(value: string): unknown[] | undefined {
|
|
|
86
88
|
return undefined;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
function formatLiteral(value: unknown, typeHint?: string): string {
|
|
91
|
+
export function formatLiteral(value: unknown, typeHint?: string): string {
|
|
90
92
|
if (value === null || value === undefined) {
|
|
91
93
|
return 'NULL';
|
|
92
94
|
}
|
|
@@ -123,7 +125,7 @@ function formatLiteral(value: unknown, typeHint?: string): string {
|
|
|
123
125
|
return `'${escaped}'`;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
function buildListLiteral(values: unknown[], elementType?: string): SQL {
|
|
128
|
+
export function buildListLiteral(values: unknown[], elementType?: string): SQL {
|
|
127
129
|
if (values.length === 0) {
|
|
128
130
|
return sql`[]`;
|
|
129
131
|
}
|
|
@@ -135,7 +137,7 @@ function buildListLiteral(values: unknown[], elementType?: string): SQL {
|
|
|
135
137
|
return sql`list_value(${sql.join(chunks, sql.raw(', '))})`;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
function buildStructLiteral(
|
|
140
|
+
export function buildStructLiteral(
|
|
139
141
|
value: Record<string, unknown>,
|
|
140
142
|
schema?: Record<string, Primitive>
|
|
141
143
|
): SQL {
|
|
@@ -154,7 +156,7 @@ function buildStructLiteral(
|
|
|
154
156
|
return sql`struct_pack(${sql.join(parts, sql.raw(', '))})`;
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
function buildMapLiteral(
|
|
159
|
+
export function buildMapLiteral(
|
|
158
160
|
value: Record<string, unknown>,
|
|
159
161
|
valueType?: string
|
|
160
162
|
): SQL {
|
|
@@ -188,11 +190,11 @@ export const duckDbList = <TData = unknown>(
|
|
|
188
190
|
}
|
|
189
191
|
if (typeof value === 'string') {
|
|
190
192
|
const parsed = coerceArrayString(value);
|
|
191
|
-
if (parsed) {
|
|
193
|
+
if (parsed !== undefined) {
|
|
192
194
|
return parsed as TData[];
|
|
193
195
|
}
|
|
194
196
|
}
|
|
195
|
-
return
|
|
197
|
+
return value as unknown as TData[];
|
|
196
198
|
},
|
|
197
199
|
})(name);
|
|
198
200
|
|
|
@@ -219,11 +221,11 @@ export const duckDbArray = <TData = unknown>(
|
|
|
219
221
|
}
|
|
220
222
|
if (typeof value === 'string') {
|
|
221
223
|
const parsed = coerceArrayString(value);
|
|
222
|
-
if (parsed) {
|
|
224
|
+
if (parsed !== undefined) {
|
|
223
225
|
return parsed as TData[];
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
|
-
return
|
|
228
|
+
return value as unknown as TData[];
|
|
227
229
|
},
|
|
228
230
|
})(name);
|
|
229
231
|
|
|
@@ -355,12 +357,35 @@ interface TimestampOptions {
|
|
|
355
357
|
withTimezone?: boolean;
|
|
356
358
|
mode?: TimestampMode;
|
|
357
359
|
precision?: number;
|
|
360
|
+
bindMode?: 'auto' | 'bind' | 'literal';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function shouldBindTimestamp(options: TimestampOptions): boolean {
|
|
364
|
+
const bindMode = options.bindMode ?? 'auto';
|
|
365
|
+
if (bindMode === 'bind') return true;
|
|
366
|
+
if (bindMode === 'literal') return false;
|
|
367
|
+
|
|
368
|
+
const isBun =
|
|
369
|
+
typeof process !== 'undefined' &&
|
|
370
|
+
typeof process.versions?.bun !== 'undefined';
|
|
371
|
+
if (isBun) return false;
|
|
372
|
+
|
|
373
|
+
const forceLiteral =
|
|
374
|
+
typeof process !== 'undefined'
|
|
375
|
+
? process.env.DRIZZLE_DUCKDB_FORCE_LITERAL_TIMESTAMPS
|
|
376
|
+
: undefined;
|
|
377
|
+
|
|
378
|
+
if (forceLiteral && forceLiteral !== '0') {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return true;
|
|
358
383
|
}
|
|
359
384
|
|
|
360
385
|
export const duckDbTimestamp = (name: string, options: TimestampOptions = {}) =>
|
|
361
386
|
customType<{
|
|
362
387
|
data: Date | string;
|
|
363
|
-
driverData: SQL | string | Date;
|
|
388
|
+
driverData: SQL | string | Date | TimestampValueWrapper;
|
|
364
389
|
}>({
|
|
365
390
|
dataType() {
|
|
366
391
|
if (options.withTimezone) {
|
|
@@ -369,14 +394,36 @@ export const duckDbTimestamp = (name: string, options: TimestampOptions = {}) =>
|
|
|
369
394
|
const precision = options.precision ? `(${options.precision})` : '';
|
|
370
395
|
return `TIMESTAMP${precision}`;
|
|
371
396
|
},
|
|
372
|
-
toDriver(
|
|
373
|
-
|
|
397
|
+
toDriver(
|
|
398
|
+
value: Date | string
|
|
399
|
+
): SQL | string | Date | TimestampValueWrapper {
|
|
400
|
+
if (shouldBindTimestamp(options)) {
|
|
401
|
+
return wrapTimestamp(
|
|
402
|
+
value,
|
|
403
|
+
options.withTimezone ?? false,
|
|
404
|
+
options.precision
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
374
408
|
const iso = value instanceof Date ? value.toISOString() : value;
|
|
375
409
|
const normalized = iso.replace('T', ' ').replace('Z', '+00');
|
|
376
410
|
const typeKeyword = options.withTimezone ? 'TIMESTAMPTZ' : 'TIMESTAMP';
|
|
377
411
|
return sql.raw(`${typeKeyword} '${normalized}'`);
|
|
378
412
|
},
|
|
379
|
-
fromDriver(value: Date | string | SQL) {
|
|
413
|
+
fromDriver(value: Date | string | SQL | TimestampValueWrapper) {
|
|
414
|
+
if (
|
|
415
|
+
value &&
|
|
416
|
+
typeof value === 'object' &&
|
|
417
|
+
'kind' in value &&
|
|
418
|
+
(value as TimestampValueWrapper).kind === 'timestamp'
|
|
419
|
+
) {
|
|
420
|
+
const wrapped = value as TimestampValueWrapper;
|
|
421
|
+
return wrapped.data instanceof Date
|
|
422
|
+
? wrapped.data
|
|
423
|
+
: typeof wrapped.data === 'number' || typeof wrapped.data === 'bigint'
|
|
424
|
+
? new Date(Number(wrapped.data) / 1000)
|
|
425
|
+
: wrapped.data;
|
|
426
|
+
}
|
|
380
427
|
if (options.mode === 'string') {
|
|
381
428
|
if (value instanceof Date) {
|
|
382
429
|
return value.toISOString().replace('T', ' ').replace('Z', '+00');
|
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)) {
|
package/src/driver.ts
CHANGED
|
@@ -23,19 +23,32 @@ import { DuckDBSession } from './session.ts';
|
|
|
23
23
|
import { DuckDBDialect } from './dialect.ts';
|
|
24
24
|
import { DuckDBSelectBuilder } from './select-builder.ts';
|
|
25
25
|
import { aliasFields } from './sql/selection.ts';
|
|
26
|
-
import type {
|
|
27
|
-
|
|
26
|
+
import type {
|
|
27
|
+
ExecuteBatchesRawChunk,
|
|
28
|
+
ExecuteInBatchesOptions,
|
|
29
|
+
RowData,
|
|
30
|
+
} from './client.ts';
|
|
31
|
+
import { closeClientConnection, isPool } from './client.ts';
|
|
28
32
|
import {
|
|
29
33
|
createDuckDBConnectionPool,
|
|
30
34
|
resolvePoolSize,
|
|
31
35
|
type DuckDBPoolConfig,
|
|
32
36
|
type PoolPreset,
|
|
33
37
|
} from './pool.ts';
|
|
38
|
+
import {
|
|
39
|
+
resolvePrepareCacheOption,
|
|
40
|
+
resolveRewriteArraysOption,
|
|
41
|
+
type PreparedStatementCacheConfig,
|
|
42
|
+
type PrepareCacheOption,
|
|
43
|
+
type RewriteArraysMode,
|
|
44
|
+
type RewriteArraysOption,
|
|
45
|
+
} from './options.ts';
|
|
34
46
|
|
|
35
47
|
export interface PgDriverOptions {
|
|
36
48
|
logger?: Logger;
|
|
37
|
-
rewriteArrays?:
|
|
49
|
+
rewriteArrays?: RewriteArraysMode;
|
|
38
50
|
rejectStringArrayLiterals?: boolean;
|
|
51
|
+
prepareCache?: PreparedStatementCacheConfig;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
export class DuckDBDriver {
|
|
@@ -52,8 +65,9 @@ export class DuckDBDriver {
|
|
|
52
65
|
): DuckDBSession<Record<string, unknown>, TablesRelationalConfig> {
|
|
53
66
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
54
67
|
logger: this.options.logger,
|
|
55
|
-
rewriteArrays: this.options.rewriteArrays,
|
|
68
|
+
rewriteArrays: this.options.rewriteArrays ?? 'auto',
|
|
56
69
|
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
70
|
+
prepareCache: this.options.prepareCache,
|
|
57
71
|
});
|
|
58
72
|
}
|
|
59
73
|
}
|
|
@@ -69,8 +83,9 @@ export interface DuckDBConnectionConfig {
|
|
|
69
83
|
export interface DuckDBDrizzleConfig<
|
|
70
84
|
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
71
85
|
> extends DrizzleConfig<TSchema> {
|
|
72
|
-
rewriteArrays?:
|
|
86
|
+
rewriteArrays?: RewriteArraysOption;
|
|
73
87
|
rejectStringArrayLiterals?: boolean;
|
|
88
|
+
prepareCache?: PrepareCacheOption;
|
|
74
89
|
/** Pool configuration. Use preset name, size config, or false to disable. */
|
|
75
90
|
pool?: DuckDBPoolConfig | PoolPreset | false;
|
|
76
91
|
}
|
|
@@ -111,6 +126,8 @@ function createFromClient<
|
|
|
111
126
|
instance?: DuckDBInstance
|
|
112
127
|
): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>> {
|
|
113
128
|
const dialect = new DuckDBDialect();
|
|
129
|
+
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
130
|
+
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
114
131
|
|
|
115
132
|
const logger =
|
|
116
133
|
config.logger === true ? new DefaultLogger() : config.logger || undefined;
|
|
@@ -131,8 +148,9 @@ function createFromClient<
|
|
|
131
148
|
|
|
132
149
|
const driver = new DuckDBDriver(client, dialect, {
|
|
133
150
|
logger,
|
|
134
|
-
rewriteArrays:
|
|
151
|
+
rewriteArrays: rewriteArraysMode,
|
|
135
152
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
153
|
+
prepareCache,
|
|
136
154
|
});
|
|
137
155
|
const session = driver.createSession(schema);
|
|
138
156
|
|
|
@@ -290,7 +308,20 @@ export class DuckDBDatabase<
|
|
|
290
308
|
if (isPool(this.$client) && this.$client.close) {
|
|
291
309
|
await this.$client.close();
|
|
292
310
|
}
|
|
293
|
-
|
|
311
|
+
if (!isPool(this.$client)) {
|
|
312
|
+
await closeClientConnection(this.$client);
|
|
313
|
+
}
|
|
314
|
+
if (this.$instance) {
|
|
315
|
+
const maybeClosable = this.$instance as unknown as {
|
|
316
|
+
close?: () => Promise<void> | void;
|
|
317
|
+
closeSync?: () => void;
|
|
318
|
+
};
|
|
319
|
+
if (typeof maybeClosable.close === 'function') {
|
|
320
|
+
await maybeClosable.close();
|
|
321
|
+
} else if (typeof maybeClosable.closeSync === 'function') {
|
|
322
|
+
maybeClosable.closeSync();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
294
325
|
}
|
|
295
326
|
|
|
296
327
|
select(): DuckDBSelectBuilder<undefined>;
|
|
@@ -317,6 +348,13 @@ export class DuckDBDatabase<
|
|
|
317
348
|
return this.session.executeBatches<T>(query, options);
|
|
318
349
|
}
|
|
319
350
|
|
|
351
|
+
executeBatchesRaw(
|
|
352
|
+
query: SQL,
|
|
353
|
+
options: ExecuteInBatchesOptions = {}
|
|
354
|
+
): AsyncGenerator<ExecuteBatchesRawChunk, void, void> {
|
|
355
|
+
return this.session.executeBatchesRaw(query, options);
|
|
356
|
+
}
|
|
357
|
+
|
|
320
358
|
executeArrow(query: SQL): Promise<unknown> {
|
|
321
359
|
return this.session.executeArrow(query);
|
|
322
360
|
}
|
package/src/index.ts
CHANGED
package/src/introspect.ts
CHANGED
|
@@ -535,7 +535,7 @@ function emitColumn(
|
|
|
535
535
|
return builder;
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
function buildDefault(defaultValue: string | null): string {
|
|
538
|
+
export function buildDefault(defaultValue: string | null): string {
|
|
539
539
|
if (!defaultValue) {
|
|
540
540
|
return '';
|
|
541
541
|
}
|
|
@@ -707,6 +707,22 @@ function mapDuckDbType(
|
|
|
707
707
|
return { builder: `text(${columnName(column.name)}) /* JSON */` };
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
+
if (upper.startsWith('ENUM')) {
|
|
711
|
+
imports.pgCore.add('text');
|
|
712
|
+
const enumLiteral = raw.replace(/^ENUM\s*/i, '').trim();
|
|
713
|
+
return {
|
|
714
|
+
builder: `text(${columnName(column.name)}) /* ENUM ${enumLiteral} */`,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (upper.startsWith('UNION')) {
|
|
719
|
+
imports.pgCore.add('text');
|
|
720
|
+
const unionLiteral = raw.replace(/^UNION\s*/i, '').trim();
|
|
721
|
+
return {
|
|
722
|
+
builder: `text(${columnName(column.name)}) /* UNION ${unionLiteral} */`,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
710
726
|
if (upper === 'INET') {
|
|
711
727
|
imports.local.add('duckDbInet');
|
|
712
728
|
return { builder: `duckDbInet(${columnName(column.name)})` };
|
|
@@ -788,15 +804,16 @@ function mapDuckDbType(
|
|
|
788
804
|
}
|
|
789
805
|
|
|
790
806
|
// Fallback: keep as text to avoid runtime failures.
|
|
807
|
+
// Unknown types are mapped to text with a comment indicating the original type.
|
|
791
808
|
imports.pgCore.add('text');
|
|
792
809
|
return {
|
|
793
810
|
builder: `text(${columnName(
|
|
794
811
|
column.name
|
|
795
|
-
)}) /*
|
|
812
|
+
)}) /* unsupported DuckDB type: ${upper} */`,
|
|
796
813
|
};
|
|
797
814
|
}
|
|
798
815
|
|
|
799
|
-
function parseStructFields(
|
|
816
|
+
export function parseStructFields(
|
|
800
817
|
inner: string
|
|
801
818
|
): Array<{ name: string; type: string }> {
|
|
802
819
|
const result: Array<{ name: string; type: string }> = [];
|
|
@@ -813,7 +830,7 @@ function parseStructFields(
|
|
|
813
830
|
return result;
|
|
814
831
|
}
|
|
815
832
|
|
|
816
|
-
function parseMapValue(raw: string): string {
|
|
833
|
+
export function parseMapValue(raw: string): string {
|
|
817
834
|
const inner = raw.replace(/^MAP\(/i, '').replace(/\)$/, '');
|
|
818
835
|
const parts = splitTopLevel(inner, ',');
|
|
819
836
|
if (parts.length < 2) {
|
|
@@ -822,7 +839,7 @@ function parseMapValue(raw: string): string {
|
|
|
822
839
|
return parts[1]?.trim() ?? 'TEXT';
|
|
823
840
|
}
|
|
824
841
|
|
|
825
|
-
function splitTopLevel(input: string, delimiter: string): string[] {
|
|
842
|
+
export function splitTopLevel(input: string, delimiter: string): string[] {
|
|
826
843
|
const parts: string[] = [];
|
|
827
844
|
let depth = 0;
|
|
828
845
|
let current = '';
|
|
@@ -847,7 +864,7 @@ function tableKey(schema: string, table: string): string {
|
|
|
847
864
|
return `${schema}.${table}`;
|
|
848
865
|
}
|
|
849
866
|
|
|
850
|
-
function toIdentifier(name: string): string {
|
|
867
|
+
export function toIdentifier(name: string): string {
|
|
851
868
|
const cleaned = name.replace(/[^A-Za-z0-9_]/g, '_');
|
|
852
869
|
const parts = cleaned.split('_').filter(Boolean);
|
|
853
870
|
const base = parts
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type RewriteArraysMode = 'auto' | 'always' | 'never';
|
|
2
|
+
|
|
3
|
+
export type RewriteArraysOption = boolean | RewriteArraysMode;
|
|
4
|
+
|
|
5
|
+
const DEFAULT_REWRITE_ARRAYS_MODE: RewriteArraysMode = 'auto';
|
|
6
|
+
|
|
7
|
+
export function resolveRewriteArraysOption(
|
|
8
|
+
value?: RewriteArraysOption
|
|
9
|
+
): RewriteArraysMode {
|
|
10
|
+
if (value === undefined) return DEFAULT_REWRITE_ARRAYS_MODE;
|
|
11
|
+
if (value === true) return 'auto';
|
|
12
|
+
if (value === false) return 'never';
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type PrepareCacheOption = boolean | number | { size?: number };
|
|
17
|
+
|
|
18
|
+
export interface PreparedStatementCacheConfig {
|
|
19
|
+
size: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
23
|
+
|
|
24
|
+
export function resolvePrepareCacheOption(
|
|
25
|
+
option?: PrepareCacheOption
|
|
26
|
+
): PreparedStatementCacheConfig | undefined {
|
|
27
|
+
if (!option) return undefined;
|
|
28
|
+
|
|
29
|
+
if (option === true) {
|
|
30
|
+
return { size: DEFAULT_PREPARED_CACHE_SIZE };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof option === 'number') {
|
|
34
|
+
const size = Math.max(1, Math.floor(option));
|
|
35
|
+
return { size };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
|
|
39
|
+
return { size: Math.max(1, Math.floor(size)) };
|
|
40
|
+
}
|