@leonardovida-md/drizzle-neo-duckdb 1.0.1 → 1.0.3
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 +36 -18
- package/dist/client.d.ts +12 -0
- package/dist/columns.d.ts +18 -10
- package/dist/driver.d.ts +4 -0
- package/dist/duckdb-introspect.mjs +245 -51
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +435 -65
- package/dist/introspect.d.ts +12 -0
- package/dist/olap.d.ts +46 -0
- package/dist/session.d.ts +5 -0
- package/dist/value-wrappers.d.ts +104 -0
- package/package.json +7 -3
- package/src/bin/duckdb-introspect.ts +40 -3
- package/src/client.ts +135 -18
- package/src/columns.ts +65 -36
- package/src/dialect.ts +2 -2
- package/src/driver.ts +20 -6
- package/src/index.ts +3 -0
- package/src/introspect.ts +97 -33
- package/src/migrator.ts +1 -3
- package/src/olap.ts +189 -0
- package/src/select-builder.ts +3 -7
- package/src/session.ts +87 -18
- package/src/sql/query-rewriters.ts +5 -8
- package/src/sql/result-mapper.ts +6 -6
- package/src/sql/selection.ts +2 -9
- package/src/value-wrappers.ts +324 -0
|
@@ -311,8 +311,78 @@ import { TransactionRollbackError } from "drizzle-orm/errors";
|
|
|
311
311
|
|
|
312
312
|
// src/client.ts
|
|
313
313
|
import {
|
|
314
|
-
listValue
|
|
314
|
+
listValue as listValue2,
|
|
315
|
+
timestampValue as timestampValue2
|
|
315
316
|
} from "@duckdb/node-api";
|
|
317
|
+
|
|
318
|
+
// src/value-wrappers.ts
|
|
319
|
+
import {
|
|
320
|
+
listValue,
|
|
321
|
+
arrayValue,
|
|
322
|
+
structValue,
|
|
323
|
+
mapValue,
|
|
324
|
+
blobValue,
|
|
325
|
+
timestampValue,
|
|
326
|
+
timestampTZValue
|
|
327
|
+
} from "@duckdb/node-api";
|
|
328
|
+
var DUCKDB_VALUE_MARKER = Symbol.for("drizzle-duckdb:value");
|
|
329
|
+
function dateToMicros(value) {
|
|
330
|
+
if (value instanceof Date) {
|
|
331
|
+
return BigInt(value.getTime()) * 1000n;
|
|
332
|
+
}
|
|
333
|
+
let normalized = value;
|
|
334
|
+
if (!value.includes("T") && value.includes(" ")) {
|
|
335
|
+
normalized = value.replace(" ", "T");
|
|
336
|
+
}
|
|
337
|
+
if (!normalized.endsWith("Z") && !/[+-]\d{2}:?\d{2}$/.test(normalized)) {
|
|
338
|
+
normalized += "Z";
|
|
339
|
+
}
|
|
340
|
+
const date = new Date(normalized);
|
|
341
|
+
if (isNaN(date.getTime())) {
|
|
342
|
+
throw new Error(`Invalid timestamp string: ${value}`);
|
|
343
|
+
}
|
|
344
|
+
return BigInt(date.getTime()) * 1000n;
|
|
345
|
+
}
|
|
346
|
+
function toUint8Array(data) {
|
|
347
|
+
return data instanceof Uint8Array && !(data instanceof Buffer) ? data : new Uint8Array(data);
|
|
348
|
+
}
|
|
349
|
+
function convertStructEntries(data, toValue) {
|
|
350
|
+
const entries = {};
|
|
351
|
+
for (const [key, val] of Object.entries(data)) {
|
|
352
|
+
entries[key] = toValue(val);
|
|
353
|
+
}
|
|
354
|
+
return entries;
|
|
355
|
+
}
|
|
356
|
+
function convertMapEntries(data, toValue) {
|
|
357
|
+
return Object.entries(data).map(([key, val]) => ({
|
|
358
|
+
key,
|
|
359
|
+
value: toValue(val)
|
|
360
|
+
}));
|
|
361
|
+
}
|
|
362
|
+
function wrapperToNodeApiValue(wrapper, toValue) {
|
|
363
|
+
switch (wrapper.kind) {
|
|
364
|
+
case "list":
|
|
365
|
+
return listValue(wrapper.data.map(toValue));
|
|
366
|
+
case "array":
|
|
367
|
+
return arrayValue(wrapper.data.map(toValue));
|
|
368
|
+
case "struct":
|
|
369
|
+
return structValue(convertStructEntries(wrapper.data, toValue));
|
|
370
|
+
case "map":
|
|
371
|
+
return mapValue(convertMapEntries(wrapper.data, toValue));
|
|
372
|
+
case "timestamp":
|
|
373
|
+
return wrapper.withTimezone ? timestampTZValue(dateToMicros(wrapper.data)) : timestampValue(dateToMicros(wrapper.data));
|
|
374
|
+
case "blob":
|
|
375
|
+
return blobValue(toUint8Array(wrapper.data));
|
|
376
|
+
case "json":
|
|
377
|
+
return JSON.stringify(wrapper.data);
|
|
378
|
+
default: {
|
|
379
|
+
const _exhaustive = wrapper;
|
|
380
|
+
throw new Error(`Unknown wrapper kind: ${_exhaustive.kind}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/client.ts
|
|
316
386
|
function isPgArrayLiteral(value) {
|
|
317
387
|
return value.startsWith("{") && value.endsWith("}");
|
|
318
388
|
}
|
|
@@ -341,30 +411,78 @@ function prepareParams(params, options = {}) {
|
|
|
341
411
|
});
|
|
342
412
|
}
|
|
343
413
|
function toNodeApiValue(value) {
|
|
414
|
+
if (value == null)
|
|
415
|
+
return null;
|
|
416
|
+
const t = typeof value;
|
|
417
|
+
if (t === "string" || t === "number" || t === "bigint" || t === "boolean") {
|
|
418
|
+
return value;
|
|
419
|
+
}
|
|
420
|
+
if (t === "object" && DUCKDB_VALUE_MARKER in value) {
|
|
421
|
+
return wrapperToNodeApiValue(value, toNodeApiValue);
|
|
422
|
+
}
|
|
344
423
|
if (Array.isArray(value)) {
|
|
345
|
-
return
|
|
424
|
+
return listValue2(value.map((inner) => toNodeApiValue(inner)));
|
|
425
|
+
}
|
|
426
|
+
if (value instanceof Date) {
|
|
427
|
+
return timestampValue2(BigInt(value.getTime()) * 1000n);
|
|
346
428
|
}
|
|
347
429
|
return value;
|
|
348
430
|
}
|
|
349
|
-
|
|
350
|
-
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
351
|
-
const result = await client.run(query, values);
|
|
352
|
-
const rows = await result.getRowsJS();
|
|
353
|
-
const columns = result.columnNames();
|
|
431
|
+
function deduplicateColumns(columns) {
|
|
354
432
|
const seen = {};
|
|
355
|
-
|
|
433
|
+
return columns.map((col) => {
|
|
356
434
|
const count = seen[col] ?? 0;
|
|
357
435
|
seen[col] = count + 1;
|
|
358
436
|
return count === 0 ? col : `${col}_${count}`;
|
|
359
437
|
});
|
|
360
|
-
|
|
438
|
+
}
|
|
439
|
+
function mapRowsToObjects(columns, rows) {
|
|
440
|
+
return rows.map((vals) => {
|
|
361
441
|
const obj = {};
|
|
362
|
-
|
|
442
|
+
columns.forEach((col, idx) => {
|
|
363
443
|
obj[col] = vals[idx];
|
|
364
444
|
});
|
|
365
445
|
return obj;
|
|
366
446
|
});
|
|
367
447
|
}
|
|
448
|
+
async function executeOnClient(client, query, params) {
|
|
449
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
450
|
+
const result = await client.run(query, values);
|
|
451
|
+
const rows = await result.getRowsJS();
|
|
452
|
+
const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
|
|
453
|
+
const uniqueColumns = deduplicateColumns(columns);
|
|
454
|
+
return rows ? mapRowsToObjects(uniqueColumns, rows) : [];
|
|
455
|
+
}
|
|
456
|
+
async function* executeInBatches(client, query, params, options = {}) {
|
|
457
|
+
const rowsPerChunk = options.rowsPerChunk && options.rowsPerChunk > 0 ? options.rowsPerChunk : 1e5;
|
|
458
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
459
|
+
const result = await client.stream(query, values);
|
|
460
|
+
const columns = result.deduplicatedColumnNames?.() ?? result.columnNames();
|
|
461
|
+
const uniqueColumns = deduplicateColumns(columns);
|
|
462
|
+
let buffer = [];
|
|
463
|
+
for await (const chunk of result.yieldRowsJs()) {
|
|
464
|
+
const objects = mapRowsToObjects(uniqueColumns, chunk);
|
|
465
|
+
for (const row of objects) {
|
|
466
|
+
buffer.push(row);
|
|
467
|
+
if (buffer.length >= rowsPerChunk) {
|
|
468
|
+
yield buffer;
|
|
469
|
+
buffer = [];
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (buffer.length > 0) {
|
|
474
|
+
yield buffer;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function executeArrowOnClient(client, query, params) {
|
|
478
|
+
const values = params.length > 0 ? params.map((param) => toNodeApiValue(param)) : undefined;
|
|
479
|
+
const result = await client.run(query, values);
|
|
480
|
+
const maybeArrow = result.toArrow ?? result.getArrowTable;
|
|
481
|
+
if (typeof maybeArrow === "function") {
|
|
482
|
+
return await maybeArrow.call(result);
|
|
483
|
+
}
|
|
484
|
+
return result.getColumnsObjectJS();
|
|
485
|
+
}
|
|
368
486
|
|
|
369
487
|
// src/session.ts
|
|
370
488
|
class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
@@ -405,11 +523,7 @@ class DuckDBPreparedQuery extends PgPreparedQuery {
|
|
|
405
523
|
this.logger.logQuery(`[duckdb] original query before array rewrite: ${this.queryString}`, params);
|
|
406
524
|
}
|
|
407
525
|
this.logger.logQuery(rewrittenQuery, params);
|
|
408
|
-
const {
|
|
409
|
-
fields,
|
|
410
|
-
joinsNotNullableMap,
|
|
411
|
-
customResultMapper
|
|
412
|
-
} = this;
|
|
526
|
+
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
413
527
|
const rows = await executeOnClient(this.client, rewrittenQuery, params);
|
|
414
528
|
if (rows.length === 0 || !fields) {
|
|
415
529
|
return rows;
|
|
@@ -469,6 +583,32 @@ class DuckDBSession extends PgSession {
|
|
|
469
583
|
this.logger.logQuery(`[duckdb] ${arrayLiteralWarning}
|
|
470
584
|
query: ${query}`, []);
|
|
471
585
|
};
|
|
586
|
+
executeBatches(query, options = {}) {
|
|
587
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
588
|
+
const params = prepareParams(builtQuery.params, {
|
|
589
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
590
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
591
|
+
});
|
|
592
|
+
const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
|
|
593
|
+
if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
|
|
594
|
+
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
595
|
+
}
|
|
596
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
597
|
+
return executeInBatches(this.client, rewrittenQuery, params, options);
|
|
598
|
+
}
|
|
599
|
+
async executeArrow(query) {
|
|
600
|
+
const builtQuery = this.dialect.sqlToQuery(query);
|
|
601
|
+
const params = prepareParams(builtQuery.params, {
|
|
602
|
+
rejectStringArrayLiterals: this.rejectStringArrayLiterals,
|
|
603
|
+
warnOnStringArrayLiteral: this.rejectStringArrayLiterals ? undefined : () => this.warnOnStringArrayLiteral(builtQuery.sql)
|
|
604
|
+
});
|
|
605
|
+
const rewrittenQuery = this.rewriteArrays ? adaptArrayOperators(builtQuery.sql) : builtQuery.sql;
|
|
606
|
+
if (this.rewriteArrays && rewrittenQuery !== builtQuery.sql) {
|
|
607
|
+
this.logger.logQuery(`[duckdb] original query before array rewrite: ${builtQuery.sql}`, params);
|
|
608
|
+
}
|
|
609
|
+
this.logger.logQuery(rewrittenQuery, params);
|
|
610
|
+
return executeArrowOnClient(this.client, rewrittenQuery, params);
|
|
611
|
+
}
|
|
472
612
|
}
|
|
473
613
|
|
|
474
614
|
class DuckDBTransaction extends PgTransaction {
|
|
@@ -492,6 +632,12 @@ class DuckDBTransaction extends PgTransaction {
|
|
|
492
632
|
setTransaction(config) {
|
|
493
633
|
return this.session.execute(sql`set transaction ${this.getTransactionConfigSQL(config)}`);
|
|
494
634
|
}
|
|
635
|
+
executeBatches(query, options = {}) {
|
|
636
|
+
return this.session.executeBatches(query, options);
|
|
637
|
+
}
|
|
638
|
+
executeArrow(query) {
|
|
639
|
+
return this.session.executeArrow(query);
|
|
640
|
+
}
|
|
495
641
|
async transaction(transaction) {
|
|
496
642
|
const nestedTx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
|
|
497
643
|
return transaction(nestedTx);
|
|
@@ -590,13 +736,7 @@ import { PgViewBase } from "drizzle-orm/pg-core/view-base";
|
|
|
590
736
|
import { SQL as SQL4 } from "drizzle-orm/sql/sql";
|
|
591
737
|
|
|
592
738
|
// src/sql/selection.ts
|
|
593
|
-
import {
|
|
594
|
-
Column as Column2,
|
|
595
|
-
SQL as SQL3,
|
|
596
|
-
getTableName as getTableName2,
|
|
597
|
-
is as is3,
|
|
598
|
-
sql as sql3
|
|
599
|
-
} from "drizzle-orm";
|
|
739
|
+
import { Column as Column2, SQL as SQL3, getTableName as getTableName2, is as is3, sql as sql3 } from "drizzle-orm";
|
|
600
740
|
function mapEntries(obj, prefix, fullJoin = false) {
|
|
601
741
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
|
|
602
742
|
const qualified = prefix ? `${prefix}.${key}` : key;
|
|
@@ -737,6 +877,12 @@ class DuckDBDatabase extends PgDatabase {
|
|
|
737
877
|
dialect: this.dialect
|
|
738
878
|
});
|
|
739
879
|
}
|
|
880
|
+
executeBatches(query, options = {}) {
|
|
881
|
+
return this.session.executeBatches(query, options);
|
|
882
|
+
}
|
|
883
|
+
executeArrow(query) {
|
|
884
|
+
return this.session.executeArrow(query);
|
|
885
|
+
}
|
|
740
886
|
async transaction(transaction) {
|
|
741
887
|
return await this.session.transaction(transaction);
|
|
742
888
|
}
|
|
@@ -747,12 +893,13 @@ import { sql as sql4 } from "drizzle-orm";
|
|
|
747
893
|
var SYSTEM_SCHEMAS = new Set(["information_schema", "pg_catalog"]);
|
|
748
894
|
var DEFAULT_IMPORT_BASE = "@leonardovida-md/drizzle-neo-duckdb";
|
|
749
895
|
async function introspect(db, opts = {}) {
|
|
750
|
-
const
|
|
896
|
+
const database = await resolveDatabase(db, opts.database, opts.allDatabases);
|
|
897
|
+
const schemas = await resolveSchemas(db, database, opts.schemas);
|
|
751
898
|
const includeViews = opts.includeViews ?? false;
|
|
752
|
-
const tables = await loadTables(db, schemas, includeViews);
|
|
753
|
-
const columns = await loadColumns(db, schemas);
|
|
754
|
-
const constraints = await loadConstraints(db, schemas);
|
|
755
|
-
const indexes = await loadIndexes(db, schemas);
|
|
899
|
+
const tables = await loadTables(db, database, schemas, includeViews);
|
|
900
|
+
const columns = await loadColumns(db, database, schemas);
|
|
901
|
+
const constraints = await loadConstraints(db, database, schemas);
|
|
902
|
+
const indexes = await loadIndexes(db, database, schemas);
|
|
756
903
|
const grouped = buildTables(tables, columns, constraints, indexes);
|
|
757
904
|
const schemaTs = emitSchema(grouped, {
|
|
758
905
|
useCustomTimeTypes: opts.useCustomTimeTypes ?? true,
|
|
@@ -766,27 +913,41 @@ async function introspect(db, opts = {}) {
|
|
|
766
913
|
}
|
|
767
914
|
};
|
|
768
915
|
}
|
|
769
|
-
async function
|
|
916
|
+
async function resolveDatabase(db, targetDatabase, allDatabases) {
|
|
917
|
+
if (allDatabases) {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
if (targetDatabase) {
|
|
921
|
+
return targetDatabase;
|
|
922
|
+
}
|
|
923
|
+
const rows = await db.execute(sql4`SELECT current_database() as current_database`);
|
|
924
|
+
return rows[0]?.current_database ?? null;
|
|
925
|
+
}
|
|
926
|
+
async function resolveSchemas(db, database, targetSchemas) {
|
|
770
927
|
if (targetSchemas?.length) {
|
|
771
928
|
return targetSchemas;
|
|
772
929
|
}
|
|
773
|
-
const
|
|
930
|
+
const databaseFilter = database ? sql4`catalog_name = ${database}` : sql4`1 = 1`;
|
|
931
|
+
const rows = await db.execute(sql4`SELECT schema_name FROM information_schema.schemata WHERE ${databaseFilter}`);
|
|
774
932
|
return rows.map((row) => row.schema_name).filter((name) => !SYSTEM_SCHEMAS.has(name));
|
|
775
933
|
}
|
|
776
|
-
async function loadTables(db, schemas, includeViews) {
|
|
934
|
+
async function loadTables(db, database, schemas, includeViews) {
|
|
777
935
|
const schemaFragments = schemas.map((schema) => sql4`${schema}`);
|
|
936
|
+
const databaseFilter = database ? sql4`table_catalog = ${database}` : sql4`1 = 1`;
|
|
778
937
|
return await db.execute(sql4`
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
938
|
+
SELECT table_schema as schema_name, table_name, table_type
|
|
939
|
+
FROM information_schema.tables
|
|
940
|
+
WHERE ${databaseFilter}
|
|
941
|
+
AND table_schema IN (${sql4.join(schemaFragments, sql4.raw(", "))})
|
|
942
|
+
AND ${includeViews ? sql4`1 = 1` : sql4`table_type = 'BASE TABLE'`}
|
|
943
|
+
ORDER BY table_schema, table_name
|
|
784
944
|
`);
|
|
785
945
|
}
|
|
786
|
-
async function loadColumns(db, schemas) {
|
|
946
|
+
async function loadColumns(db, database, schemas) {
|
|
787
947
|
const schemaFragments = schemas.map((schema) => sql4`${schema}`);
|
|
948
|
+
const databaseFilter = database ? sql4`database_name = ${database}` : sql4`1 = 1`;
|
|
788
949
|
return await db.execute(sql4`
|
|
789
|
-
|
|
950
|
+
SELECT
|
|
790
951
|
schema_name,
|
|
791
952
|
table_name,
|
|
792
953
|
column_name,
|
|
@@ -798,15 +959,17 @@ async function loadColumns(db, schemas) {
|
|
|
798
959
|
numeric_precision,
|
|
799
960
|
numeric_scale,
|
|
800
961
|
internal
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
962
|
+
FROM duckdb_columns()
|
|
963
|
+
WHERE ${databaseFilter}
|
|
964
|
+
AND schema_name IN (${sql4.join(schemaFragments, sql4.raw(", "))})
|
|
965
|
+
ORDER BY schema_name, table_name, column_index
|
|
804
966
|
`);
|
|
805
967
|
}
|
|
806
|
-
async function loadConstraints(db, schemas) {
|
|
968
|
+
async function loadConstraints(db, database, schemas) {
|
|
807
969
|
const schemaFragments = schemas.map((schema) => sql4`${schema}`);
|
|
970
|
+
const databaseFilter = database ? sql4`database_name = ${database}` : sql4`1 = 1`;
|
|
808
971
|
return await db.execute(sql4`
|
|
809
|
-
|
|
972
|
+
SELECT
|
|
810
973
|
schema_name,
|
|
811
974
|
table_name,
|
|
812
975
|
constraint_name,
|
|
@@ -815,23 +978,26 @@ async function loadConstraints(db, schemas) {
|
|
|
815
978
|
constraint_column_names,
|
|
816
979
|
referenced_table,
|
|
817
980
|
referenced_column_names
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
981
|
+
FROM duckdb_constraints()
|
|
982
|
+
WHERE ${databaseFilter}
|
|
983
|
+
AND schema_name IN (${sql4.join(schemaFragments, sql4.raw(", "))})
|
|
984
|
+
ORDER BY schema_name, table_name, constraint_index
|
|
821
985
|
`);
|
|
822
986
|
}
|
|
823
|
-
async function loadIndexes(db, schemas) {
|
|
987
|
+
async function loadIndexes(db, database, schemas) {
|
|
824
988
|
const schemaFragments = schemas.map((schema) => sql4`${schema}`);
|
|
989
|
+
const databaseFilter = database ? sql4`database_name = ${database}` : sql4`1 = 1`;
|
|
825
990
|
return await db.execute(sql4`
|
|
826
|
-
|
|
991
|
+
SELECT
|
|
827
992
|
schema_name,
|
|
828
993
|
table_name,
|
|
829
994
|
index_name,
|
|
830
995
|
is_unique,
|
|
831
996
|
expressions
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
997
|
+
FROM duckdb_indexes()
|
|
998
|
+
WHERE ${databaseFilter}
|
|
999
|
+
AND schema_name IN (${sql4.join(schemaFragments, sql4.raw(", "))})
|
|
1000
|
+
ORDER BY schema_name, table_name, index_name
|
|
835
1001
|
`);
|
|
836
1002
|
}
|
|
837
1003
|
function buildTables(tables, columns, constraints, indexes) {
|
|
@@ -1026,7 +1192,9 @@ function mapDuckDbType(column, imports, options) {
|
|
|
1026
1192
|
}
|
|
1027
1193
|
if (upper === "BIGINT" || upper === "INT8" || upper === "UBIGINT") {
|
|
1028
1194
|
imports.pgCore.add("bigint");
|
|
1029
|
-
return {
|
|
1195
|
+
return {
|
|
1196
|
+
builder: `bigint(${columnName(column.name)}, { mode: 'number' })`
|
|
1197
|
+
};
|
|
1030
1198
|
}
|
|
1031
1199
|
const decimalMatch = /^DECIMAL\((\d+),(\d+)\)/i.exec(upper);
|
|
1032
1200
|
const numericMatch = /^NUMERIC\((\d+),(\d+)\)/i.exec(upper);
|
|
@@ -1278,6 +1446,7 @@ function renderImports(imports, importBasePath) {
|
|
|
1278
1446
|
function parseArgs(argv) {
|
|
1279
1447
|
const options = {
|
|
1280
1448
|
outFile: path.resolve(process.cwd(), "drizzle/schema.ts"),
|
|
1449
|
+
allDatabases: false,
|
|
1281
1450
|
includeViews: false,
|
|
1282
1451
|
useCustomTimeTypes: true
|
|
1283
1452
|
};
|
|
@@ -1287,6 +1456,13 @@ function parseArgs(argv) {
|
|
|
1287
1456
|
case "--url":
|
|
1288
1457
|
options.url = argv[++i];
|
|
1289
1458
|
break;
|
|
1459
|
+
case "--database":
|
|
1460
|
+
case "--db":
|
|
1461
|
+
options.database = argv[++i];
|
|
1462
|
+
break;
|
|
1463
|
+
case "--all-databases":
|
|
1464
|
+
options.allDatabases = true;
|
|
1465
|
+
break;
|
|
1290
1466
|
case "--schema":
|
|
1291
1467
|
case "--schemas":
|
|
1292
1468
|
options.schemas = argv[++i]?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -1325,11 +1501,27 @@ Usage:
|
|
|
1325
1501
|
|
|
1326
1502
|
Options:
|
|
1327
1503
|
--url DuckDB database path (e.g. :memory:, ./local.duckdb, md:)
|
|
1504
|
+
--database, --db Database/catalog to introspect (default: current database)
|
|
1505
|
+
--all-databases Introspect all attached databases (not just current)
|
|
1328
1506
|
--schema Comma separated schema list (defaults to all non-system schemas)
|
|
1329
1507
|
--out Output file (default: ./drizzle/schema.ts)
|
|
1330
1508
|
--include-views Include views in the generated schema
|
|
1331
1509
|
--use-pg-time Use pg-core timestamp/date/time instead of DuckDB custom helpers
|
|
1332
1510
|
--import-base Override import path for duckdb helpers (default: package name)
|
|
1511
|
+
|
|
1512
|
+
Database Filtering:
|
|
1513
|
+
By default, only tables from the current database are introspected. This prevents
|
|
1514
|
+
returning tables from all attached databases in MotherDuck workspaces.
|
|
1515
|
+
|
|
1516
|
+
Use --database to specify a different database, or --all-databases to introspect
|
|
1517
|
+
all attached databases.
|
|
1518
|
+
|
|
1519
|
+
Examples:
|
|
1520
|
+
# Local DuckDB file
|
|
1521
|
+
bun x duckdb-introspect --url ./my-database.duckdb --out ./schema.ts
|
|
1522
|
+
|
|
1523
|
+
# MotherDuck (requires MOTHERDUCK_TOKEN env var)
|
|
1524
|
+
MOTHERDUCK_TOKEN=xxx bun x duckdb-introspect --url md: --database my_cloud_db --out ./schema.ts
|
|
1333
1525
|
`);
|
|
1334
1526
|
}
|
|
1335
1527
|
async function main() {
|
|
@@ -1344,6 +1536,8 @@ async function main() {
|
|
|
1344
1536
|
const db = drizzle(connection);
|
|
1345
1537
|
try {
|
|
1346
1538
|
const result = await introspect(db, {
|
|
1539
|
+
database: options.database,
|
|
1540
|
+
allDatabases: options.allDatabases,
|
|
1347
1541
|
schemas: options.schemas,
|
|
1348
1542
|
includeViews: options.includeViews,
|
|
1349
1543
|
useCustomTimeTypes: options.useCustomTimeTypes,
|