@leonardovida-md/drizzle-neo-duckdb 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -18
- package/dist/client.d.ts +19 -1
- package/dist/columns.d.ts +18 -10
- package/dist/driver.d.ts +37 -1
- package/dist/duckdb-introspect.mjs +382 -60
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.mjs +319 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +588 -72
- package/dist/introspect.d.ts +1 -0
- package/dist/olap.d.ts +46 -0
- package/dist/pool.d.ts +22 -0
- package/dist/session.d.ts +5 -0
- package/dist/sql/query-rewriters.d.ts +0 -1
- package/dist/utils.d.ts +1 -1
- package/dist/value-wrappers-core.d.ts +42 -0
- package/dist/value-wrappers.d.ts +8 -0
- package/package.json +12 -4
- package/src/bin/duckdb-introspect.ts +12 -3
- package/src/client.ts +178 -23
- package/src/columns.ts +65 -36
- package/src/dialect.ts +2 -2
- package/src/driver.ts +211 -13
- package/src/helpers.ts +18 -0
- package/src/index.ts +4 -0
- package/src/introspect.ts +39 -33
- package/src/migrator.ts +2 -4
- package/src/olap.ts +190 -0
- package/src/pool.ts +104 -0
- package/src/select-builder.ts +3 -7
- package/src/session.ts +123 -28
- package/src/sql/query-rewriters.ts +4 -54
- package/src/sql/result-mapper.ts +6 -6
- package/src/sql/selection.ts +2 -9
- package/src/utils.ts +1 -1
- package/src/value-wrappers-core.ts +156 -0
- package/src/value-wrappers.ts +155 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin/duckdb-introspect.ts
|
|
4
|
-
import { DuckDBInstance } from "@duckdb/node-api";
|
|
4
|
+
import { DuckDBInstance as DuckDBInstance3 } from "@duckdb/node-api";
|
|
5
5
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
|
|
9
9
|
// src/driver.ts
|
|
10
|
+
import { DuckDBInstance as DuckDBInstance2 } from "@duckdb/node-api";
|
|
10
11
|
import { entityKind as entityKind3 } from "drizzle-orm/entity";
|
|
11
12
|
import { DefaultLogger } from "drizzle-orm/logger";
|
|
12
13
|
import { PgDatabase } from "drizzle-orm/pg-core/db";
|
|
@@ -23,10 +24,6 @@ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
|
|
|
23
24
|
import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
|
|
24
25
|
|
|
25
26
|
// src/sql/query-rewriters.ts
|
|
26
|
-
var tableIdPropSelectionRegex = new RegExp([
|
|
27
|
-
`("(.+)"\\."(.+)")`,
|
|
28
|
-
`(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`
|
|
29
|
-
].join(""), "i");
|
|
30
27
|
function adaptArrayOperators(query) {
|
|
31
28
|
const operators = [
|
|
32
29
|
{ token: "@>", fn: "array_has_all" },
|
|
@@ -43,6 +40,8 @@ function adaptArrayOperators(query) {
|
|
|
43
40
|
let inString = false;
|
|
44
41
|
for (;idx >= 0; idx--) {
|
|
45
42
|
const ch = source[idx];
|
|
43
|
+
if (ch === undefined)
|
|
44
|
+
break;
|
|
46
45
|
if (ch === "'" && source[idx - 1] !== "\\") {
|
|
47
46
|
inString = !inString;
|
|
48
47
|
}
|
|
@@ -70,6 +69,8 @@ function adaptArrayOperators(query) {
|
|
|
70
69
|
let inString = false;
|
|
71
70
|
for (;idx < source.length; idx++) {
|
|
72
71
|
const ch = source[idx];
|
|
72
|
+
if (ch === undefined)
|
|
73
|
+
break;
|
|
73
74
|
if (ch === "'" && source[idx - 1] !== "\\") {
|
|
74
75
|
inString = !inString;
|
|
75
76
|
}
|
|
@@ -311,8 +312,85 @@ import { TransactionRollbackError } from "drizzle-orm/errors";
|
|
|
311
312
|
|
|
312
313
|
// src/client.ts
|
|
313
314
|
import {
|
|
314
|
-
listValue
|
|
315
|
+
listValue as listValue2,
|
|
316
|
+
timestampValue as timestampValue2
|
|
317
|
+
} from "@duckdb/node-api";
|
|
318
|
+
|
|
319
|
+
// src/value-wrappers.ts
|
|
320
|
+
import {
|
|
321
|
+
listValue,
|
|
322
|
+
arrayValue,
|
|
323
|
+
structValue,
|
|
324
|
+
mapValue,
|
|
325
|
+
blobValue,
|
|
326
|
+
timestampValue,
|
|
327
|
+
timestampTZValue
|
|
315
328
|
} from "@duckdb/node-api";
|
|
329
|
+
|
|
330
|
+
// src/value-wrappers-core.ts
|
|
331
|
+
var DUCKDB_VALUE_MARKER = Symbol.for("drizzle-duckdb:value");
|
|
332
|
+
|
|
333
|
+
// src/value-wrappers.ts
|
|
334
|
+
function dateToMicros(value) {
|
|
335
|
+
if (value instanceof Date) {
|
|
336
|
+
return BigInt(value.getTime()) * 1000n;
|
|
337
|
+
}
|
|
338
|
+
let normalized = value;
|
|
339
|
+
if (!value.includes("T") && value.includes(" ")) {
|
|
340
|
+
normalized = value.replace(" ", "T");
|
|
341
|
+
}
|
|
342
|
+
if (!normalized.endsWith("Z") && !/[+-]\d{2}:?\d{2}$/.test(normalized)) {
|
|
343
|
+
normalized += "Z";
|
|
344
|
+
}
|
|
345
|
+
const date = new Date(normalized);
|
|
346
|
+
if (isNaN(date.getTime())) {
|
|
347
|
+
throw new Error(`Invalid timestamp string: ${value}`);
|
|
348
|
+
}
|
|
349
|
+
return BigInt(date.getTime()) * 1000n;
|
|
350
|
+
}
|
|
351
|
+
function toUint8Array(data) {
|
|
352
|
+
return data instanceof Uint8Array && !(data instanceof Buffer) ? data : new Uint8Array(data);
|
|
353
|
+
}
|
|
354
|
+
function convertStructEntries(data, toValue) {
|
|
355
|
+
const entries = {};
|
|
356
|
+
for (const [key, val] of Object.entries(data)) {
|
|
357
|
+
entries[key] = toValue(val);
|
|
358
|
+
}
|
|
359
|
+
return entries;
|
|
360
|
+
}
|
|
361
|
+
function convertMapEntries(data, toValue) {
|
|
362
|
+
return Object.entries(data).map(([key, val]) => ({
|
|
363
|
+
key,
|
|
364
|
+
value: toValue(val)
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
function wrapperToNodeApiValue(wrapper, toValue) {
|
|
368
|
+
switch (wrapper.kind) {
|
|
369
|
+
case "list":
|
|
370
|
+
return listValue(wrapper.data.map(toValue));
|
|
371
|
+
case "array":
|
|
372
|
+
return arrayValue(wrapper.data.map(toValue));
|
|
373
|
+
case "struct":
|
|
374
|
+
return structValue(convertStructEntries(wrapper.data, toValue));
|
|
375
|
+
case "map":
|
|
376
|
+
return mapValue(convertMapEntries(wrapper.data, toValue));
|
|
377
|
+
case "timestamp":
|
|
378
|
+
return wrapper.withTimezone ? timestampTZValue(dateToMicros(wrapper.data)) : timestampValue(dateToMicros(wrapper.data));
|
|
379
|
+
case "blob":
|
|
380
|
+
return blobValue(toUint8Array(wrapper.data));
|
|
381
|
+
case "json":
|
|
382
|
+
return JSON.stringify(wrapper.data);
|
|
383
|
+
default: {
|
|
384
|
+
const _exhaustive = wrapper;
|
|
385
|
+
throw new Error(`Unknown wrapper kind: ${_exhaustive.kind}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/client.ts
|
|
391
|
+
function isPool(client) {
|
|
392
|
+
return typeof client.acquire === "function";
|
|
393
|
+
}
|
|
316
394
|
function isPgArrayLiteral(value) {
|
|
317
395
|
return value.startsWith("{") && value.endsWith("}");
|
|
318
396
|
}
|
|
@@ -324,15 +402,13 @@ function parsePgArrayLiteral(value) {
|
|
|
324
402
|
return value;
|
|
325
403
|
}
|
|
326
404
|
}
|
|
327
|
-
var warnedArrayLiteral = false;
|
|
328
405
|
function prepareParams(params, options = {}) {
|
|
329
406
|
return params.map((param) => {
|
|
330
407
|
if (typeof param === "string" && isPgArrayLiteral(param)) {
|
|
331
408
|
if (options.rejectStringArrayLiterals) {
|
|
332
409
|
throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
|
|
333
410
|
}
|
|
334
|
-
if (
|
|
335
|
-
warnedArrayLiteral = true;
|
|
411
|
+
if (options.warnOnStringArrayLiteral) {
|
|
336
412
|
options.warnOnStringArrayLiteral();
|
|
337
413
|
}
|
|
338
414
|
return parsePgArrayLiteral(param);
|
|
@@ -341,30 +417,116 @@ function prepareParams(params, options = {}) {
|
|
|
341
417
|
});
|
|
342
418
|
}
|
|
343
419
|
function toNodeApiValue(value) {
|
|
420
|
+
if (value == null)
|
|
421
|
+
return null;
|
|
422
|
+
const t = typeof value;
|
|
423
|
+
if (t === "string" || t === "number" || t === "bigint" || t === "boolean") {
|
|
424
|
+
return value;
|
|
425
|
+
}
|
|
426
|
+
if (t === "object" && DUCKDB_VALUE_MARKER in value) {
|
|
427
|
+
return wrapperToNodeApiValue(value, toNodeApiValue);
|
|
428
|
+
}
|
|
344
429
|
if (Array.isArray(value)) {
|
|
345
|
-
return
|
|
430
|
+
return listValue2(value.map((inner) => toNodeApiValue(inner)));
|
|
431
|
+
}
|
|
432
|
+
if (value instanceof Date) {
|
|
433
|
+
return timestampValue2(BigInt(value.getTime()) * 1000n);
|
|
346
434
|
}
|
|
347
435
|
return value;
|
|
348
436
|
}
|
|
349
|
-
|
|
350
|
-
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
351
|
-
const result = await client.run(query, values);
|
|
352
|
-
const rows = await result.getRowsJS();
|
|
353
|
-
const columns = result.columnNames();
|
|
437
|
+
function deduplicateColumns(columns) {
|
|
354
438
|
const seen = {};
|
|
355
|
-
|
|
439
|
+
return columns.map((col) => {
|
|
356
440
|
const count = seen[col] ?? 0;
|
|
357
441
|
seen[col] = count + 1;
|
|
358
442
|
return count === 0 ? col : `${col}_${count}`;
|
|
359
443
|
});
|
|
360
|
-
|
|
444
|
+
}
|
|
445
|
+
function mapRowsToObjects(columns, rows) {
|
|
446
|
+
return rows.map((vals) => {
|
|
361
447
|
const obj = {};
|
|
362
|
-
|
|
448
|
+
columns.forEach((col, idx) => {
|
|
363
449
|
obj[col] = vals[idx];
|
|
364
450
|
});
|
|
365
451
|
return obj;
|
|
366
452
|
});
|
|
367
453
|
}
|
|
454
|
+
async function closeClientConnection(connection) {
|
|
455
|
+
if ("close" in connection && typeof connection.close === "function") {
|
|
456
|
+
await connection.close();
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if ("closeSync" in connection && typeof connection.closeSync === "function") {
|
|
460
|
+
connection.closeSync();
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if ("disconnectSync" in connection && typeof connection.disconnectSync === "function") {
|
|
464
|
+
connection.disconnectSync();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function executeOnClient(client, query, params) {
|
|
468
|
+
if (isPool(client)) {
|
|
469
|
+
const connection = await client.acquire();
|
|
470
|
+
try {
|
|
471
|
+
return await executeOnClient(connection, query, params);
|
|
472
|
+
} finally {
|
|
473
|
+
await client.release(connection);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
477
|
+
const result = await client.run(query, values);
|
|
478
|
+
const rows = await result.getRowsJS();
|
|
479
|
+
const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
|
|
480
|
+
const uniqueColumns = deduplicateColumns(columns);
|
|
481
|
+
return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
|
|
482
|
+
}
|
|
483
|
+
async function* executeInBatches(client, query, params, options = {}) {
|
|
484
|
+
if (isPool(client)) {
|
|
485
|
+
const connection = await client.acquire();
|
|
486
|
+
try {
|
|
487
|
+
yield* executeInBatches(connection, query, params, options);
|
|
488
|
+
return;
|
|
489
|
+
} finally {
|
|
490
|
+
await client.release(connection);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
494
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
495
|
+
const result = await client.stream(query, values);
|
|
496
|
+
const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
|
|
497
|
+
const uniqueColumns = deduplicateColumns(columns);
|
|
498
|
+
let buffer = [];
|
|
499
|
+
for await (const chunk of result.yieldRowsJs()) {
|
|
500
|
+
const objects = mapRowsToObjects(uniqueColumns, chunk);
|
|
501
|
+
for (const row of objects) {
|
|
502
|
+
buffer.push(row);
|
|
503
|
+
if (buffer.length >= rowsPerChunk) {
|
|
504
|
+
yield buffer;
|
|
505
|
+
buffer = [];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (buffer.length > 0) {
|
|
510
|
+
yield buffer;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function executeArrowOnClient(client, query, params) {
|
|
514
|
+
if (isPool(client)) {
|
|
515
|
+
const connection = await client.acquire();
|
|
516
|
+
try {
|
|
517
|
+
return await executeArrowOnClient(connection, query, params);
|
|
518
|
+
} finally {
|
|
519
|
+
await client.release(connection);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
523
|
+
const result = await client.run(query, values);
|
|
524
|
+
const maybeArrow = result.toArrow ?? result.getArrowTable;
|
|
525
|
+
if (typeof maybeArrow === "function") {
|
|
526
|
+
return await maybeArrow.call(result);
|
|
527
|
+
}
|
|
528
|
+
return result.getColumnsObjectJS();
|
|
529
|
+
}
|
|
368
530
|
|
|
369
531
|
// src/session.ts
|
|
370
532
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
@@ -405,11 +567,7 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
405
567
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
|
|
406
568
|
}
|
|
407
569
|
this.logger.logQuery(rewrittenQuery, params);
|
|
408
|
-
const {
|
|
409
|
-
fields,
|
|
410
|
-
joinsNotNullableMap,
|
|
411
|
-
customResultMapper
|
|
412
|
-
} = this;
|
|
570
|
+
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
413
571
|
const rows = await executeOnClient(this.client, rewrittenQuery, params);
|
|
414
572
|
if (rows.length === 0 || !fields) {
|
|
415
573
|
return rows;
|
|
@@ -449,16 +607,30 @@ class DuckDBSession extends PgSession {
|
|
|
449
607
|
return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.rewriteArrays, this.rejectStringArrayLiterals, this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral);
|
|
450
608
|
}
|
|
451
609
|
async transaction(transaction) {
|
|
452
|
-
|
|
610
|
+
let pinnedConnection;
|
|
611
|
+
let pool;
|
|
612
|
+
let clientForTx = this.client;
|
|
613
|
+
if (isPool(this.client)) {
|
|
614
|
+
pool = this.client;
|
|
615
|
+
pinnedConnection = await pool.acquire();
|
|
616
|
+
clientForTx = pinnedConnection;
|
|
617
|
+
}
|
|
618
|
+
const session = new DuckDBSession(clientForTx, this.dialect, this.schema, this.options);
|
|
453
619
|
const tx = new DuckDBTransaction(this.dialect, session, this.schema);
|
|
454
|
-
await tx.execute(sql`BEGIN TRANSACTION;`);
|
|
455
620
|
try {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
621
|
+
await tx.execute(sql`BEGIN TRANSACTION;`);
|
|
622
|
+
try {
|
|
623
|
+
const result = await transaction(tx);
|
|
624
|
+
await tx.execute(sql`commit`);
|
|
625
|
+
return result;
|
|
626
|
+
} catch (error) {
|
|
627
|
+
await tx.execute(sql`rollback`);
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
} finally {
|
|
631
|
+
if (pinnedConnection && pool) {
|
|
632
|
+
await pool.release(pinnedConnection);
|
|
633
|
+
}
|
|
462
634
|
}
|
|
463
635
|
}
|
|
464
636
|
warnOnStringArrayLiteral = (query) => {
|
|
@@ -469,6 +641,32 @@ class DuckDBSession extends PgSession {
|
|
|
469
641
|
this.logger.logQuery(`[duckdb] ${arrayLiteralWarning}
|
|
470
642
|
query: ${query}`, []);
|
|
471
643
|
};
|
|
644
|
+
executeBatches(query, options = {}) {
|
|
645
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
646
|
+
const params = prepareParams(builtQuery.params, {
|
|
647
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
648
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
649
|
+
});
|
|
650
|
+
const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
|
|
651
|
+
if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
|
|
652
|
+
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
653
|
+
}
|
|
654
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
655
|
+
return executeInBatches(this.client, rewrittenQuery, params, options);
|
|
656
|
+
}
|
|
657
|
+
async executeArrow(query) {
|
|
658
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
659
|
+
const params = prepareParams(builtQuery.params, {
|
|
660
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
661
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
662
|
+
});
|
|
663
|
+
const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
|
|
664
|
+
if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
|
|
665
|
+
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
666
|
+
}
|
|
667
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
668
|
+
return executeArrowOnClient(this.client, rewrittenQuery, params);
|
|
669
|
+
}
|
|
472
670
|
}
|
|
473
671
|
|
|
474
672
|
class DuckDBTransaction extends PgTransaction {
|
|
@@ -492,6 +690,12 @@ class DuckDBTransaction extends PgTransaction {
|
|
|
492
690
|
setTransaction(config) {
|
|
493
691
|
return this.session.execute(sql`set transaction ${this.getTransactionConfigSQL(config)}`);
|
|
494
692
|
}
|
|
693
|
+
executeBatches(query, options = {}) {
|
|
694
|
+
return this.session.executeBatches(query, options);
|
|
695
|
+
}
|
|
696
|
+
executeArrow(query) {
|
|
697
|
+
return this.session.executeArrow(query);
|
|
698
|
+
}
|
|
495
699
|
async transaction(transaction) {
|
|
496
700
|
const nestedTx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
|
|
497
701
|
return transaction(nestedTx);
|
|
@@ -590,13 +794,7 @@ import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
|
590
794
|
import { SQL as SQL4 } from "drizzle-orm/sql/sql";
|
|
591
795
|
|
|
592
796
|
// src/sql/selection.ts
|
|
593
|
-
import {
|
|
594
|
-
Column as Column2,
|
|
595
|
-
SQL as SQL3,
|
|
596
|
-
getTableName as getTableName2,
|
|
597
|
-
is as is3,
|
|
598
|
-
sql as sql3
|
|
599
|
-
} from "drizzle-orm";
|
|
797
|
+
import { Column as Column2, SQL as SQL3, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
|
|
600
798
|
function mapEntries(obj, prefix, fullJoin = false) {
|
|
601
799
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
|
|
602
800
|
const qualified = prefix ? `${prefix}.${key}` : key;
|
|
@@ -680,6 +878,72 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
|
|
|
680
878
|
}
|
|
681
879
|
}
|
|
682
880
|
|
|
881
|
+
// src/pool.ts
|
|
882
|
+
import { DuckDBConnection } from "@duckdb/node-api";
|
|
883
|
+
var POOL_PRESETS = {
|
|
884
|
+
pulse: 4,
|
|
885
|
+
standard: 6,
|
|
886
|
+
jumbo: 8,
|
|
887
|
+
mega: 12,
|
|
888
|
+
giga: 16,
|
|
889
|
+
local: 8,
|
|
890
|
+
memory: 4
|
|
891
|
+
};
|
|
892
|
+
function resolvePoolSize(pool) {
|
|
893
|
+
if (pool === false)
|
|
894
|
+
return false;
|
|
895
|
+
if (pool === undefined)
|
|
896
|
+
return 4;
|
|
897
|
+
if (typeof pool === "string")
|
|
898
|
+
return POOL_PRESETS[pool];
|
|
899
|
+
return pool.size ?? 4;
|
|
900
|
+
}
|
|
901
|
+
function createDuckDBConnectionPool(instance, options = {}) {
|
|
902
|
+
const size = options.size && options.size > 0 ? options.size : 4;
|
|
903
|
+
const idle = [];
|
|
904
|
+
const waiting = [];
|
|
905
|
+
let total = 0;
|
|
906
|
+
let closed = false;
|
|
907
|
+
const acquire = async () => {
|
|
908
|
+
if (closed) {
|
|
909
|
+
throw new Error("DuckDB connection pool is closed");
|
|
910
|
+
}
|
|
911
|
+
if (idle.length > 0) {
|
|
912
|
+
return idle.pop();
|
|
913
|
+
}
|
|
914
|
+
if (total < size) {
|
|
915
|
+
total += 1;
|
|
916
|
+
return await DuckDBConnection.create(instance);
|
|
917
|
+
}
|
|
918
|
+
return await new Promise((resolve) => {
|
|
919
|
+
waiting.push(resolve);
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
const release = async (connection) => {
|
|
923
|
+
if (closed) {
|
|
924
|
+
await closeClientConnection(connection);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const waiter = waiting.shift();
|
|
928
|
+
if (waiter) {
|
|
929
|
+
waiter(connection);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
idle.push(connection);
|
|
933
|
+
};
|
|
934
|
+
const close = async () => {
|
|
935
|
+
closed = true;
|
|
936
|
+
const toClose = idle.splice(0, idle.length);
|
|
937
|
+
await Promise.all(toClose.map((conn) => closeClientConnection(conn)));
|
|
938
|
+
};
|
|
939
|
+
return {
|
|
940
|
+
acquire,
|
|
941
|
+
release,
|
|
942
|
+
close,
|
|
943
|
+
size
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
683
947
|
// src/driver.ts
|
|
684
948
|
class DuckDBDriver {
|
|
685
949
|
client;
|
|
@@ -699,7 +963,14 @@ class DuckDBDriver {
|
|
|
699
963
|
});
|
|
700
964
|
}
|
|
701
965
|
}
|
|
702
|
-
function
|
|
966
|
+
function isConfigObject(data) {
|
|
967
|
+
if (typeof data !== "object" || data === null)
|
|
968
|
+
return false;
|
|
969
|
+
if (data.constructor?.name !== "Object")
|
|
970
|
+
return false;
|
|
971
|
+
return "connection" in data || "client" in data || "pool" in data || "schema" in data || "logger" in data;
|
|
972
|
+
}
|
|
973
|
+
function createFromClient(client, config = {}, instance) {
|
|
703
974
|
const dialect = new DuckDBDialect;
|
|
704
975
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
705
976
|
let schema;
|
|
@@ -717,17 +988,60 @@ function drizzle(client, config = {}) {
|
|
|
717
988
|
rejectStringArrayLiterals: config.rejectStringArrayLiterals
|
|
718
989
|
});
|
|
719
990
|
const session = driver.createSession(schema);
|
|
720
|
-
|
|
991
|
+
const db = new DuckDBDatabase(dialect, session, schema, client, instance);
|
|
992
|
+
return db;
|
|
993
|
+
}
|
|
994
|
+
async function createFromConnectionString(path, instanceOptions, config = {}) {
|
|
995
|
+
const instance = await DuckDBInstance2.create(path, instanceOptions);
|
|
996
|
+
const poolSize = resolvePoolSize(config.pool);
|
|
997
|
+
if (poolSize === false) {
|
|
998
|
+
const connection = await instance.connect();
|
|
999
|
+
return createFromClient(connection, config, instance);
|
|
1000
|
+
}
|
|
1001
|
+
const pool = createDuckDBConnectionPool(instance, { size: poolSize });
|
|
1002
|
+
return createFromClient(pool, config, instance);
|
|
1003
|
+
}
|
|
1004
|
+
function drizzle(clientOrConfigOrPath, config) {
|
|
1005
|
+
if (typeof clientOrConfigOrPath === "string") {
|
|
1006
|
+
return createFromConnectionString(clientOrConfigOrPath, undefined, config);
|
|
1007
|
+
}
|
|
1008
|
+
if (isConfigObject(clientOrConfigOrPath)) {
|
|
1009
|
+
const configObj = clientOrConfigOrPath;
|
|
1010
|
+
if ("connection" in configObj) {
|
|
1011
|
+
const connConfig = configObj;
|
|
1012
|
+
const { connection, ...restConfig } = connConfig;
|
|
1013
|
+
if (typeof connection === "string") {
|
|
1014
|
+
return createFromConnectionString(connection, undefined, restConfig);
|
|
1015
|
+
}
|
|
1016
|
+
return createFromConnectionString(connection.path, connection.options, restConfig);
|
|
1017
|
+
}
|
|
1018
|
+
if ("client" in configObj) {
|
|
1019
|
+
const clientConfig = configObj;
|
|
1020
|
+
const { client: clientValue, ...restConfig } = clientConfig;
|
|
1021
|
+
return createFromClient(clientValue, restConfig);
|
|
1022
|
+
}
|
|
1023
|
+
throw new Error("Invalid drizzle config: either connection or client must be provided");
|
|
1024
|
+
}
|
|
1025
|
+
return createFromClient(clientOrConfigOrPath, config);
|
|
721
1026
|
}
|
|
722
1027
|
|
|
723
1028
|
class DuckDBDatabase extends PgDatabase {
|
|
724
1029
|
dialect;
|
|
725
1030
|
session;
|
|
726
1031
|
static [entityKind3] = "DuckDBDatabase";
|
|
727
|
-
|
|
1032
|
+
$client;
|
|
1033
|
+
$instance;
|
|
1034
|
+
constructor(dialect, session, schema, client, instance) {
|
|
728
1035
|
super(dialect, session, schema);
|
|
729
1036
|
this.dialect = dialect;
|
|
730
1037
|
this.session = session;
|
|
1038
|
+
this.$client = client;
|
|
1039
|
+
this.$instance = instance;
|
|
1040
|
+
}
|
|
1041
|
+
async close() {
|
|
1042
|
+
if (isPool(this.$client) && this.$client.close) {
|
|
1043
|
+
await this.$client.close();
|
|
1044
|
+
}
|
|
731
1045
|
}
|
|
732
1046
|
select(fields) {
|
|
733
1047
|
const selectedFields = fields ? aliasFields(fields) : undefined;
|
|
@@ -737,6 +1051,12 @@ class DuckDBDatabase extends PgDatabase {
|
|
|
737
1051
|
dialect: this.dialect
|
|
738
1052
|
});
|
|
739
1053
|
}
|
|
1054
|
+
executeBatches(query, options = {}) {
|
|
1055
|
+
return this.session.executeBatches(query, options);
|
|
1056
|
+
}
|
|
1057
|
+
executeArrow(query) {
|
|
1058
|
+
return this.session.executeArrow(query);
|
|
1059
|
+
}
|
|
740
1060
|
async transaction(transaction) {
|
|
741
1061
|
return await this.session.transaction(transaction);
|
|
742
1062
|
}
|
|
@@ -745,7 +1065,7 @@ class DuckDBDatabase extends PgDatabase {
|
|
|
745
1065
|
// src/introspect.ts
|
|
746
1066
|
import { sql as sql4 } from "drizzle-orm";
|
|
747
1067
|
var SYSTEM_SCHEMAS = new Set(["information_schema", "pg_catalog"]);
|
|
748
|
-
var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb";
|
|
1068
|
+
var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb/helpers";
|
|
749
1069
|
async function introspect(db, opts = {}) {
|
|
750
1070
|
const database = await resolveDatabase(db, opts.database, opts.allDatabases);
|
|
751
1071
|
const schemas = await resolveSchemas(db, database, opts.schemas);
|
|
@@ -1046,7 +1366,9 @@ function mapDuckDbType(column, imports, options) {
|
|
|
1046
1366
|
}
|
|
1047
1367
|
if (upper === "BIGINT" || upper === "INT8" || upper === "UBIGINT") {
|
|
1048
1368
|
imports.pgCore.add("bigint");
|
|
1049
|
-
return {
|
|
1369
|
+
return {
|
|
1370
|
+
builder: `bigint(${columnName(column.name)}, { mode: 'number' })`
|
|
1371
|
+
};
|
|
1050
1372
|
}
|
|
1051
1373
|
const decimalMatch = /^DECIMAL\((\d+),(\d+)\)/i.exec(upper);
|
|
1052
1374
|
const numericMatch = /^NUMERIC\((\d+),(\d+)\)/i.exec(upper);
|
|
@@ -1079,6 +1401,22 @@ function mapDuckDbType(column, imports, options) {
|
|
|
1079
1401
|
imports.pgCore.add("doublePrecision");
|
|
1080
1402
|
return { builder: `doublePrecision(${columnName(column.name)})` };
|
|
1081
1403
|
}
|
|
1404
|
+
const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
|
|
1405
|
+
if (arrayMatch) {
|
|
1406
|
+
imports.local.add("duckDbArray");
|
|
1407
|
+
const [, base, length] = arrayMatch;
|
|
1408
|
+
return {
|
|
1409
|
+
builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
const listMatch = /^(.*)\[\]$/.exec(upper);
|
|
1413
|
+
if (listMatch) {
|
|
1414
|
+
imports.local.add("duckDbList");
|
|
1415
|
+
const [, base] = listMatch;
|
|
1416
|
+
return {
|
|
1417
|
+
builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1082
1420
|
if (upper.startsWith("CHAR(") || upper === "CHAR") {
|
|
1083
1421
|
imports.pgCore.add("char");
|
|
1084
1422
|
const length = column.characterLength;
|
|
@@ -1119,22 +1457,6 @@ function mapDuckDbType(column, imports, options) {
|
|
|
1119
1457
|
imports.local.add("duckDbBlob");
|
|
1120
1458
|
return { builder: `duckDbBlob(${columnName(column.name)})` };
|
|
1121
1459
|
}
|
|
1122
|
-
const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
|
|
1123
|
-
if (arrayMatch) {
|
|
1124
|
-
imports.local.add("duckDbArray");
|
|
1125
|
-
const [, base, length] = arrayMatch;
|
|
1126
|
-
return {
|
|
1127
|
-
builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
const listMatch = /^(.*)\[\]$/.exec(upper);
|
|
1131
|
-
if (listMatch) {
|
|
1132
|
-
imports.local.add("duckDbList");
|
|
1133
|
-
const [, base] = listMatch;
|
|
1134
|
-
return {
|
|
1135
|
-
builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
1460
|
if (upper.startsWith("STRUCT")) {
|
|
1139
1461
|
imports.local.add("duckDbStruct");
|
|
1140
1462
|
const inner = upper.replace(/^STRUCT\s*\(/i, "").replace(/\)$/, "");
|
|
@@ -1383,7 +1705,7 @@ async function main() {
|
|
|
1383
1705
|
throw new Error("Missing required --url");
|
|
1384
1706
|
}
|
|
1385
1707
|
const instanceOptions = options.url.startsWith("md:") && process.env.MOTHERDUCK_TOKEN ? { motherduck_token: process.env.MOTHERDUCK_TOKEN } : undefined;
|
|
1386
|
-
const instance = await
|
|
1708
|
+
const instance = await DuckDBInstance3.create(options.url, instanceOptions);
|
|
1387
1709
|
const connection = await instance.connect();
|
|
1388
1710
|
const db = drizzle(connection);
|
|
1389
1711
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { duckDbList, duckDbArray, duckDbMap, duckDbStruct, duckDbJson, duckDbBlob, duckDbInet, duckDbInterval, duckDbTimestamp, duckDbDate, duckDbTime, duckDbArrayContains, duckDbArrayContained, duckDbArrayOverlaps, } from './columns.ts';
|