@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/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/driver.ts
2
+ import { DuckDBInstance as DuckDBInstance2 } from "@duckdb/node-api";
2
3
  import { entityKind as entityKind3 } from "drizzle-orm/entity";
3
4
  import { DefaultLogger } from "drizzle-orm/logger";
4
5
  import { PgDatabase } from "drizzle-orm/pg-core/db";
@@ -15,10 +16,6 @@ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
15
16
  import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
16
17
 
17
18
  // src/sql/query-rewriters.ts
18
- var tableIdPropSelectionRegex = new RegExp([
19
- `("(.+)"\\."(.+)")`,
20
- `(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`
21
- ].join(""), "i");
22
19
  function adaptArrayOperators(query) {
23
20
  const operators = [
24
21
  { token: "@>", fn: "array_has_all" },
@@ -35,6 +32,8 @@ function adaptArrayOperators(query) {
35
32
  let inString = false;
36
33
  for (;idx >= 0; idx--) {
37
34
  const ch = source[idx];
35
+ if (ch === undefined)
36
+ break;
38
37
  if (ch === "'" && source[idx - 1] !== "\\") {
39
38
  inString = !inString;
40
39
  }
@@ -62,6 +61,8 @@ function adaptArrayOperators(query) {
62
61
  let inString = false;
63
62
  for (;idx < source.length; idx++) {
64
63
  const ch = source[idx];
64
+ if (ch === undefined)
65
+ break;
65
66
  if (ch === "'" && source[idx - 1] !== "\\") {
66
67
  inString = !inString;
67
68
  }
@@ -303,8 +304,144 @@ import { TransactionRollbackError } from "drizzle-orm/errors";
303
304
 
304
305
  // src/client.ts
305
306
  import {
306
- listValue
307
+ listValue as listValue2,
308
+ timestampValue as timestampValue2
307
309
  } from "@duckdb/node-api";
310
+
311
+ // src/value-wrappers.ts
312
+ import {
313
+ listValue,
314
+ arrayValue,
315
+ structValue,
316
+ mapValue,
317
+ blobValue,
318
+ timestampValue,
319
+ timestampTZValue
320
+ } from "@duckdb/node-api";
321
+
322
+ // src/value-wrappers-core.ts
323
+ var DUCKDB_VALUE_MARKER = Symbol.for("drizzle-duckdb:value");
324
+ function isDuckDBWrapper(value) {
325
+ return value !== null && typeof value === "object" && DUCKDB_VALUE_MARKER in value && value[DUCKDB_VALUE_MARKER] === true;
326
+ }
327
+ function wrapList(data, elementType) {
328
+ return {
329
+ [DUCKDB_VALUE_MARKER]: true,
330
+ kind: "list",
331
+ data,
332
+ elementType
333
+ };
334
+ }
335
+ function wrapArray(data, elementType, fixedLength) {
336
+ return {
337
+ [DUCKDB_VALUE_MARKER]: true,
338
+ kind: "array",
339
+ data,
340
+ elementType,
341
+ fixedLength
342
+ };
343
+ }
344
+ function wrapStruct(data, schema) {
345
+ return {
346
+ [DUCKDB_VALUE_MARKER]: true,
347
+ kind: "struct",
348
+ data,
349
+ schema
350
+ };
351
+ }
352
+ function wrapMap(data, valueType) {
353
+ return {
354
+ [DUCKDB_VALUE_MARKER]: true,
355
+ kind: "map",
356
+ data,
357
+ valueType
358
+ };
359
+ }
360
+ function wrapTimestamp(data, withTimezone, precision) {
361
+ return {
362
+ [DUCKDB_VALUE_MARKER]: true,
363
+ kind: "timestamp",
364
+ data,
365
+ withTimezone,
366
+ precision
367
+ };
368
+ }
369
+ function wrapBlob(data) {
370
+ return {
371
+ [DUCKDB_VALUE_MARKER]: true,
372
+ kind: "blob",
373
+ data
374
+ };
375
+ }
376
+ function wrapJson(data) {
377
+ return {
378
+ [DUCKDB_VALUE_MARKER]: true,
379
+ kind: "json",
380
+ data
381
+ };
382
+ }
383
+
384
+ // src/value-wrappers.ts
385
+ function dateToMicros(value) {
386
+ if (value instanceof Date) {
387
+ return BigInt(value.getTime()) * 1000n;
388
+ }
389
+ let normalized = value;
390
+ if (!value.includes("T") && value.includes(" ")) {
391
+ normalized = value.replace(" ", "T");
392
+ }
393
+ if (!normalized.endsWith("Z") && !/[+-]\d{2}:?\d{2}$/.test(normalized)) {
394
+ normalized += "Z";
395
+ }
396
+ const date = new Date(normalized);
397
+ if (isNaN(date.getTime())) {
398
+ throw new Error(`Invalid timestamp string: ${value}`);
399
+ }
400
+ return BigInt(date.getTime()) * 1000n;
401
+ }
402
+ function toUint8Array(data) {
403
+ return data instanceof Uint8Array && !(data instanceof Buffer) ? data : new Uint8Array(data);
404
+ }
405
+ function convertStructEntries(data, toValue) {
406
+ const entries = {};
407
+ for (const [key, val] of Object.entries(data)) {
408
+ entries[key] = toValue(val);
409
+ }
410
+ return entries;
411
+ }
412
+ function convertMapEntries(data, toValue) {
413
+ return Object.entries(data).map(([key, val]) => ({
414
+ key,
415
+ value: toValue(val)
416
+ }));
417
+ }
418
+ function wrapperToNodeApiValue(wrapper, toValue) {
419
+ switch (wrapper.kind) {
420
+ case "list":
421
+ return listValue(wrapper.data.map(toValue));
422
+ case "array":
423
+ return arrayValue(wrapper.data.map(toValue));
424
+ case "struct":
425
+ return structValue(convertStructEntries(wrapper.data, toValue));
426
+ case "map":
427
+ return mapValue(convertMapEntries(wrapper.data, toValue));
428
+ case "timestamp":
429
+ return wrapper.withTimezone ? timestampTZValue(dateToMicros(wrapper.data)) : timestampValue(dateToMicros(wrapper.data));
430
+ case "blob":
431
+ return blobValue(toUint8Array(wrapper.data));
432
+ case "json":
433
+ return JSON.stringify(wrapper.data);
434
+ default: {
435
+ const _exhaustive = wrapper;
436
+ throw new Error(`Unknown wrapper kind: ${_exhaustive.kind}`);
437
+ }
438
+ }
439
+ }
440
+
441
+ // src/client.ts
442
+ function isPool(client) {
443
+ return typeof client.acquire === "function";
444
+ }
308
445
  function isPgArrayLiteral(value) {
309
446
  return value.startsWith("{") && value.endsWith("}");
310
447
  }
@@ -316,15 +453,13 @@ function parsePgArrayLiteral(value) {
316
453
  return value;
317
454
  }
318
455
  }
319
- var warnedArrayLiteral = false;
320
456
  function prepareParams(params, options = {}) {
321
457
  return params.map((param) => {
322
458
  if (typeof param === "string" && isPgArrayLiteral(param)) {
323
459
  if (options.rejectStringArrayLiterals) {
324
460
  throw new Error("Stringified array literals are not supported. Use duckDbList()/duckDbArray() or pass native arrays.");
325
461
  }
326
- if (!warnedArrayLiteral && options.warnOnStringArrayLiteral) {
327
- warnedArrayLiteral = true;
462
+ if (options.warnOnStringArrayLiteral) {
328
463
  options.warnOnStringArrayLiteral();
329
464
  }
330
465
  return parsePgArrayLiteral(param);
@@ -333,30 +468,116 @@ function prepareParams(params, options = {}) {
333
468
  });
334
469
  }
335
470
  function toNodeApiValue(value) {
471
+ if (value == null)
472
+ return null;
473
+ const t = typeof value;
474
+ if (t === "string" || t === "number" || t === "bigint" || t === "boolean") {
475
+ return value;
476
+ }
477
+ if (t === "object" && DUCKDB_VALUE_MARKER in value) {
478
+ return wrapperToNodeApiValue(value, toNodeApiValue);
479
+ }
336
480
  if (Array.isArray(value)) {
337
- return listValue(value.map((inner) => toNodeApiValue(inner)));
481
+ return listValue2(value.map((inner) => toNodeApiValue(inner)));
482
+ }
483
+ if (value instanceof Date) {
484
+ return timestampValue2(BigInt(value.getTime()) * 1000n);
338
485
  }
339
486
  return value;
340
487
  }
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();
488
+ function deduplicateColumns(columns) {
346
489
  const seen = {};
347
- const uniqueColumns = columns.map((col) => {
490
+ return columns.map((col) => {
348
491
  const count = seen[col] ?? 0;
349
492
  seen[col] = count + 1;
350
493
  return count === 0 ? col : `${col}_${count}`;
351
494
  });
352
- return (rows ?? []).map((vals) => {
495
+ }
496
+ function mapRowsToObjects(columns, rows) {
497
+ return rows.map((vals) => {
353
498
  const obj = {};
354
- uniqueColumns.forEach((col, idx) => {
499
+ columns.forEach((col, idx) => {
355
500
  obj[col] = vals[idx];
356
501
  });
357
502
  return obj;
358
503
  });
359
504
  }
505
+ async function closeClientConnection(connection) {
506
+ if ("close" in connection && typeof connection.close === "function") {
507
+ await connection.close();
508
+ return;
509
+ }
510
+ if ("closeSync" in connection && typeof connection.closeSync === "function") {
511
+ connection.closeSync();
512
+ return;
513
+ }
514
+ if ("disconnectSync" in connection && typeof connection.disconnectSync === "function") {
515
+ connection.disconnectSync();
516
+ }
517
+ }
518
+ async function executeOnClient(client, query, params) {
519
+ if (isPool(client)) {
520
+ const connection = await client.acquire();
521
+ try {
522
+ return await executeOnClient(connection, query, params);
523
+ } finally {
524
+ await client.release(connection);
525
+ }
526
+ }
527
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
528
+ const result = await client.run(query, values);
529
+ const rows = await result.getRowsJS();
530
+ const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
531
+ const uniqueColumns = deduplicateColumns(columns);
532
+ return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
533
+ }
534
+ async function* executeInBatches(client, query, params, options = {}) {
535
+ if (isPool(client)) {
536
+ const connection = await client.acquire();
537
+ try {
538
+ yield* executeInBatches(connection, query, params, options);
539
+ return;
540
+ } finally {
541
+ await client.release(connection);
542
+ }
543
+ }
544
+ const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
545
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
546
+ const result = await client.stream(query, values);
547
+ const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
548
+ const uniqueColumns = deduplicateColumns(columns);
549
+ let buffer = [];
550
+ for await (const chunk of result.yieldRowsJs()) {
551
+ const objects = mapRowsToObjects(uniqueColumns, chunk);
552
+ for (const row of objects) {
553
+ buffer.push(row);
554
+ if (buffer.length >= rowsPerChunk) {
555
+ yield buffer;
556
+ buffer = [];
557
+ }
558
+ }
559
+ }
560
+ if (buffer.length > 0) {
561
+ yield buffer;
562
+ }
563
+ }
564
+ async function executeArrowOnClient(client, query, params) {
565
+ if (isPool(client)) {
566
+ const connection = await client.acquire();
567
+ try {
568
+ return await executeArrowOnClient(connection, query, params);
569
+ } finally {
570
+ await client.release(connection);
571
+ }
572
+ }
573
+ const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
574
+ const result = await client.run(query, values);
575
+ const maybeArrow = result.toArrow ?? result.getArrowTable;
576
+ if (typeof maybeArrow === "function") {
577
+ return await maybeArrow.call(result);
578
+ }
579
+ return result.getColumnsObjectJS();
580
+ }
360
581
 
361
582
  // src/session.ts
362
583
  class DuckDBPreparedQuery extends PgPreparedQuery {
@@ -397,11 +618,7 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
397
618
  this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
398
619
  }
399
620
  this.logger.logQuery(rewrittenQuery, params);
400
- const {
401
- fields,
402
- joinsNotNullableMap,
403
- customResultMapper
404
- } = this;
621
+ const { fields, joinsNotNullableMap, customResultMapper } = this;
405
622
  const rows = await executeOnClient(this.client, rewrittenQuery, params);
406
623
  if (rows.length === 0 || !fields) {
407
624
  return rows;
@@ -441,16 +658,30 @@ class DuckDBSession extends PgSession {
441
658
  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
659
  }
443
660
  async transaction(transaction) {
444
- const session = new DuckDBSession(this.client, this.dialect, this.schema, this.options);
661
+ let pinnedConnection;
662
+ let pool;
663
+ let clientForTx = this.client;
664
+ if (isPool(this.client)) {
665
+ pool = this.client;
666
+ pinnedConnection = await pool.acquire();
667
+ clientForTx = pinnedConnection;
668
+ }
669
+ const session = new DuckDBSession(clientForTx, this.dialect, this.schema, this.options);
445
670
  const tx = new DuckDBTransaction(this.dialect, session, this.schema);
446
- await tx.execute(sql`BEGIN TRANSACTION;`);
447
671
  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;
672
+ await tx.execute(sql`BEGIN TRANSACTION;`);
673
+ try {
674
+ const result = await transaction(tx);
675
+ await tx.execute(sql`commit`);
676
+ return result;
677
+ } catch (error) {
678
+ await tx.execute(sql`rollback`);
679
+ throw error;
680
+ }
681
+ } finally {
682
+ if (pinnedConnection && pool) {
683
+ await pool.release(pinnedConnection);
684
+ }
454
685
  }
455
686
  }
456
687
  warnOnStringArrayLiteral = (query) => {
@@ -461,6 +692,32 @@ class DuckDBSession extends PgSession {
461
692
  this.logger.logQuery(`[duckdb] ${arrayLiteralWarning}
462
693
  query: ${query}`, []);
463
694
  };
695
+ executeBatches(query, options = {}) {
696
+ const builtQuery = this.dialect.sqlToQuery(query);
697
+ const params = prepareParams(builtQuery.params, {
698
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
699
+ warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
700
+ });
701
+ const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
702
+ if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
703
+ this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
704
+ }
705
+ this.logger.logQuery(rewrittenQuery, params);
706
+ return executeInBatches(this.client, rewrittenQuery, params, options);
707
+ }
708
+ async executeArrow(query) {
709
+ const builtQuery = this.dialect.sqlToQuery(query);
710
+ const params = prepareParams(builtQuery.params, {
711
+ rejectStringArrayLiterals: this.rejectStringArrayLiterals,
712
+ warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
713
+ });
714
+ const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
715
+ if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
716
+ this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
717
+ }
718
+ this.logger.logQuery(rewrittenQuery, params);
719
+ return executeArrowOnClient(this.client, rewrittenQuery, params);
720
+ }
464
721
  }
465
722
 
466
723
  class DuckDBTransaction extends PgTransaction {
@@ -484,6 +741,12 @@ class DuckDBTransaction extends PgTransaction {
484
741
  setTransaction(config) {
485
742
  return this.session.execute(sql`set transaction ${this.getTransactionConfigSQL(config)}`);
486
743
  }
744
+ executeBatches(query, options = {}) {
745
+ return this.session.executeBatches(query, options);
746
+ }
747
+ executeArrow(query) {
748
+ return this.session.executeArrow(query);
749
+ }
487
750
  async transaction(transaction) {
488
751
  const nestedTx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
489
752
  return transaction(nestedTx);
@@ -582,13 +845,7 @@ import { PgViewBase } from "drizzle-orm/pg-core/view-base";
582
845
  import { SQL as SQL4 } from "drizzle-orm/sql/sql";
583
846
 
584
847
  // 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";
848
+ import { Column as Column2, SQL as SQL3, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
592
849
  function mapEntries(obj, prefix, fullJoin = false) {
593
850
  return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
594
851
  const qualified = prefix ? `${prefix}.${key}` : key;
@@ -672,6 +929,72 @@ class DuckDBSelectBuilder extends PgSelectBuilder {
672
929
  }
673
930
  }
674
931
 
932
+ // src/pool.ts
933
+ import { DuckDBConnection } from "@duckdb/node-api";
934
+ var POOL_PRESETS = {
935
+ pulse: 4,
936
+ standard: 6,
937
+ jumbo: 8,
938
+ mega: 12,
939
+ giga: 16,
940
+ local: 8,
941
+ memory: 4
942
+ };
943
+ function resolvePoolSize(pool) {
944
+ if (pool === false)
945
+ return false;
946
+ if (pool === undefined)
947
+ return 4;
948
+ if (typeof pool === "string")
949
+ return POOL_PRESETS[pool];
950
+ return pool.size ?? 4;
951
+ }
952
+ function createDuckDBConnectionPool(instance, options = {}) {
953
+ const size = options.size && options.size > 0 ? options.size : 4;
954
+ const idle = [];
955
+ const waiting = [];
956
+ let total = 0;
957
+ let closed = false;
958
+ const acquire = async () => {
959
+ if (closed) {
960
+ throw new Error("DuckDB connection pool is closed");
961
+ }
962
+ if (idle.length > 0) {
963
+ return idle.pop();
964
+ }
965
+ if (total < size) {
966
+ total += 1;
967
+ return await DuckDBConnection.create(instance);
968
+ }
969
+ return await new Promise((resolve) => {
970
+ waiting.push(resolve);
971
+ });
972
+ };
973
+ const release = async (connection) => {
974
+ if (closed) {
975
+ await closeClientConnection(connection);
976
+ return;
977
+ }
978
+ const waiter = waiting.shift();
979
+ if (waiter) {
980
+ waiter(connection);
981
+ return;
982
+ }
983
+ idle.push(connection);
984
+ };
985
+ const close = async () => {
986
+ closed = true;
987
+ const toClose = idle.splice(0, idle.length);
988
+ await Promise.all(toClose.map((conn) => closeClientConnection(conn)));
989
+ };
990
+ return {
991
+ acquire,
992
+ release,
993
+ close,
994
+ size
995
+ };
996
+ }
997
+
675
998
  // src/driver.ts
676
999
  class DuckDBDriver {
677
1000
  client;
@@ -691,7 +1014,14 @@ class DuckDBDriver {
691
1014
  });
692
1015
  }
693
1016
  }
694
- function drizzle(client, config = {}) {
1017
+ function isConfigObject(data) {
1018
+ if (typeof data !== "object" || data === null)
1019
+ return false;
1020
+ if (data.constructor?.name !== "Object")
1021
+ return false;
1022
+ return "connection" in data || "client" in data || "pool" in data || "schema" in data || "logger" in data;
1023
+ }
1024
+ function createFromClient(client, config = {}, instance) {
695
1025
  const dialect = new DuckDBDialect;
696
1026
  const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
697
1027
  let schema;
@@ -709,17 +1039,60 @@ function drizzle(client, config = {}) {
709
1039
  rejectStringArrayLiterals: config.rejectStringArrayLiterals
710
1040
  });
711
1041
  const session = driver.createSession(schema);
712
- return new DuckDBDatabase(dialect, session, schema);
1042
+ const db = new DuckDBDatabase(dialect, session, schema, client, instance);
1043
+ return db;
1044
+ }
1045
+ async function createFromConnectionString(path, instanceOptions, config = {}) {
1046
+ const instance = await DuckDBInstance2.create(path, instanceOptions);
1047
+ const poolSize = resolvePoolSize(config.pool);
1048
+ if (poolSize === false) {
1049
+ const connection = await instance.connect();
1050
+ return createFromClient(connection, config, instance);
1051
+ }
1052
+ const pool = createDuckDBConnectionPool(instance, { size: poolSize });
1053
+ return createFromClient(pool, config, instance);
1054
+ }
1055
+ function drizzle(clientOrConfigOrPath, config) {
1056
+ if (typeof clientOrConfigOrPath === "string") {
1057
+ return createFromConnectionString(clientOrConfigOrPath, undefined, config);
1058
+ }
1059
+ if (isConfigObject(clientOrConfigOrPath)) {
1060
+ const configObj = clientOrConfigOrPath;
1061
+ if ("connection" in configObj) {
1062
+ const connConfig = configObj;
1063
+ const { connection, ...restConfig } = connConfig;
1064
+ if (typeof connection === "string") {
1065
+ return createFromConnectionString(connection, undefined, restConfig);
1066
+ }
1067
+ return createFromConnectionString(connection.path, connection.options, restConfig);
1068
+ }
1069
+ if ("client" in configObj) {
1070
+ const clientConfig = configObj;
1071
+ const { client: clientValue, ...restConfig } = clientConfig;
1072
+ return createFromClient(clientValue, restConfig);
1073
+ }
1074
+ throw new Error("Invalid drizzle config: either connection or client must be provided");
1075
+ }
1076
+ return createFromClient(clientOrConfigOrPath, config);
713
1077
  }
714
1078
 
715
1079
  class DuckDBDatabase extends PgDatabase {
716
1080
  dialect;
717
1081
  session;
718
1082
  static [entityKind3] = "DuckDBDatabase";
719
- constructor(dialect, session, schema) {
1083
+ $client;
1084
+ $instance;
1085
+ constructor(dialect, session, schema, client, instance) {
720
1086
  super(dialect, session, schema);
721
1087
  this.dialect = dialect;
722
1088
  this.session = session;
1089
+ this.$client = client;
1090
+ this.$instance = instance;
1091
+ }
1092
+ async close() {
1093
+ if (isPool(this.$client) && this.$client.close) {
1094
+ await this.$client.close();
1095
+ }
723
1096
  }
724
1097
  select(fields) {
725
1098
  const selectedFields = fields ? aliasFields(fields) : undefined;
@@ -729,6 +1102,12 @@ class DuckDBDatabase extends PgDatabase {
729
1102
  dialect: this.dialect
730
1103
  });
731
1104
  }
1105
+ executeBatches(query, options = {}) {
1106
+ return this.session.executeBatches(query, options);
1107
+ }
1108
+ executeArrow(query) {
1109
+ return this.session.executeArrow(query);
1110
+ }
732
1111
  async transaction(transaction) {
733
1112
  return await this.session.transaction(transaction);
734
1113
  }
@@ -797,19 +1176,12 @@ function buildStructLiteral(value, schema) {
797
1176
  });
798
1177
  return sql4`struct_pack(${sql4.join(parts, sql4.raw(", "))})`;
799
1178
  }
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
1179
  var duckDbList = (name, elementType) => customType({
808
1180
  dataType() {
809
1181
  return `${elementType}[]`;
810
1182
  },
811
1183
  toDriver(value) {
812
- return buildListLiteral(value, elementType);
1184
+ return wrapList(value, elementType);
813
1185
  },
814
1186
  fromDriver(value) {
815
1187
  if (Array.isArray(value)) {
@@ -829,7 +1201,7 @@ var duckDbArray = (name, elementType, fixedLength) => customType({
829
1201
  return fixedLength ? `${elementType}[${fixedLength}]` : `${elementType}[]`;
830
1202
  },
831
1203
  toDriver(value) {
832
- return buildListLiteral(value, elementType);
1204
+ return wrapArray(value, elementType, fixedLength);
833
1205
  },
834
1206
  fromDriver(value) {
835
1207
  if (Array.isArray(value)) {
@@ -849,7 +1221,7 @@ var duckDbMap = (name, valueType) => customType({
849
1221
  return `MAP (STRING, ${valueType})`;
850
1222
  },
851
1223
  toDriver(value) {
852
- return buildMapLiteral(value, valueType);
1224
+ return wrapMap(value, valueType);
853
1225
  },
854
1226
  fromDriver(value) {
855
1227
  return value;
@@ -885,7 +1257,7 @@ var duckDbJson = (name) => customType({
885
1257
  if (value !== null && typeof value === "object" && "queryChunks" in value) {
886
1258
  return value;
887
1259
  }
888
- return JSON.stringify(value ?? null);
1260
+ return wrapJson(value);
889
1261
  },
890
1262
  fromDriver(value) {
891
1263
  if (typeof value !== "string") {
@@ -907,8 +1279,7 @@ var duckDbBlob = customType({
907
1279
  return "BLOB";
908
1280
  },
909
1281
  toDriver(value) {
910
- const hexString = value.toString("hex");
911
- return sql4`from_hex(${hexString})`;
1282
+ return wrapBlob(value);
912
1283
  }
913
1284
  });
914
1285
  var duckDbInet = (name) => customType({
@@ -1010,7 +1381,7 @@ async function migrate(db, config) {
1010
1381
  // src/introspect.ts
1011
1382
  import { sql as sql5 } from "drizzle-orm";
1012
1383
  var SYSTEM_SCHEMAS = new Set(["information_schema", "pg_catalog"]);
1013
- var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb";
1384
+ var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb/helpers";
1014
1385
  async function introspect(db, opts = {}) {
1015
1386
  const database = await resolveDatabase(db, opts.database, opts.allDatabases);
1016
1387
  const schemas = await resolveSchemas(db, database, opts.schemas);
@@ -1311,7 +1682,9 @@ function mapDuckDbType(column, imports, options) {
1311
1682
  }
1312
1683
  if (upper === "BIGINT" || upper === "INT8" || upper === "UBIGINT") {
1313
1684
  imports.pgCore.add("bigint");
1314
- return { builder: `bigint(${columnName(column.name)})` };
1685
+ return {
1686
+ builder: `bigint(${columnName(column.name)}, { mode: 'number' })`
1687
+ };
1315
1688
  }
1316
1689
  const decimalMatch = /^DECIMAL\((\d+),(\d+)\)/i.exec(upper);
1317
1690
  const numericMatch = /^NUMERIC\((\d+),(\d+)\)/i.exec(upper);
@@ -1344,6 +1717,22 @@ function mapDuckDbType(column, imports, options) {
1344
1717
  imports.pgCore.add("doublePrecision");
1345
1718
  return { builder: `doublePrecision(${columnName(column.name)})` };
1346
1719
  }
1720
+ const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
1721
+ if (arrayMatch) {
1722
+ imports.local.add("duckDbArray");
1723
+ const [, base, length] = arrayMatch;
1724
+ return {
1725
+ builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
1726
+ };
1727
+ }
1728
+ const listMatch = /^(.*)\[\]$/.exec(upper);
1729
+ if (listMatch) {
1730
+ imports.local.add("duckDbList");
1731
+ const [, base] = listMatch;
1732
+ return {
1733
+ builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
1734
+ };
1735
+ }
1347
1736
  if (upper.startsWith("CHAR(") || upper === "CHAR") {
1348
1737
  imports.pgCore.add("char");
1349
1738
  const length = column.characterLength;
@@ -1384,22 +1773,6 @@ function mapDuckDbType(column, imports, options) {
1384
1773
  imports.local.add("duckDbBlob");
1385
1774
  return { builder: `duckDbBlob(${columnName(column.name)})` };
1386
1775
  }
1387
- const arrayMatch = /^(.*)\[(\d+)\]$/.exec(upper);
1388
- if (arrayMatch) {
1389
- imports.local.add("duckDbArray");
1390
- const [, base, length] = arrayMatch;
1391
- return {
1392
- builder: `duckDbArray(${columnName(column.name)}, ${JSON.stringify(base)}, ${Number(length)})`
1393
- };
1394
- }
1395
- const listMatch = /^(.*)\[\]$/.exec(upper);
1396
- if (listMatch) {
1397
- imports.local.add("duckDbList");
1398
- const [, base] = listMatch;
1399
- return {
1400
- builder: `duckDbList(${columnName(column.name)}, ${JSON.stringify(base)})`
1401
- };
1402
- }
1403
1776
  if (upper.startsWith("STRUCT")) {
1404
1777
  imports.local.add("duckDbStruct");
1405
1778
  const inner = upper.replace(/^STRUCT\s*\(/i, "").replace(/\)$/, "");
@@ -1558,9 +1931,142 @@ function renderImports(imports, importBasePath) {
1558
1931
  return lines.join(`
1559
1932
  `);
1560
1933
  }
1934
+ // src/olap.ts
1935
+ import { is as is5 } from "drizzle-orm/entity";
1936
+ import { sql as sql6 } from "drizzle-orm";
1937
+ import { SQL as SQL5 } from "drizzle-orm/sql/sql";
1938
+ import { Column as Column3, getTableName as getTableName3 } from "drizzle-orm";
1939
+ var countN = (expr = sql6`*`) => sql6`count(${expr})`.mapWith(Number);
1940
+ var sumN = (expr) => sql6`sum(${expr})`.mapWith(Number);
1941
+ var avgN = (expr) => sql6`avg(${expr})`.mapWith(Number);
1942
+ var sumDistinctN = (expr) => sql6`sum(distinct ${expr})`.mapWith(Number);
1943
+ var percentileCont = (p, expr) => sql6`percentile_cont(${p}) within group (order by ${expr})`.mapWith(Number);
1944
+ var median = (expr) => percentileCont(0.5, expr);
1945
+ var anyValue = (expr) => sql6`any_value(${expr})`;
1946
+ function normalizeArray(value) {
1947
+ if (!value)
1948
+ return [];
1949
+ return Array.isArray(value) ? value : [value];
1950
+ }
1951
+ function overClause(options) {
1952
+ const partitions = normalizeArray(options?.partitionBy);
1953
+ const orders = normalizeArray(options?.orderBy);
1954
+ const chunks = [];
1955
+ if (partitions.length > 0) {
1956
+ chunks.push(sql6`partition by ${sql6.join(partitions, sql6`, `)}`);
1957
+ }
1958
+ if (orders.length > 0) {
1959
+ chunks.push(sql6`order by ${sql6.join(orders, sql6`, `)}`);
1960
+ }
1961
+ if (chunks.length === 0) {
1962
+ return sql6``;
1963
+ }
1964
+ return sql6`over (${sql6.join(chunks, sql6` `)})`;
1965
+ }
1966
+ var rowNumber = (options) => sql6`row_number() ${overClause(options)}`.mapWith(Number);
1967
+ var rank = (options) => sql6`rank() ${overClause(options)}`.mapWith(Number);
1968
+ var denseRank = (options) => sql6`dense_rank() ${overClause(options)}`.mapWith(Number);
1969
+ var lag = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lag(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lag(${expr}, ${offset}) ${overClause(options)}`;
1970
+ var lead = (expr, offset = 1, defaultValue, options) => defaultValue ? sql6`lead(${expr}, ${offset}, ${defaultValue}) ${overClause(options)}` : sql6`lead(${expr}, ${offset}) ${overClause(options)}`;
1971
+ function keyAlias(key, fallback) {
1972
+ if (is5(key, SQL5.Aliased)) {
1973
+ return key.fieldAlias ?? fallback;
1974
+ }
1975
+ if (is5(key, Column3)) {
1976
+ return `${getTableName3(key.table)}.${key.name}`;
1977
+ }
1978
+ return fallback;
1979
+ }
1980
+
1981
+ class OlapBuilder {
1982
+ db;
1983
+ source;
1984
+ keys = [];
1985
+ measureMap = {};
1986
+ nonAggregates = {};
1987
+ wrapNonAggWithAnyValue = false;
1988
+ orderByClauses = [];
1989
+ constructor(db) {
1990
+ this.db = db;
1991
+ }
1992
+ from(source) {
1993
+ this.source = source;
1994
+ return this;
1995
+ }
1996
+ groupBy(keys) {
1997
+ this.keys = keys;
1998
+ return this;
1999
+ }
2000
+ measures(measures) {
2001
+ this.measureMap = measures;
2002
+ return this;
2003
+ }
2004
+ selectNonAggregates(fields, options = {}) {
2005
+ this.nonAggregates = fields;
2006
+ this.wrapNonAggWithAnyValue = options.anyValue ?? false;
2007
+ return this;
2008
+ }
2009
+ orderBy(...clauses) {
2010
+ this.orderByClauses = clauses;
2011
+ return this;
2012
+ }
2013
+ build() {
2014
+ if (!this.source) {
2015
+ throw new Error("olap: .from() is required");
2016
+ }
2017
+ if (this.keys.length === 0) {
2018
+ throw new Error("olap: .groupBy() is required");
2019
+ }
2020
+ if (Object.keys(this.measureMap).length === 0) {
2021
+ throw new Error("olap: .measures() is required");
2022
+ }
2023
+ const selection = {};
2024
+ this.keys.forEach((key, idx) => {
2025
+ const alias = keyAlias(key, `key_${idx}`);
2026
+ selection[alias] = key;
2027
+ });
2028
+ Object.entries(this.nonAggregates).forEach(([alias, expr]) => {
2029
+ selection[alias] = this.wrapNonAggWithAnyValue ? anyValue(expr) : expr;
2030
+ });
2031
+ Object.assign(selection, this.measureMap);
2032
+ let query = this.db.select(selection).from(this.source).groupBy(...this.keys);
2033
+ if (this.orderByClauses.length > 0) {
2034
+ query = query.orderBy(...this.orderByClauses);
2035
+ }
2036
+ return query;
2037
+ }
2038
+ run() {
2039
+ return this.build();
2040
+ }
2041
+ }
2042
+ var olap = (db) => new OlapBuilder(db);
1561
2043
  export {
2044
+ wrapperToNodeApiValue,
2045
+ wrapTimestamp,
2046
+ wrapStruct,
2047
+ wrapMap,
2048
+ wrapList,
2049
+ wrapJson,
2050
+ wrapBlob,
2051
+ wrapArray,
2052
+ sumN,
2053
+ sumDistinctN,
2054
+ rowNumber,
2055
+ resolvePoolSize,
2056
+ rank,
2057
+ prepareParams,
2058
+ percentileCont,
2059
+ olap,
1562
2060
  migrate,
2061
+ median,
2062
+ lead,
2063
+ lag,
2064
+ isPool,
2065
+ isDuckDBWrapper,
1563
2066
  introspect,
2067
+ executeOnClient,
2068
+ executeInBatches,
2069
+ executeArrowOnClient,
1564
2070
  duckDbTimestamp,
1565
2071
  duckDbTime,
1566
2072
  duckDbStruct,
@@ -1576,9 +2082,19 @@ export {
1576
2082
  duckDbArrayContained,
1577
2083
  duckDbArray,
1578
2084
  drizzle,
2085
+ denseRank,
2086
+ createDuckDBConnectionPool,
2087
+ countN,
2088
+ closeClientConnection,
2089
+ avgN,
2090
+ anyValue,
2091
+ POOL_PRESETS,
2092
+ OlapBuilder,
1579
2093
  DuckDBTransaction,
1580
2094
  DuckDBSession,
1581
2095
  DuckDBPreparedQuery,
1582
2096
  DuckDBDriver,
1583
- DuckDBDatabase
2097
+ DuckDBDatabase,
2098
+ DUCKDB_VALUE_MARKER,
2099
+ DEFAULT_IMPORT_BASE
1584
2100
  };