@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
package/dist/index.mjs
CHANGED
|
@@ -153,6 +153,9 @@ function walkRight(source, scrubbed, start) {
|
|
|
153
153
|
return [scrubbed.length, source.slice(start)];
|
|
154
154
|
}
|
|
155
155
|
function adaptArrayOperators(query) {
|
|
156
|
+
if (query.indexOf("@>") === -1 && query.indexOf("<@") === -1 && query.indexOf("&&") === -1) {
|
|
157
|
+
return query;
|
|
158
|
+
}
|
|
156
159
|
let rewritten = query;
|
|
157
160
|
let scrubbed = scrubForRewrite(query);
|
|
158
161
|
let searchStart = 0;
|
|
@@ -524,6 +527,7 @@ function wrapperToNodeApiValue(wrapper, toValue) {
|
|
|
524
527
|
function isPool(client) {
|
|
525
528
|
return typeof client.acquire === "function";
|
|
526
529
|
}
|
|
530
|
+
var PREPARED_CACHE = Symbol.for("drizzle-duckdb:prepared-cache");
|
|
527
531
|
function isPgArrayLiteral(value) {
|
|
528
532
|
return value.startsWith("{") && value.endsWith("}");
|
|
529
533
|
}
|
|
@@ -537,16 +541,20 @@ function parsePgArrayLiteral(value) {
|
|
|
537
541
|
}
|
|
538
542
|
function prepareParams(params, options = {}) {
|
|
539
543
|
return params.map((param) => {
|
|
540
|
-
if (typeof param === "string") {
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
544
|
+
if (typeof param === "string" && param.length > 0) {
|
|
545
|
+
const firstChar = param[0];
|
|
546
|
+
const maybeArrayLiteral = firstChar === "{" || firstChar === "[" || firstChar === " " || firstChar === "\t";
|
|
547
|
+
if (maybeArrayLiteral) {
|
|
548
|
+
const trimmed = firstChar === "{" || firstChar === "[" ? param : param.trim();
|
|
549
|
+
if (trimmed && isPgArrayLiteral(trimmed)) {
|
|
550
|
+
if (options.rejectStringArrayLiterals) {
|
|
551
|
+
throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
|
|
552
|
+
}
|
|
553
|
+
if (options.warnOnStringArrayLiteral) {
|
|
554
|
+
options.warnOnStringArrayLiteral();
|
|
555
|
+
}
|
|
556
|
+
return parsePgArrayLiteral(trimmed);
|
|
548
557
|
}
|
|
549
|
-
return parsePgArrayLiteral(trimmed);
|
|
550
558
|
}
|
|
551
559
|
}
|
|
552
560
|
return param;
|
|
@@ -571,13 +579,121 @@ function toNodeApiValue(value) {
|
|
|
571
579
|
return value;
|
|
572
580
|
}
|
|
573
581
|
function deduplicateColumns(columns) {
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
582
|
+
const counts = new Map;
|
|
583
|
+
let hasDuplicates = false;
|
|
584
|
+
for (const column of columns) {
|
|
585
|
+
const next = (counts.get(column) ?? 0) + 1;
|
|
586
|
+
counts.set(column, next);
|
|
587
|
+
if (next > 1) {
|
|
588
|
+
hasDuplicates = true;
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (!hasDuplicates) {
|
|
593
|
+
return columns;
|
|
594
|
+
}
|
|
595
|
+
counts.clear();
|
|
596
|
+
return columns.map((column) => {
|
|
597
|
+
const count = counts.get(column) ?? 0;
|
|
598
|
+
counts.set(column, count + 1);
|
|
599
|
+
return count === 0 ? column : `${column}_${count}`;
|
|
579
600
|
});
|
|
580
601
|
}
|
|
602
|
+
function destroyPreparedStatement(entry) {
|
|
603
|
+
if (!entry)
|
|
604
|
+
return;
|
|
605
|
+
try {
|
|
606
|
+
entry.statement.destroySync();
|
|
607
|
+
} catch {}
|
|
608
|
+
}
|
|
609
|
+
function getPreparedCache(connection, size) {
|
|
610
|
+
const store = connection;
|
|
611
|
+
const existing = store[PREPARED_CACHE];
|
|
612
|
+
if (existing) {
|
|
613
|
+
existing.size = size;
|
|
614
|
+
return existing;
|
|
615
|
+
}
|
|
616
|
+
const cache = { size, entries: new Map };
|
|
617
|
+
store[PREPARED_CACHE] = cache;
|
|
618
|
+
return cache;
|
|
619
|
+
}
|
|
620
|
+
function evictOldest(cache) {
|
|
621
|
+
const oldest = cache.entries.keys().next();
|
|
622
|
+
if (!oldest.done) {
|
|
623
|
+
const key = oldest.value;
|
|
624
|
+
const entry = cache.entries.get(key);
|
|
625
|
+
cache.entries.delete(key);
|
|
626
|
+
destroyPreparedStatement(entry);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function evictCacheEntry(cache, key) {
|
|
630
|
+
const entry = cache.entries.get(key);
|
|
631
|
+
cache.entries.delete(key);
|
|
632
|
+
destroyPreparedStatement(entry);
|
|
633
|
+
}
|
|
634
|
+
async function getOrPrepareStatement(connection, query, cacheConfig) {
|
|
635
|
+
const cache = getPreparedCache(connection, cacheConfig.size);
|
|
636
|
+
const cached = cache.entries.get(query);
|
|
637
|
+
if (cached) {
|
|
638
|
+
cache.entries.delete(query);
|
|
639
|
+
cache.entries.set(query, cached);
|
|
640
|
+
return cached.statement;
|
|
641
|
+
}
|
|
642
|
+
const statement = await connection.prepare(query);
|
|
643
|
+
cache.entries.set(query, { statement });
|
|
644
|
+
while (cache.entries.size > cache.size) {
|
|
645
|
+
evictOldest(cache);
|
|
646
|
+
}
|
|
647
|
+
return statement;
|
|
648
|
+
}
|
|
649
|
+
async function materializeResultRows(result) {
|
|
650
|
+
const rows = await result.getRowsJS() ?? [];
|
|
651
|
+
const baseColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
652
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? baseColumns : deduplicateColumns(baseColumns);
|
|
653
|
+
return { columns, rows };
|
|
654
|
+
}
|
|
655
|
+
async function materializeRows(client, query, params, options = {}) {
|
|
656
|
+
if (isPool(client)) {
|
|
657
|
+
const connection2 = await client.acquire();
|
|
658
|
+
try {
|
|
659
|
+
return await materializeRows(connection2, query, params, options);
|
|
660
|
+
} finally {
|
|
661
|
+
await client.release(connection2);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
665
|
+
const connection = client;
|
|
666
|
+
if (options.prepareCache && typeof connection.prepare === "function") {
|
|
667
|
+
const cache = getPreparedCache(connection, options.prepareCache.size);
|
|
668
|
+
try {
|
|
669
|
+
const statement = await getOrPrepareStatement(connection, query, options.prepareCache);
|
|
670
|
+
if (values) {
|
|
671
|
+
statement.bind(values);
|
|
672
|
+
} else {
|
|
673
|
+
statement.clearBindings?.();
|
|
674
|
+
}
|
|
675
|
+
const result2 = await statement.run();
|
|
676
|
+
cache.entries.delete(query);
|
|
677
|
+
cache.entries.set(query, { statement });
|
|
678
|
+
return await materializeResultRows(result2);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
evictCacheEntry(cache, query);
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const result = await connection.run(query, values);
|
|
685
|
+
return await materializeResultRows(result);
|
|
686
|
+
}
|
|
687
|
+
function clearPreparedCache(connection) {
|
|
688
|
+
const store = connection;
|
|
689
|
+
const cache = store[PREPARED_CACHE];
|
|
690
|
+
if (!cache)
|
|
691
|
+
return;
|
|
692
|
+
for (const entry of cache.entries.values()) {
|
|
693
|
+
destroyPreparedStatement(entry);
|
|
694
|
+
}
|
|
695
|
+
cache.entries.clear();
|
|
696
|
+
}
|
|
581
697
|
function mapRowsToObjects(columns, rows) {
|
|
582
698
|
return rows.map((vals) => {
|
|
583
699
|
const obj = {};
|
|
@@ -588,6 +704,7 @@ function mapRowsToObjects(columns, rows) {
|
|
|
588
704
|
});
|
|
589
705
|
}
|
|
590
706
|
async function closeClientConnection(connection) {
|
|
707
|
+
clearPreparedCache(connection);
|
|
591
708
|
if ("close" in connection && typeof connection.close === "function") {
|
|
592
709
|
await connection.close();
|
|
593
710
|
return;
|
|
@@ -600,27 +717,51 @@ async function closeClientConnection(connection) {
|
|
|
600
717
|
connection.disconnectSync();
|
|
601
718
|
}
|
|
602
719
|
}
|
|
603
|
-
async function executeOnClient(client, query, params) {
|
|
720
|
+
async function executeOnClient(client, query, params, options = {}) {
|
|
721
|
+
const { columns, rows } = await materializeRows(client, query, params, options);
|
|
722
|
+
if (!rows || rows.length === 0) {
|
|
723
|
+
return [];
|
|
724
|
+
}
|
|
725
|
+
return mapRowsToObjects(columns, rows);
|
|
726
|
+
}
|
|
727
|
+
async function executeArraysOnClient(client, query, params, options = {}) {
|
|
728
|
+
return await materializeRows(client, query, params, options);
|
|
729
|
+
}
|
|
730
|
+
async function* executeInBatches(client, query, params, options = {}) {
|
|
604
731
|
if (isPool(client)) {
|
|
605
732
|
const connection = await client.acquire();
|
|
606
733
|
try {
|
|
607
|
-
|
|
734
|
+
yield* executeInBatches(connection, query, params, options);
|
|
735
|
+
return;
|
|
608
736
|
} finally {
|
|
609
737
|
await client.release(connection);
|
|
610
738
|
}
|
|
611
739
|
}
|
|
740
|
+
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
612
741
|
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
613
|
-
const result = await client.
|
|
614
|
-
const
|
|
615
|
-
const columns = result.deduplicatedColumnNames
|
|
616
|
-
|
|
617
|
-
|
|
742
|
+
const result = await client.stream(query, values);
|
|
743
|
+
const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
744
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
|
|
745
|
+
let buffer = [];
|
|
746
|
+
for await (const chunk of result.yieldRowsJs()) {
|
|
747
|
+
const objects = mapRowsToObjects(columns, chunk);
|
|
748
|
+
for (const row of objects) {
|
|
749
|
+
buffer.push(row);
|
|
750
|
+
if (buffer.length >= rowsPerChunk) {
|
|
751
|
+
yield buffer;
|
|
752
|
+
buffer = [];
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (buffer.length > 0) {
|
|
757
|
+
yield buffer;
|
|
758
|
+
}
|
|
618
759
|
}
|
|
619
|
-
async function*
|
|
760
|
+
async function* executeInBatchesRaw(client, query, params, options = {}) {
|
|
620
761
|
if (isPool(client)) {
|
|
621
762
|
const connection = await client.acquire();
|
|
622
763
|
try {
|
|
623
|
-
yield*
|
|
764
|
+
yield* executeInBatchesRaw(connection, query, params, options);
|
|
624
765
|
return;
|
|
625
766
|
} finally {
|
|
626
767
|
await client.release(connection);
|
|
@@ -629,21 +770,20 @@ async function* executeInBatches(client, query, params, options = {}) {
|
|
|
629
770
|
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
630
771
|
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
631
772
|
const result = await client.stream(query, values);
|
|
632
|
-
const
|
|
633
|
-
const
|
|
773
|
+
const rawColumns = typeof result.deduplicatedColumnNames === "function" ? result.deduplicatedColumnNames() : result.columnNames();
|
|
774
|
+
const columns = typeof result.deduplicatedColumnNames === "function" ? rawColumns : deduplicateColumns(rawColumns);
|
|
634
775
|
let buffer = [];
|
|
635
776
|
for await (const chunk of result.yieldRowsJs()) {
|
|
636
|
-
const
|
|
637
|
-
for (const row of objects) {
|
|
777
|
+
for (const row of chunk) {
|
|
638
778
|
buffer.push(row);
|
|
639
779
|
if (buffer.length >= rowsPerChunk) {
|
|
640
|
-
yield buffer;
|
|
780
|
+
yield { columns, rows: buffer };
|
|
641
781
|
buffer = [];
|
|
642
782
|
}
|
|
643
783
|
}
|
|
644
784
|
}
|
|
645
785
|
if (buffer.length > 0) {
|
|
646
|
-
yield buffer;
|
|
786
|
+
yield { columns, rows: buffer };
|
|
647
787
|
}
|
|
648
788
|
}
|
|
649
789
|
async function executeArrowOnClient(client, query, params) {
|
|
@@ -671,6 +811,13 @@ function isSavepointSyntaxError(error) {
|
|
|
671
811
|
}
|
|
672
812
|
return error.message.toLowerCase().includes("savepoint") && error.message.toLowerCase().includes("syntax error");
|
|
673
813
|
}
|
|
814
|
+
function rewriteQuery(mode, query) {
|
|
815
|
+
if (mode === "never") {
|
|
816
|
+
return { sql: query, rewritten: false };
|
|
817
|
+
}
|
|
818
|
+
const rewritten = adaptArrayOperators(query);
|
|
819
|
+
return { sql: rewritten, rewritten: rewritten !== query };
|
|
820
|
+
}
|
|
674
821
|
|
|
675
822
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
676
823
|
client;
|
|
@@ -681,11 +828,12 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
681
828
|
fields;
|
|
682
829
|
_isResponseInArrayMode;
|
|
683
830
|
customResultMapper;
|
|
684
|
-
|
|
831
|
+
rewriteArraysMode;
|
|
685
832
|
rejectStringArrayLiterals;
|
|
833
|
+
prepareCache;
|
|
686
834
|
warnOnStringArrayLiteral;
|
|
687
835
|
static [entityKind] = "DuckDBPreparedQuery";
|
|
688
|
-
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper,
|
|
836
|
+
constructor(client, dialect, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper, rewriteArraysMode, rejectStringArrayLiterals, prepareCache, warnOnStringArrayLiteral) {
|
|
689
837
|
super({ sql: queryString, params });
|
|
690
838
|
this.client = client;
|
|
691
839
|
this.dialect = dialect;
|
|
@@ -695,8 +843,9 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
695
843
|
this.fields = fields;
|
|
696
844
|
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
697
845
|
this.customResultMapper = customResultMapper;
|
|
698
|
-
this.
|
|
846
|
+
this.rewriteArraysMode = rewriteArraysMode;
|
|
699
847
|
this.rejectStringArrayLiterals = rejectStringArrayLiterals;
|
|
848
|
+
this.prepareCache = prepareCache;
|
|
700
849
|
this.warnOnStringArrayLiteral = warnOnStringArrayLiteral;
|
|
701
850
|
}
|
|
702
851
|
async execute(placeholderValues = {}) {
|
|
@@ -705,18 +854,23 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
705
854
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
706
855
|
warnOnStringArrayLiteral: this.warnOnStringArrayLiteral ? () => this.warnOnStringArrayLiteral?.(this.queryString) : undefined
|
|
707
856
|
});
|
|
708
|
-
const rewrittenQuery
|
|
709
|
-
if (
|
|
857
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, this.queryString);
|
|
858
|
+
if (didRewrite) {
|
|
710
859
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
|
|
711
860
|
}
|
|
712
861
|
this.logger.logQuery(rewrittenQuery, params);
|
|
713
862
|
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
863
|
+
if (fields) {
|
|
864
|
+
const { rows: rows2 } = await executeArraysOnClient(this.client, rewrittenQuery, params, { prepareCache: this.prepareCache });
|
|
865
|
+
if (rows2.length === 0) {
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
717
869
|
}
|
|
718
|
-
const
|
|
719
|
-
|
|
870
|
+
const rows = await executeOnClient(this.client, rewrittenQuery, params, {
|
|
871
|
+
prepareCache: this.prepareCache
|
|
872
|
+
});
|
|
873
|
+
return rows;
|
|
720
874
|
}
|
|
721
875
|
all(placeholderValues = {}) {
|
|
722
876
|
return this.execute(placeholderValues);
|
|
@@ -733,8 +887,9 @@ class DuckDBSession extends PgSession {
|
|
|
733
887
|
static [entityKind] = "DuckDBSession";
|
|
734
888
|
dialect;
|
|
735
889
|
logger;
|
|
736
|
-
|
|
890
|
+
rewriteArraysMode;
|
|
737
891
|
rejectStringArrayLiterals;
|
|
892
|
+
prepareCache;
|
|
738
893
|
hasWarnedArrayLiteral = false;
|
|
739
894
|
rollbackOnly = false;
|
|
740
895
|
constructor(client, dialect, schema, options = {}) {
|
|
@@ -744,11 +899,17 @@ class DuckDBSession extends PgSession {
|
|
|
744
899
|
this.options = options;
|
|
745
900
|
this.dialect = dialect;
|
|
746
901
|
this.logger = options.logger ?? new NoopLogger;
|
|
747
|
-
this.
|
|
902
|
+
this.rewriteArraysMode = options.rewriteArrays ?? "auto";
|
|
748
903
|
this.rejectStringArrayLiterals = options.rejectStringArrayLiterals ?? false;
|
|
904
|
+
this.prepareCache = options.prepareCache;
|
|
905
|
+
this.options = {
|
|
906
|
+
...options,
|
|
907
|
+
rewriteArrays: this.rewriteArraysMode,
|
|
908
|
+
prepareCache: this.prepareCache
|
|
909
|
+
};
|
|
749
910
|
}
|
|
750
911
|
prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
|
|
751
|
-
return new DuckDBPreparedQuery(this.client, this.dialect, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper, this.
|
|
912
|
+
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);
|
|
752
913
|
}
|
|
753
914
|
execute(query) {
|
|
754
915
|
this.dialect.resetPgJsonFlag();
|
|
@@ -808,13 +969,28 @@ query: ${query}`, []);
|
|
|
808
969
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
809
970
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
810
971
|
});
|
|
811
|
-
const rewrittenQuery = this.
|
|
812
|
-
if (
|
|
972
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
|
|
973
|
+
if (didRewrite) {
|
|
813
974
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
814
975
|
}
|
|
815
976
|
this.logger.logQuery(rewrittenQuery, params);
|
|
816
977
|
return executeInBatches(this.client, rewrittenQuery, params, options);
|
|
817
978
|
}
|
|
979
|
+
executeBatchesRaw(query, options = {}) {
|
|
980
|
+
this.dialect.resetPgJsonFlag();
|
|
981
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
982
|
+
this.dialect.assertNoPgJsonColumns();
|
|
983
|
+
const params = prepareParams(builtQuery.params, {
|
|
984
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
985
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
986
|
+
});
|
|
987
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
|
|
988
|
+
if (didRewrite) {
|
|
989
|
+
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
990
|
+
}
|
|
991
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
992
|
+
return executeInBatchesRaw(this.client, rewrittenQuery, params, options);
|
|
993
|
+
}
|
|
818
994
|
async executeArrow(query) {
|
|
819
995
|
this.dialect.resetPgJsonFlag();
|
|
820
996
|
const builtQuery = this.dialect.sqlToQuery(query);
|
|
@@ -823,8 +999,8 @@ query: ${query}`, []);
|
|
|
823
999
|
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
824
1000
|
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
825
1001
|
});
|
|
826
|
-
const rewrittenQuery = this.
|
|
827
|
-
if (
|
|
1002
|
+
const { sql: rewrittenQuery, rewritten: didRewrite } = rewriteQuery(this.rewriteArraysMode, builtQuery.sql);
|
|
1003
|
+
if (didRewrite) {
|
|
828
1004
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
829
1005
|
}
|
|
830
1006
|
this.logger.logQuery(rewrittenQuery, params);
|
|
@@ -862,6 +1038,9 @@ class DuckDBTransaction extends PgTransaction {
|
|
|
862
1038
|
executeBatches(query, options = {}) {
|
|
863
1039
|
return this.session.executeBatches(query, options);
|
|
864
1040
|
}
|
|
1041
|
+
executeBatchesRaw(query, options = {}) {
|
|
1042
|
+
return this.session.executeBatchesRaw(query, options);
|
|
1043
|
+
}
|
|
865
1044
|
executeArrow(query) {
|
|
866
1045
|
return this.session.executeArrow(query);
|
|
867
1046
|
}
|
|
@@ -1273,6 +1452,32 @@ function createDuckDBConnectionPool(instance, options = {}) {
|
|
|
1273
1452
|
};
|
|
1274
1453
|
}
|
|
1275
1454
|
|
|
1455
|
+
// src/options.ts
|
|
1456
|
+
var DEFAULT_REWRITE_ARRAYS_MODE = "auto";
|
|
1457
|
+
function resolveRewriteArraysOption(value) {
|
|
1458
|
+
if (value === undefined)
|
|
1459
|
+
return DEFAULT_REWRITE_ARRAYS_MODE;
|
|
1460
|
+
if (value === true)
|
|
1461
|
+
return "auto";
|
|
1462
|
+
if (value === false)
|
|
1463
|
+
return "never";
|
|
1464
|
+
return value;
|
|
1465
|
+
}
|
|
1466
|
+
var DEFAULT_PREPARED_CACHE_SIZE = 32;
|
|
1467
|
+
function resolvePrepareCacheOption(option) {
|
|
1468
|
+
if (!option)
|
|
1469
|
+
return;
|
|
1470
|
+
if (option === true) {
|
|
1471
|
+
return { size: DEFAULT_PREPARED_CACHE_SIZE };
|
|
1472
|
+
}
|
|
1473
|
+
if (typeof option === "number") {
|
|
1474
|
+
const size2 = Math.max(1, Math.floor(option));
|
|
1475
|
+
return { size: size2 };
|
|
1476
|
+
}
|
|
1477
|
+
const size = option.size ?? DEFAULT_PREPARED_CACHE_SIZE;
|
|
1478
|
+
return { size: Math.max(1, Math.floor(size)) };
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1276
1481
|
// src/driver.ts
|
|
1277
1482
|
class DuckDBDriver {
|
|
1278
1483
|
client;
|
|
@@ -1287,8 +1492,9 @@ class DuckDBDriver {
|
|
|
1287
1492
|
createSession(schema) {
|
|
1288
1493
|
return new DuckDBSession(this.client, this.dialect, schema, {
|
|
1289
1494
|
logger: this.options.logger,
|
|
1290
|
-
rewriteArrays: this.options.rewriteArrays,
|
|
1291
|
-
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals
|
|
1495
|
+
rewriteArrays: this.options.rewriteArrays ?? "auto",
|
|
1496
|
+
rejectStringArrayLiterals: this.options.rejectStringArrayLiterals,
|
|
1497
|
+
prepareCache: this.options.prepareCache
|
|
1292
1498
|
});
|
|
1293
1499
|
}
|
|
1294
1500
|
}
|
|
@@ -1301,6 +1507,8 @@ function isConfigObject(data) {
|
|
|
1301
1507
|
}
|
|
1302
1508
|
function createFromClient(client, config = {}, instance) {
|
|
1303
1509
|
const dialect = new DuckDBDialect;
|
|
1510
|
+
const rewriteArraysMode = resolveRewriteArraysOption(config.rewriteArrays);
|
|
1511
|
+
const prepareCache = resolvePrepareCacheOption(config.prepareCache);
|
|
1304
1512
|
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
1305
1513
|
let schema;
|
|
1306
1514
|
if (config.schema) {
|
|
@@ -1313,8 +1521,9 @@ function createFromClient(client, config = {}, instance) {
|
|
|
1313
1521
|
}
|
|
1314
1522
|
const driver = new DuckDBDriver(client, dialect, {
|
|
1315
1523
|
logger,
|
|
1316
|
-
rewriteArrays:
|
|
1317
|
-
rejectStringArrayLiterals: config.rejectStringArrayLiterals
|
|
1524
|
+
rewriteArrays: rewriteArraysMode,
|
|
1525
|
+
rejectStringArrayLiterals: config.rejectStringArrayLiterals,
|
|
1526
|
+
prepareCache
|
|
1318
1527
|
});
|
|
1319
1528
|
const session = driver.createSession(schema);
|
|
1320
1529
|
const db = new DuckDBDatabase(dialect, session, schema, client, instance);
|
|
@@ -1394,6 +1603,9 @@ class DuckDBDatabase extends PgDatabase {
|
|
|
1394
1603
|
executeBatches(query, options = {}) {
|
|
1395
1604
|
return this.session.executeBatches(query, options);
|
|
1396
1605
|
}
|
|
1606
|
+
executeBatchesRaw(query, options = {}) {
|
|
1607
|
+
return this.session.executeBatchesRaw(query, options);
|
|
1608
|
+
}
|
|
1397
1609
|
executeArrow(query) {
|
|
1398
1610
|
return this.session.executeArrow(query);
|
|
1399
1611
|
}
|
|
@@ -1594,6 +1806,21 @@ var duckDbInterval = (name) => customType({
|
|
|
1594
1806
|
return value;
|
|
1595
1807
|
}
|
|
1596
1808
|
})(name);
|
|
1809
|
+
function shouldBindTimestamp(options) {
|
|
1810
|
+
const bindMode = options.bindMode ?? "auto";
|
|
1811
|
+
if (bindMode === "bind")
|
|
1812
|
+
return true;
|
|
1813
|
+
if (bindMode === "literal")
|
|
1814
|
+
return false;
|
|
1815
|
+
const isBun = typeof process !== "undefined" && typeof process.versions?.bun !== "undefined";
|
|
1816
|
+
if (isBun)
|
|
1817
|
+
return false;
|
|
1818
|
+
const forceLiteral = typeof process !== "undefined" ? process.env.DRIZZLE_DUCKDB_FORCE_LITERAL_TIMESTAMPS : undefined;
|
|
1819
|
+
if (forceLiteral && forceLiteral !== "0") {
|
|
1820
|
+
return false;
|
|
1821
|
+
}
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1597
1824
|
var duckDbTimestamp = (name, options = {}) => customType({
|
|
1598
1825
|
dataType() {
|
|
1599
1826
|
if (options.withTimezone) {
|
|
@@ -1603,12 +1830,19 @@ var duckDbTimestamp = (name, options = {}) => customType({
|
|
|
1603
1830
|
return `TIMESTAMP${precision}`;
|
|
1604
1831
|
},
|
|
1605
1832
|
toDriver(value) {
|
|
1833
|
+
if (shouldBindTimestamp(options)) {
|
|
1834
|
+
return wrapTimestamp(value, options.withTimezone ?? false, options.precision);
|
|
1835
|
+
}
|
|
1606
1836
|
const iso = value instanceof Date ? value.toISOString() : value;
|
|
1607
1837
|
const normalized = iso.replace("T", " ").replace("Z", "+00");
|
|
1608
1838
|
const typeKeyword = options.withTimezone ? "TIMESTAMPTZ" : "TIMESTAMP";
|
|
1609
1839
|
return sql4.raw(`${typeKeyword} '${normalized}'`);
|
|
1610
1840
|
},
|
|
1611
1841
|
fromDriver(value) {
|
|
1842
|
+
if (value && typeof value === "object" && "kind" in value && value.kind === "timestamp") {
|
|
1843
|
+
const wrapped = value;
|
|
1844
|
+
return wrapped.data instanceof Date ? wrapped.data : typeof wrapped.data === "number" || typeof wrapped.data === "bigint" ? new Date(Number(wrapped.data) / 1000) : wrapped.data;
|
|
1845
|
+
}
|
|
1612
1846
|
if (options.mode === "string") {
|
|
1613
1847
|
if (value instanceof Date) {
|
|
1614
1848
|
return value.toISOString().replace("T", " ").replace("Z", "+00");
|
|
@@ -2364,6 +2598,8 @@ export {
|
|
|
2364
2598
|
sumDistinctN,
|
|
2365
2599
|
splitTopLevel,
|
|
2366
2600
|
rowNumber,
|
|
2601
|
+
resolveRewriteArraysOption,
|
|
2602
|
+
resolvePrepareCacheOption,
|
|
2367
2603
|
resolvePoolSize,
|
|
2368
2604
|
rank,
|
|
2369
2605
|
prepareParams,
|
|
@@ -2380,8 +2616,10 @@ export {
|
|
|
2380
2616
|
introspect,
|
|
2381
2617
|
formatLiteral,
|
|
2382
2618
|
executeOnClient,
|
|
2619
|
+
executeInBatchesRaw,
|
|
2383
2620
|
executeInBatches,
|
|
2384
2621
|
executeArrowOnClient,
|
|
2622
|
+
executeArraysOnClient,
|
|
2385
2623
|
duckDbTimestamp,
|
|
2386
2624
|
duckDbTime,
|
|
2387
2625
|
duckDbStruct,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type RewriteArraysMode = 'auto' | 'always' | 'never';
|
|
2
|
+
export type RewriteArraysOption = boolean | RewriteArraysMode;
|
|
3
|
+
export declare function resolveRewriteArraysOption(value?: RewriteArraysOption): RewriteArraysMode;
|
|
4
|
+
export type PrepareCacheOption = boolean | number | {
|
|
5
|
+
size?: number;
|
|
6
|
+
};
|
|
7
|
+
export interface PreparedStatementCacheConfig {
|
|
8
|
+
size: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolvePrepareCacheOption(option?: PrepareCacheOption): PreparedStatementCacheConfig | undefined;
|
package/dist/session.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ import { type Query, SQL } from 'drizzle-orm/sql/sql';
|
|
|
9
9
|
import type { Assume } from 'drizzle-orm/utils';
|
|
10
10
|
import type { DuckDBDialect } from './dialect.ts';
|
|
11
11
|
import type { DuckDBClientLike, RowData } from './client.ts';
|
|
12
|
-
import { type ExecuteInBatchesOptions } from './client.ts';
|
|
12
|
+
import { type ExecuteBatchesRawChunk, type ExecuteInBatchesOptions } from './client.ts';
|
|
13
|
+
import type { PreparedStatementCacheConfig, RewriteArraysMode } from './options.ts';
|
|
13
14
|
export type { DuckDBClientLike, RowData } from './client.ts';
|
|
14
15
|
export declare class DuckDBPreparedQuery<T extends PreparedQueryConfig> extends PgPreparedQuery<T> {
|
|
15
16
|
private client;
|
|
@@ -20,19 +21,21 @@ export declare class DuckDBPreparedQuery<T extends PreparedQueryConfig> extends
|
|
|
20
21
|
private fields;
|
|
21
22
|
private _isResponseInArrayMode;
|
|
22
23
|
private customResultMapper;
|
|
23
|
-
private
|
|
24
|
+
private rewriteArraysMode;
|
|
24
25
|
private rejectStringArrayLiterals;
|
|
26
|
+
private prepareCache;
|
|
25
27
|
private warnOnStringArrayLiteral?;
|
|
26
28
|
static readonly [entityKind]: string;
|
|
27
|
-
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, queryString: string, params: unknown[], logger: Logger, fields: SelectedFieldsOrdered | undefined, _isResponseInArrayMode: boolean, customResultMapper: ((rows: unknown[][]) => T['execute']) | undefined,
|
|
29
|
+
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, queryString: string, params: unknown[], logger: Logger, fields: SelectedFieldsOrdered | undefined, _isResponseInArrayMode: boolean, customResultMapper: ((rows: unknown[][]) => T['execute']) | undefined, rewriteArraysMode: RewriteArraysMode, rejectStringArrayLiterals: boolean, prepareCache: PreparedStatementCacheConfig | undefined, warnOnStringArrayLiteral?: ((sql: string) => void) | undefined);
|
|
28
30
|
execute(placeholderValues?: Record<string, unknown> | undefined): Promise<T['execute']>;
|
|
29
31
|
all(placeholderValues?: Record<string, unknown> | undefined): Promise<T['all']>;
|
|
30
32
|
isResponseInArrayMode(): boolean;
|
|
31
33
|
}
|
|
32
34
|
export interface DuckDBSessionOptions {
|
|
33
35
|
logger?: Logger;
|
|
34
|
-
rewriteArrays?:
|
|
36
|
+
rewriteArrays?: RewriteArraysMode;
|
|
35
37
|
rejectStringArrayLiterals?: boolean;
|
|
38
|
+
prepareCache?: PreparedStatementCacheConfig;
|
|
36
39
|
}
|
|
37
40
|
export declare class DuckDBSession<TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = Record<string, never>> extends PgSession<DuckDBQueryResultHKT, TFullSchema, TSchema> {
|
|
38
41
|
private client;
|
|
@@ -41,8 +44,9 @@ export declare class DuckDBSession<TFullSchema extends Record<string, unknown> =
|
|
|
41
44
|
static readonly [entityKind]: string;
|
|
42
45
|
protected dialect: DuckDBDialect;
|
|
43
46
|
private logger;
|
|
44
|
-
private
|
|
47
|
+
private rewriteArraysMode;
|
|
45
48
|
private rejectStringArrayLiterals;
|
|
49
|
+
private prepareCache;
|
|
46
50
|
private hasWarnedArrayLiteral;
|
|
47
51
|
private rollbackOnly;
|
|
48
52
|
constructor(client: DuckDBClientLike, dialect: DuckDBDialect, schema: RelationalSchemaConfig<TSchema> | undefined, options?: DuckDBSessionOptions);
|
|
@@ -52,6 +56,7 @@ export declare class DuckDBSession<TFullSchema extends Record<string, unknown> =
|
|
|
52
56
|
transaction<T>(transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>, config?: PgTransactionConfig): Promise<T>;
|
|
53
57
|
private warnOnStringArrayLiteral;
|
|
54
58
|
executeBatches<T extends RowData = RowData>(query: SQL, options?: ExecuteInBatchesOptions): AsyncGenerator<GenericRowData<T>[], void, void>;
|
|
59
|
+
executeBatchesRaw(query: SQL, options?: ExecuteInBatchesOptions): AsyncGenerator<ExecuteBatchesRawChunk, void, void>;
|
|
55
60
|
executeArrow(query: SQL): Promise<unknown>;
|
|
56
61
|
markRollbackOnly(): void;
|
|
57
62
|
isRollbackOnly(): boolean;
|
|
@@ -62,6 +67,7 @@ export declare class DuckDBTransaction<TFullSchema extends Record<string, unknow
|
|
|
62
67
|
getTransactionConfigSQL(config: PgTransactionConfig): SQL;
|
|
63
68
|
setTransaction(config: PgTransactionConfig): Promise<void>;
|
|
64
69
|
executeBatches<T extends RowData = RowData>(query: SQL, options?: ExecuteInBatchesOptions): AsyncGenerator<GenericRowData<T>[], void, void>;
|
|
70
|
+
executeBatchesRaw(query: SQL, options?: ExecuteInBatchesOptions): AsyncGenerator<ExecuteBatchesRawChunk, void, void>;
|
|
65
71
|
executeArrow(query: SQL): Promise<unknown>;
|
|
66
72
|
transaction<T>(transaction: (tx: DuckDBTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T>;
|
|
67
73
|
private runNestedWithoutSavepoint;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"module": "./dist/index.mjs",
|
|
4
4
|
"main": "./dist/index.mjs",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
|
-
"version": "1.1.
|
|
6
|
+
"version": "1.1.2",
|
|
7
7
|
"description": "A drizzle ORM client for use with DuckDB. Based on drizzle's Postgres client.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|