@leonardovida-md/drizzle-neo-duckdb 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -85
- package/dist/client.d.ts +15 -1
- package/dist/columns.d.ts +3 -2
- package/dist/driver.d.ts +7 -3
- package/dist/duckdb-introspect.mjs +262 -50
- package/dist/helpers.mjs +31 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +288 -50
- package/dist/options.d.ts +10 -0
- package/dist/session.d.ts +11 -5
- package/package.json +1 -1
- package/src/client.ts +300 -40
- package/src/columns.ts +51 -4
- package/src/driver.ts +30 -5
- package/src/index.ts +1 -0
- package/src/options.ts +40 -0
- package/src/session.ts +108 -26
- package/src/sql/query-rewriters.ts +8 -0
|
@@ -161,6 +161,9 @@ function walkRight(source, scrubbed, start) {
|
|
|
161
161
|
return [scrubbed.length, source.slice(start)];
|
|
162
162
|
}
|
|
163
163
|
function adaptArrayOperators(query) {
|
|
164
|
+
if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
|
|
165
|
+
return query;
|
|
166
|
+
}
|
|
164
167
|
let rewritten = query;
|
|
165
168
|
let scrubbed = scrubForRewrite(query);
|
|
166
169
|
let searchStart = 0;
|
|
@@ -473,6 +476,7 @@ function wrapperToNodeApiValue(wrapper, toValue) {
|
|
|
473
476
|
function isPool(client) {
|
|
474
477
|
return typeof client.acquire === "function";
|
|
475
478
|
}
|
|
479
|
+
var PREPARED_CACHE = Symbol.for("drizzle-duckdb:prepared-cache");
|
|
476
480
|
function isPgArrayLiteral(value) {
|
|
477
481
|
return value.startsWith("{") && value.endsWith("}");
|
|
478
482
|
}
|
|
@@ -486,16 +490,20 @@ function parsePgArrayLiteral(value) {
|
|
|
486
490
|
}
|
|
487
491
|
function prepareParams(params, options = {}) {
|
|
488
492
|
return params.map((param) => {
|
|
489
|
-
if (typeof param === "string") {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
493
|
+
if (typeof param === "string" && param.length > 0) {
|
|
494
|
+
const firstChar = param[0];
|
|
495
|
+
const maybeArrayLiteral = firstChar === "{" || firstChar === "[" || firstChar === " " || firstChar === "\t";
|
|
496
|
+
if (maybeArrayLiteral) {
|
|
497
|
+
const trimmed = firstChar === "{" || firstChar === "[" ? param : param.trim();
|
|
498
|
+
if (trimmed && isPgArrayLiteral(trimmed)) {
|
|
499
|
+
if (options.rejectStringArrayLiterals) {
|
|
500
|
+
throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
|
|
501
|
+
}
|
|
502
|
+
if (options.warnOnStringArrayLiteral) {
|
|
503
|
+
options.warnOnStringArrayLiteral();
|
|
504
|
+
}
|
|
505
|
+
return parsePgArrayLiteral(trimmed);
|
|
497
506
|
}
|
|
498
|
-
return parsePgArrayLiteral(trimmed);
|
|
499
507
|
}
|
|
500
508
|
}
|
|
501
509
|
return param;
|
|
@@ -520,13 +528,121 @@ function toNodeApiValue(value) {
|
|
|
520
528
|
return value;
|
|
521
529
|
}
|
|
522
530
|
function deduplicateColumns(columns) {
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
531
|
+
const counts = new Map;
|
|
532
|
+
let hasDuplicates = false;
|
|
533
|
+
for (const column of columns) {
|
|
534
|
+
const next = (counts.get(column) ?? 0) + 1;
|
|
535
|
+
counts.set(column, next);
|
|
536
|
+
if (next > 1) {
|
|
537
|
+
hasDuplicates = true;
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (!hasDuplicates) {
|
|
542
|
+
return columns;
|
|
543
|
+
}
|
|
544
|
+
counts.clear();
|
|
545
|
+
return columns.map((column) => {
|
|
546
|
+
const count = counts.get(column) ?? 0;
|
|
547
|
+
counts.set(column, count + 1);
|
|
548
|
+
return count === 0 ? column : `${column}_${count}`;
|
|
528
549
|
});
|
|
529
550
|
}
|
|
551
|
+
function destroyPreparedStatement(entry) {
|
|
552
|
+
if (!entry)
|
|
553
|
+
return;
|
|
554
|
+
try {
|
|
555
|
+
entry.statement.destroySync();
|
|
556
|
+
} catch {}
|
|
557
|
+
}
|
|
558
|
+
function getPreparedCache(connection, size) {
|
|
559
|
+
const store = connection;
|
|
560
|
+
const existing = store[PREPARED_CACHE];
|
|
561
|
+
if (existing) {
|
|
562
|
+
existing.size = size;
|
|
563
|
+
return existing;
|
|
564
|
+
}
|
|
565
|
+
const cache = { size, entries: new Map };
|
|
566
|
+
store[PREPARED_CACHE] = cache;
|
|
567
|
+
return cache;
|
|
568
|
+
}
|
|
569
|
+
function evictOldest(cache) {
|
|
570
|
+
const oldest = cache.entries.keys().next();
|
|
571
|
+
if (!oldest.done) {
|
|
572
|
+
const key = oldest.value;
|
|
573
|
+
const entry = cache.entries.get(key);
|
|
574
|
+
cache.entries.delete(key);
|
|
575
|
+
destroyPreparedStatement(entry);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function evictCacheEntry(cache, key) {
|
|
579
|
+
const entry = cache.entries.get(key);
|
|
580
|
+
cache.entries.delete(key);
|
|
581
|
+
destroyPreparedStatement(entry);
|
|
582
|
+
}
|
|
583
|
+
async function getOrPrepareStatement(connection, query, cacheConfig) {
|
|
584
|
+
const cache = getPreparedCache(connection, cacheConfig.size);
|
|
585
|
+
const cached = cache.entries.get(query);
|
|
586
|
+
if (cached) {
|
|
587
|
+
cache.entries.delete(query);
|
|
588
|
+
cache.entries.set(query, cached);
|
|
589
|
+
return cached.statement;
|
|
590
|
+
}
|
|
591
|
+
const statement = await connection.prepare(query);
|
|
592
|
+
cache.entries.set(query, { statement });
|
|
593
|
+
while (cache.entries.size > cache.size) {
|
|
594
|
+
evictOldest(cache);
|
|
595
|
+
}
|
|
596
|
+
return statement;
|
|
597
|
+
}
|
|
598
|
+
async function materializeResultRows(result) {
|
|
599
|
+
const rows = await result.getRowsJS() ?? [];
|
|
600
|
+
const baseColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
601
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? baseColumns : deduplicateColumns(baseColumns);
|
|
602
|
+
return { columns, rows };
|
|
603
|
+
}
|
|
604
|
+
async function materializeRows(client, query, params, options = {}) {
|
|
605
|
+
if (isPool(client)) {
|
|
606
|
+
const connection2 = await client.acquire();
|
|
607
|
+
try {
|
|
608
|
+
return await materializeRows(connection2, query, params, options);
|
|
609
|
+
} finally {
|
|
610
|
+
await client.release(connection2);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
614
|
+
const connection = client;
|
|
615
|
+
if (options.prepareCache && typeof connection.prepare === "function") {
|
|
616
|
+
const cache = getPreparedCache(connection, options.prepareCache.size);
|
|
617
|
+
try {
|
|
618
|
+
const statement = await getOrPrepareStatement(connection, query, options.prepareCache);
|
|
619
|
+
if (values) {
|
|
620
|
+
statement.bind(values);
|
|
621
|
+
} else {
|
|
622
|
+
statement.clearBindings?.();
|
|
623
|
+
}
|
|
624
|
+
const result2 = await statement.run();
|
|
625
|
+
cache.entries.delete(query);
|
|
626
|
+
cache.entries.set(query, { statement });
|
|
627
|
+
return await materializeResultRows(result2);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
evictCacheEntry(cache, query);
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const result = await connection.run(query, values);
|
|
634
|
+
return await materializeResultRows(result);
|
|
635
|
+
}
|
|
636
|
+
function clearPreparedCache(connection) {
|
|
637
|
+
const store = connection;
|
|
638
|
+
const cache = store[PREPARED_CACHE];
|
|
639
|
+
if (!cache)
|
|
640
|
+
return;
|
|
641
|
+
for (const entry of cache.entries.values()) {
|
|
642
|
+
destroyPreparedStatement(entry);
|
|
643
|
+
}
|
|
644
|
+
cache.entries.clear();
|
|
645
|
+
}
|
|
530
646
|
function mapRowsToObjects(columns, rows) {
|
|
531
647
|
return rows.map((vals) => {
|
|
532
648
|
const obj = {};
|
|
@@ -537,6 +653,7 @@ function mapRowsToObjects(columns, rows) {
|
|
|
537
653
|
});
|
|
538
654
|
}
|
|
539
655
|
async function closeClientConnection(connection) {
|
|
656
|
+
clearPreparedCache(connection);
|
|
540
657
|
if ("close" in connection && typeof connection.close === "function") {
|
|
541
658
|
await connection.close();
|
|
542
659
|
return;
|
|
@@ -549,27 +666,51 @@ async function closeClientConnection(connection) {
|
|
|
549
666
|
connection.disconnectSync();
|
|
550
667
|
}
|
|
551
668
|
}
|
|
552
|
-
async function executeOnClient(client, query, params) {
|
|
669
|
+
async function executeOnClient(client, query, params, options = {}) {
|
|
670
|
+
const { columns, rows } = await materializeRows(client, query, params, options);
|
|
671
|
+
if (!rows || rows.length === 0) {
|
|
672
|
+
return [];
|
|
673
|
+
}
|
|
674
|
+
return mapRowsToObjects(columns, rows);
|
|
675
|
+
}
|
|
676
|
+
async function executeArraysOnClient(client, query, params, options = {}) {
|
|
677
|
+
return await materializeRows(client, query, params, options);
|
|
678
|
+
}
|
|
679
|
+
async function* executeInBatches(client, query, params, options = {}) {
|
|
553
680
|
if (isPool(client)) {
|
|
554
681
|
const connection = await client.acquire();
|
|
555
682
|
try {
|
|
556
|
-
|
|
683
|
+
yield* executeInBatches(connection, query, params, options);
|
|
684
|
+
return;
|
|
557
685
|
} finally {
|
|
558
686
|
await client.release(connection);
|
|
559
687
|
}
|
|
560
688
|
}
|
|
689
|
+
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
561
690
|
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
562
|
-
const result = await client.
|
|
563
|
-
const
|
|
564
|
-
const columns = result.deduplicatedColumnNames
|
|
565
|
-
|
|
566
|
-
|
|
691
|
+
const result = await client.stream(query, values);
|
|
692
|
+
const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
693
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
|
|
694
|
+
let buffer = [];
|
|
695
|
+
for await (const chunk of result.yieldRowsJs()) {
|
|
696
|
+
const objects = mapRowsToObjects(columns, chunk);
|
|
697
|
+
for (const row of objects) {
|
|
698
|
+
buffer.push(row);
|
|
699
|
+
if (buffer.length >= rowsPerChunk) {
|
|
700
|
+
yield buffer;
|
|
701
|
+
buffer = [];
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (buffer.length > 0) {
|
|
706
|
+
yield buffer;
|
|
707
|
+
}
|
|
567
708
|
}
|
|
568
|
-
async function*
|
|
709
|
+
async function* executeInBatchesRaw(client, query, params, options = {}) {
|
|
569
710
|
if (isPool(client)) {
|
|
570
711
|
const connection = await client.acquire();
|
|
571
712
|
try {
|
|
572
|
-
yield*
|
|
713
|
+
yield* executeInBatchesRaw(connection, query, params, options);
|
|
573
714
|
return;
|
|
574
715
|
} finally {
|
|
575
716
|
await client.release(connection);
|
|
@@ -578,21 +719,20 @@ async function* executeInBatches(client, query, params, options = {}) {
|
|
|
578
719
|
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
579
720
|
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
580
721
|
const result = await client.stream(query, values);
|
|
581
|
-
const
|
|
582
|
-
const
|
|
722
|
+
const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
723
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
|
|
583
724
|
let buffer = [];
|
|
584
725
|
for await (const chunk of result.yieldRowsJs()) {
|
|
585
|
-
const
|
|
586
|
-
for (const row of objects) {
|
|
726
|
+
for (const row of chunk) {
|
|
587
727
|
buffer.push(row);
|
|
588
728
|
if (buffer.length >= rowsPerChunk) {
|
|
589
|
-
yield buffer;
|
|
729
|
+
yield { columns, rows: buffer };
|
|
590
730
|
buffer = [];
|
|
591
731
|
}
|
|
592
732
|
}
|
|
593
733
|
}
|
|
594
734
|
if (buffer.length > 0) {
|
|
595
|
-
yield buffer;
|
|
735
|
+
yield { columns, rows: buffer };
|
|
596
736
|
}
|
|
597
737
|
}
|
|
598
738
|
async function executeArrowOnClient(client, query, params) {
|
|
@@ -620,6 +760,13 @@ function isSavepointSyntaxError(error) {
|
|
|
620
760
|
}
|
|
621
761
|
return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
|
|
622
762
|
}
|
|
763
|
+
function rewriteQuery(mode, query) {
|
|
764
|
+
if (mode === "never") {
|
|
765
|
+
return { sql: query, rewritten: false };
|
|
766
|
+
}
|
|
767
|
+
const rewritten = adaptArrayOperators(query);
|
|
768
|
+
return { sql: rewritten, rewritten: rewritten !== query };
|
|
769
|
+
}
|
|
623
770
|
|
|
624
771
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
625
772
|
client;
|
|
@@ -630,11 +777,12 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
630
777
|
fields;
|
|
631
778
|
_isResponseInArrayMode;
|
|
632
779
|
customResultMapper;
|
|
633
|
-
|
|
780
|
+
rewriteArraysMode;
|
|
634
781
|
rejectStringArrayLiterals;
|
|
782
|
+
prepareCache;
|
|
635
783
|
warnOnStringArrayLiteral;
|
|
636
784
|
static [entityKind] = "DuckDBPreparedQuery";
|
|
637
|
-
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper,
|
|
785
|
+
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArraysMode, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
|
|
638
786
|
super({ sql: queryString, params });
|
|
639
787
|
this.client = client;
|
|
640
788
|
this.dialect = dialect;
|
|
@@ -644,8 +792,9 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
644
792
|
this.fields = fields;
|
|
645
793
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
646
794
|
this.customResultMapper = customResultMapper;
|
|
647
|
-
this.
|
|
795
|
+
this.rewriteArraysMode = rewriteArraysMode;
|
|
648
796
|
this.rejectStringArrayLiterals = rejectStringArrayLiterals;
|
|
797
|
+
this.prepareCache = prepareCache;
|
|
649
798
|
this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
|
|
650
799
|
}
|
|
651
800
|
async execute(placeholderValues = {}) {
|
|
@@ -654,18 +803,23 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
654
803
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
655
804
|
warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
|
|
656
805
|
});
|
|
657
|
-
const rewrittenQuery
|
|
658
|
-
if (
|
|
806
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, this.queryString);
|
|
807
|
+
if (didRewrite) {
|
|
659
808
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
|
|
660
809
|
}
|
|
661
810
|
this.logger.logQuery(rewrittenQuery, params);
|
|
662
811
|
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
812
|
+
if (fields) {
|
|
813
|
+
const { rows: rows2 } = await executeArraysOnClient(this.client, rewrittenQuery, params, { prepareCache: this.prepareCache });
|
|
814
|
+
if (rows2.length === 0) {
|
|
815
|
+
return [];
|
|
816
|
+
}
|
|
817
|
+
return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
666
818
|
}
|
|
667
|
-
const
|
|
668
|
-
|
|
819
|
+
const rows = await executeOnClient(this.client, rewrittenQuery, params, {
|
|
820
|
+
prepareCache: this.prepareCache
|
|
821
|
+
});
|
|
822
|
+
return rows;
|
|
669
823
|
}
|
|
670
824
|
all(placeholderValues = {}) {
|
|
671
825
|
return this.execute(placeholderValues);
|
|
@@ -682,8 +836,9 @@ class DuckDBSession extends PgSession {
|
|
|
682
836
|
static [entityKind] = "DuckDBSession";
|
|
683
837
|
dialect;
|
|
684
838
|
logger;
|
|
685
|
-
|
|
839
|
+
rewriteArraysMode;
|
|
686
840
|
rejectStringArrayLiterals;
|
|
841
|
+
prepareCache;
|
|
687
842
|
hasWarnedArrayLiteral = false;
|
|
688
843
|
rollbackOnly = false;
|
|
689
844
|
constructor(client, dialect, schema, options = {}) {
|
|
@@ -693,11 +848,17 @@ class DuckDBSession extends PgSession {
|
|
|
693
848
|
this.options = options;
|
|
694
849
|
this.dialect = dialect;
|
|
695
850
|
this.logger = options.logger ?? new NoopLogger;
|
|
696
|
-
this.
|
|
851
|
+
this.rewriteArraysMode = options.rewriteArrays ?? "auto";
|
|
697
852
|
this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
|
|
853
|
+
this.prepareCache = options.prepareCache;
|
|
854
|
+
this.options = {
|
|
855
|
+
...options,
|
|
856
|
+
rewriteArrays: this.rewriteArraysMode,
|
|
857
|
+
prepareCache: this.prepareCache
|
|
858
|
+
};
|
|
698
859
|
}
|
|
699
860
|
prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
|
|
700
|
-
return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.
|
|
861
|
+
return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.rewriteArraysMode, this.rejectStringArrayLiterals, this.prepareCache, this.rejectStringArrayLiterals ? undefined : this.warnOnStringArrayLiteral);
|
|
701
862
|
}
|
|
702
863
|
execute(query) {
|
|
703
864
|
this.dialect.resetPgJsonFlag();
|
|
@@ -757,13 +918,28 @@ query: ${query}`, []);
|
|
|
757
918
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
758
919
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
759
920
|
});
|
|
760
|
-
const rewrittenQuery = this.
|
|
761
|
-
if (
|
|
921
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
|
|
922
|
+
if (didRewrite) {
|
|
762
923
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
763
924
|
}
|
|
764
925
|
this.logger.logQuery(rewrittenQuery, params);
|
|
765
926
|
return executeInBatches(this.client, rewrittenQuery, params, options);
|
|
766
927
|
}
|
|
928
|
+
executeBatchesRaw(query, options = {}) {
|
|
929
|
+
this.dialect.resetPgJsonFlag();
|
|
930
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
931
|
+
this.dialect.assertNoPgJsonColumns();
|
|
932
|
+
const params = prepareParams(builtQuery.params, {
|
|
933
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
934
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
935
|
+
});
|
|
936
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
|
|
937
|
+
if (didRewrite) {
|
|
938
|
+
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
939
|
+
}
|
|
940
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
941
|
+
return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
|
|
942
|
+
}
|
|
767
943
|
async executeArrow(query) {
|
|
768
944
|
this.dialect.resetPgJsonFlag();
|
|
769
945
|
const builtQuery = this.dialect.sqlToQuery(query);
|
|
@@ -772,8 +948,8 @@ query: ${query}`, []);
|
|
|
772
948
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
773
949
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
774
950
|
});
|
|
775
|
-
const rewrittenQuery = this.
|
|
776
|
-
if (
|
|
951
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
|
|
952
|
+
if (didRewrite) {
|
|
777
953
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
778
954
|
}
|
|
779
955
|
this.logger.logQuery(rewrittenQuery, params);
|
|
@@ -811,6 +987,9 @@ class DuckDBTransaction extends PgTransaction {
|
|
|
811
987
|
executeBatches(query, options = {}) {
|
|
812
988
|
return this.session.executeBatches(query, options);
|
|
813
989
|
}
|
|
990
|
+
executeBatchesRaw(query, options = {}) {
|
|
991
|
+
return this.session.executeBatchesRaw(query, options);
|
|
992
|
+
}
|
|
814
993
|
executeArrow(query) {
|
|
815
994
|
return this.session.executeArrow(query);
|
|
816
995
|
}
|
|
@@ -1222,6 +1401,32 @@ function createDuckDBConnectionPool(instance, options = {}) {
|
|
|
1222
1401
|
};
|
|
1223
1402
|
}
|
|
1224
1403
|
|
|
1404
|
+
// src/options.ts
|
|
1405
|
+
var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
|
|
1406
|
+
function resolveRewriteArraysOption(value) {
|
|
1407
|
+
if (value === undefined)
|
|
1408
|
+
return DEFAULT_REWRITE_ARRAYS_MODE;
|
|
1409
|
+
if (value === true)
|
|
1410
|
+
return "auto";
|
|
1411
|
+
if (value === false)
|
|
1412
|
+
return "never";
|
|
1413
|
+
return value;
|
|
1414
|
+
}
|
|
1415
|
+
var DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
1416
|
+
function resolvePrepareCacheOption(option) {
|
|
1417
|
+
if (!option)
|
|
1418
|
+
return;
|
|
1419
|
+
if (option === true) {
|
|
1420
|
+
return { size: DEFAULT_PREPARED_CACHE_SIZE };
|
|
1421
|
+
}
|
|
1422
|
+
if (typeof option === "number") {
|
|
1423
|
+
const size2 = Math.max(1, Math.floor(option));
|
|
1424
|
+
return { size: size2 };
|
|
1425
|
+
}
|
|
1426
|
+
const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
|
|
1427
|
+
return { size: Math.max(1, Math.floor(size)) };
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1225
1430
|
// src/driver.ts
|
|
1226
1431
|
class DuckDBDriver {
|
|
1227
1432
|
client;
|
|
@@ -1236,8 +1441,9 @@ class DuckDBDriver {
|
|
|
1236
1441
|
createSession(schema) {
|
|
1237
1442
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
1238
1443
|
logger: this.options.logger,
|
|
1239
|
-
rewriteArrays: this.options.rewriteArrays,
|
|
1240
|
-
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals
|
|
1444
|
+
rewriteArrays: this.options.rewriteArrays ?? "auto",
|
|
1445
|
+
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
1446
|
+
prepareCache: this.options.prepareCache
|
|
1241
1447
|
});
|
|
1242
1448
|
}
|
|
1243
1449
|
}
|
|
@@ -1250,6 +1456,8 @@ function isConfigObject(data) {
|
|
|
1250
1456
|
}
|
|
1251
1457
|
function createFromClient(client, config = {}, instance) {
|
|
1252
1458
|
const dialect = new DuckDBDialect;
|
|
1459
|
+
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
1460
|
+
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
1253
1461
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
1254
1462
|
let schema;
|
|
1255
1463
|
if (config.schema) {
|
|
@@ -1262,8 +1470,9 @@ function createFromClient(client, config = {}, instance) {
|
|
|
1262
1470
|
}
|
|
1263
1471
|
const driver = new DuckDBDriver(client, dialect, {
|
|
1264
1472
|
logger,
|
|
1265
|
-
rewriteArrays:
|
|
1266
|
-
rejectStringArrayLiterals: config.rejectStringArrayLiterals
|
|
1473
|
+
rewriteArrays: rewriteArraysMode,
|
|
1474
|
+
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
1475
|
+
prepareCache
|
|
1267
1476
|
});
|
|
1268
1477
|
const session = driver.createSession(schema);
|
|
1269
1478
|
const db = new DuckDBDatabase(dialect, session, schema, client, instance);
|
|
@@ -1343,6 +1552,9 @@ class DuckDBDatabase extends PgDatabase {
|
|
|
1343
1552
|
executeBatches(query, options = {}) {
|
|
1344
1553
|
return this.session.executeBatches(query, options);
|
|
1345
1554
|
}
|
|
1555
|
+
executeBatchesRaw(query, options = {}) {
|
|
1556
|
+
return this.session.executeBatchesRaw(query, options);
|
|
1557
|
+
}
|
|
1346
1558
|
executeArrow(query) {
|
|
1347
1559
|
return this.session.executeArrow(query);
|
|
1348
1560
|
}
|
package/dist/helpers.mjs
CHANGED
|
@@ -29,6 +29,15 @@ function wrapMap(data, valueType) {
|
|
|
29
29
|
valueType
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
function wrapTimestamp(data, withTimezone, precision) {
|
|
33
|
+
return {
|
|
34
|
+
[DUCKDB_VALUE_MARKER]: true,
|
|
35
|
+
kind: "timestamp",
|
|
36
|
+
data,
|
|
37
|
+
withTimezone,
|
|
38
|
+
precision
|
|
39
|
+
};
|
|
40
|
+
}
|
|
32
41
|
function wrapBlob(data) {
|
|
33
42
|
return {
|
|
34
43
|
[DUCKDB_VALUE_MARKER]: true,
|
|
@@ -228,6 +237,21 @@ var duckDbInterval = (name) => customType({
|
|
|
228
237
|
return value;
|
|
229
238
|
}
|
|
230
239
|
})(name);
|
|
240
|
+
function shouldBindTimestamp(options) {
|
|
241
|
+
const bindMode = options.bindMode ?? "auto";
|
|
242
|
+
if (bindMode === "bind")
|
|
243
|
+
return true;
|
|
244
|
+
if (bindMode === "literal")
|
|
245
|
+
return false;
|
|
246
|
+
const isBun = typeof process !== "undefined" && typeof process.versions?.bun !== "undefined";
|
|
247
|
+
if (isBun)
|
|
248
|
+
return false;
|
|
249
|
+
const forceLiteral = typeof process !== "undefined" ? process.env.DRIZZLE_DUCKDB_FORCE_LITERAL_TIMESTAMPS : undefined;
|
|
250
|
+
if (forceLiteral && forceLiteral !== "0") {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
231
255
|
var duckDbTimestamp = (name, options = {}) => customType({
|
|
232
256
|
dataType() {
|
|
233
257
|
if (options.withTimezone) {
|
|
@@ -237,12 +261,19 @@ var duckDbTimestamp = (name, options = {}) => customType({
|
|
|
237
261
|
return `TIMESTAMP${precision}`;
|
|
238
262
|
},
|
|
239
263
|
toDriver(value) {
|
|
264
|
+
if (shouldBindTimestamp(options)) {
|
|
265
|
+
return wrapTimestamp(value, options.withTimezone ?? false, options.precision);
|
|
266
|
+
}
|
|
240
267
|
const iso = value instanceof Date ? value.toISOString() : value;
|
|
241
268
|
const normalized = iso.replace("T", " ").replace("Z", "+00");
|
|
242
269
|
const typeKeyword = options.withTimezone ? "TIMESTAMPTZ" : "TIMESTAMP";
|
|
243
270
|
return sql.raw(`${typeKeyword} '${normalized}'`);
|
|
244
271
|
},
|
|
245
272
|
fromDriver(value) {
|
|
273
|
+
if (value && typeof value === "object" && "kind" in value && value.kind === "timestamp") {
|
|
274
|
+
const wrapped = value;
|
|
275
|
+
return wrapped.data instanceof Date ? wrapped.data : typeof wrapped.data === "number" || typeof wrapped.data === "bigint" ? new Date(Number(wrapped.data) / 1000) : wrapped.data;
|
|
276
|
+
}
|
|
246
277
|
if (options.mode === "string") {
|
|
247
278
|
if (value instanceof Date) {
|
|
248
279
|
return value.toISOString().replace("T", " ").replace("Z", "+00");
|
package/dist/index.d.ts
CHANGED