@duckdbfan/drizzle-duckdb 0.0.3 → 0.0.4

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 ADDED
@@ -0,0 +1,429 @@
1
+ // src/driver.ts
2
+ import { entityKind as entityKind3, is as is3 } from "drizzle-orm/entity";
3
+ import { DefaultLogger } from "drizzle-orm/logger";
4
+ import { PgDatabase } from "drizzle-orm/pg-core/db";
5
+ import {
6
+ createTableRelationsHelpers,
7
+ extractTablesRelationalConfig
8
+ } from "drizzle-orm/relations";
9
+ import {
10
+ getTableColumns
11
+ } from "drizzle-orm/utils";
12
+
13
+ // src/session.ts
14
+ import { entityKind } from "drizzle-orm/entity";
15
+ import { NoopLogger } from "drizzle-orm/logger";
16
+ import { PgTransaction } from "drizzle-orm/pg-core";
17
+ import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
18
+ import { fillPlaceholders, sql as sql2 } from "drizzle-orm/sql/sql";
19
+
20
+ // src/utils.ts
21
+ import {
22
+ is,
23
+ Column,
24
+ SQL,
25
+ getTableName,
26
+ sql
27
+ } from "drizzle-orm";
28
+ function mapResultRow(columns, row, joinsNotNullableMap) {
29
+ const nullifyMap = {};
30
+ const result = columns.reduce((result2, { path, field }, columnIndex) => {
31
+ let decoder;
32
+ if (is(field, Column)) {
33
+ decoder = field;
34
+ } else if (is(field, SQL)) {
35
+ decoder = field.decoder;
36
+ } else {
37
+ decoder = field.sql.decoder;
38
+ }
39
+ let node = result2;
40
+ for (const [pathChunkIndex, pathChunk] of path.entries()) {
41
+ if (pathChunkIndex < path.length - 1) {
42
+ if (!(pathChunk in node)) {
43
+ node[pathChunk] = {};
44
+ }
45
+ node = node[pathChunk];
46
+ continue;
47
+ }
48
+ const rawValue = row[columnIndex];
49
+ const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
50
+ if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
51
+ const objectName = path[0];
52
+ if (!(objectName in nullifyMap)) {
53
+ nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
54
+ } else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
55
+ nullifyMap[objectName] = false;
56
+ }
57
+ continue;
58
+ }
59
+ if (joinsNotNullableMap && is(field, SQL.Aliased) && path.length === 2) {
60
+ const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
61
+ const tableName = col?.table && getTableName(col?.table);
62
+ if (!tableName) {
63
+ continue;
64
+ }
65
+ const objectName = path[0];
66
+ if (!(objectName in nullifyMap)) {
67
+ nullifyMap[objectName] = value === null ? tableName : false;
68
+ continue;
69
+ }
70
+ if (nullifyMap[objectName] && nullifyMap[objectName] !== tableName) {
71
+ nullifyMap[objectName] = false;
72
+ }
73
+ continue;
74
+ }
75
+ }
76
+ return result2;
77
+ }, {});
78
+ if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
79
+ for (const [objectName, tableName] of Object.entries(nullifyMap)) {
80
+ if (typeof tableName === "string" && !joinsNotNullableMap[tableName]) {
81
+ result[objectName] = null;
82
+ }
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ function aliasFields(fields, fullJoin = false) {
88
+ return Object.fromEntries(Object.entries(fields).filter(([key]) => key !== "enableRLS").map(([key, value]) => {
89
+ if (fullJoin && is(value, Column)) {
90
+ return [
91
+ key,
92
+ sql`${value}`.as(`${getTableName(value.table)}.${value.name}`)
93
+ ];
94
+ }
95
+ if (fullJoin && is(value, SQL)) {
96
+ const col = value.getSQL().queryChunks.find((chunk) => is(chunk, Column));
97
+ const tableName = col?.table && getTableName(col?.table);
98
+ return [key, value.as(tableName ? `${tableName}.${key}` : key)];
99
+ }
100
+ if (is(value, SQL) || is(value, Column)) {
101
+ return [key, (is(value, SQL) ? value : sql`${value}`).as(key)];
102
+ }
103
+ if (is(value, SQL.Aliased)) {
104
+ return [key, value];
105
+ }
106
+ if (typeof value === "object") {
107
+ const parentKey = key;
108
+ return [
109
+ key,
110
+ Object.fromEntries(Object.entries(value).filter(([childKey]) => childKey !== "enableRLS").map(([childKey, childValue]) => [
111
+ childKey,
112
+ (is(childValue, SQL) ? childValue : sql`${childValue}`).as(`${parentKey}.${childKey}`)
113
+ ]))
114
+ ];
115
+ }
116
+ return [key, value];
117
+ }));
118
+ }
119
+ var tableIdPropSelectionRegex = new RegExp([
120
+ `("(.+)"\\."(.+)")`,
121
+ `(\\s+as\\s+'?(.+?)'?\\.'?(.+?)'?)?`
122
+ ].join(""), "i");
123
+
124
+ // src/session.ts
125
+ import { TransactionRollbackError } from "drizzle-orm/errors";
126
+
127
+ class DuckDBPreparedQuery extends PgPreparedQuery {
128
+ client;
129
+ queryString;
130
+ params;
131
+ logger;
132
+ fields;
133
+ _isResponseInArrayMode;
134
+ customResultMapper;
135
+ static [entityKind] = "DuckDBPreparedQuery";
136
+ constructor(client, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper) {
137
+ super({ sql: queryString, params });
138
+ this.client = client;
139
+ this.queryString = queryString;
140
+ this.params = params;
141
+ this.logger = logger;
142
+ this.fields = fields;
143
+ this._isResponseInArrayMode = _isResponseInArrayMode;
144
+ this.customResultMapper = customResultMapper;
145
+ }
146
+ async execute(placeholderValues = {}) {
147
+ const params = fillPlaceholders(this.params, placeholderValues);
148
+ this.logger.logQuery(this.queryString, params);
149
+ const {
150
+ fields,
151
+ client,
152
+ joinsNotNullableMap,
153
+ customResultMapper,
154
+ queryString
155
+ } = this;
156
+ const rows = await client.all(queryString, ...params) ?? [];
157
+ if (rows.length === 0 || !fields) {
158
+ return rows;
159
+ }
160
+ const rowValues = rows.map((row) => Object.values(row));
161
+ return customResultMapper ? customResultMapper(rowValues) : rowValues.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
162
+ }
163
+ all(placeholderValues = {}) {
164
+ return this.execute(placeholderValues);
165
+ }
166
+ isResponseInArrayMode() {
167
+ return false;
168
+ }
169
+ }
170
+
171
+ class DuckDBSession extends PgSession {
172
+ client;
173
+ schema;
174
+ options;
175
+ static [entityKind] = "DuckDBSession";
176
+ logger;
177
+ constructor(client, dialect, schema, options = {}) {
178
+ super(dialect);
179
+ this.client = client;
180
+ this.schema = schema;
181
+ this.options = options;
182
+ this.logger = options.logger ?? new NoopLogger;
183
+ }
184
+ prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
185
+ return new DuckDBPreparedQuery(this.client, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper);
186
+ }
187
+ async transaction(transaction) {
188
+ const connection = "connect" in this.client ? await this.client.connect() : this.client;
189
+ const session = new DuckDBSession(connection, this.dialect, this.schema, this.options);
190
+ const tx = new DuckDBTransaction(this.dialect, session, this.schema);
191
+ await tx.execute(sql2`BEGIN TRANSACTION;`);
192
+ try {
193
+ const result = await transaction(tx);
194
+ await tx.execute(sql2`commit`);
195
+ return result;
196
+ } catch (error) {
197
+ await tx.execute(sql2`rollback`);
198
+ throw error;
199
+ } finally {
200
+ await connection.close();
201
+ }
202
+ }
203
+ }
204
+
205
+ class DuckDBTransaction extends PgTransaction {
206
+ static [entityKind] = "DuckDBTransaction";
207
+ rollback() {
208
+ throw new TransactionRollbackError;
209
+ }
210
+ getTransactionConfigSQL(config) {
211
+ const chunks = [];
212
+ if (config.isolationLevel) {
213
+ chunks.push(`isolation level ${config.isolationLevel}`);
214
+ }
215
+ if (config.accessMode) {
216
+ chunks.push(config.accessMode);
217
+ }
218
+ if (typeof config.deferrable === "boolean") {
219
+ chunks.push(config.deferrable ? "deferrable" : "not deferrable");
220
+ }
221
+ return sql2.raw(chunks.join(" "));
222
+ }
223
+ setTransaction(config) {
224
+ return this.session.execute(sql2`set transaction ${this.getTransactionConfigSQL(config)}`);
225
+ }
226
+ async transaction(transaction) {
227
+ const savepointName = `sp${this.nestedIndex + 1}`;
228
+ const tx = new DuckDBTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
229
+ await tx.execute(sql2.raw(`savepoint ${savepointName}`));
230
+ try {
231
+ const result = await transaction(tx);
232
+ await tx.execute(sql2.raw(`release savepoint ${savepointName}`));
233
+ return result;
234
+ } catch (err) {
235
+ await tx.execute(sql2.raw(`rollback to savepoint ${savepointName}`));
236
+ throw err;
237
+ }
238
+ }
239
+ }
240
+
241
+ // src/dialect.ts
242
+ import { entityKind as entityKind2, is as is2 } from "drizzle-orm/entity";
243
+ import {
244
+ PgDate,
245
+ PgDateString,
246
+ PgDialect,
247
+ PgJson,
248
+ PgJsonb,
249
+ PgNumeric,
250
+ PgTime,
251
+ PgTimestamp,
252
+ PgTimestampString,
253
+ PgUUID
254
+ } from "drizzle-orm/pg-core";
255
+ import {
256
+ sql as sql3
257
+ } from "drizzle-orm";
258
+
259
+ class DuckDBDialect extends PgDialect {
260
+ static [entityKind2] = "DuckDBPgDialect";
261
+ async migrate(migrations, session, config) {
262
+ const migrationsSchema = config.migrationsSchema ?? "drizzle";
263
+ const migrationsTable = config.migrationsTable ?? "__drizzle_migrations";
264
+ const migrationTableCreate = sql3`
265
+ CREATE TABLE IF NOT EXISTS ${sql3.identifier(migrationsSchema)}.${sql3.identifier(migrationsTable)} (
266
+ id integer PRIMARY KEY default nextval('migrations_pk_seq'),
267
+ hash text NOT NULL,
268
+ created_at bigint
269
+ )
270
+ `;
271
+ await session.execute(sql3.raw("CREATE SEQUENCE IF NOT EXISTS migrations_pk_seq"));
272
+ await session.execute(sql3`CREATE SCHEMA IF NOT EXISTS ${sql3.identifier(migrationsSchema)}`);
273
+ await session.execute(migrationTableCreate);
274
+ const dbMigrations = await session.all(sql3`select id, hash, created_at from ${sql3.identifier(migrationsSchema)}.${sql3.identifier(migrationsTable)} order by created_at desc limit 1`);
275
+ const lastDbMigration = dbMigrations[0];
276
+ await session.transaction(async (tx) => {
277
+ for await (const migration of migrations) {
278
+ if (!lastDbMigration || Number(lastDbMigration.created_at) < migration.folderMillis) {
279
+ for (const stmt of migration.sql) {
280
+ await tx.execute(sql3.raw(stmt));
281
+ }
282
+ await tx.execute(sql3`insert into ${sql3.identifier(migrationsSchema)}.${sql3.identifier(migrationsTable)} ("hash", "created_at") values(${migration.hash}, ${migration.folderMillis})`);
283
+ }
284
+ }
285
+ });
286
+ }
287
+ prepareTyping(encoder) {
288
+ if (is2(encoder, PgJsonb) || is2(encoder, PgJson)) {
289
+ throw new Error("JSON and JSONB types are not supported in DuckDB");
290
+ } else if (is2(encoder, PgNumeric)) {
291
+ return "decimal";
292
+ } else if (is2(encoder, PgTime)) {
293
+ return "time";
294
+ } else if (is2(encoder, PgTimestamp) || is2(encoder, PgTimestampString)) {
295
+ return "timestamp";
296
+ } else if (is2(encoder, PgDate) || is2(encoder, PgDateString)) {
297
+ return "date";
298
+ } else if (is2(encoder, PgUUID)) {
299
+ return "uuid";
300
+ } else {
301
+ return "none";
302
+ }
303
+ }
304
+ }
305
+
306
+ // src/driver.ts
307
+ import {
308
+ PgSelectBase,
309
+ PgSelectBuilder
310
+ } from "drizzle-orm/pg-core/query-builders";
311
+ import { SQL as SQL3 } from "drizzle-orm/sql/sql";
312
+ import { Subquery, ViewBaseConfig } from "drizzle-orm";
313
+ import { PgViewBase } from "drizzle-orm/pg-core/view-base";
314
+ class DuckDBDriver {
315
+ client;
316
+ dialect;
317
+ options;
318
+ static [entityKind3] = "DuckDBDriver";
319
+ constructor(client, dialect, options = {}) {
320
+ this.client = client;
321
+ this.dialect = dialect;
322
+ this.options = options;
323
+ }
324
+ createSession(schema) {
325
+ return new DuckDBSession(this.client, this.dialect, schema, {
326
+ logger: this.options.logger
327
+ });
328
+ }
329
+ }
330
+ function drizzle(client, config = {}) {
331
+ const dialect = new DuckDBDialect;
332
+ const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
333
+ let schema;
334
+ if (config.schema) {
335
+ const tablesConfig = extractTablesRelationalConfig(config.schema, createTableRelationsHelpers);
336
+ schema = {
337
+ fullSchema: config.schema,
338
+ schema: tablesConfig.tables,
339
+ tableNamesMap: tablesConfig.tableNamesMap
340
+ };
341
+ }
342
+ const driver = new DuckDBDriver(client, dialect, { logger });
343
+ const session = driver.createSession(schema);
344
+ return new DuckDBDatabase(dialect, session, schema);
345
+ }
346
+
347
+ class DuckDBDatabase extends PgDatabase {
348
+ dialect;
349
+ session;
350
+ static [entityKind3] = "DuckDBDatabase";
351
+ constructor(dialect, session, schema) {
352
+ super(dialect, session, schema);
353
+ this.dialect = dialect;
354
+ this.session = session;
355
+ }
356
+ select(fields) {
357
+ if (!fields) {
358
+ return new DuckDBSelectBuilder({
359
+ fields: fields ?? undefined,
360
+ session: this.session,
361
+ dialect: this.dialect
362
+ });
363
+ }
364
+ const aliasedFields = aliasFields(fields);
365
+ return new DuckDBSelectBuilder({
366
+ fields: aliasedFields,
367
+ session: this.session,
368
+ dialect: this.dialect
369
+ });
370
+ }
371
+ async transaction(transaction) {
372
+ return await this.session.transaction(transaction);
373
+ }
374
+ }
375
+
376
+ class DuckDBSelectBuilder extends PgSelectBuilder {
377
+ _fields;
378
+ _session;
379
+ _dialect;
380
+ _withList = [];
381
+ _distinct;
382
+ constructor(config) {
383
+ super(config);
384
+ this._fields = config.fields;
385
+ this._session = config.session;
386
+ this._dialect = config.dialect;
387
+ if (config.withList) {
388
+ this._withList = config.withList;
389
+ }
390
+ this._distinct = config.distinct;
391
+ }
392
+ from(source) {
393
+ const isPartialSelect = !!this._fields;
394
+ const src = source;
395
+ let fields;
396
+ if (this._fields) {
397
+ fields = this._fields;
398
+ } else if (is3(src, Subquery)) {
399
+ fields = Object.fromEntries(Object.keys(src._.selectedFields).map((key) => [
400
+ key,
401
+ src[key]
402
+ ]));
403
+ } else if (is3(src, PgViewBase)) {
404
+ fields = src[ViewBaseConfig]?.selectedFields;
405
+ } else if (is3(src, SQL3)) {
406
+ fields = {};
407
+ } else {
408
+ fields = aliasFields(getTableColumns(src), !isPartialSelect);
409
+ }
410
+ return new PgSelectBase({
411
+ table: src,
412
+ fields,
413
+ isPartialSelect,
414
+ session: this._session,
415
+ dialect: this._dialect,
416
+ withList: this._withList,
417
+ distinct: this._distinct
418
+ });
419
+ }
420
+ }
421
+ export {
422
+ drizzle,
423
+ DuckDBTransaction,
424
+ DuckDBSession,
425
+ DuckDBSelectBuilder,
426
+ DuckDBPreparedQuery,
427
+ DuckDBDriver,
428
+ DuckDBDatabase
429
+ };
package/package.json CHANGED
@@ -3,23 +3,25 @@
3
3
  "module": "index.ts",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
- "version": "0.0.3",
6
+ "version": "0.0.4",
7
7
  "description": "A drizzle ORM client for use with DuckDB. Based on drizzle's Postgres client.",
8
8
  "type": "module",
9
9
  "scripts": {
10
- "build": "bun build --target=node ./src/index.ts --outfile=dist/index.js && bun run build:declarations",
10
+ "build": "bun build --target=node ./src/index.ts --outfile=./dist/index.mjs --packages=external && bun run build:declarations",
11
11
  "build:declarations": "tsc --emitDeclarationOnly --project tsconfig.types.json",
12
12
  "test": "vitest",
13
13
  "t": "vitest --watch --ui"
14
14
  },
15
- "dependencies": {
16
- "drizzle-orm": "0.40.0",
17
- "duckdb-async": "^1.2.0"
15
+ "peerDependencies": {
16
+ "drizzle-orm": "^0.40.0",
17
+ "duckdb-async": "^1.0.0"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/bun": "^1.2.5",
21
21
  "@types/uuid": "^10.0.0",
22
22
  "@vitest/ui": "^1.6.0",
23
+ "drizzle-orm": "0.40.0",
24
+ "duckdb-async": "^1.2.0",
23
25
  "prettier": "^3.5.3",
24
26
  "typescript": "^5.8.2",
25
27
  "uuid": "^10.0.0",
@@ -41,7 +43,7 @@
41
43
  "homepage": "https://github.com/duckdbfan/drizzle-duckdb#readme",
42
44
  "files": [
43
45
  "src/*.ts",
44
- "dist/*.js",
46
+ "dist/*.mjs",
45
47
  "dist/*.d.ts"
46
48
  ]
47
49
  }