@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/src/driver.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 { DefaultLogger } from 'drizzle-orm/logger';
|
|
@@ -23,6 +24,13 @@ import { DuckDBDialect } from './dialect.ts';
|
|
|
23
24
|
import { DuckDBSelectBuilder } from './select-builder.ts';
|
|
24
25
|
import { aliasFields } from './sql/selection.ts';
|
|
25
26
|
import type { ExecuteInBatchesOptions, RowData } from './client.ts';
|
|
27
|
+
import { closeClientConnection, isPool } from './client.ts';
|
|
28
|
+
import {
|
|
29
|
+
createDuckDBConnectionPool,
|
|
30
|
+
resolvePoolSize,
|
|
31
|
+
type DuckDBPoolConfig,
|
|
32
|
+
type PoolPreset,
|
|
33
|
+
} from './pool.ts';
|
|
26
34
|
|
|
27
35
|
export interface PgDriverOptions {
|
|
28
36
|
logger?: Logger;
|
|
@@ -50,18 +58,57 @@ export class DuckDBDriver {
|
|
|
50
58
|
}
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
/** Connection configuration when using path-based connection */
|
|
62
|
+
export interface DuckDBConnectionConfig {
|
|
63
|
+
/** Database path: ':memory:', './file.duckdb', 'md:', 'md:database' */
|
|
64
|
+
path: string;
|
|
65
|
+
/** DuckDB instance options (e.g., motherduck_token) */
|
|
66
|
+
options?: Record<string, string>;
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
export interface DuckDBDrizzleConfig<
|
|
54
70
|
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
55
71
|
> extends DrizzleConfig<TSchema> {
|
|
56
72
|
rewriteArrays?: boolean;
|
|
57
73
|
rejectStringArrayLiterals?: boolean;
|
|
74
|
+
/** Pool configuration. Use preset name, size config, or false to disable. */
|
|
75
|
+
pool?: DuckDBPoolConfig | PoolPreset | false;
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
export
|
|
78
|
+
export interface DuckDBDrizzleConfigWithConnection<
|
|
79
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
80
|
+
> extends DuckDBDrizzleConfig<TSchema> {
|
|
81
|
+
/** Connection string or config object */
|
|
82
|
+
connection: string | DuckDBConnectionConfig;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DuckDBDrizzleConfigWithClient<
|
|
86
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
87
|
+
> extends DuckDBDrizzleConfig<TSchema> {
|
|
88
|
+
/** Explicit client (connection or pool) */
|
|
89
|
+
client: DuckDBClientLike;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Check if a value looks like a config object (not a client) */
|
|
93
|
+
function isConfigObject(data: unknown): data is Record<string, unknown> {
|
|
94
|
+
if (typeof data !== 'object' || data === null) return false;
|
|
95
|
+
if (data.constructor?.name !== 'Object') return false;
|
|
96
|
+
return (
|
|
97
|
+
'connection' in data ||
|
|
98
|
+
'client' in data ||
|
|
99
|
+
'pool' in data ||
|
|
100
|
+
'schema' in data ||
|
|
101
|
+
'logger' in data
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Internal: create database from a client (connection or pool) */
|
|
106
|
+
function createFromClient<
|
|
61
107
|
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
62
108
|
>(
|
|
63
109
|
client: DuckDBClientLike,
|
|
64
|
-
config: DuckDBDrizzleConfig<TSchema> = {}
|
|
110
|
+
config: DuckDBDrizzleConfig<TSchema> = {},
|
|
111
|
+
instance?: DuckDBInstance
|
|
65
112
|
): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>> {
|
|
66
113
|
const dialect = new DuckDBDialect();
|
|
67
114
|
|
|
@@ -89,10 +136,125 @@ export function drizzle<
|
|
|
89
136
|
});
|
|
90
137
|
const session = driver.createSession(schema);
|
|
91
138
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
139
|
+
const db = new DuckDBDatabase(dialect, session, schema, client, instance);
|
|
140
|
+
return db as DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Internal: create database from a connection string */
|
|
144
|
+
async function createFromConnectionString<
|
|
145
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
146
|
+
>(
|
|
147
|
+
path: string,
|
|
148
|
+
instanceOptions: Record<string, string> | undefined,
|
|
149
|
+
config: DuckDBDrizzleConfig<TSchema> = {}
|
|
150
|
+
): Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>> {
|
|
151
|
+
const instance = await DuckDBInstance.create(path, instanceOptions);
|
|
152
|
+
const poolSize = resolvePoolSize(config.pool);
|
|
153
|
+
|
|
154
|
+
if (poolSize === false) {
|
|
155
|
+
const connection = await instance.connect();
|
|
156
|
+
return createFromClient(connection, config, instance);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const pool = createDuckDBConnectionPool(instance, { size: poolSize });
|
|
160
|
+
return createFromClient(pool, config, instance);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Overload 1: Connection string (async, auto-pools)
|
|
164
|
+
export function drizzle<
|
|
165
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
166
|
+
>(
|
|
167
|
+
connectionString: string
|
|
168
|
+
): Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
169
|
+
|
|
170
|
+
// Overload 2: Connection string + config (async, auto-pools)
|
|
171
|
+
export function drizzle<
|
|
172
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
173
|
+
>(
|
|
174
|
+
connectionString: string,
|
|
175
|
+
config: DuckDBDrizzleConfig<TSchema>
|
|
176
|
+
): Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
177
|
+
|
|
178
|
+
// Overload 3: Config with connection (async, auto-pools)
|
|
179
|
+
export function drizzle<
|
|
180
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
181
|
+
>(
|
|
182
|
+
config: DuckDBDrizzleConfigWithConnection<TSchema>
|
|
183
|
+
): Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
184
|
+
|
|
185
|
+
// Overload 4: Config with explicit client (sync)
|
|
186
|
+
export function drizzle<
|
|
187
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
188
|
+
>(
|
|
189
|
+
config: DuckDBDrizzleConfigWithClient<TSchema>
|
|
190
|
+
): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
191
|
+
|
|
192
|
+
// Overload 5: Explicit client (sync, backward compatible)
|
|
193
|
+
export function drizzle<
|
|
194
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
195
|
+
>(
|
|
196
|
+
client: DuckDBClientLike,
|
|
197
|
+
config?: DuckDBDrizzleConfig<TSchema>
|
|
198
|
+
): DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
199
|
+
|
|
200
|
+
// Implementation
|
|
201
|
+
export function drizzle<
|
|
202
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
203
|
+
>(
|
|
204
|
+
clientOrConfigOrPath:
|
|
205
|
+
| string
|
|
206
|
+
| DuckDBClientLike
|
|
207
|
+
| DuckDBDrizzleConfigWithConnection<TSchema>
|
|
208
|
+
| DuckDBDrizzleConfigWithClient<TSchema>,
|
|
209
|
+
config?: DuckDBDrizzleConfig<TSchema>
|
|
210
|
+
):
|
|
211
|
+
| DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>
|
|
212
|
+
| Promise<DuckDBDatabase<TSchema, ExtractTablesWithRelations<TSchema>>> {
|
|
213
|
+
// String path -> async with auto-pool
|
|
214
|
+
if (typeof clientOrConfigOrPath === 'string') {
|
|
215
|
+
return createFromConnectionString(clientOrConfigOrPath, undefined, config);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Config object with connection or client
|
|
219
|
+
if (isConfigObject(clientOrConfigOrPath)) {
|
|
220
|
+
const configObj = clientOrConfigOrPath as
|
|
221
|
+
| DuckDBDrizzleConfigWithConnection<TSchema>
|
|
222
|
+
| DuckDBDrizzleConfigWithClient<TSchema>;
|
|
223
|
+
|
|
224
|
+
if ('connection' in configObj) {
|
|
225
|
+
const connConfig =
|
|
226
|
+
configObj as DuckDBDrizzleConfigWithConnection<TSchema>;
|
|
227
|
+
const { connection, ...restConfig } = connConfig;
|
|
228
|
+
if (typeof connection === 'string') {
|
|
229
|
+
return createFromConnectionString(
|
|
230
|
+
connection,
|
|
231
|
+
undefined,
|
|
232
|
+
restConfig as DuckDBDrizzleConfig<TSchema>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return createFromConnectionString(
|
|
236
|
+
connection.path,
|
|
237
|
+
connection.options,
|
|
238
|
+
restConfig as DuckDBDrizzleConfig<TSchema>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if ('client' in configObj) {
|
|
243
|
+
const clientConfig = configObj as DuckDBDrizzleConfigWithClient<TSchema>;
|
|
244
|
+
const { client: clientValue, ...restConfig } = clientConfig;
|
|
245
|
+
return createFromClient(
|
|
246
|
+
clientValue,
|
|
247
|
+
restConfig as DuckDBDrizzleConfig<TSchema>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
throw new Error(
|
|
252
|
+
'Invalid drizzle config: either connection or client must be provided'
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Direct client (backward compatible)
|
|
257
|
+
return createFromClient(clientOrConfigOrPath as DuckDBClientLike, config);
|
|
96
258
|
}
|
|
97
259
|
|
|
98
260
|
export class DuckDBDatabase<
|
|
@@ -102,12 +264,46 @@ export class DuckDBDatabase<
|
|
|
102
264
|
> extends PgDatabase<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
103
265
|
static readonly [entityKind]: string = 'DuckDBDatabase';
|
|
104
266
|
|
|
267
|
+
/** The underlying connection or pool */
|
|
268
|
+
readonly $client: DuckDBClientLike;
|
|
269
|
+
|
|
270
|
+
/** The DuckDB instance (when created from connection string) */
|
|
271
|
+
readonly $instance?: DuckDBInstance;
|
|
272
|
+
|
|
105
273
|
constructor(
|
|
106
274
|
readonly dialect: DuckDBDialect,
|
|
107
275
|
readonly session: DuckDBSession<TFullSchema, TSchema>,
|
|
108
|
-
schema: RelationalSchemaConfig<TSchema> | undefined
|
|
276
|
+
schema: RelationalSchemaConfig<TSchema> | undefined,
|
|
277
|
+
client: DuckDBClientLike,
|
|
278
|
+
instance?: DuckDBInstance
|
|
109
279
|
) {
|
|
110
280
|
super(dialect, session, schema);
|
|
281
|
+
this.$client = client;
|
|
282
|
+
this.$instance = instance;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Close the database connection pool and instance.
|
|
287
|
+
* Should be called when shutting down the application.
|
|
288
|
+
*/
|
|
289
|
+
async close(): Promise<void> {
|
|
290
|
+
if (isPool(this.$client) && this.$client.close) {
|
|
291
|
+
await this.$client.close();
|
|
292
|
+
}
|
|
293
|
+
if (!isPool(this.$client)) {
|
|
294
|
+
await closeClientConnection(this.$client);
|
|
295
|
+
}
|
|
296
|
+
if (this.$instance) {
|
|
297
|
+
const maybeClosable = this.$instance as unknown as {
|
|
298
|
+
close?: () => Promise<void> | void;
|
|
299
|
+
closeSync?: () => void;
|
|
300
|
+
};
|
|
301
|
+
if (typeof maybeClosable.close === 'function') {
|
|
302
|
+
await maybeClosable.close();
|
|
303
|
+
} else if (typeof maybeClosable.closeSync === 'function') {
|
|
304
|
+
maybeClosable.closeSync();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
111
307
|
}
|
|
112
308
|
|
|
113
309
|
select(): DuckDBSelectBuilder<undefined>;
|
|
@@ -119,6 +315,7 @@ export class DuckDBDatabase<
|
|
|
119
315
|
): DuckDBSelectBuilder<SelectedFields | undefined> {
|
|
120
316
|
const selectedFields = fields ? aliasFields(fields) : undefined;
|
|
121
317
|
|
|
318
|
+
// Cast needed: DuckDBSession is compatible but types don't align exactly with PgSession
|
|
122
319
|
return new DuckDBSelectBuilder({
|
|
123
320
|
fields: selectedFields ?? undefined,
|
|
124
321
|
session: this.session as unknown as PgSession<DuckDBQueryResultHKT>,
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Client-safe entrypoint exposing schema builder utilities without pulling
|
|
2
|
+
// the DuckDB Node API bindings. Intended for generated schemas and browser use.
|
|
3
|
+
export {
|
|
4
|
+
duckDbList,
|
|
5
|
+
duckDbArray,
|
|
6
|
+
duckDbMap,
|
|
7
|
+
duckDbStruct,
|
|
8
|
+
duckDbJson,
|
|
9
|
+
duckDbBlob,
|
|
10
|
+
duckDbInet,
|
|
11
|
+
duckDbInterval,
|
|
12
|
+
duckDbTimestamp,
|
|
13
|
+
duckDbDate,
|
|
14
|
+
duckDbTime,
|
|
15
|
+
duckDbArrayContains,
|
|
16
|
+
duckDbArrayContained,
|
|
17
|
+
duckDbArrayOverlaps,
|
|
18
|
+
} from './columns.ts';
|
package/src/index.ts
CHANGED
package/src/introspect.ts
CHANGED
|
@@ -108,7 +108,8 @@ type ImportBuckets = {
|
|
|
108
108
|
local: Set<string>;
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
-
const DEFAULT_IMPORT_BASE =
|
|
111
|
+
export const DEFAULT_IMPORT_BASE =
|
|
112
|
+
'@leonardovida-md/drizzle-neo-duckdb/helpers';
|
|
112
113
|
|
|
113
114
|
export async function introspect(
|
|
114
115
|
db: DuckDBDatabase,
|
|
@@ -534,7 +535,7 @@ function emitColumn(
|
|
|
534
535
|
return builder;
|
|
535
536
|
}
|
|
536
537
|
|
|
537
|
-
function buildDefault(defaultValue: string | null): string {
|
|
538
|
+
export function buildDefault(defaultValue: string | null): string {
|
|
538
539
|
if (!defaultValue) {
|
|
539
540
|
return '';
|
|
540
541
|
}
|
|
@@ -649,6 +650,28 @@ function mapDuckDbType(
|
|
|
649
650
|
return { builder: `doublePrecision(${columnName(column.name)})` };
|
|
650
651
|
}
|
|
651
652
|
|
|
653
|
+
const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
|
|
654
|
+
if (arrayMatch) {
|
|
655
|
+
imports.local.add('duckDbArray');
|
|
656
|
+
const [, base, length] = arrayMatch;
|
|
657
|
+
return {
|
|
658
|
+
builder: `duckDbArray(${columnName(
|
|
659
|
+
column.name
|
|
660
|
+
)}, ${JSON.stringify(base)}, ${Number(length)})`,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const listMatch = /^(.*)\[\]$/.exec(upper);
|
|
665
|
+
if (listMatch) {
|
|
666
|
+
imports.local.add('duckDbList');
|
|
667
|
+
const [, base] = listMatch;
|
|
668
|
+
return {
|
|
669
|
+
builder: `duckDbList(${columnName(
|
|
670
|
+
column.name
|
|
671
|
+
)}, ${JSON.stringify(base)})`,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
652
675
|
if (upper.startsWith('CHAR(') || upper === 'CHAR') {
|
|
653
676
|
imports.pgCore.add('char');
|
|
654
677
|
const length = column.characterLength;
|
|
@@ -684,6 +707,22 @@ function mapDuckDbType(
|
|
|
684
707
|
return { builder: `text(${columnName(column.name)}) /* JSON */` };
|
|
685
708
|
}
|
|
686
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
|
+
|
|
687
726
|
if (upper === 'INET') {
|
|
688
727
|
imports.local.add('duckDbInet');
|
|
689
728
|
return { builder: `duckDbInet(${columnName(column.name)})` };
|
|
@@ -699,28 +738,6 @@ function mapDuckDbType(
|
|
|
699
738
|
return { builder: `duckDbBlob(${columnName(column.name)})` };
|
|
700
739
|
}
|
|
701
740
|
|
|
702
|
-
const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
|
|
703
|
-
if (arrayMatch) {
|
|
704
|
-
imports.local.add('duckDbArray');
|
|
705
|
-
const [, base, length] = arrayMatch;
|
|
706
|
-
return {
|
|
707
|
-
builder: `duckDbArray(${columnName(
|
|
708
|
-
column.name
|
|
709
|
-
)}, ${JSON.stringify(base)}, ${Number(length)})`,
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const listMatch = /^(.*)\[\]$/.exec(upper);
|
|
714
|
-
if (listMatch) {
|
|
715
|
-
imports.local.add('duckDbList');
|
|
716
|
-
const [, base] = listMatch;
|
|
717
|
-
return {
|
|
718
|
-
builder: `duckDbList(${columnName(
|
|
719
|
-
column.name
|
|
720
|
-
)}, ${JSON.stringify(base)})`,
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
|
|
724
741
|
if (upper.startsWith('STRUCT')) {
|
|
725
742
|
imports.local.add('duckDbStruct');
|
|
726
743
|
const inner = upper.replace(/^STRUCT\s*\(/i, '').replace(/\)$/, '');
|
|
@@ -787,15 +804,16 @@ function mapDuckDbType(
|
|
|
787
804
|
}
|
|
788
805
|
|
|
789
806
|
// Fallback: keep as text to avoid runtime failures.
|
|
807
|
+
// Unknown types are mapped to text with a comment indicating the original type.
|
|
790
808
|
imports.pgCore.add('text');
|
|
791
809
|
return {
|
|
792
810
|
builder: `text(${columnName(
|
|
793
811
|
column.name
|
|
794
|
-
)}) /*
|
|
812
|
+
)}) /* unsupported DuckDB type: ${upper} */`,
|
|
795
813
|
};
|
|
796
814
|
}
|
|
797
815
|
|
|
798
|
-
function parseStructFields(
|
|
816
|
+
export function parseStructFields(
|
|
799
817
|
inner: string
|
|
800
818
|
): Array<{ name: string; type: string }> {
|
|
801
819
|
const result: Array<{ name: string; type: string }> = [];
|
|
@@ -812,7 +830,7 @@ function parseStructFields(
|
|
|
812
830
|
return result;
|
|
813
831
|
}
|
|
814
832
|
|
|
815
|
-
function parseMapValue(raw: string): string {
|
|
833
|
+
export function parseMapValue(raw: string): string {
|
|
816
834
|
const inner = raw.replace(/^MAP\(/i, '').replace(/\)$/, '');
|
|
817
835
|
const parts = splitTopLevel(inner, ',');
|
|
818
836
|
if (parts.length < 2) {
|
|
@@ -821,7 +839,7 @@ function parseMapValue(raw: string): string {
|
|
|
821
839
|
return parts[1]?.trim() ?? 'TEXT';
|
|
822
840
|
}
|
|
823
841
|
|
|
824
|
-
function splitTopLevel(input: string, delimiter: string): string[] {
|
|
842
|
+
export function splitTopLevel(input: string, delimiter: string): string[] {
|
|
825
843
|
const parts: string[] = [];
|
|
826
844
|
let depth = 0;
|
|
827
845
|
let current = '';
|
|
@@ -846,7 +864,7 @@ function tableKey(schema: string, table: string): string {
|
|
|
846
864
|
return `${schema}.${table}`;
|
|
847
865
|
}
|
|
848
866
|
|
|
849
|
-
function toIdentifier(name: string): string {
|
|
867
|
+
export function toIdentifier(name: string): string {
|
|
850
868
|
const cleaned = name.replace(/[^A-Za-z0-9_]/g, '_');
|
|
851
869
|
const parts = cleaned.split('_').filter(Boolean);
|
|
852
870
|
const base = parts
|
package/src/migrator.ts
CHANGED
|
@@ -14,9 +14,9 @@ export async function migrate<TSchema extends Record<string, unknown>>(
|
|
|
14
14
|
|
|
15
15
|
const migrations = readMigrationFiles(migrationConfig);
|
|
16
16
|
|
|
17
|
+
// Cast needed: Drizzle's internal PgSession type differs from exported type
|
|
17
18
|
await db.dialect.migrate(
|
|
18
19
|
migrations,
|
|
19
|
-
// Need to work around omitted internal types from drizzle...
|
|
20
20
|
db.session as unknown as PgSession,
|
|
21
21
|
migrationConfig
|
|
22
22
|
);
|
package/src/olap.ts
CHANGED
|
@@ -169,6 +169,7 @@ export class OlapBuilder {
|
|
|
169
169
|
|
|
170
170
|
Object.assign(selection, this.measureMap);
|
|
171
171
|
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Drizzle's query builder types don't allow reassignment after groupBy
|
|
172
173
|
let query: any = this.db
|
|
173
174
|
.select(selection as SelectedFields)
|
|
174
175
|
.from(this.source!)
|