@leonardovida-md/drizzle-neo-duckdb 1.0.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/dist/index.mjs ADDED
@@ -0,0 +1,1564 @@
1
+ // src/driver.ts
2
+ import { entityKind as entityKind3 } from "drizzle-orm/entity";
3
+ import { DefaultLogger } from "drizzle-orm/logger";
4
+ import { PgDatabase } from "drizzle-orm/pg-core/db";
5
+ import {
6
+ createTableRelationsHelpers,
7
+ extractTablesRelationalConfig
8
+ } from "drizzle-orm/relations";
9
+
10
+ // src/session.ts
11
+ import { entityKind } from "drizzle-orm/entity";
12
+ import { NoopLogger } from "drizzle-orm/logger";
13
+ import { PgTransaction } from "drizzle-orm/pg-core";
14
+ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
15
+ import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
16
+
17
+ // src/sql/query-rewriters.ts
18
+ var tableIdPropSelectionRegex = new RegExp([
19
+ `("(.+)"\\."(.+)")`,
20
+ `(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`
21
+ ].join(""), "i");
22
+ function adaptArrayOperators(query) {
23
+ const operators = [
24
+ { token: "@>", fn: "array_has_all" },
25
+ { token: "<@", fn: "array_has_all", swap: true },
26
+ { token: "&&", fn: "array_has_any" }
27
+ ];
28
+ const isWhitespace = (char) => char !== undefined && /\s/.test(char);
29
+ const walkLeft = (source, start) => {
30
+ let idx = start;
31
+ while (idx >= 0 && isWhitespace(source[idx])) {
32
+ idx--;
33
+ }
34
+ let depth = 0;
35
+ let inString = false;
36
+ for (;idx >= 0; idx--) {
37
+ const ch = source[idx];
38
+ if (ch === "'" && source[idx - 1] !== "\\") {
39
+ inString = !inString;
40
+ }
41
+ if (inString)
42
+ continue;
43
+ if (ch === ")" || ch === "]") {
44
+ depth++;
45
+ } else if (ch === "(" || ch === "[") {
46
+ depth--;
47
+ if (depth < 0) {
48
+ return [idx + 1, source.slice(idx + 1, start + 1)];
49
+ }
50
+ } else if (depth === 0 && isWhitespace(ch)) {
51
+ return [idx + 1, source.slice(idx + 1, start + 1)];
52
+ }
53
+ }
54
+ return [0, source.slice(0, start + 1)];
55
+ };
56
+ const walkRight = (source, start) => {
57
+ let idx = start;
58
+ while (idx < source.length && isWhitespace(source[idx])) {
59
+ idx++;
60
+ }
61
+ let depth = 0;
62
+ let inString = false;
63
+ for (;idx < source.length; idx++) {
64
+ const ch = source[idx];
65
+ if (ch === "'" && source[idx - 1] !== "\\") {
66
+ inString = !inString;
67
+ }
68
+ if (inString)
69
+ continue;
70
+ if (ch === "(" || ch === "[") {
71
+ depth++;
72
+ } else if (ch === ")" || ch === "]") {
73
+ depth--;
74
+ if (depth < 0) {
75
+ return [idx, source.slice(start, idx)];
76
+ }
77
+ } else if (depth === 0 && isWhitespace(ch)) {
78
+ return [idx, source.slice(start, idx)];
79
+ }
80
+ }
81
+ return [source.length, source.slice(start)];
82
+ };
83
+ let rewritten = query;
84
+ for (const { token, fn, swap } of operators) {
85
+ let idx = rewritten.indexOf(token);
86
+ while (idx !== -1) {
87
+ const [leftStart, leftExpr] = walkLeft(rewritten, idx - 1);
88
+ const [rightEnd, rightExpr] = walkRight(rewritten, idx + token.length);
89
+ const left = leftExpr.trim();
90
+ const right = rightExpr.trim();
91
+ const replacement = `${fn}(${swap ? right : left}, ${swap ? left : right})`;
92
+ rewritten = rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
93
+ idx = rewritten.indexOf(token, leftStart + replacement.length);
94
+ }
95
+ }
96
+ return rewritten;
97
+ }
98
+
99
+ // src/sql/result-mapper.ts
100
+ import {
101
+ Column,
102
+ SQL,
103
+ getTableName,
104
+ is
105
+ } from "drizzle-orm";
106
+ import {
107
+ PgCustomColumn,
108
+ PgDate,
109
+ PgDateString,
110
+ PgInterval,
111
+ PgTime,
112
+ PgTimestamp,
113
+ PgTimestampString
114
+ } from "drizzle-orm/pg-core";
115
+ function toDecoderInput(decoder, value) {
116
+ return value;
117
+ }
118
+ function normalizeInet(value) {
119
+ if (value && typeof value === "object" && "address" in value && typeof value.address !== "undefined") {
120
+ const { address, mask } = value;
121
+ if (typeof address === "bigint" || typeof address === "number") {
122
+ const inet = typeof address === "number" ? BigInt(address) : address;
123
+ const maxIpv4 = (1n << 32n) - 1n;
124
+ if (inet >= 0 && inet <= maxIpv4) {
125
+ const num = Number(inet);
126
+ const octets = [
127
+ num >>> 24 & 255,
128
+ num >>> 16 & 255,
129
+ num >>> 8 & 255,
130
+ num & 255
131
+ ];
132
+ const suffix = typeof mask === "number" && mask !== 32 ? `/${mask}` : "";
133
+ return `${octets.join(".")}${suffix}`;
134
+ }
135
+ }
136
+ const fallback = value.toString?.();
137
+ if (fallback && fallback !== "[object Object]") {
138
+ return fallback;
139
+ }
140
+ }
141
+ return value;
142
+ }
143
+ function normalizeTimestampString(value, withTimezone) {
144
+ if (value instanceof Date) {
145
+ const iso = value.toISOString().replace("T", " ");
146
+ return withTimezone ? iso.replace("Z", "+00") : iso.replace("Z", "");
147
+ }
148
+ if (typeof value === "string") {
149
+ const normalized = value.replace("T", " ");
150
+ if (withTimezone) {
151
+ return normalized.includes("+") ? normalized : `${normalized}+00`;
152
+ }
153
+ return normalized.replace(/\+00$/, "");
154
+ }
155
+ return value;
156
+ }
157
+ function normalizeTimestamp(value, withTimezone) {
158
+ if (value instanceof Date) {
159
+ return value;
160
+ }
161
+ if (typeof value === "string") {
162
+ const hasOffset = value.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(value.trim());
163
+ const spaced = value.replace(" ", "T");
164
+ const normalized = withTimezone || hasOffset ? spaced : `${spaced}+00`;
165
+ return new Date(normalized);
166
+ }
167
+ return value;
168
+ }
169
+ function normalizeDateString(value) {
170
+ if (value instanceof Date) {
171
+ return value.toISOString().slice(0, 10);
172
+ }
173
+ if (typeof value === "string") {
174
+ return value.slice(0, 10);
175
+ }
176
+ return value;
177
+ }
178
+ function normalizeDateValue(value) {
179
+ if (value instanceof Date) {
180
+ return value;
181
+ }
182
+ if (typeof value === "string") {
183
+ return new Date(`${value.slice(0, 10)}T00:00:00Z`);
184
+ }
185
+ return value;
186
+ }
187
+ function normalizeTime(value) {
188
+ if (typeof value === "bigint") {
189
+ const totalMillis = Number(value) / 1000;
190
+ const date = new Date(totalMillis);
191
+ return date.toISOString().split("T")[1].replace("Z", "");
192
+ }
193
+ if (value instanceof Date) {
194
+ return value.toISOString().split("T")[1].replace("Z", "");
195
+ }
196
+ return value;
197
+ }
198
+ function normalizeInterval(value) {
199
+ if (value && typeof value === "object" && "days" in value && "months" in value) {
200
+ const { months, days, micros } = value;
201
+ if (months === 0 && days !== undefined) {
202
+ if (micros && Number(micros) !== 0) {
203
+ const seconds = Number(micros) / 1e6;
204
+ return `${days} day${days === 1 ? "" : "s"} ${seconds} seconds`.trim();
205
+ }
206
+ return `${days} day${days === 1 ? "" : "s"}`;
207
+ }
208
+ }
209
+ return value;
210
+ }
211
+ function mapDriverValue(decoder, rawValue) {
212
+ if (is(decoder, PgTimestampString)) {
213
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeTimestampString(rawValue, decoder.withTimezone)));
214
+ }
215
+ if (is(decoder, PgTimestamp)) {
216
+ const normalized = normalizeTimestamp(rawValue, decoder.withTimezone);
217
+ if (normalized instanceof Date) {
218
+ return normalized;
219
+ }
220
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, normalized));
221
+ }
222
+ if (is(decoder, PgDateString)) {
223
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeDateString(rawValue)));
224
+ }
225
+ if (is(decoder, PgDate)) {
226
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeDateValue(rawValue)));
227
+ }
228
+ if (is(decoder, PgTime)) {
229
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeTime(rawValue)));
230
+ }
231
+ if (is(decoder, PgInterval)) {
232
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeInterval(rawValue)));
233
+ }
234
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, rawValue));
235
+ }
236
+ function mapResultRow(columns, row, joinsNotNullableMap) {
237
+ const nullifyMap = {};
238
+ const result = columns.reduce((acc, { path, field }, columnIndex) => {
239
+ let decoder;
240
+ if (is(field, Column)) {
241
+ decoder = field;
242
+ } else if (is(field, SQL)) {
243
+ decoder = field.decoder;
244
+ } else {
245
+ const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
246
+ if (is(col, PgCustomColumn)) {
247
+ decoder = col;
248
+ } else {
249
+ decoder = field.sql.decoder;
250
+ }
251
+ }
252
+ let node = acc;
253
+ for (const [pathChunkIndex, pathChunk] of path.entries()) {
254
+ if (pathChunkIndex < path.length - 1) {
255
+ if (!(pathChunk in node)) {
256
+ node[pathChunk] = {};
257
+ }
258
+ node = node[pathChunk];
259
+ continue;
260
+ }
261
+ const rawValue = normalizeInet(row[columnIndex]);
262
+ const value = node[pathChunk] = rawValue === null ? null : mapDriverValue(decoder, rawValue);
263
+ if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
264
+ const objectName = path[0];
265
+ if (!(objectName in nullifyMap)) {
266
+ nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
267
+ } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
268
+ nullifyMap[objectName] = false;
269
+ }
270
+ continue;
271
+ }
272
+ if (joinsNotNullableMap && is(field, SQL.Aliased) && path.length === 2) {
273
+ const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
274
+ const tableName = col?.table && getTableName(col?.table);
275
+ if (!tableName) {
276
+ continue;
277
+ }
278
+ const objectName = path[0];
279
+ if (!(objectName in nullifyMap)) {
280
+ nullifyMap[objectName] = value === null ? tableName : false;
281
+ continue;
282
+ }
283
+ if (nullifyMap[objectName] && nullifyMap[objectName] !== tableName) {
284
+ nullifyMap[objectName] = false;
285
+ }
286
+ continue;
287
+ }
288
+ }
289
+ return acc;
290
+ }, {});
291
+ if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
292
+ for (const [objectName, tableName] of Object.entries(nullifyMap)) {
293
+ if (typeof tableName === "string" && !joinsNotNullableMap[tableName]) {
294
+ result[objectName] = null;
295
+ }
296
+ }
297
+ }
298
+ return result;
299
+ }
300
+
301
+ // src/session.ts
302
+ import { TransactionRollbackError } from "drizzle-orm/errors";
303
+
304
+ // src/client.ts
305
+ import {
306
+ listValue
307
+ } from "@duckdb/node-api";
308
+ function isPgArrayLiteral(value) {
309
+ return value.startsWith("{") && value.endsWith("}");
310
+ }
311
+ function parsePgArrayLiteral(value) {
312
+ const json = value.replace(/{/g, "[").replace(/}/g, "]");
313
+ try {
314
+ return JSON.parse(json);
315
+ } catch {
316
+ return value;
317
+ }
318
+ }
319
+ var warnedArrayLiteral = false;
320
+ function prepareParams(params, options = {}) {
321
+ return params.map((param) => {
322
+ if (typeof param === "string" && isPgArrayLiteral(param)) {
323
+ if (options.rejectStringArrayLiterals) {
324
+ throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
325
+ }
326
+ if (!warnedArrayLiteral && options.warnOnStringArrayLiteral) {
327
+ warnedArrayLiteral = true;
328
+ options.warnOnStringArrayLiteral();
329
+ }
330
+ return parsePgArrayLiteral(param);
331
+ }
332
+ return param;
333
+ });
334
+ }
335
+ function toNodeApiValue(value) {
336
+ if (Array.isArray(value)) {
337
+ return listValue(value.map((inner) => toNodeApiValue(inner)));
338
+ }
339
+ return value;
340
+ }
341
+ async function executeOnClient(client, query, params) {
342
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
343
+ const result = await client.run(query, values);
344
+ const rows = await result.getRowsJS();
345
+ const columns = result.columnNames();
346
+ const seen = {};
347
+ const uniqueColumns = columns.map((col) => {
348
+ const count = seen[col] ?? 0;
349
+ seen[col] = count + 1;
350
+ return count === 0 ? col : `${col}_${count}`;
351
+ });
352
+ return (rows ?? []).map((vals) => {
353
+ const obj = {};
354
+ uniqueColumns.forEach((col, idx) => {
355
+ obj[col] = vals[idx];
356
+ });
357
+ return obj;
358
+ });
359
+ }
360
+
361
+ // src/session.ts
362
+ class DuckDBPreparedQuery extends PgPreparedQuery {
363
+ client;
364
+ dialect;
365
+ queryString;
366
+ params;
367
+ logger;
368
+ fields;
369
+ _isResponseInArrayMode;
370
+ customResultMapper;
371
+ rewriteArrays;
372
+ rejectStringArrayLiterals;
373
+ warnOnStringArrayLiteral;
374
+ static [entityKind] = "DuckDBPreparedQuery";
375
+ constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArrays, rejectStringArrayLiterals, warnOnStringArrayLiteral) {
376
+ super({ sql: queryString, params });
377
+ this.client = client;
378
+ this.dialect = dialect;
379
+ this.queryString = queryString;
380
+ this.params = params;
381
+ this.logger = logger;
382
+ this.fields = fields;
383
+ this._isResponseInArrayMode = _isResponseInArrayMode;
384
+ this.customResultMapper = customResultMapper;
385
+ this.rewriteArrays = rewriteArrays;
386
+ this.rejectStringArrayLiterals = rejectStringArrayLiterals;
387
+ this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
388
+ }
389
+ async execute(placeholderValues = {}) {
390
+ this.dialect.assertNoPgJsonColumns();
391
+ const params = prepareParams(fillPlaceholders(this.params, placeholderValues), {
392
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
393
+ warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
394
+ });
395
+ const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(this.queryString) : this.queryString;
396
+ if (this.rewriteArrays && rewrittenQuery !== this.queryString) {
397
+ this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
398
+ }
399
+ this.logger.logQuery(rewrittenQuery, params);
400
+ const {
401
+ fields,
402
+ joinsNotNullableMap,
403
+ customResultMapper
404
+ } = this;
405
+ const rows = await executeOnClient(this.client, rewrittenQuery, params);
406
+ if (rows.length === 0 || !fields) {
407
+ return rows;
408
+ }
409
+ const rowValues = rows.map((row) => Object.values(row));
410
+ return customResultMapper ? customResultMapper(rowValues) : rowValues.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
411
+ }
412
+ all(placeholderValues = {}) {
413
+ return this.execute(placeholderValues);
414
+ }
415
+ isResponseInArrayMode() {
416
+ return this._isResponseInArrayMode;
417
+ }
418
+ }
419
+
420
+ class DuckDBSession extends PgSession {
421
+ client;
422
+ schema;
423
+ options;
424
+ static [entityKind] = "DuckDBSession";
425
+ dialect;
426
+ logger;
427
+ rewriteArrays;
428
+ rejectStringArrayLiterals;
429
+ hasWarnedArrayLiteral = false;
430
+ constructor(client, dialect, schema, options = {}) {
431
+ super(dialect);
432
+ this.client = client;
433
+ this.schema = schema;
434
+ this.options = options;
435
+ this.dialect = dialect;
436
+ this.logger = options.logger ?? new NoopLogger;
437
+ this.rewriteArrays = options.rewriteArrays ?? true;
438
+ this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
439
+ }
440
+ prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
441
+ 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);
442
+ }
443
+ async transaction(transaction) {
444
+ const session = new DuckDBSession(this.client, this.dialect, this.schema, this.options);
445
+ const tx = new DuckDBTransaction(this.dialect, session, this.schema);
446
+ await tx.execute(sql`BEGIN TRANSACTION;`);
447
+ try {
448
+ const result = await transaction(tx);
449
+ await tx.execute(sql`commit`);
450
+ return result;
451
+ } catch (error) {
452
+ await tx.execute(sql`rollback`);
453
+ throw error;
454
+ }
455
+ }
456
+ warnOnStringArrayLiteral = (query) => {
457
+ if (this.hasWarnedArrayLiteral) {
458
+ return;
459
+ }
460
+ this.hasWarnedArrayLiteral = true;
461
+ this.logger.logQuery(`[duckdb] ${arrayLiteralWarning}
462
+ query: ${query}`, []);
463
+ };
464
+ }
465
+
466
+ class DuckDBTransaction extends PgTransaction {
467
+ static [entityKind] = "DuckDBTransaction";
468
+ rollback() {
469
+ throw new TransactionRollbackError;
470
+ }
471
+ getTransactionConfigSQL(config) {
472
+ const chunks = [];
473
+ if (config.isolationLevel) {
474
+ chunks.push(`isolation level ${config.isolationLevel}`);
475
+ }
476
+ if (config.accessMode) {
477
+ chunks.push(config.accessMode);
478
+ }
479
+ if (typeof config.deferrable === "boolean") {
480
+ chunks.push(config.deferrable ? "deferrable" : "not deferrable");
481
+ }
482
+ return sql.raw(chunks.join(" "));
483
+ }
484
+ setTransaction(config) {
485
+ return this.session.execute(sql`set transaction ${this.getTransactionConfigSQL(config)}`);
486
+ }
487
+ async transaction(transaction) {
488
+ const nestedTx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
489
+ return transaction(nestedTx);
490
+ }
491
+ }
492
+ var arrayLiteralWarning = "Received a stringified Postgres-style array literal. Use duckDbList()/duckDbArray() or pass native arrays instead. You can also set rejectStringArrayLiterals=true to throw.";
493
+
494
+ // src/dialect.ts
495
+ import { entityKind as entityKind2, is as is2 } from "drizzle-orm/entity";
496
+ import {
497
+ PgDate as PgDate2,
498
+ PgDateString as PgDateString2,
499
+ PgDialect,
500
+ PgJson,
501
+ PgJsonb,
502
+ PgNumeric,
503
+ PgTime as PgTime2,
504
+ PgTimestamp as PgTimestamp2,
505
+ PgTimestampString as PgTimestampString2,
506
+ PgUUID
507
+ } from "drizzle-orm/pg-core";
508
+ import {
509
+ sql as sql2
510
+ } from "drizzle-orm";
511
+
512
+ class DuckDBDialect extends PgDialect {
513
+ static [entityKind2] = "DuckDBPgDialect";
514
+ hasPgJsonColumn = false;
515
+ assertNoPgJsonColumns() {
516
+ if (this.hasPgJsonColumn) {
517
+ throw new Error("Pg JSON/JSONB columns are not supported in DuckDB. Replace them with duckDbJson() to use DuckDB’s native JSON type.");
518
+ }
519
+ }
520
+ async migrate(migrations, session, config) {
521
+ const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
522
+ const migrationsSchema = migrationConfig.migrationsSchema ?? "drizzle";
523
+ const migrationsTable = migrationConfig.migrationsTable ?? "__drizzle_migrations";
524
+ const migrationsSequence = `${migrationsTable}_id_seq`;
525
+ const legacySequence = "migrations_pk_seq";
526
+ const escapeIdentifier = (value) => value.replace(/"/g, '""');
527
+ const sequenceLiteral = `"${escapeIdentifier(migrationsSchema)}"."${escapeIdentifier(migrationsSequence)}"`;
528
+ const migrationTableCreate = sql2`
529
+ CREATE TABLE IF NOT EXISTS ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} (
530
+ id integer PRIMARY KEY default nextval('${sql2.raw(sequenceLiteral)}'),
531
+ hash text NOT NULL,
532
+ created_at bigint
533
+ )
534
+ `;
535
+ await session.execute(sql2`CREATE SCHEMA IF NOT EXISTS ${sql2.identifier(migrationsSchema)}`);
536
+ await session.execute(sql2`CREATE SEQUENCE IF NOT EXISTS ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsSequence)}`);
537
+ if (legacySequence !== migrationsSequence) {
538
+ await session.execute(sql2`CREATE SEQUENCE IF NOT EXISTS ${sql2.identifier(migrationsSchema)}.${sql2.identifier(legacySequence)}`);
539
+ }
540
+ await session.execute(migrationTableCreate);
541
+ const dbMigrations = await session.all(sql2`select id, hash, created_at from ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} order by created_at desc limit 1`);
542
+ const lastDbMigration = dbMigrations[0];
543
+ await session.transaction(async (tx) => {
544
+ for await (const migration of migrations) {
545
+ if (!lastDbMigration || Number(lastDbMigration.created_at) < migration.folderMillis) {
546
+ for (const stmt of migration.sql) {
547
+ await tx.execute(sql2.raw(stmt));
548
+ }
549
+ await tx.execute(sql2`insert into ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} ("hash", "created_at") values(${migration.hash}, ${migration.folderMillis})`);
550
+ }
551
+ }
552
+ });
553
+ }
554
+ prepareTyping(encoder) {
555
+ if (is2(encoder, PgJsonb) || is2(encoder, PgJson)) {
556
+ this.hasPgJsonColumn = true;
557
+ return "none";
558
+ } else if (is2(encoder, PgNumeric)) {
559
+ return "decimal";
560
+ } else if (is2(encoder, PgTime2)) {
561
+ return "time";
562
+ } else if (is2(encoder, PgTimestamp2) || is2(encoder, PgTimestampString2)) {
563
+ return "timestamp";
564
+ } else if (is2(encoder, PgDate2) || is2(encoder, PgDateString2)) {
565
+ return "date";
566
+ } else if (is2(encoder, PgUUID)) {
567
+ return "uuid";
568
+ } else {
569
+ return "none";
570
+ }
571
+ }
572
+ }
573
+
574
+ // src/select-builder.ts
575
+ import { is as is4 } from "drizzle-orm/entity";
576
+ import {
577
+ PgSelectBase,
578
+ PgSelectBuilder
579
+ } from "drizzle-orm/pg-core/query-builders";
580
+ import { Subquery, ViewBaseConfig } from "drizzle-orm";
581
+ import { PgViewBase } from "drizzle-orm/pg-core/view-base";
582
+ import { SQL as SQL4 } from "drizzle-orm/sql/sql";
583
+
584
+ // src/sql/selection.ts
585
+ import {
586
+ Column as Column2,
587
+ SQL as SQL3,
588
+ getTableName as getTableName2,
589
+ is as is3,
590
+ sql as sql3
591
+ } from "drizzle-orm";
592
+ function mapEntries(obj, prefix, fullJoin = false) {
593
+ return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
594
+ const qualified = prefix ? `${prefix}.${key}` : key;
595
+ if (fullJoin && is3(value, Column2)) {
596
+ return [
597
+ key,
598
+ sql3`${value}`.mapWith(value).as(`${getTableName2(value.table)}.${value.name}`)
599
+ ];
600
+ }
601
+ if (fullJoin && is3(value, SQL3)) {
602
+ const col = value.getSQL().queryChunks.find((chunk) => is3(chunk, Column2));
603
+ const tableName = col?.table && getTableName2(col?.table);
604
+ return [key, value.as(tableName ? `${tableName}.${key}` : key)];
605
+ }
606
+ if (is3(value, SQL3) || is3(value, Column2)) {
607
+ const aliased = is3(value, SQL3) ? value : sql3`${value}`.mapWith(value);
608
+ return [key, aliased.as(qualified)];
609
+ }
610
+ if (is3(value, SQL3.Aliased)) {
611
+ return [key, value];
612
+ }
613
+ if (typeof value === "object" && value !== null) {
614
+ return [
615
+ key,
616
+ mapEntries(value, qualified, fullJoin)
617
+ ];
618
+ }
619
+ return [key, value];
620
+ }));
621
+ }
622
+ function aliasFields(fields, fullJoin = false) {
623
+ return mapEntries(fields, undefined, fullJoin);
624
+ }
625
+
626
+ // src/select-builder.ts
627
+ import { getTableColumns } from "drizzle-orm/utils";
628
+
629
+ class DuckDBSelectBuilder extends PgSelectBuilder {
630
+ _fields;
631
+ _session;
632
+ _dialect;
633
+ _withList = [];
634
+ _distinct;
635
+ constructor(config) {
636
+ super(config);
637
+ this._fields = config.fields;
638
+ this._session = config.session;
639
+ this._dialect = config.dialect;
640
+ if (config.withList) {
641
+ this._withList = config.withList;
642
+ }
643
+ this._distinct = config.distinct;
644
+ }
645
+ from(source) {
646
+ const isPartialSelect = !!this._fields;
647
+ const src = source;
648
+ let fields;
649
+ if (this._fields) {
650
+ fields = this._fields;
651
+ } else if (is4(src, Subquery)) {
652
+ fields = Object.fromEntries(Object.keys(src._.selectedFields).map((key) => [
653
+ key,
654
+ src[key]
655
+ ]));
656
+ } else if (is4(src, PgViewBase)) {
657
+ fields = src[ViewBaseConfig]?.selectedFields;
658
+ } else if (is4(src, SQL4)) {
659
+ fields = {};
660
+ } else {
661
+ fields = aliasFields(getTableColumns(src), !isPartialSelect);
662
+ }
663
+ return new PgSelectBase({
664
+ table: src,
665
+ fields,
666
+ isPartialSelect,
667
+ session: this._session,
668
+ dialect: this._dialect,
669
+ withList: this._withList,
670
+ distinct: this._distinct
671
+ });
672
+ }
673
+ }
674
+
675
+ // src/driver.ts
676
+ class DuckDBDriver {
677
+ client;
678
+ dialect;
679
+ options;
680
+ static [entityKind3] = "DuckDBDriver";
681
+ constructor(client, dialect, options = {}) {
682
+ this.client = client;
683
+ this.dialect = dialect;
684
+ this.options = options;
685
+ }
686
+ createSession(schema) {
687
+ return new DuckDBSession(this.client, this.dialect, schema, {
688
+ logger: this.options.logger,
689
+ rewriteArrays: this.options.rewriteArrays,
690
+ rejectStringArrayLiterals: this.options.rejectStringArrayLiterals
691
+ });
692
+ }
693
+ }
694
+ function drizzle(client, config = {}) {
695
+ const dialect = new DuckDBDialect;
696
+ const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
697
+ let schema;
698
+ if (config.schema) {
699
+ const tablesConfig = extractTablesRelationalConfig(config.schema, createTableRelationsHelpers);
700
+ schema = {
701
+ fullSchema: config.schema,
702
+ schema: tablesConfig.tables,
703
+ tableNamesMap: tablesConfig.tableNamesMap
704
+ };
705
+ }
706
+ const driver = new DuckDBDriver(client, dialect, {
707
+ logger,
708
+ rewriteArrays: config.rewriteArrays,
709
+ rejectStringArrayLiterals: config.rejectStringArrayLiterals
710
+ });
711
+ const session = driver.createSession(schema);
712
+ return new DuckDBDatabase(dialect, session, schema);
713
+ }
714
+
715
+ class DuckDBDatabase extends PgDatabase {
716
+ dialect;
717
+ session;
718
+ static [entityKind3] = "DuckDBDatabase";
719
+ constructor(dialect, session, schema) {
720
+ super(dialect, session, schema);
721
+ this.dialect = dialect;
722
+ this.session = session;
723
+ }
724
+ select(fields) {
725
+ const selectedFields = fields ? aliasFields(fields) : undefined;
726
+ return new DuckDBSelectBuilder({
727
+ fields: selectedFields ?? undefined,
728
+ session: this.session,
729
+ dialect: this.dialect
730
+ });
731
+ }
732
+ async transaction(transaction) {
733
+ return await this.session.transaction(transaction);
734
+ }
735
+ }
736
+ // src/columns.ts
737
+ import { sql as sql4 } from "drizzle-orm";
738
+ import { customType } from "drizzle-orm/pg-core";
739
+ function coerceArrayString(value) {
740
+ const trimmed = value.trim();
741
+ if (!trimmed) {
742
+ return [];
743
+ }
744
+ if (trimmed.startsWith("[")) {
745
+ try {
746
+ return JSON.parse(trimmed);
747
+ } catch {
748
+ return;
749
+ }
750
+ }
751
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
752
+ try {
753
+ const json = trimmed.replace(/{/g, "[").replace(/}/g, "]");
754
+ return JSON.parse(json);
755
+ } catch {
756
+ return;
757
+ }
758
+ }
759
+ return;
760
+ }
761
+ function formatLiteral(value, typeHint) {
762
+ if (value === null || value === undefined) {
763
+ return "NULL";
764
+ }
765
+ const upperType = typeHint?.toUpperCase() ?? "";
766
+ if (value instanceof Date) {
767
+ return `'${value.toISOString()}'`;
768
+ }
769
+ if (typeof value === "number" || typeof value === "bigint") {
770
+ return value.toString();
771
+ }
772
+ if (typeof value === "boolean") {
773
+ return value ? "TRUE" : "FALSE";
774
+ }
775
+ const str = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
776
+ const escaped = str.replace(/'/g, "''");
777
+ if (upperType.includes("CHAR") || upperType.includes("TEXT") || upperType.includes("STRING") || upperType.includes("VARCHAR")) {
778
+ return `'${escaped}'`;
779
+ }
780
+ return `'${escaped}'`;
781
+ }
782
+ function buildListLiteral(values, elementType) {
783
+ if (values.length === 0) {
784
+ return sql4`[]`;
785
+ }
786
+ const chunks = values.map((v) => typeof v === "object" && !Array.isArray(v) ? sql4`${v}` : sql4.raw(formatLiteral(v, elementType)));
787
+ return sql4`list_value(${sql4.join(chunks, sql4.raw(", "))})`;
788
+ }
789
+ function buildStructLiteral(value, schema) {
790
+ const parts = Object.entries(value).map(([key, val]) => {
791
+ const typeHint = schema?.[key];
792
+ if (Array.isArray(val)) {
793
+ const inner = typeof typeHint === "string" && typeHint.endsWith("[]") ? typeHint.slice(0, -2) : undefined;
794
+ return sql4`${sql4.identifier(key)} := ${buildListLiteral(val, inner)}`;
795
+ }
796
+ return sql4`${sql4.identifier(key)} := ${val}`;
797
+ });
798
+ return sql4`struct_pack(${sql4.join(parts, sql4.raw(", "))})`;
799
+ }
800
+ function buildMapLiteral(value, valueType) {
801
+ const keys = Object.keys(value);
802
+ const vals = Object.values(value);
803
+ const keyList = buildListLiteral(keys, "TEXT");
804
+ const valList = buildListLiteral(vals, valueType?.endsWith("[]") ? valueType.slice(0, -2) : valueType);
805
+ return sql4`map(${keyList}, ${valList})`;
806
+ }
807
+ var duckDbList = (name, elementType) => customType({
808
+ dataType() {
809
+ return `${elementType}[]`;
810
+ },
811
+ toDriver(value) {
812
+ return buildListLiteral(value, elementType);
813
+ },
814
+ fromDriver(value) {
815
+ if (Array.isArray(value)) {
816
+ return value;
817
+ }
818
+ if (typeof value === "string") {
819
+ const parsed = coerceArrayString(value);
820
+ if (parsed) {
821
+ return parsed;
822
+ }
823
+ }
824
+ return [];
825
+ }
826
+ })(name);
827
+ var duckDbArray = (name, elementType, fixedLength) => customType({
828
+ dataType() {
829
+ return fixedLength ? `${elementType}[${fixedLength}]` : `${elementType}[]`;
830
+ },
831
+ toDriver(value) {
832
+ return buildListLiteral(value, elementType);
833
+ },
834
+ fromDriver(value) {
835
+ if (Array.isArray(value)) {
836
+ return value;
837
+ }
838
+ if (typeof value === "string") {
839
+ const parsed = coerceArrayString(value);
840
+ if (parsed) {
841
+ return parsed;
842
+ }
843
+ }
844
+ return [];
845
+ }
846
+ })(name);
847
+ var duckDbMap = (name, valueType) => customType({
848
+ dataType() {
849
+ return `MAP (STRING, ${valueType})`;
850
+ },
851
+ toDriver(value) {
852
+ return buildMapLiteral(value, valueType);
853
+ },
854
+ fromDriver(value) {
855
+ return value;
856
+ }
857
+ })(name);
858
+ var duckDbStruct = (name, schema) => customType({
859
+ dataType() {
860
+ const fields = Object.entries(schema).map(([key, type]) => `${key} ${type}`);
861
+ return `STRUCT (${fields.join(", ")})`;
862
+ },
863
+ toDriver(value) {
864
+ return buildStructLiteral(value, schema);
865
+ },
866
+ fromDriver(value) {
867
+ if (typeof value === "string") {
868
+ try {
869
+ return JSON.parse(value);
870
+ } catch {
871
+ return value;
872
+ }
873
+ }
874
+ return value;
875
+ }
876
+ })(name);
877
+ var duckDbJson = (name) => customType({
878
+ dataType() {
879
+ return "JSON";
880
+ },
881
+ toDriver(value) {
882
+ if (typeof value === "string") {
883
+ return value;
884
+ }
885
+ if (value !== null && typeof value === "object" && "queryChunks" in value) {
886
+ return value;
887
+ }
888
+ return JSON.stringify(value ?? null);
889
+ },
890
+ fromDriver(value) {
891
+ if (typeof value !== "string") {
892
+ return value;
893
+ }
894
+ const trimmed = value.trim();
895
+ if (!trimmed) {
896
+ return value;
897
+ }
898
+ try {
899
+ return JSON.parse(trimmed);
900
+ } catch {
901
+ return value;
902
+ }
903
+ }
904
+ })(name);
905
+ var duckDbBlob = customType({
906
+ dataType() {
907
+ return "BLOB";
908
+ },
909
+ toDriver(value) {
910
+ const hexString = value.toString("hex");
911
+ return sql4`from_hex(${hexString})`;
912
+ }
913
+ });
914
+ var duckDbInet = (name) => customType({
915
+ dataType() {
916
+ return "INET";
917
+ },
918
+ toDriver(value) {
919
+ return value;
920
+ }
921
+ })(name);
922
+ var duckDbInterval = (name) => customType({
923
+ dataType() {
924
+ return "INTERVAL";
925
+ },
926
+ toDriver(value) {
927
+ return value;
928
+ }
929
+ })(name);
930
+ var duckDbTimestamp = (name, options = {}) => customType({
931
+ dataType() {
932
+ if (options.withTimezone) {
933
+ return "TIMESTAMPTZ";
934
+ }
935
+ const precision = options.precision ? `(${options.precision})` : "";
936
+ return `TIMESTAMP${precision}`;
937
+ },
938
+ toDriver(value) {
939
+ const iso = value instanceof Date ? value.toISOString() : value;
940
+ const normalized = iso.replace("T", " ").replace("Z", "+00");
941
+ const typeKeyword = options.withTimezone ? "TIMESTAMPTZ" : "TIMESTAMP";
942
+ return sql4.raw(`${typeKeyword} '${normalized}'`);
943
+ },
944
+ fromDriver(value) {
945
+ if (options.mode === "string") {
946
+ if (value instanceof Date) {
947
+ return value.toISOString().replace("T", " ").replace("Z", "+00");
948
+ }
949
+ return typeof value === "string" ? value : value.toString();
950
+ }
951
+ if (value instanceof Date) {
952
+ return value;
953
+ }
954
+ const stringValue = typeof value === "string" ? value : value.toString();
955
+ const hasOffset = stringValue.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(stringValue);
956
+ const normalized = hasOffset ? stringValue.replace(" ", "T") : `${stringValue.replace(" ", "T")}Z`;
957
+ return new Date(normalized);
958
+ }
959
+ })(name);
960
+ var duckDbDate = (name) => customType({
961
+ dataType() {
962
+ return "DATE";
963
+ },
964
+ toDriver(value) {
965
+ return value;
966
+ },
967
+ fromDriver(value) {
968
+ const str = value instanceof Date ? value.toISOString().slice(0, 10) : value;
969
+ return str;
970
+ }
971
+ })(name);
972
+ var duckDbTime = (name) => customType({
973
+ dataType() {
974
+ return "TIME";
975
+ },
976
+ toDriver(value) {
977
+ return value;
978
+ },
979
+ fromDriver(value) {
980
+ if (typeof value === "bigint") {
981
+ const totalMillis = Number(value) / 1000;
982
+ const date = new Date(totalMillis);
983
+ return date.toISOString().split("T")[1].replace("Z", "");
984
+ }
985
+ return value;
986
+ }
987
+ })(name);
988
+ function toListValue(values) {
989
+ return buildListLiteral(values);
990
+ }
991
+ function duckDbArrayContains(column, values) {
992
+ const rhs = Array.isArray(values) ? toListValue(values) : values;
993
+ return sql4`array_has_all(${column}, ${rhs})`;
994
+ }
995
+ function duckDbArrayContained(column, values) {
996
+ const rhs = Array.isArray(values) ? toListValue(values) : values;
997
+ return sql4`array_has_all(${rhs}, ${column})`;
998
+ }
999
+ function duckDbArrayOverlaps(column, values) {
1000
+ const rhs = Array.isArray(values) ? toListValue(values) : values;
1001
+ return sql4`array_has_any(${column}, ${rhs})`;
1002
+ }
1003
+ // src/migrator.ts
1004
+ import { readMigrationFiles } from "drizzle-orm/migrator";
1005
+ async function migrate(db, config) {
1006
+ const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
1007
+ const migrations = readMigrationFiles(migrationConfig);
1008
+ await db.dialect.migrate(migrations, db.session, migrationConfig);
1009
+ }
1010
+ // src/introspect.ts
1011
+ import { sql as sql5 } from "drizzle-orm";
1012
+ var SYSTEM_SCHEMAS = new Set(["information_schema", "pg_catalog"]);
1013
+ var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb";
1014
+ async function introspect(db, opts = {}) {
1015
+ const schemas = await resolveSchemas(db, opts.schemas);
1016
+ const includeViews = opts.includeViews ?? false;
1017
+ const tables = await loadTables(db, schemas, includeViews);
1018
+ const columns = await loadColumns(db, schemas);
1019
+ const constraints = await loadConstraints(db, schemas);
1020
+ const indexes = await loadIndexes(db, schemas);
1021
+ const grouped = buildTables(tables, columns, constraints, indexes);
1022
+ const schemaTs = emitSchema(grouped, {
1023
+ useCustomTimeTypes: opts.useCustomTimeTypes ?? true,
1024
+ mapJsonAsDuckDbJson: opts.mapJsonAsDuckDbJson ?? true,
1025
+ importBasePath: opts.importBasePath ?? DEFAULT_IMPORT_BASE
1026
+ });
1027
+ return {
1028
+ files: {
1029
+ schemaTs,
1030
+ metaJson: grouped
1031
+ }
1032
+ };
1033
+ }
1034
+ async function resolveSchemas(db, targetSchemas) {
1035
+ if (targetSchemas?.length) {
1036
+ return targetSchemas;
1037
+ }
1038
+ const rows = await db.execute(sql5`select schema_name from information_schema.schemata`);
1039
+ return rows.map((row) => row.schema_name).filter((name) => !SYSTEM_SCHEMAS.has(name));
1040
+ }
1041
+ async function loadTables(db, schemas, includeViews) {
1042
+ const schemaFragments = schemas.map((schema) => sql5`${schema}`);
1043
+ return await db.execute(sql5`
1044
+ select table_schema as schema_name, table_name, table_type
1045
+ from information_schema.tables
1046
+ where table_schema in (${sql5.join(schemaFragments, sql5.raw(", "))})
1047
+ and ${includeViews ? sql5`1 = 1` : sql5`table_type = 'BASE TABLE'`}
1048
+ order by table_schema, table_name
1049
+ `);
1050
+ }
1051
+ async function loadColumns(db, schemas) {
1052
+ const schemaFragments = schemas.map((schema) => sql5`${schema}`);
1053
+ return await db.execute(sql5`
1054
+ select
1055
+ schema_name,
1056
+ table_name,
1057
+ column_name,
1058
+ column_index,
1059
+ column_default,
1060
+ is_nullable,
1061
+ data_type,
1062
+ character_maximum_length,
1063
+ numeric_precision,
1064
+ numeric_scale,
1065
+ internal
1066
+ from duckdb_columns()
1067
+ where schema_name in (${sql5.join(schemaFragments, sql5.raw(", "))})
1068
+ order by schema_name, table_name, column_index
1069
+ `);
1070
+ }
1071
+ async function loadConstraints(db, schemas) {
1072
+ const schemaFragments = schemas.map((schema) => sql5`${schema}`);
1073
+ return await db.execute(sql5`
1074
+ select
1075
+ schema_name,
1076
+ table_name,
1077
+ constraint_name,
1078
+ constraint_type,
1079
+ constraint_text,
1080
+ constraint_column_names,
1081
+ referenced_table,
1082
+ referenced_column_names
1083
+ from duckdb_constraints()
1084
+ where schema_name in (${sql5.join(schemaFragments, sql5.raw(", "))})
1085
+ order by schema_name, table_name, constraint_index
1086
+ `);
1087
+ }
1088
+ async function loadIndexes(db, schemas) {
1089
+ const schemaFragments = schemas.map((schema) => sql5`${schema}`);
1090
+ return await db.execute(sql5`
1091
+ select
1092
+ schema_name,
1093
+ table_name,
1094
+ index_name,
1095
+ is_unique,
1096
+ expressions
1097
+ from duckdb_indexes()
1098
+ where schema_name in (${sql5.join(schemaFragments, sql5.raw(", "))})
1099
+ order by schema_name, table_name, index_name
1100
+ `);
1101
+ }
1102
+ function buildTables(tables, columns, constraints, indexes) {
1103
+ const byTable = {};
1104
+ for (const table of tables) {
1105
+ const key = tableKey(table.schema_name, table.table_name);
1106
+ byTable[key] = {
1107
+ schema: table.schema_name,
1108
+ name: table.table_name,
1109
+ kind: table.table_type === "VIEW" ? "view" : "table",
1110
+ columns: [],
1111
+ constraints: [],
1112
+ indexes: []
1113
+ };
1114
+ }
1115
+ for (const column of columns) {
1116
+ if (column.internal) {
1117
+ continue;
1118
+ }
1119
+ const key = tableKey(column.schema_name, column.table_name);
1120
+ const table = byTable[key];
1121
+ if (!table) {
1122
+ continue;
1123
+ }
1124
+ table.columns.push({
1125
+ name: column.column_name,
1126
+ dataType: column.data_type,
1127
+ columnDefault: column.column_default,
1128
+ nullable: column.is_nullable,
1129
+ characterLength: column.character_maximum_length,
1130
+ numericPrecision: column.numeric_precision,
1131
+ numericScale: column.numeric_scale
1132
+ });
1133
+ }
1134
+ for (const constraint of constraints) {
1135
+ const key = tableKey(constraint.schema_name, constraint.table_name);
1136
+ const table = byTable[key];
1137
+ if (!table) {
1138
+ continue;
1139
+ }
1140
+ if (!constraint.constraint_column_names?.length) {
1141
+ continue;
1142
+ }
1143
+ table.constraints.push({
1144
+ name: constraint.constraint_name,
1145
+ type: constraint.constraint_type,
1146
+ columns: constraint.constraint_column_names ?? [],
1147
+ referencedTable: constraint.referenced_table && constraint.referenced_column_names ? {
1148
+ schema: constraint.schema_name,
1149
+ name: constraint.referenced_table,
1150
+ columns: constraint.referenced_column_names
1151
+ } : undefined,
1152
+ rawExpression: constraint.constraint_text
1153
+ });
1154
+ }
1155
+ for (const index of indexes) {
1156
+ const key = tableKey(index.schema_name, index.table_name);
1157
+ const table = byTable[key];
1158
+ if (!table) {
1159
+ continue;
1160
+ }
1161
+ table.indexes.push(index);
1162
+ }
1163
+ return Object.values(byTable);
1164
+ }
1165
+ function emitSchema(catalog, options) {
1166
+ const imports = {
1167
+ drizzle: new Set,
1168
+ pgCore: new Set,
1169
+ local: new Set
1170
+ };
1171
+ imports.pgCore.add("pgSchema");
1172
+ const sorted = [...catalog].sort((a, b) => a.schema === b.schema ? a.name.localeCompare(b.name) : a.schema.localeCompare(b.schema));
1173
+ const lines = [];
1174
+ for (const schema of uniqueSchemas(sorted)) {
1175
+ imports.pgCore.add("pgSchema");
1176
+ const schemaVar = toSchemaIdentifier(schema);
1177
+ lines.push(`export const ${schemaVar} = pgSchema(${JSON.stringify(schema)});`, "");
1178
+ const tables = sorted.filter((table) => table.schema === schema);
1179
+ for (const table of tables) {
1180
+ lines.push(...emitTable(schemaVar, table, imports, options));
1181
+ lines.push("");
1182
+ }
1183
+ }
1184
+ const importsBlock = renderImports(imports, options.importBasePath);
1185
+ return [importsBlock, ...lines].join(`
1186
+ `).trim() + `
1187
+ `;
1188
+ }
1189
+ function emitTable(schemaVar, table, imports, options) {
1190
+ const tableVar = toIdentifier(table.name);
1191
+ const columnLines = [];
1192
+ for (const column of table.columns) {
1193
+ columnLines.push(` ${columnProperty(column.name)}: ${emitColumn(column, imports, options)},`);
1194
+ }
1195
+ const constraintBlock = emitConstraints(table, imports);
1196
+ const tableLines = [];
1197
+ tableLines.push(`export const ${tableVar} = ${schemaVar}.table(${JSON.stringify(table.name)}, {`);
1198
+ tableLines.push(...columnLines);
1199
+ tableLines.push(`}${constraintBlock ? "," : ""}${constraintBlock ? ` ${constraintBlock}` : ""});`);
1200
+ return tableLines;
1201
+ }
1202
+ function emitConstraints(table, imports) {
1203
+ const constraints = table.constraints.filter((constraint) => ["PRIMARY KEY", "FOREIGN KEY", "UNIQUE"].includes(constraint.type));
1204
+ if (!constraints.length) {
1205
+ return "";
1206
+ }
1207
+ const entries = [];
1208
+ for (const constraint of constraints) {
1209
+ const key = toIdentifier(constraint.name || `${table.name}_constraint`);
1210
+ if (constraint.type === "PRIMARY KEY") {
1211
+ imports.pgCore.add("primaryKey");
1212
+ entries.push(`${key}: primaryKey({ columns: [${constraint.columns.map((col) => `t.${toIdentifier(col)}`).join(", ")}], name: ${JSON.stringify(constraint.name)} })`);
1213
+ } else if (constraint.type === "UNIQUE" && constraint.columns.length > 1) {
1214
+ imports.pgCore.add("unique");
1215
+ entries.push(`${key}: unique(${JSON.stringify(constraint.name)}).on(${constraint.columns.map((col) => `t.${toIdentifier(col)}`).join(", ")})`);
1216
+ } else if (constraint.type === "FOREIGN KEY" && constraint.referencedTable) {
1217
+ imports.pgCore.add("foreignKey");
1218
+ const targetTable = toIdentifier(constraint.referencedTable.name);
1219
+ entries.push(`${key}: foreignKey({ columns: [${constraint.columns.map((col) => `t.${toIdentifier(col)}`).join(", ")}], foreignColumns: [${constraint.referencedTable.columns.map((col) => `${targetTable}.${toIdentifier(col)}`).join(", ")}], name: ${JSON.stringify(constraint.name)} })`);
1220
+ } else if (constraint.type === "UNIQUE" && constraint.columns.length === 1) {
1221
+ const columnName = constraint.columns[0];
1222
+ entries.push(`${key}: t.${toIdentifier(columnName)}.unique(${JSON.stringify(constraint.name)})`);
1223
+ }
1224
+ }
1225
+ if (!entries.length) {
1226
+ return "";
1227
+ }
1228
+ const lines = ["(t) => ({"];
1229
+ for (const entry of entries) {
1230
+ lines.push(` ${entry},`);
1231
+ }
1232
+ lines.push("})");
1233
+ return lines.join(`
1234
+ `);
1235
+ }
1236
+ function emitColumn(column, imports, options) {
1237
+ const mapping = mapDuckDbType(column, imports, options);
1238
+ let builder = mapping.builder;
1239
+ if (!column.nullable) {
1240
+ builder += ".notNull()";
1241
+ }
1242
+ const defaultFragment = buildDefault(column.columnDefault);
1243
+ if (defaultFragment) {
1244
+ imports.drizzle.add("sql");
1245
+ builder += defaultFragment;
1246
+ }
1247
+ return builder;
1248
+ }
1249
+ function buildDefault(defaultValue) {
1250
+ if (!defaultValue) {
1251
+ return "";
1252
+ }
1253
+ const trimmed = defaultValue.trim();
1254
+ if (!trimmed || trimmed.toUpperCase() === "NULL") {
1255
+ return "";
1256
+ }
1257
+ if (/^nextval\(/i.test(trimmed)) {
1258
+ return `.default(sql\`${trimmed}\`)`;
1259
+ }
1260
+ if (/^current_timestamp(?:\(\))?$/i.test(trimmed) || /^now\(\)$/i.test(trimmed)) {
1261
+ return `.defaultNow()`;
1262
+ }
1263
+ if (trimmed === "true" || trimmed === "false") {
1264
+ return `.default(${trimmed})`;
1265
+ }
1266
+ const numberValue = Number(trimmed);
1267
+ if (!Number.isNaN(numberValue)) {
1268
+ return `.default(${trimmed})`;
1269
+ }
1270
+ const stringLiteralMatch = /^'(.*)'$/.exec(trimmed);
1271
+ if (stringLiteralMatch) {
1272
+ const value = stringLiteralMatch[1]?.replace(/''/g, "'");
1273
+ return `.default(${JSON.stringify(value)})`;
1274
+ }
1275
+ return "";
1276
+ }
1277
+ function mapDuckDbType(column, imports, options) {
1278
+ const raw = column.dataType.trim();
1279
+ const upper = raw.toUpperCase();
1280
+ if (upper === "BOOLEAN" || upper === "BOOL") {
1281
+ imports.pgCore.add("boolean");
1282
+ return { builder: `boolean(${columnName(column.name)})` };
1283
+ }
1284
+ if (upper === "SMALLINT" || upper === "INT2" || upper === "INT16" || upper === "TINYINT") {
1285
+ imports.pgCore.add("integer");
1286
+ return { builder: `integer(${columnName(column.name)})` };
1287
+ }
1288
+ if (upper === "INTEGER" || upper === "INT" || upper === "INT4" || upper === "SIGNED") {
1289
+ imports.pgCore.add("integer");
1290
+ return { builder: `integer(${columnName(column.name)})` };
1291
+ }
1292
+ if (upper === "BIGINT" || upper === "INT8" || upper === "UBIGINT") {
1293
+ imports.pgCore.add("bigint");
1294
+ return { builder: `bigint(${columnName(column.name)})` };
1295
+ }
1296
+ const decimalMatch = /^DECIMAL\((\d+),(\d+)\)/i.exec(upper);
1297
+ const numericMatch = /^NUMERIC\((\d+),(\d+)\)/i.exec(upper);
1298
+ if (decimalMatch || numericMatch) {
1299
+ imports.pgCore.add("numeric");
1300
+ const [, precision, scale] = decimalMatch ?? numericMatch;
1301
+ return {
1302
+ builder: `numeric(${columnName(column.name)}, { precision: ${precision}, scale: ${scale} })`
1303
+ };
1304
+ }
1305
+ if (upper.startsWith("DECIMAL") || upper.startsWith("NUMERIC")) {
1306
+ imports.pgCore.add("numeric");
1307
+ const precision = column.numericPrecision;
1308
+ const scale = column.numericScale;
1309
+ const options2 = [];
1310
+ if (precision !== null && precision !== undefined) {
1311
+ options2.push(`precision: ${precision}`);
1312
+ }
1313
+ if (scale !== null && scale !== undefined) {
1314
+ options2.push(`scale: ${scale}`);
1315
+ }
1316
+ const suffix = options2.length ? `, { ${options2.join(", ")} }` : "";
1317
+ return { builder: `numeric(${columnName(column.name)}${suffix})` };
1318
+ }
1319
+ if (upper === "REAL" || upper === "FLOAT4") {
1320
+ imports.pgCore.add("real");
1321
+ return { builder: `real(${columnName(column.name)})` };
1322
+ }
1323
+ if (upper === "DOUBLE" || upper === "DOUBLE PRECISION" || upper === "FLOAT") {
1324
+ imports.pgCore.add("doublePrecision");
1325
+ return { builder: `doublePrecision(${columnName(column.name)})` };
1326
+ }
1327
+ if (upper.startsWith("CHAR(") || upper === "CHAR") {
1328
+ imports.pgCore.add("char");
1329
+ const length = column.characterLength;
1330
+ const lengthPart = typeof length === "number" ? `, { length: ${length} }` : "";
1331
+ return { builder: `char(${columnName(column.name)}${lengthPart})` };
1332
+ }
1333
+ if (upper.startsWith("VARCHAR")) {
1334
+ imports.pgCore.add("varchar");
1335
+ const length = column.characterLength;
1336
+ const lengthPart = typeof length === "number" ? `, { length: ${length} }` : "";
1337
+ return { builder: `varchar(${columnName(column.name)}${lengthPart})` };
1338
+ }
1339
+ if (upper === "TEXT" || upper === "STRING") {
1340
+ imports.pgCore.add("text");
1341
+ return { builder: `text(${columnName(column.name)})` };
1342
+ }
1343
+ if (upper === "UUID") {
1344
+ imports.pgCore.add("uuid");
1345
+ return { builder: `uuid(${columnName(column.name)})` };
1346
+ }
1347
+ if (upper === "JSON") {
1348
+ if (options.mapJsonAsDuckDbJson) {
1349
+ imports.local.add("duckDbJson");
1350
+ return { builder: `duckDbJson(${columnName(column.name)})` };
1351
+ }
1352
+ imports.pgCore.add("text");
1353
+ return { builder: `text(${columnName(column.name)}) /* JSON */` };
1354
+ }
1355
+ if (upper === "INET") {
1356
+ imports.local.add("duckDbInet");
1357
+ return { builder: `duckDbInet(${columnName(column.name)})` };
1358
+ }
1359
+ if (upper === "INTERVAL") {
1360
+ imports.local.add("duckDbInterval");
1361
+ return { builder: `duckDbInterval(${columnName(column.name)})` };
1362
+ }
1363
+ if (upper === "BLOB" || upper === "BYTEA" || upper === "VARBINARY") {
1364
+ imports.local.add("duckDbBlob");
1365
+ return { builder: `duckDbBlob(${columnName(column.name)})` };
1366
+ }
1367
+ const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
1368
+ if (arrayMatch) {
1369
+ imports.local.add("duckDbArray");
1370
+ const [, base, length] = arrayMatch;
1371
+ return {
1372
+ builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
1373
+ };
1374
+ }
1375
+ const listMatch = /^(.*)\[\]$/.exec(upper);
1376
+ if (listMatch) {
1377
+ imports.local.add("duckDbList");
1378
+ const [, base] = listMatch;
1379
+ return {
1380
+ builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
1381
+ };
1382
+ }
1383
+ if (upper.startsWith("STRUCT")) {
1384
+ imports.local.add("duckDbStruct");
1385
+ const inner = upper.replace(/^STRUCT\s*\(/i, "").replace(/\)$/, "");
1386
+ const fields = parseStructFields(inner);
1387
+ const entries = fields.map(({ name, type }) => `${JSON.stringify(name)}: ${JSON.stringify(type)}`);
1388
+ return {
1389
+ builder: `duckDbStruct(${columnName(column.name)}, { ${entries.join(", ")} })`
1390
+ };
1391
+ }
1392
+ if (upper.startsWith("MAP(")) {
1393
+ imports.local.add("duckDbMap");
1394
+ const valueType = parseMapValue(upper);
1395
+ return {
1396
+ builder: `duckDbMap(${columnName(column.name)}, ${JSON.stringify(valueType)})`
1397
+ };
1398
+ }
1399
+ if (upper.startsWith("TIMESTAMP WITH TIME ZONE")) {
1400
+ if (options.useCustomTimeTypes) {
1401
+ imports.local.add("duckDbTimestamp");
1402
+ } else {
1403
+ imports.pgCore.add("timestamp");
1404
+ }
1405
+ const factory = options.useCustomTimeTypes ? `duckDbTimestamp(${columnName(column.name)}, { withTimezone: true })` : `timestamp(${columnName(column.name)}, { withTimezone: true })`;
1406
+ return { builder: factory };
1407
+ }
1408
+ if (upper.startsWith("TIMESTAMP")) {
1409
+ if (options.useCustomTimeTypes) {
1410
+ imports.local.add("duckDbTimestamp");
1411
+ return {
1412
+ builder: `duckDbTimestamp(${columnName(column.name)})`
1413
+ };
1414
+ }
1415
+ imports.pgCore.add("timestamp");
1416
+ return { builder: `timestamp(${columnName(column.name)})` };
1417
+ }
1418
+ if (upper === "TIME") {
1419
+ if (options.useCustomTimeTypes) {
1420
+ imports.local.add("duckDbTime");
1421
+ return { builder: `duckDbTime(${columnName(column.name)})` };
1422
+ }
1423
+ imports.pgCore.add("time");
1424
+ return { builder: `time(${columnName(column.name)})` };
1425
+ }
1426
+ if (upper === "DATE") {
1427
+ if (options.useCustomTimeTypes) {
1428
+ imports.local.add("duckDbDate");
1429
+ return { builder: `duckDbDate(${columnName(column.name)})` };
1430
+ }
1431
+ imports.pgCore.add("date");
1432
+ return { builder: `date(${columnName(column.name)})` };
1433
+ }
1434
+ imports.pgCore.add("text");
1435
+ return {
1436
+ builder: `text(${columnName(column.name)}) /* TODO: verify type ${upper} */`
1437
+ };
1438
+ }
1439
+ function parseStructFields(inner) {
1440
+ const result = [];
1441
+ for (const part of splitTopLevel(inner, ",")) {
1442
+ const trimmed = part.trim();
1443
+ if (!trimmed)
1444
+ continue;
1445
+ const match = /^"?([^"]+)"?\s+(.*)$/i.exec(trimmed);
1446
+ if (!match) {
1447
+ continue;
1448
+ }
1449
+ const [, name, type] = match;
1450
+ result.push({ name, type: type.trim() });
1451
+ }
1452
+ return result;
1453
+ }
1454
+ function parseMapValue(raw) {
1455
+ const inner = raw.replace(/^MAP\(/i, "").replace(/\)$/, "");
1456
+ const parts = splitTopLevel(inner, ",");
1457
+ if (parts.length < 2) {
1458
+ return "TEXT";
1459
+ }
1460
+ return parts[1]?.trim() ?? "TEXT";
1461
+ }
1462
+ function splitTopLevel(input, delimiter) {
1463
+ const parts = [];
1464
+ let depth = 0;
1465
+ let current = "";
1466
+ for (let i = 0;i < input.length; i += 1) {
1467
+ const char = input[i];
1468
+ if (char === "(")
1469
+ depth += 1;
1470
+ if (char === ")")
1471
+ depth = Math.max(0, depth - 1);
1472
+ if (char === delimiter && depth === 0) {
1473
+ parts.push(current);
1474
+ current = "";
1475
+ continue;
1476
+ }
1477
+ current += char;
1478
+ }
1479
+ if (current) {
1480
+ parts.push(current);
1481
+ }
1482
+ return parts;
1483
+ }
1484
+ function tableKey(schema, table) {
1485
+ return `${schema}.${table}`;
1486
+ }
1487
+ function toIdentifier(name) {
1488
+ const cleaned = name.replace(/[^A-Za-z0-9_]/g, "_");
1489
+ const parts = cleaned.split("_").filter(Boolean);
1490
+ const base = parts.map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part.toLowerCase())).join("");
1491
+ const candidate = base || "item";
1492
+ return /^[A-Za-z_]/.test(candidate) ? candidate : `t${candidate}`;
1493
+ }
1494
+ function toSchemaIdentifier(schema) {
1495
+ const base = toIdentifier(schema);
1496
+ return base.endsWith("Schema") ? base : `${base}Schema`;
1497
+ }
1498
+ function columnProperty(column) {
1499
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(column)) {
1500
+ return toIdentifier(column);
1501
+ }
1502
+ return JSON.stringify(column);
1503
+ }
1504
+ function columnName(name) {
1505
+ return JSON.stringify(name);
1506
+ }
1507
+ function capitalize(value) {
1508
+ if (!value)
1509
+ return value;
1510
+ return value[0].toUpperCase() + value.slice(1);
1511
+ }
1512
+ function uniqueSchemas(tables) {
1513
+ const seen = new Set;
1514
+ const result = [];
1515
+ for (const table of tables) {
1516
+ if (!seen.has(table.schema)) {
1517
+ seen.add(table.schema);
1518
+ result.push(table.schema);
1519
+ }
1520
+ }
1521
+ return result;
1522
+ }
1523
+ function renderImports(imports, importBasePath) {
1524
+ const lines = [];
1525
+ const drizzle2 = [...imports.drizzle];
1526
+ if (drizzle2.length) {
1527
+ lines.push(`import { ${drizzle2.sort().join(", ")} } from 'drizzle-orm';`);
1528
+ }
1529
+ const pgCore = [...imports.pgCore];
1530
+ if (pgCore.length) {
1531
+ lines.push(`import { ${pgCore.sort().join(", ")} } from 'drizzle-orm/pg-core';`);
1532
+ }
1533
+ const local = [...imports.local];
1534
+ if (local.length) {
1535
+ lines.push(`import { ${local.sort().join(", ")} } from '${importBasePath}';`);
1536
+ }
1537
+ lines.push("");
1538
+ return lines.join(`
1539
+ `);
1540
+ }
1541
+ export {
1542
+ migrate,
1543
+ introspect,
1544
+ duckDbTimestamp,
1545
+ duckDbTime,
1546
+ duckDbStruct,
1547
+ duckDbMap,
1548
+ duckDbList,
1549
+ duckDbJson,
1550
+ duckDbInterval,
1551
+ duckDbInet,
1552
+ duckDbDate,
1553
+ duckDbBlob,
1554
+ duckDbArrayOverlaps,
1555
+ duckDbArrayContains,
1556
+ duckDbArrayContained,
1557
+ duckDbArray,
1558
+ drizzle,
1559
+ DuckDBTransaction,
1560
+ DuckDBSession,
1561
+ DuckDBPreparedQuery,
1562
+ DuckDBDriver,
1563
+ DuckDBDatabase
1564
+ };