ts-prorm-orm 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.
Files changed (310) hide show
  1. package/CHANGELOG.md +1111 -0
  2. package/LICENSE +21 -0
  3. package/README.md +573 -0
  4. package/dist/audit/history-query.js +180 -0
  5. package/dist/audit/index.js +23 -0
  6. package/dist/audit/logger.js +236 -0
  7. package/dist/cache/cache-manager.js +84 -0
  8. package/dist/cache/index.js +16 -0
  9. package/dist/cache/redis-cluster-cache.js +557 -0
  10. package/dist/cli.js +2200 -0
  11. package/dist/compliance/audit-trail.js +68 -0
  12. package/dist/compliance/backup-verification.js +685 -0
  13. package/dist/compliance/breach-detector.js +505 -0
  14. package/dist/compliance/consent-record.js +227 -0
  15. package/dist/compliance/consent-versioning.js +331 -0
  16. package/dist/compliance/cross-border-log.js +530 -0
  17. package/dist/compliance/data-classifier.js +258 -0
  18. package/dist/compliance/data-lineage.js +565 -0
  19. package/dist/compliance/data-masker.js +303 -0
  20. package/dist/compliance/data-portability.js +265 -0
  21. package/dist/compliance/data-retention.js +195 -0
  22. package/dist/compliance/dsar-workflow.js +342 -0
  23. package/dist/compliance/field-encryption.js +223 -0
  24. package/dist/compliance/immutable-record.js +148 -0
  25. package/dist/compliance/index.js +260 -0
  26. package/dist/compliance/privacy-impact-assessment.js +364 -0
  27. package/dist/compliance/pseudonymization.js +269 -0
  28. package/dist/compliance/query-firewall.js +1182 -0
  29. package/dist/compliance/rate-limiter.js +580 -0
  30. package/dist/compliance/right-to-erasure.js +117 -0
  31. package/dist/compliance/row-level-security.js +246 -0
  32. package/dist/compliance/security-decorator.js +574 -0
  33. package/dist/compliance/security-monitor.js +525 -0
  34. package/dist/compliance/sensitive-data-discovery.js +476 -0
  35. package/dist/compliance/session-isolation.js +472 -0
  36. package/dist/compliance/tls-enforcer.js +108 -0
  37. package/dist/compliance/worm-storage.js +712 -0
  38. package/dist/connection-manager.js +198 -0
  39. package/dist/connection-pool.js +519 -0
  40. package/dist/decorators/audit.js +136 -0
  41. package/dist/decorators/belongs-to-many.js +115 -0
  42. package/dist/decorators/belongs-to.js +115 -0
  43. package/dist/decorators/check.js +435 -0
  44. package/dist/decorators/collate.js +329 -0
  45. package/dist/decorators/comment.js +205 -0
  46. package/dist/decorators/database-settings.js +236 -0
  47. package/dist/decorators/default.js +244 -0
  48. package/dist/decorators/encryption.js +235 -0
  49. package/dist/decorators/engine.js +97 -0
  50. package/dist/decorators/fk-constraints.js +594 -0
  51. package/dist/decorators/foreign-table.js +136 -0
  52. package/dist/decorators/generated.js +274 -0
  53. package/dist/decorators/has-many.js +127 -0
  54. package/dist/decorators/has-one.js +116 -0
  55. package/dist/decorators/hstore.js +129 -0
  56. package/dist/decorators/index.js +355 -0
  57. package/dist/decorators/json-column.js +83 -0
  58. package/dist/decorators/jsonb.js +101 -0
  59. package/dist/decorators/orm-decorators.js +425 -0
  60. package/dist/decorators/permissions.js +294 -0
  61. package/dist/decorators/procedure.js +210 -0
  62. package/dist/decorators/query-options.js +556 -0
  63. package/dist/decorators/range.js +167 -0
  64. package/dist/decorators/set-column.js +83 -0
  65. package/dist/decorators/spatial.js +114 -0
  66. package/dist/decorators/storage.js +580 -0
  67. package/dist/decorators/timezone.js +512 -0
  68. package/dist/decorators/trigger.js +90 -0
  69. package/dist/decorators/uuid.js +135 -0
  70. package/dist/decorators/view.js +258 -0
  71. package/dist/diagrams/chen-diagram.js +354 -0
  72. package/dist/diagrams/class-diagram.js +384 -0
  73. package/dist/diagrams/dependency-diagram.js +432 -0
  74. package/dist/diagrams/er-diagram.js +605 -0
  75. package/dist/diagrams/flow-diagram.js +394 -0
  76. package/dist/diagrams/gantt-diagram.js +411 -0
  77. package/dist/diagrams/index-diagram.js +353 -0
  78. package/dist/diagrams/index.js +184 -0
  79. package/dist/diagrams/migration-diagram.js +316 -0
  80. package/dist/diagrams/model-diagram.js +616 -0
  81. package/dist/diagrams/package-diagram.js +376 -0
  82. package/dist/diagrams/palette.js +85 -0
  83. package/dist/diagrams/relational-diagram.js +455 -0
  84. package/dist/diagrams/schemadoc-diagram.js +309 -0
  85. package/dist/diagrams/sequence-diagram.js +307 -0
  86. package/dist/diagrams/state-diagram.js +344 -0
  87. package/dist/diagrams/svg-dom.js +111 -0
  88. package/dist/diagrams/tree-diagram.js +250 -0
  89. package/dist/dialects/clickhouse/index.js +1742 -0
  90. package/dist/dialects/cockroachdb/index.js +4677 -0
  91. package/dist/dialects/cratedb/index.js +441 -0
  92. package/dist/dialects/databricks/index.js +517 -0
  93. package/dist/dialects/db2/index.js +2225 -0
  94. package/dist/dialects/dialect.js +720 -0
  95. package/dist/dialects/duckdb/index.js +2014 -0
  96. package/dist/dialects/exasol/index.js +357 -0
  97. package/dist/dialects/firebird/index.js +495 -0
  98. package/dist/dialects/greenplum/index.js +457 -0
  99. package/dist/dialects/hana/index.js +1741 -0
  100. package/dist/dialects/mariadb/index.js +3590 -0
  101. package/dist/dialects/mssql/index.js +2296 -0
  102. package/dist/dialects/mysql/index.js +4058 -0
  103. package/dist/dialects/oracle/index.js +2999 -0
  104. package/dist/dialects/postgres/index.js +5319 -0
  105. package/dist/dialects/query-stream-helper.js +152 -0
  106. package/dist/dialects/questdb/index.js +433 -0
  107. package/dist/dialects/redshift/index.js +2277 -0
  108. package/dist/dialects/singlestore/index.js +354 -0
  109. package/dist/dialects/snowflake/index.js +2082 -0
  110. package/dist/dialects/spanner/index.js +1768 -0
  111. package/dist/dialects/sqlite/index.js +3382 -0
  112. package/dist/dialects/tidb/index.js +377 -0
  113. package/dist/dialects/timescaledb/index.js +164 -0
  114. package/dist/dialects/trino/index.js +420 -0
  115. package/dist/dialects/turso/index.js +227 -0
  116. package/dist/dialects/vertica/index.js +317 -0
  117. package/dist/dialects/yugabytedb/index.js +424 -0
  118. package/dist/errors/index.js +472 -0
  119. package/dist/errors/utils.js +340 -0
  120. package/dist/errors.js +21 -0
  121. package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
  122. package/dist/extensions/catalog/mssql-features.js +147 -0
  123. package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
  124. package/dist/extensions/catalog/oracle-db2-features.js +196 -0
  125. package/dist/extensions/catalog/postgres-extensions.js +516 -0
  126. package/dist/extensions/index.js +71 -0
  127. package/dist/extensions/types.js +13 -0
  128. package/dist/foreign-data.js +173 -0
  129. package/dist/hooks/hooks-manager.js +350 -0
  130. package/dist/hooks/index.js +37 -0
  131. package/dist/index.js +463 -0
  132. package/dist/logging.js +335 -0
  133. package/dist/migrations/index.js +40 -0
  134. package/dist/migrations/migration.js +196 -0
  135. package/dist/migrations/migrator.js +411 -0
  136. package/dist/migrations/prormmigration.js +275 -0
  137. package/dist/migrations/query-interface.js +435 -0
  138. package/dist/migrations/seeder.js +353 -0
  139. package/dist/models/associations.js +852 -0
  140. package/dist/models/constraints.js +288 -0
  141. package/dist/models/data-types.js +2518 -0
  142. package/dist/models/decorators.js +445 -0
  143. package/dist/models/index.js +33 -0
  144. package/dist/models/indexes.js +531 -0
  145. package/dist/models/methods.js +382 -0
  146. package/dist/models/model-manager.js +103 -0
  147. package/dist/models/model.js +5349 -0
  148. package/dist/models/operators.js +67 -0
  149. package/dist/models/scopes.js +189 -0
  150. package/dist/models/typescript-types.js +26 -0
  151. package/dist/nosql/aerospike/index.js +205 -0
  152. package/dist/nosql/allegrograph/index.js +169 -0
  153. package/dist/nosql/arangodb/index.js +364 -0
  154. package/dist/nosql/azure-blob/index.js +206 -0
  155. package/dist/nosql/beanstalkd/index.js +231 -0
  156. package/dist/nosql/beequeue/index.js +210 -0
  157. package/dist/nosql/bigchaindb/index.js +195 -0
  158. package/dist/nosql/bigtable/index.js +224 -0
  159. package/dist/nosql/blazegraph/index.js +173 -0
  160. package/dist/nosql/bullmq/index.js +191 -0
  161. package/dist/nosql/cassandra/index.js +174 -0
  162. package/dist/nosql/chroma/index.js +190 -0
  163. package/dist/nosql/cloudflare-kv/index.js +220 -0
  164. package/dist/nosql/coherence/index.js +200 -0
  165. package/dist/nosql/cosmosdb/index.js +157 -0
  166. package/dist/nosql/couchbase/index.js +213 -0
  167. package/dist/nosql/dax/index.js +212 -0
  168. package/dist/nosql/deno-kv/index.js +206 -0
  169. package/dist/nosql/dgraph/index.js +171 -0
  170. package/dist/nosql/doris/index.js +169 -0
  171. package/dist/nosql/druid/index.js +162 -0
  172. package/dist/nosql/dynamodb/index.js +937 -0
  173. package/dist/nosql/elasticsearch/index.js +377 -0
  174. package/dist/nosql/etcd/index.js +502 -0
  175. package/dist/nosql/eventhubs/index.js +213 -0
  176. package/dist/nosql/eventstore/index.js +254 -0
  177. package/dist/nosql/faunadb/index.js +188 -0
  178. package/dist/nosql/firestore/index.js +177 -0
  179. package/dist/nosql/fluree/index.js +148 -0
  180. package/dist/nosql/fuseki/index.js +170 -0
  181. package/dist/nosql/gcs/index.js +172 -0
  182. package/dist/nosql/gearman/index.js +160 -0
  183. package/dist/nosql/geode/index.js +196 -0
  184. package/dist/nosql/graphdb/index.js +169 -0
  185. package/dist/nosql/graylog/index.js +188 -0
  186. package/dist/nosql/gridgain/index.js +171 -0
  187. package/dist/nosql/hazelcast/index.js +162 -0
  188. package/dist/nosql/hbase/index.js +230 -0
  189. package/dist/nosql/ignite/index.js +173 -0
  190. package/dist/nosql/immudb/index.js +184 -0
  191. package/dist/nosql/index.js +232 -0
  192. package/dist/nosql/infinispan/index.js +200 -0
  193. package/dist/nosql/influxdb/index.js +0 -0
  194. package/dist/nosql/kafka/index.js +234 -0
  195. package/dist/nosql/keyspaces/index.js +189 -0
  196. package/dist/nosql/kinesis/index.js +253 -0
  197. package/dist/nosql/leveldb/index.js +153 -0
  198. package/dist/nosql/lmdb/index.js +160 -0
  199. package/dist/nosql/loki/index.js +201 -0
  200. package/dist/nosql/marklogic/index.js +204 -0
  201. package/dist/nosql/materialize/index.js +145 -0
  202. package/dist/nosql/meilisearch/index.js +154 -0
  203. package/dist/nosql/memcached/index.js +223 -0
  204. package/dist/nosql/milvus/index.js +410 -0
  205. package/dist/nosql/minio/index.js +265 -0
  206. package/dist/nosql/momento/index.js +179 -0
  207. package/dist/nosql/mongodb/index.js +461 -0
  208. package/dist/nosql/nats/index.js +247 -0
  209. package/dist/nosql/nedb/index.js +164 -0
  210. package/dist/nosql/neo4j/index.js +450 -0
  211. package/dist/nosql/neptune/index.js +470 -0
  212. package/dist/nosql/nsq/index.js +200 -0
  213. package/dist/nosql/opensearch/index.js +186 -0
  214. package/dist/nosql/orientdb/index.js +175 -0
  215. package/dist/nosql/papertrail/index.js +200 -0
  216. package/dist/nosql/pinecone/index.js +0 -0
  217. package/dist/nosql/pinot/index.js +133 -0
  218. package/dist/nosql/pouchdb/index.js +172 -0
  219. package/dist/nosql/prometheus/index.js +174 -0
  220. package/dist/nosql/provendb/index.js +147 -0
  221. package/dist/nosql/pubsub/index.js +187 -0
  222. package/dist/nosql/pulsar/index.js +232 -0
  223. package/dist/nosql/qdrant/index.js +295 -0
  224. package/dist/nosql/qldb/index.js +192 -0
  225. package/dist/nosql/r2/index.js +281 -0
  226. package/dist/nosql/rabbitmq/index.js +237 -0
  227. package/dist/nosql/ravendb/index.js +175 -0
  228. package/dist/nosql/redis/index.js +607 -0
  229. package/dist/nosql/redpanda/index.js +237 -0
  230. package/dist/nosql/resque/index.js +203 -0
  231. package/dist/nosql/rethinkdb/index.js +232 -0
  232. package/dist/nosql/rocksdb/index.js +152 -0
  233. package/dist/nosql/rockset/index.js +126 -0
  234. package/dist/nosql/s3/index.js +298 -0
  235. package/dist/nosql/scylladb/index.js +178 -0
  236. package/dist/nosql/signoz/index.js +227 -0
  237. package/dist/nosql/sns/index.js +201 -0
  238. package/dist/nosql/solr/index.js +231 -0
  239. package/dist/nosql/splunk/index.js +227 -0
  240. package/dist/nosql/sqs/index.js +246 -0
  241. package/dist/nosql/stardog/index.js +169 -0
  242. package/dist/nosql/starrocks/index.js +170 -0
  243. package/dist/nosql/store.js +2 -0
  244. package/dist/nosql/sumologic/index.js +213 -0
  245. package/dist/nosql/surrealdb/index.js +179 -0
  246. package/dist/nosql/terminusdb/index.js +151 -0
  247. package/dist/nosql/tigergraph/index.js +469 -0
  248. package/dist/nosql/typesense/index.js +148 -0
  249. package/dist/nosql/unqlite/index.js +157 -0
  250. package/dist/nosql/upstash/index.js +167 -0
  251. package/dist/nosql/vercel-kv/index.js +166 -0
  252. package/dist/nosql/victoriametrics/index.js +213 -0
  253. package/dist/nosql/virtuoso/index.js +169 -0
  254. package/dist/nosql/weaviate/index.js +276 -0
  255. package/dist/operators/index.js +108 -0
  256. package/dist/operators.js +2690 -0
  257. package/dist/prisma-migrate/index.js +38 -0
  258. package/dist/prisma-migrate/migration-generator.js +125 -0
  259. package/dist/prisma-migrate/model-generator.js +172 -0
  260. package/dist/prisma-migrate/relations.js +100 -0
  261. package/dist/prisma-migrate/schema-parser.js +167 -0
  262. package/dist/prisma-migrate/type-mapper.js +40 -0
  263. package/dist/prorm.js +6832 -0
  264. package/dist/query-builders/cte-builder.js +80 -0
  265. package/dist/query-builders/functions/aggregate.js +390 -0
  266. package/dist/query-builders/functions/conditional.js +503 -0
  267. package/dist/query-builders/functions/datetime.js +695 -0
  268. package/dist/query-builders/functions/fulltext.js +439 -0
  269. package/dist/query-builders/functions/index.js +93 -0
  270. package/dist/query-builders/functions/json.js +427 -0
  271. package/dist/query-builders/functions/math.js +399 -0
  272. package/dist/query-builders/functions/string.js +518 -0
  273. package/dist/query-builders/functions/window.js +328 -0
  274. package/dist/query-builders/include-builder.js +323 -0
  275. package/dist/query-builders/index-expression-builder.js +242 -0
  276. package/dist/query-builders/index.js +161 -0
  277. package/dist/query-builders/insert-builder.js +164 -0
  278. package/dist/query-builders/model-helpers.js +38 -0
  279. package/dist/query-builders/order-limit-builder.js +239 -0
  280. package/dist/query-builders/sql-compiler.js +2040 -0
  281. package/dist/query-builders/subquery-builder.js +152 -0
  282. package/dist/query-builders/update-builder.js +182 -0
  283. package/dist/query-builders/view-builder.js +218 -0
  284. package/dist/query-builders/where-builder.js +1532 -0
  285. package/dist/query-interface.js +622 -0
  286. package/dist/query-optimizers/batch-optimizer.js +426 -0
  287. package/dist/query-optimizers/explain-plans.js +415 -0
  288. package/dist/query-optimizers/index.js +51 -0
  289. package/dist/query-optimizers/prepared-statement-cache.js +423 -0
  290. package/dist/query-optimizers/query-hints.js +438 -0
  291. package/dist/query-optimizers/query-optimizer.js +278 -0
  292. package/dist/query-optimizers/slow-query-logger.js +271 -0
  293. package/dist/replica-manager.js +507 -0
  294. package/dist/schema/index.js +15 -0
  295. package/dist/schema/migration-generator.js +374 -0
  296. package/dist/schema/schema-differ.js +549 -0
  297. package/dist/schema/types.js +6 -0
  298. package/dist/sql-constants.js +300 -0
  299. package/dist/sqlite-advanced.js +1047 -0
  300. package/dist/streams/index.js +12 -0
  301. package/dist/streams/transforms.js +178 -0
  302. package/dist/transaction.js +456 -0
  303. package/dist/types/index.js +180 -0
  304. package/dist/user-management.js +127 -0
  305. package/dist/utils/date.js +300 -0
  306. package/dist/utils/index.js +1079 -0
  307. package/dist/utils/string.js +161 -0
  308. package/dist/validators/index.js +19 -0
  309. package/dist/validators/validator.js +911 -0
  310. package/package.json +190 -0
@@ -0,0 +1,2014 @@
1
+ "use strict";
2
+ /**
3
+ * DuckDB dialect implementation
4
+ *
5
+ * DuckDB is an embedded, in-process OLAP database (similar in deployment
6
+ * model to SQLite - a single file or `:memory:`, no server/host/port/auth)
7
+ * but with a genuinely modern analytical SQL engine: real window functions,
8
+ * recursive CTEs, rich composite types (LIST/STRUCT/MAP), first-class
9
+ * Parquet/CSV/JSON table functions, native sequences, and a real extension
10
+ * system (INSTALL/LOAD).
11
+ *
12
+ * The `duckdb` npm driver's public API (`duckdb/lib/duckdb.d.ts`) is
13
+ * callback-based (`new duckdb.Database(path, cb)`, `db.all(sql, ...args, cb)`,
14
+ * `db.run(sql, ...args, cb)`). All of that is wrapped in Promises here.
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.DuckDBTransaction = exports.DuckDBDialect = void 0;
51
+ exports.createDuckDBDialect = createDuckDBDialect;
52
+ const duckdb = __importStar(require("duckdb"));
53
+ const query_stream_helper_1 = require("../query-stream-helper");
54
+ const prorm_1 = require("../../prorm");
55
+ const operators_1 = require("../../operators");
56
+ function promisifyAll(target, sql, params) {
57
+ return new Promise((resolve, reject) => {
58
+ const cb = (err, rows) => {
59
+ if (err)
60
+ reject(err);
61
+ else
62
+ resolve(rows);
63
+ };
64
+ if (params.length > 0) {
65
+ target.all(sql, ...params, cb);
66
+ }
67
+ else {
68
+ target.all(sql, cb);
69
+ }
70
+ });
71
+ }
72
+ function promisifyRun(target, sql, params) {
73
+ return new Promise((resolve, reject) => {
74
+ const cb = (err) => {
75
+ if (err)
76
+ reject(err);
77
+ else
78
+ resolve();
79
+ };
80
+ if (params.length > 0) {
81
+ target.run(sql, ...params, cb);
82
+ }
83
+ else {
84
+ target.run(sql, cb);
85
+ }
86
+ });
87
+ }
88
+ function promisifyExec(target, sql) {
89
+ return new Promise((resolve, reject) => {
90
+ target.exec(sql, (err) => {
91
+ if (err)
92
+ reject(err);
93
+ else
94
+ resolve();
95
+ });
96
+ });
97
+ }
98
+ /**
99
+ * DuckDB dialect class implementing the shared `Dialect` interface.
100
+ */
101
+ class DuckDBDialect {
102
+ constructor(config) {
103
+ this.name = 'duckdb';
104
+ this.library = 'duckdb';
105
+ this.db = null;
106
+ this.connection = null;
107
+ this._isConnected = false;
108
+ this.transactionDepth = 0;
109
+ this.config = {
110
+ storage: ':memory:',
111
+ ...config,
112
+ };
113
+ }
114
+ /**
115
+ * Connect to the DuckDB database (embedded - opens a local file or :memory:).
116
+ * host/port/username/password (if present on the config for API parity with
117
+ * networked dialects) are intentionally ignored - DuckDB is single-process.
118
+ */
119
+ async connect() {
120
+ try {
121
+ this.db = await new Promise((resolve, reject) => {
122
+ let accessMode = this.config.config;
123
+ if (this.config.readonly) {
124
+ if (accessMode && typeof accessMode === 'object') {
125
+ // Merge readonly into the existing config object rather than letting
126
+ // `config` silently override `readonly`.
127
+ accessMode = { ...accessMode, access_mode: 'READ_ONLY' };
128
+ }
129
+ else if (!accessMode) {
130
+ accessMode = duckdb.OPEN_READONLY;
131
+ }
132
+ }
133
+ const cb = (err) => {
134
+ // duckdb.Database's constructor callback fires asynchronously once the
135
+ // native handle has actually been opened (or failed to open). Only
136
+ // resolve/reject here - resolving eagerly right after construction
137
+ // would let open failures (corrupted file, permission denied,
138
+ // readonly-open against a locked writer) race past a settled promise
139
+ // and surface only later as a confusing error on the first query.
140
+ if (err)
141
+ reject(err);
142
+ else
143
+ resolve(database);
144
+ };
145
+ const database = accessMode
146
+ ? new duckdb.Database(this.config.storage, accessMode, cb)
147
+ : new duckdb.Database(this.config.storage, cb);
148
+ });
149
+ this.connection = this.db.connect();
150
+ this._isConnected = true;
151
+ }
152
+ catch (error) {
153
+ throw new Error(`Failed to connect to DuckDB database: ${error}`);
154
+ }
155
+ }
156
+ async disconnect() {
157
+ if (this.connection) {
158
+ await new Promise((resolve, reject) => {
159
+ this.connection.close((err) => (err ? reject(err) : resolve()));
160
+ }).catch(() => undefined);
161
+ this.connection = null;
162
+ }
163
+ if (this.db) {
164
+ await new Promise((resolve, reject) => {
165
+ this.db.close((err) => (err ? reject(err) : resolve()));
166
+ }).catch(() => undefined);
167
+ this.db = null;
168
+ }
169
+ this._isConnected = false;
170
+ }
171
+ getConnection() {
172
+ return this.connection;
173
+ }
174
+ isConnected() {
175
+ return this._isConnected && this.connection !== null;
176
+ }
177
+ /**
178
+ * Stream query results by paging through `sql` via repeated
179
+ * dialect-appropriate LIMIT/OFFSET queries (see
180
+ * `createPaginatedQueryStream()` in `src/dialects/query-stream-helper.ts`)
181
+ * instead of loading the whole result set into memory at once.
182
+ * @param sql - The SELECT statement to stream
183
+ * @param options - Streaming options (batch size, backpressure watermark, model mapping)
184
+ */
185
+ queryStream(sql, options) {
186
+ return (0, query_stream_helper_1.createPaginatedQueryStream)(this, sql, options);
187
+ }
188
+ /**
189
+ * Execute a raw SQL query with retry support.
190
+ */
191
+ async query(sql, options) {
192
+ const defaultQueryRetryOptions = {
193
+ max: 0,
194
+ timeout: 1000,
195
+ match: ['is closed', 'connection', 'write-write conflict', 'database is locked'],
196
+ backoff: false,
197
+ backoffMultiplier: 2,
198
+ backoffMax: 10000,
199
+ };
200
+ const retryOptions = options?.retry;
201
+ const effectiveRetry = retryOptions
202
+ ? {
203
+ ...defaultQueryRetryOptions,
204
+ ...retryOptions,
205
+ match: retryOptions.match || defaultQueryRetryOptions.match,
206
+ }
207
+ : null;
208
+ const executeQuery = async () => {
209
+ if (!this.connection) {
210
+ throw new Error('Not connected to database');
211
+ }
212
+ const sqlUpper = sql.trim().toUpperCase();
213
+ const isSelect = sqlUpper.startsWith('SELECT') ||
214
+ sqlUpper.startsWith('WITH') ||
215
+ sqlUpper.startsWith('PRAGMA') ||
216
+ sqlUpper.startsWith('SHOW') ||
217
+ sqlUpper.startsWith('DESCRIBE') ||
218
+ sqlUpper.startsWith('EXPLAIN');
219
+ const params = Array.isArray(options?.replacements)
220
+ ? options.replacements
221
+ : options?.replacements
222
+ ? Object.values(options.replacements)
223
+ : Array.isArray(options?.bind)
224
+ ? options.bind
225
+ : options?.bind
226
+ ? Object.values(options.bind)
227
+ : [];
228
+ try {
229
+ if (isSelect || /\bRETURNING\b/i.test(sql)) {
230
+ const rows = await promisifyAll(this.connection, sql, params);
231
+ return {
232
+ rows: rows,
233
+ rowCount: rows.length,
234
+ fields: [],
235
+ };
236
+ }
237
+ else {
238
+ await promisifyRun(this.connection, sql, params);
239
+ return {
240
+ rows: [],
241
+ rowCount: 0,
242
+ fields: [],
243
+ };
244
+ }
245
+ }
246
+ catch (error) {
247
+ throw new Error(`Query error: ${error}`);
248
+ }
249
+ };
250
+ if (effectiveRetry && effectiveRetry.max > 0) {
251
+ return this.executeWithRetry(executeQuery, effectiveRetry);
252
+ }
253
+ return executeQuery();
254
+ }
255
+ async executeWithRetry(fn, retryOptions) {
256
+ let lastError;
257
+ for (let attempt = 0; attempt <= retryOptions.max; attempt++) {
258
+ try {
259
+ return await fn();
260
+ }
261
+ catch (error) {
262
+ lastError = error;
263
+ const errorMessage = lastError.message.toLowerCase();
264
+ const shouldRetry = retryOptions.match.some((pattern) => errorMessage.includes(pattern.toLowerCase()));
265
+ if (!shouldRetry || attempt === retryOptions.max) {
266
+ throw lastError;
267
+ }
268
+ await new Promise((resolve) => setTimeout(resolve, retryOptions.timeout));
269
+ }
270
+ }
271
+ throw lastError;
272
+ }
273
+ /**
274
+ * Replace placeholders in SQL with actual values.
275
+ */
276
+ replaceReplacements(sql, replacements) {
277
+ if (!replacements) {
278
+ return sql;
279
+ }
280
+ if (Array.isArray(replacements)) {
281
+ let result = sql;
282
+ for (const value of replacements) {
283
+ result = result.replace(/\?/, () => this.escape(value));
284
+ }
285
+ return result;
286
+ }
287
+ let result = sql;
288
+ for (const [key, value] of Object.entries(replacements)) {
289
+ if (Array.isArray(value)) {
290
+ const escapedValues = value.map((v) => this.escape(v)).join(', ');
291
+ const regex = new RegExp(`:${key}(?![\\w])`, 'g');
292
+ result = result.replace(regex, escapedValues);
293
+ }
294
+ else if (value === null) {
295
+ const regex = new RegExp(`:${key}(?![\\w])`, 'g');
296
+ result = result.replace(regex, 'NULL');
297
+ }
298
+ else {
299
+ const regex = new RegExp(`:${key}(?![\\w])`, 'g');
300
+ result = result.replace(regex, () => this.escape(value));
301
+ }
302
+ }
303
+ return result;
304
+ }
305
+ /**
306
+ * Escape a value for use in a query. DuckDB uses standard SQL literals -
307
+ * TRUE/FALSE for booleans (not 0/1), single-quoted strings, and a
308
+ * `'\x..'::BLOB` cast for binary data.
309
+ */
310
+ escape(value) {
311
+ if (value === null || value === undefined) {
312
+ return 'NULL';
313
+ }
314
+ if (value instanceof prorm_1.Literal) {
315
+ return value.val;
316
+ }
317
+ if (typeof value === 'string') {
318
+ return `'${this.escapeString(value)}'`;
319
+ }
320
+ if (typeof value === 'number') {
321
+ return String(value);
322
+ }
323
+ if (typeof value === 'bigint') {
324
+ return value.toString();
325
+ }
326
+ if (typeof value === 'boolean') {
327
+ return value ? 'TRUE' : 'FALSE';
328
+ }
329
+ if (value instanceof Date) {
330
+ return `TIMESTAMP '${value.toISOString().replace('T', ' ').replace('Z', '')}'`;
331
+ }
332
+ if (Buffer.isBuffer(value)) {
333
+ const hex = value.toString('hex').match(/../g)?.map((b) => `\\x${b}`).join('') ?? '';
334
+ return `'${hex}'::BLOB`;
335
+ }
336
+ if (Array.isArray(value)) {
337
+ return `[${value.map((v) => this.escape(v)).join(', ')}]`;
338
+ }
339
+ return `'${this.escapeString(JSON.stringify(value))}'`;
340
+ }
341
+ escapeString(str) {
342
+ return str.replace(/'/g, "''");
343
+ }
344
+ escapeId(identifier) {
345
+ const id = String(identifier ?? '');
346
+ return `"${id.replace(/"/g, '""')}"`;
347
+ }
348
+ quoteIdentifier(identifier) {
349
+ return this.escapeId(identifier);
350
+ }
351
+ quoteTable(tableName, schema) {
352
+ if (schema) {
353
+ return `${this.escapeId(schema)}.${this.escapeId(tableName)}`;
354
+ }
355
+ return this.escapeId(tableName);
356
+ }
357
+ async getDatabaseVersion() {
358
+ const result = await this.query('SELECT version() AS version');
359
+ return String(result.rows[0]?.version ?? '');
360
+ }
361
+ createDatabaseSQL(options) {
362
+ // DuckDB has no CREATE DATABASE statement of its own (a "database" is a
363
+ // file, opened via ATTACH). Document this via attachDatabase() instead.
364
+ return `ATTACH '${options.name}' AS ${this.escapeId(options.name)}`;
365
+ }
366
+ dropDatabaseSQL(name) {
367
+ return `DETACH ${this.escapeId(name)}`;
368
+ }
369
+ /**
370
+ * Attach another DuckDB (or SQLite/Postgres, via the respective DuckDB
371
+ * extension) database file under `alias`, making it queryable as
372
+ * `alias.table`.
373
+ */
374
+ async attachDatabase(path, alias, options) {
375
+ if (!alias || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(alias)) {
376
+ throw new Error(`Invalid database alias: ${alias}`);
377
+ }
378
+ const opts = [];
379
+ if (options?.readOnly)
380
+ opts.push('READ_ONLY');
381
+ if (options?.type)
382
+ opts.push(`TYPE ${options.type.toUpperCase()}`);
383
+ const optsSql = opts.length > 0 ? ` (${opts.join(', ')})` : '';
384
+ await this.query(`ATTACH '${path.replace(/'/g, "''")}' AS ${this.escapeId(alias)}${optsSql}`);
385
+ }
386
+ async detachDatabase(alias) {
387
+ if (!alias || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(alias)) {
388
+ throw new Error(`Invalid database alias: ${alias}`);
389
+ }
390
+ await this.query(`DETACH ${this.escapeId(alias)}`);
391
+ }
392
+ createSavepointSQL(name) {
393
+ const savepointName = name || `sp_${Date.now()}`;
394
+ return `SAVEPOINT ${savepointName}`;
395
+ }
396
+ releaseSavepointSQL(name) {
397
+ return `RELEASE SAVEPOINT ${name}`;
398
+ }
399
+ rollbackToSavepointSQL(name) {
400
+ return `ROLLBACK TO SAVEPOINT ${name}`;
401
+ }
402
+ // ==================== Schemas ====================
403
+ async createSchema(schema) {
404
+ await this.query(`CREATE SCHEMA IF NOT EXISTS ${this.escapeId(schema)}`);
405
+ }
406
+ async dropSchema(schema, options) {
407
+ let sql = 'DROP SCHEMA';
408
+ if (options?.ifExists)
409
+ sql += ' IF EXISTS';
410
+ sql += ` ${this.escapeId(schema)}`;
411
+ if (options?.cascade)
412
+ sql += ' CASCADE';
413
+ await this.query(sql);
414
+ }
415
+ async showAllSchemas() {
416
+ const result = await this.query(`SELECT schema_name FROM information_schema.schemata ORDER BY schema_name`);
417
+ return result.rows.map((row) => row.schema_name);
418
+ }
419
+ async listSchemas() {
420
+ return this.showAllSchemas();
421
+ }
422
+ // ==================== Extensions (real DuckDB feature) ====================
423
+ // Unlike SQLite/Postgres extensions, DuckDB's extension system (INSTALL/LOAD)
424
+ // is a core, well-known part of the engine and is implemented for real here
425
+ // rather than being stubbed out - it's how `parquet`, `httpfs`, `json`,
426
+ // `fts`, and `spatial` support are enabled.
427
+ async createExtension(extensionName, options) {
428
+ const name = this.escapeId(extensionName).replace(/"/g, '');
429
+ await this.query(`INSTALL ${name}${options?.version ? ` VERSION '${options.version}'` : ''}`);
430
+ await this.query(`LOAD ${name}`);
431
+ }
432
+ async dropExtension(_extensionName, _options) {
433
+ // DuckDB has no UNINSTALL/DROP EXTENSION statement - installed extensions
434
+ // simply live in the local extension directory. There is nothing to run;
435
+ // to stop using one, just don't LOAD it in future connections.
436
+ throw new Error('DuckDB does not support dropping/uninstalling extensions once installed. ' +
437
+ 'Simply avoid LOADing the extension in future connections.');
438
+ }
439
+ async getExtensions() {
440
+ const result = await this.query(`SELECT extension_name, installed, loaded, description, extension_version FROM duckdb_extensions()`);
441
+ return result.rows.map((row) => ({
442
+ name: row.extension_name,
443
+ defaultVersion: row.extension_version ?? null,
444
+ installedVersion: row.installed ? (row.extension_version ?? '') : null,
445
+ comment: row.description ?? null,
446
+ }));
447
+ }
448
+ async hasExtension(extensionName) {
449
+ const result = await this.query(`SELECT 1 FROM duckdb_extensions() WHERE extension_name = ? AND loaded = true`, { replacements: [extensionName] });
450
+ return result.rows.length > 0;
451
+ }
452
+ // ==================== Tables ====================
453
+ async createTable(tableName, columns, options) {
454
+ // AUTOINCREMENT columns need a backing SEQUENCE created first, since
455
+ // DuckDB has no AUTOINCREMENT keyword - it uses `DEFAULT nextval('seq')`.
456
+ for (const [columnName, definition] of Object.entries(columns)) {
457
+ if (definition.autoIncrement) {
458
+ const seqName = this._autoIncrementSequenceName(tableName, columnName);
459
+ await this.query(`CREATE SEQUENCE IF NOT EXISTS ${this.escapeId(seqName)}`);
460
+ }
461
+ }
462
+ const columnDefs = [];
463
+ for (const [columnName, definition] of Object.entries(columns)) {
464
+ const typeObj = definition.type;
465
+ if (typeObj && typeObj.key === 'VIRTUAL') {
466
+ continue;
467
+ }
468
+ columnDefs.push(this.getColumnDefinitionSql(tableName, columnName, definition));
469
+ }
470
+ if (options?.constraints) {
471
+ for (const constraint of options.constraints) {
472
+ const constraintSql = this.buildConstraintSql(constraint);
473
+ if (constraintSql)
474
+ columnDefs.push(constraintSql);
475
+ }
476
+ }
477
+ let sql = 'CREATE TABLE';
478
+ if (options?.ifNotExists)
479
+ sql += ' IF NOT EXISTS';
480
+ sql += ` ${this.escapeId(tableName)} (${columnDefs.join(', ')})`;
481
+ if (options?.engine || options?.charset || options?.rowFormat) {
482
+ console.warn(`DuckDB: engine/charset/rowFormat options are not supported and will be ignored`);
483
+ }
484
+ await this.query(sql);
485
+ if (options?.uniqueKeys) {
486
+ const uniqueKeysList = Array.isArray(options.uniqueKeys)
487
+ ? options.uniqueKeys
488
+ : Object.entries(options.uniqueKeys).map(([keyName, fields]) => ({ name: keyName, fields }));
489
+ for (const uk of uniqueKeysList) {
490
+ await this.addIndex(tableName, uk.name || `uq_${tableName}_${uk.fields.join('_')}`, uk.fields, { unique: true });
491
+ }
492
+ }
493
+ if (options?.indexes) {
494
+ for (const index of options.indexes) {
495
+ await this.addIndex(tableName, index.name || `idx_${tableName}_${index.fields.join('_')}`, index.fields, {
496
+ unique: index.unique,
497
+ type: index.type,
498
+ using: index.using,
499
+ where: index.where,
500
+ expression: index.expression,
501
+ });
502
+ }
503
+ }
504
+ if (options?.comment) {
505
+ await this.commentTable(tableName, options.comment);
506
+ }
507
+ }
508
+ _autoIncrementSequenceName(tableName, columnName) {
509
+ return `seq_${tableName}_${columnName}`;
510
+ }
511
+ buildConstraintSql(constraint) {
512
+ const name = constraint.name ? `CONSTRAINT ${this.escapeId(constraint.name)} ` : '';
513
+ const fields = constraint.fields?.map((f) => this.escapeId(f)).join(', ') || '';
514
+ switch (constraint.type) {
515
+ case 'PRIMARY KEY':
516
+ return `${name}PRIMARY KEY (${fields})`;
517
+ case 'UNIQUE':
518
+ return `${name}UNIQUE (${fields})`;
519
+ case 'FOREIGN KEY': {
520
+ if (!constraint.references)
521
+ return null;
522
+ const refField = constraint.references.field;
523
+ const refFieldSql = Array.isArray(refField)
524
+ ? `(${refField.map((f) => this.escapeId(f)).join(', ')})`
525
+ : `(${this.escapeId(refField)})`;
526
+ let fkSql = `${name}FOREIGN KEY (${fields}) REFERENCES ${this.escapeId(constraint.references.table)}${refFieldSql}`;
527
+ if (constraint.references.onDelete)
528
+ fkSql += ` ON DELETE ${constraint.references.onDelete}`;
529
+ if (constraint.references.onUpdate)
530
+ fkSql += ` ON UPDATE ${constraint.references.onUpdate}`;
531
+ return fkSql;
532
+ }
533
+ case 'CHECK':
534
+ if (!constraint.check)
535
+ return null;
536
+ return `${name}CHECK (${constraint.check})`;
537
+ default:
538
+ return null;
539
+ }
540
+ }
541
+ getColumnDefinitionSql(tableName, columnName, definition) {
542
+ let sql = `${this.escapeId(columnName)} ${this.getDataTypeSql(definition.type, columnName)}`;
543
+ if (definition.primaryKey) {
544
+ sql += ' PRIMARY KEY';
545
+ }
546
+ if (definition.allowNull === false) {
547
+ sql += ' NOT NULL';
548
+ }
549
+ if (definition.autoIncrement) {
550
+ const seqName = this._autoIncrementSequenceName(tableName, columnName);
551
+ sql += ` DEFAULT nextval(${this.escape(seqName)})`;
552
+ }
553
+ else if (definition.defaultValue !== undefined) {
554
+ sql += ` DEFAULT ${this.getDefaultValue(definition.defaultValue)}`;
555
+ }
556
+ if (definition.unique) {
557
+ sql += ' UNIQUE';
558
+ }
559
+ if (definition.references) {
560
+ const refField = definition.references.field;
561
+ const refFieldSql = Array.isArray(refField)
562
+ ? `(${refField.map((f) => this.escapeId(f)).join(', ')})`
563
+ : `(${this.escapeId(refField)})`;
564
+ sql += ` REFERENCES ${this.escapeId(definition.references.table)}${refFieldSql}`;
565
+ if (definition.references.onDelete)
566
+ sql += ` ON DELETE ${definition.references.onDelete}`;
567
+ if (definition.references.onUpdate)
568
+ sql += ` ON UPDATE ${definition.references.onUpdate}`;
569
+ }
570
+ return sql;
571
+ }
572
+ getDefaultValue(value) {
573
+ if (value instanceof prorm_1.Literal) {
574
+ return value.val;
575
+ }
576
+ if (value === null)
577
+ return 'NULL';
578
+ if (typeof value === 'string') {
579
+ if (value.toUpperCase().includes('CURRENT_') || /^nextval\(/i.test(value)) {
580
+ return value;
581
+ }
582
+ return `'${this.escapeString(value)}'`;
583
+ }
584
+ if (typeof value === 'number')
585
+ return String(value);
586
+ if (typeof value === 'boolean')
587
+ return value ? 'TRUE' : 'FALSE';
588
+ if (value instanceof Date)
589
+ return `TIMESTAMP '${value.toISOString().replace('T', ' ').replace('Z', '')}'`;
590
+ if (typeof value === 'object' &&
591
+ value !== null &&
592
+ typeof value.toDefaultValue === 'function') {
593
+ return value.toDefaultValue();
594
+ }
595
+ return `'${this.escapeString(String(value))}'`;
596
+ }
597
+ async dropTable(tableName, options) {
598
+ let sql = 'DROP TABLE';
599
+ if (options?.ifExists)
600
+ sql += ' IF EXISTS';
601
+ sql += ` ${this.escapeId(tableName)}`;
602
+ if (options?.cascade)
603
+ sql += ' CASCADE';
604
+ await this.query(sql);
605
+ }
606
+ // ==================== Partitioning (emulated) ====================
607
+ // DuckDB has no ALTER-TABLE-level partitioning system like PostgreSQL. Its
608
+ // real partitioning concept lives at the storage/IO layer instead - Hive-
609
+ // style partitioned Parquet directories, read via `read_parquet(...,
610
+ // hive_partitioning=true)` and written via `COPY ... TO ... (PARTITION_BY
611
+ // (...))` (see `copyTo`/`readParquet` below). To still satisfy the shared
612
+ // partition-table API (used by cross-dialect model code), the same
613
+ // child-table + metadata-table + UNION ALL view emulation SQLite uses is
614
+ // applied here.
615
+ async createPartitionedTable(tableName, columns, options) {
616
+ const partitions = options?.partitions ?? [];
617
+ await this.createTable(tableName, columns, { ...options, ifNotExists: options?.ifNotExists ?? true });
618
+ const metadataTable = `${tableName}_partitions`;
619
+ await this.query(`CREATE TABLE IF NOT EXISTS ${this.escapeId(metadataTable)} (
620
+ partition_name VARCHAR PRIMARY KEY,
621
+ parent_table VARCHAR NOT NULL,
622
+ bound VARCHAR,
623
+ is_detached BOOLEAN DEFAULT FALSE,
624
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
625
+ )`);
626
+ for (const partition of partitions) {
627
+ await this.createPartition({
628
+ parentTable: tableName,
629
+ name: partition.name,
630
+ bound: partition.bound,
631
+ tablespace: partition.tablespace,
632
+ storageParameters: partition.storageParameters,
633
+ });
634
+ }
635
+ if (partitions.length > 0) {
636
+ const partitionSelects = partitions
637
+ .map((p) => `SELECT * FROM ${this.escapeId(p.name)}`)
638
+ .join('\n UNION ALL\n ');
639
+ await this.createView(`${tableName}_union`, partitionSelects, { replace: true });
640
+ }
641
+ }
642
+ async createPartition(options) {
643
+ const { parentTable, name, bound } = options;
644
+ const parentColumns = await this.describeTable(parentTable);
645
+ const partitionColumns = {};
646
+ for (const [colName, colDef] of Object.entries(parentColumns)) {
647
+ partitionColumns[colName] = {
648
+ type: colDef.type,
649
+ allowNull: colDef.allowNull,
650
+ defaultValue: colDef.defaultValue,
651
+ };
652
+ }
653
+ await this.createTable(name, partitionColumns, { ifNotExists: true });
654
+ const metadataTable = `${parentTable}_partitions`;
655
+ const boundStr = bound ? this.escape(JSON.stringify(bound)) : 'NULL';
656
+ await this.query(`INSERT OR IGNORE INTO ${this.escapeId(metadataTable)} (partition_name, parent_table, bound) VALUES (${this.escape(name)}, ${this.escape(parentTable)}, ${boundStr})`);
657
+ }
658
+ async attachPartition(options) {
659
+ const { parentTable, partitionName } = options;
660
+ const tables = await this.showTables();
661
+ if (!tables.includes(partitionName)) {
662
+ throw new Error(`Partition table '${partitionName}' does not exist`);
663
+ }
664
+ const metadataTable = `${parentTable}_partitions`;
665
+ await this.query(`INSERT INTO ${this.escapeId(metadataTable)} (partition_name, parent_table, is_detached) ` +
666
+ `VALUES (${this.escape(partitionName)}, ${this.escape(parentTable)}, FALSE) ` +
667
+ `ON CONFLICT (partition_name) DO UPDATE SET is_detached = FALSE`);
668
+ }
669
+ async detachPartition(options) {
670
+ const { partitionName } = options;
671
+ const partitions = await this.listPartitions(partitionName);
672
+ if (partitions.length === 0) {
673
+ throw new Error(`Partition '${partitionName}' not found`);
674
+ }
675
+ const metadataTable = `${partitions[0].parent_table}_partitions`;
676
+ await this.query(`UPDATE ${this.escapeId(metadataTable)} SET is_detached = TRUE WHERE partition_name = ${this.escape(partitionName)}`);
677
+ }
678
+ async dropPartition(partitionName, options) {
679
+ const partitions = await this.listPartitions(partitionName);
680
+ const parentTable = partitions[0]?.parent_table ?? null;
681
+ let sql = 'DROP TABLE';
682
+ if (options?.ifExists)
683
+ sql += ' IF EXISTS';
684
+ sql += ` ${this.escapeId(partitionName)}`;
685
+ if (options?.cascade)
686
+ sql += ' CASCADE';
687
+ await this.query(sql);
688
+ if (parentTable) {
689
+ const metadataTable = `${parentTable}_partitions`;
690
+ try {
691
+ await this.query(`DELETE FROM ${this.escapeId(metadataTable)} WHERE partition_name = ${this.escape(partitionName)}`);
692
+ }
693
+ catch {
694
+ // Ignore if metadata table doesn't exist
695
+ }
696
+ }
697
+ }
698
+ async listPartitions(tableName) {
699
+ const metadataTable = `${tableName}_partitions`;
700
+ try {
701
+ const result = await this.query(`SELECT * FROM ${this.escapeId(metadataTable)}`);
702
+ return result.rows.map((row) => ({
703
+ name: row.partition_name,
704
+ parent_table: row.parent_table,
705
+ bound: row.bound,
706
+ is_detached: row.is_detached,
707
+ }));
708
+ }
709
+ catch {
710
+ return [];
711
+ }
712
+ }
713
+ // ==================== Views ====================
714
+ async createView(viewName, query, options) {
715
+ let sql = options?.replace ? 'CREATE OR REPLACE VIEW' : 'CREATE VIEW';
716
+ sql += ` ${options?.schema ? this.quoteTable(viewName, options.schema) : this.escapeId(viewName)} AS ${query}`;
717
+ await this.query(sql);
718
+ }
719
+ async dropView(viewName, options) {
720
+ let sql = 'DROP VIEW';
721
+ if (options?.ifExists)
722
+ sql += ' IF EXISTS';
723
+ sql += ` ${this.escapeId(viewName)}`;
724
+ if (options?.cascade)
725
+ sql += ' CASCADE';
726
+ await this.query(sql);
727
+ }
728
+ async showViews() {
729
+ const result = await this.query(`SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'`);
730
+ return result.rows.map((row) => row.table_name);
731
+ }
732
+ /**
733
+ * Materialized views (not supported). DuckDB has no `CREATE MATERIALIZED
734
+ * VIEW` statement - the closest equivalents are a plain table populated via
735
+ * `CREATE TABLE ... AS SELECT ...` (refreshed by re-running the query) or
736
+ * exporting to Parquet with `copyTo()` and reading it back with
737
+ * `readParquet()`.
738
+ */
739
+ async createMaterializedView() {
740
+ throw new Error('DuckDB does not support materialized views. Use `CREATE TABLE ... AS SELECT ...` ' +
741
+ '(re-run to refresh) or `copyTo()` to a Parquet file plus `readParquet()` to read it back.');
742
+ }
743
+ async refreshMaterializedView() {
744
+ throw new Error('DuckDB does not support materialized views.');
745
+ }
746
+ async dropMaterializedView() {
747
+ throw new Error('DuckDB does not support materialized views.');
748
+ }
749
+ async hasMaterializedView() {
750
+ return false;
751
+ }
752
+ async showMaterializedViews() {
753
+ return [];
754
+ }
755
+ // ==================== Columns ====================
756
+ async addColumn(tableName, columnName, definition) {
757
+ const sql = `ALTER TABLE ${this.escapeId(tableName)} ADD COLUMN ${this.getColumnDefinitionSql(tableName, columnName, definition)}`;
758
+ await this.query(sql);
759
+ }
760
+ async removeColumn(tableName, columnName) {
761
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} DROP COLUMN ${this.escapeId(columnName)}`);
762
+ }
763
+ async changeColumn(tableName, columnName, definition) {
764
+ // DuckDB supports ALTER TABLE ... ALTER COLUMN ... TYPE / SET/DROP DEFAULT
765
+ // / SET/DROP NOT NULL as separate statements (no single combined form).
766
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} ALTER COLUMN ${this.escapeId(columnName)} TYPE ${this.getDataTypeSql(definition.type, columnName)}`);
767
+ if (definition.defaultValue !== undefined) {
768
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} ALTER COLUMN ${this.escapeId(columnName)} SET DEFAULT ${this.getDefaultValue(definition.defaultValue)}`);
769
+ }
770
+ if (definition.allowNull === false) {
771
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} ALTER COLUMN ${this.escapeId(columnName)} SET NOT NULL`);
772
+ }
773
+ else if (definition.allowNull === true) {
774
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} ALTER COLUMN ${this.escapeId(columnName)} DROP NOT NULL`);
775
+ }
776
+ }
777
+ async renameColumn(tableName, oldColumnName, newColumnName) {
778
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} RENAME COLUMN ${this.escapeId(oldColumnName)} TO ${this.escapeId(newColumnName)}`);
779
+ }
780
+ async addForeignKey(tableName, _columnName, _referencedTableName, _referencedColumnName) {
781
+ throw new Error(`DuckDB does not support adding foreign keys to an existing table ("${tableName}") via ALTER TABLE. ` +
782
+ `Define the foreign key when creating the table instead.`);
783
+ }
784
+ async bulkInsert(tableName, records, _options) {
785
+ if (records.length === 0) {
786
+ return { rows: [], rowCount: 0, fields: [] };
787
+ }
788
+ const columns = Object.keys(records[0]);
789
+ const placeholders = records.map(() => `(${columns.map(() => '?').join(', ')})`);
790
+ const values = records.flatMap((record) => columns.map((col) => record[col]));
791
+ const sql = `INSERT INTO ${this.quoteTable(tableName)} (${columns
792
+ .map((c) => this.escapeId(c))
793
+ .join(', ')}) VALUES ${placeholders.join(', ')}`;
794
+ return this.query(sql, { replacements: values });
795
+ }
796
+ async showTables() {
797
+ const result = await this.query(`SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'`);
798
+ return result.rows.map((row) => row.table_name);
799
+ }
800
+ async showConstraints(tableName) {
801
+ const result = await this.query(`SELECT constraint_name, constraint_type FROM duckdb_constraints() WHERE table_name = ?`, { replacements: [tableName] });
802
+ return result.rows;
803
+ }
804
+ async addConstraint(tableName, options) {
805
+ const name = options.name ? `CONSTRAINT ${this.escapeId(options.name)} ` : '';
806
+ let clause = '';
807
+ switch (options.type) {
808
+ case 'UNIQUE':
809
+ clause = `${name}UNIQUE (${options.fields.map((f) => this.escapeId(f)).join(', ')})`;
810
+ break;
811
+ case 'PRIMARY KEY':
812
+ clause = `${name}PRIMARY KEY (${options.fields.map((f) => this.escapeId(f)).join(', ')})`;
813
+ break;
814
+ case 'CHECK':
815
+ clause = `${name}CHECK (${options.check})`;
816
+ break;
817
+ case 'FOREIGN KEY':
818
+ if (!options.references)
819
+ throw new Error('references required for FOREIGN KEY constraint');
820
+ clause = `${name}FOREIGN KEY (${options.fields.map((f) => this.escapeId(f)).join(', ')}) REFERENCES ${this.escapeId(options.references.table)} (${options.references.fields.map((f) => this.escapeId(f)).join(', ')})`;
821
+ break;
822
+ }
823
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} ADD ${clause}`);
824
+ }
825
+ async removeConstraint(tableName, constraintName) {
826
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} DROP CONSTRAINT ${this.escapeId(constraintName)}`);
827
+ }
828
+ async showIndexes(tableName) {
829
+ const result = await this.query(`SELECT * FROM duckdb_indexes() WHERE table_name = ?`, {
830
+ replacements: [tableName],
831
+ });
832
+ return result.rows;
833
+ }
834
+ async describeTable(tableName) {
835
+ const result = await this.query(`DESCRIBE ${this.escapeId(tableName)}`);
836
+ const description = {};
837
+ for (const row of result.rows) {
838
+ const name = row.column_name;
839
+ const nullStr = String(row.null ?? 'YES').toUpperCase();
840
+ const keyStr = String(row.key ?? '');
841
+ description[name] = {
842
+ type: row.column_type || 'VARCHAR',
843
+ allowNull: nullStr !== 'NO',
844
+ defaultValue: row.default,
845
+ primaryKey: keyStr.includes('PRI'),
846
+ autoIncrement: typeof row.default === 'string' && /nextval\(/i.test(row.default),
847
+ };
848
+ }
849
+ return description;
850
+ }
851
+ async renameTable(oldName, newName) {
852
+ await this.query(`ALTER TABLE ${this.escapeId(oldName)} RENAME TO ${this.escapeId(newName)}`);
853
+ }
854
+ async addIndex(tableName, indexName, fields = [], options) {
855
+ let sql = 'CREATE';
856
+ if (options?.unique)
857
+ sql += ' UNIQUE';
858
+ sql += ' INDEX';
859
+ if (options?.ifNotExists !== false)
860
+ sql += ' IF NOT EXISTS';
861
+ sql += ` ${this.escapeId(indexName)} ON ${this.escapeId(tableName)}`;
862
+ if (options?.expression) {
863
+ sql += ` (${options.expression})`;
864
+ }
865
+ else {
866
+ sql += ` (${fields.map((f) => this.escapeId(f)).join(', ')})`;
867
+ }
868
+ // Note: DuckDB indexes do not support partial (WHERE) index predicates;
869
+ // silently ignoring `options.where` here would hide data-shape bugs, so
870
+ // surface it clearly instead.
871
+ if (options?.where) {
872
+ throw new Error('DuckDB does not support partial indexes (WHERE clause on CREATE INDEX).');
873
+ }
874
+ await this.query(sql);
875
+ }
876
+ async removeIndex(_tableName, indexName) {
877
+ await this.query(`DROP INDEX ${this.escapeId(indexName)}`);
878
+ }
879
+ async createIndex(tableName, indexDef) {
880
+ await this.addIndex(tableName, indexDef.name, indexDef.fields, {
881
+ unique: indexDef.unique,
882
+ type: indexDef.type,
883
+ using: indexDef.using,
884
+ where: indexDef.where,
885
+ });
886
+ }
887
+ async createFulltextIndex() {
888
+ throw new Error('DuckDB does not have a built-in CREATE FULLTEXT INDEX statement. Install/load the ' +
889
+ "'fts' extension and use its `PRAGMA create_fts_index(...)` function instead.");
890
+ }
891
+ async createSpatialIndex() {
892
+ throw new Error("DuckDB spatial indexing requires the 'spatial' extension's RTREE index type " +
893
+ "(CREATE INDEX ... USING RTREE (...)); use createExtension('spatial') then addIndex " +
894
+ "with { using: 'RTREE' }.");
895
+ }
896
+ async dropIndex(_tableName, indexName, _options) {
897
+ await this.query(`DROP INDEX ${this.escapeId(indexName)}`);
898
+ }
899
+ async createConstraint(tableName, constraintDef) {
900
+ await this.addConstraint(tableName, {
901
+ type: constraintDef.type,
902
+ fields: constraintDef.fields || [],
903
+ name: constraintDef.name,
904
+ references: constraintDef.references
905
+ ? { table: constraintDef.references.table, fields: [constraintDef.references.field] }
906
+ : undefined,
907
+ check: constraintDef.check,
908
+ });
909
+ }
910
+ async dropConstraint(tableName, constraintName, options) {
911
+ let sql = `ALTER TABLE ${this.escapeId(tableName)} DROP CONSTRAINT`;
912
+ if (options?.ifExists)
913
+ sql += ' IF EXISTS';
914
+ sql += ` ${this.escapeId(constraintName)}`;
915
+ await this.query(sql);
916
+ }
917
+ async changeOwner() {
918
+ throw new Error('DuckDB has no per-table ownership model to change (it is a single-process embedded database).');
919
+ }
920
+ // ==================== Foreign Data Wrappers (PostgreSQL-only feature) ====================
921
+ fdwNotSupported() {
922
+ throw new Error('Foreign Data Wrappers are not supported by DuckDB. This is a PostgreSQL-specific feature.');
923
+ }
924
+ async createForeignDataWrapper() {
925
+ this.fdwNotSupported();
926
+ }
927
+ async dropForeignDataWrapper() {
928
+ this.fdwNotSupported();
929
+ }
930
+ async createForeignServer() {
931
+ this.fdwNotSupported();
932
+ }
933
+ async dropForeignServer() {
934
+ this.fdwNotSupported();
935
+ }
936
+ async createForeignTable() {
937
+ this.fdwNotSupported();
938
+ }
939
+ buildCreateServerQuery(_name, _opts) {
940
+ this.fdwNotSupported();
941
+ }
942
+ buildAlterServerQuery(_name, _opts) {
943
+ this.fdwNotSupported();
944
+ }
945
+ buildDropServerQuery() {
946
+ this.fdwNotSupported();
947
+ }
948
+ buildCreateUserMappingQuery(_opts) {
949
+ this.fdwNotSupported();
950
+ }
951
+ buildAlterUserMappingQuery(_opts) {
952
+ this.fdwNotSupported();
953
+ }
954
+ buildDropUserMappingQuery() {
955
+ this.fdwNotSupported();
956
+ }
957
+ async createUserMapping() {
958
+ this.fdwNotSupported();
959
+ }
960
+ async dropUserMapping() {
961
+ this.fdwNotSupported();
962
+ }
963
+ buildCreateForeignTableQuery(_tableName, _opts) {
964
+ this.fdwNotSupported();
965
+ }
966
+ buildDropForeignTableQuery() {
967
+ this.fdwNotSupported();
968
+ }
969
+ buildImportForeignSchemaQuery(_remoteSchema, _serverName, _opts) {
970
+ this.fdwNotSupported();
971
+ }
972
+ getServersQuery() {
973
+ this.fdwNotSupported();
974
+ }
975
+ // ==================== Security policies (MSSQL RLS-style feature) ====================
976
+ async createSecurityPolicy() {
977
+ throw new Error('Row-level security policies are not supported by DuckDB.');
978
+ }
979
+ async dropSecurityPolicy() {
980
+ throw new Error('Row-level security policies are not supported by DuckDB.');
981
+ }
982
+ // ==================== User / privilege management ====================
983
+ // DuckDB is embedded and single-process/single-user: there is no user or
984
+ // role system to manage.
985
+ userMgmtNotSupported() {
986
+ throw new Error('User/role management is not supported by DuckDB (it is an embedded, single-user database).');
987
+ }
988
+ buildCreateUserQuery() {
989
+ this.userMgmtNotSupported();
990
+ }
991
+ buildAlterUserQuery() {
992
+ this.userMgmtNotSupported();
993
+ }
994
+ buildDropUserQuery() {
995
+ this.userMgmtNotSupported();
996
+ }
997
+ getUsersQuery() {
998
+ this.userMgmtNotSupported();
999
+ }
1000
+ buildGrantQuery() {
1001
+ this.userMgmtNotSupported();
1002
+ }
1003
+ buildRevokeQuery() {
1004
+ this.userMgmtNotSupported();
1005
+ }
1006
+ buildShowGrantsQuery() {
1007
+ this.userMgmtNotSupported();
1008
+ }
1009
+ buildFlushPrivilegesQuery() {
1010
+ this.userMgmtNotSupported();
1011
+ }
1012
+ buildCreateRoleQuery() {
1013
+ this.userMgmtNotSupported();
1014
+ }
1015
+ buildDropRoleQuery() {
1016
+ this.userMgmtNotSupported();
1017
+ }
1018
+ buildGrantRoleQuery() {
1019
+ this.userMgmtNotSupported();
1020
+ }
1021
+ buildRevokeRoleQuery() {
1022
+ this.userMgmtNotSupported();
1023
+ }
1024
+ getRolesQuery() {
1025
+ this.userMgmtNotSupported();
1026
+ }
1027
+ // ==================== Stored procedures ====================
1028
+ // DuckDB has no procedural SQL language. Its closest analogue is a scalar/
1029
+ // table MACRO (`CREATE MACRO name(args) AS expr`), which is expression-only
1030
+ // (no statement bodies, loops, or side effects), so it cannot faithfully
1031
+ // stand in for a stored procedure - fail clearly rather than pretend.
1032
+ async createStoredProcedure(options) {
1033
+ throw new Error(`DuckDB does not support stored procedures. Cannot create '${options.name}'.\n\n` +
1034
+ `Workarounds:\n` +
1035
+ `- Move logic to application code\n` +
1036
+ `- Use a scalar/table MACRO for simple reusable expressions: ` +
1037
+ `CREATE MACRO ${options.name}(...) AS (...)\n` +
1038
+ `- Use views for read-only reusable queries`);
1039
+ }
1040
+ async createProcedure(options) {
1041
+ return this.createStoredProcedure(options);
1042
+ }
1043
+ async dropStoredProcedure(procedureName, _options) {
1044
+ throw new Error(`DuckDB does not support stored procedures. Cannot drop '${procedureName}'.`);
1045
+ }
1046
+ async dropProcedure(procedureName, options) {
1047
+ return this.dropStoredProcedure(procedureName, options);
1048
+ }
1049
+ async executeStoredProcedure(options) {
1050
+ throw new Error(`DuckDB does not support stored procedures. Cannot execute '${options.procedureName}'. ` +
1051
+ `Run the equivalent SQL directly via query(), or define a MACRO.`);
1052
+ }
1053
+ async hasStoredProcedure() {
1054
+ return false;
1055
+ }
1056
+ // ==================== Triggers (not supported) ====================
1057
+ async createTrigger(options) {
1058
+ throw new Error(`DuckDB does not support triggers. Cannot create '${options.name}' on '${options.tableName}'. ` +
1059
+ `Move the reactive logic into application code around the write path instead.`);
1060
+ }
1061
+ async dropTrigger(triggerName, _tableName, _options) {
1062
+ throw new Error(`DuckDB does not support triggers. Cannot drop '${triggerName}'.`);
1063
+ }
1064
+ async hasTrigger() {
1065
+ return false;
1066
+ }
1067
+ // ==================== Sequences (real, native DuckDB feature) ====================
1068
+ async createSequence(options) {
1069
+ let sql = 'CREATE';
1070
+ if (options.replace)
1071
+ sql += ' OR REPLACE';
1072
+ sql += ' SEQUENCE';
1073
+ if (options.ifNotExists)
1074
+ sql += ' IF NOT EXISTS';
1075
+ sql += ` ${this.quoteTable(options.name, options.schema)}`;
1076
+ if (options.startWith !== undefined)
1077
+ sql += ` START WITH ${options.startWith}`;
1078
+ if (options.incrementBy !== undefined)
1079
+ sql += ` INCREMENT BY ${options.incrementBy}`;
1080
+ if (options.minvalue !== undefined)
1081
+ sql += ` MINVALUE ${options.minvalue}`;
1082
+ if (options.maxvalue !== undefined)
1083
+ sql += ` MAXVALUE ${options.maxvalue}`;
1084
+ if (options.cycle)
1085
+ sql += ' CYCLE';
1086
+ await this.query(sql);
1087
+ }
1088
+ async dropSequence(sequenceName, options) {
1089
+ let sql = 'DROP SEQUENCE';
1090
+ if (options?.ifExists)
1091
+ sql += ' IF EXISTS';
1092
+ sql += ` ${this.quoteTable(sequenceName, options?.schema)}`;
1093
+ if (options?.cascade)
1094
+ sql += ' CASCADE';
1095
+ await this.query(sql);
1096
+ }
1097
+ async nextSequenceValue(sequenceName) {
1098
+ const result = await this.query(`SELECT nextval(${this.escape(sequenceName)}) AS value`);
1099
+ return Number(result.rows[0].value);
1100
+ }
1101
+ async hasSequence(sequenceName, schema) {
1102
+ const result = await this.query(`SELECT 1 FROM duckdb_sequences() WHERE sequence_name = ?${schema ? ' AND schema_name = ?' : ''}`, { replacements: (schema ? [sequenceName, schema] : [sequenceName]) });
1103
+ return result.rows.length > 0;
1104
+ }
1105
+ async listSequences() {
1106
+ const result = await this.query(`SELECT sequence_name FROM duckdb_sequences()`);
1107
+ return result.rows.map((row) => row.sequence_name);
1108
+ }
1109
+ // ==================== Row-level security (not supported) ====================
1110
+ async createPolicy() {
1111
+ throw new Error('Row-level security is not supported by DuckDB.');
1112
+ }
1113
+ async dropPolicy() {
1114
+ throw new Error('Row-level security is not supported by DuckDB.');
1115
+ }
1116
+ async enableRLS() {
1117
+ throw new Error('Row-level security is not supported by DuckDB.');
1118
+ }
1119
+ async disableRLS() {
1120
+ throw new Error('Row-level security is not supported by DuckDB.');
1121
+ }
1122
+ async hasPolicy() {
1123
+ return false;
1124
+ }
1125
+ // ==================== Comments ====================
1126
+ async commentTable(tableName, comment) {
1127
+ await this.query(`COMMENT ON TABLE ${this.escapeId(tableName)} IS ${this.escape(comment)}`);
1128
+ }
1129
+ async commentColumn(tableName, columnName, comment) {
1130
+ await this.query(`COMMENT ON COLUMN ${this.escapeId(tableName)}.${this.escapeId(columnName)} IS ${this.escape(comment)}`);
1131
+ }
1132
+ // ==================== Advanced indexes ====================
1133
+ async createPartialIndex() {
1134
+ throw new Error('DuckDB does not support partial indexes (WHERE clause on CREATE INDEX).');
1135
+ }
1136
+ async createExpressionIndex(tableName, indexName, expression, options) {
1137
+ let sql = 'CREATE';
1138
+ if (options?.unique)
1139
+ sql += ' UNIQUE';
1140
+ sql += ` INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${expression})`;
1141
+ await this.query(sql);
1142
+ }
1143
+ // ==================== Identity / computed columns ====================
1144
+ async createIdentityColumn(tableName, columnName, options) {
1145
+ // DuckDB has no GENERATED ... AS IDENTITY syntax; emulate with a sequence-backed default.
1146
+ const seqName = this._autoIncrementSequenceName(tableName, columnName);
1147
+ await this.query(`CREATE SEQUENCE IF NOT EXISTS ${this.escapeId(seqName)} START ${options?.startWith ?? 1} INCREMENT ${options?.incrementBy ?? 1}`);
1148
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} ALTER COLUMN ${this.escapeId(columnName)} SET DEFAULT nextval(${this.escape(seqName)})`);
1149
+ }
1150
+ async createComputedColumn(tableName, columnName, expression, options) {
1151
+ const typeSql = options?.type || 'VARCHAR';
1152
+ const modeSql = options?.persisted ? '' : ' VIRTUAL';
1153
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} ADD COLUMN ${this.escapeId(columnName)} ${typeSql} GENERATED ALWAYS AS (${expression})${modeSql}`);
1154
+ }
1155
+ // ==================== Transactions ====================
1156
+ async startTransaction(options) {
1157
+ if (!this.connection) {
1158
+ throw new Error('Not connected to database');
1159
+ }
1160
+ const transaction = new DuckDBTransaction(this.transactionDepth++, options, this);
1161
+ if (this.transactionDepth === 1) {
1162
+ await this.query('BEGIN TRANSACTION');
1163
+ }
1164
+ else {
1165
+ await this.query(`SAVEPOINT sp_${transaction.id}`);
1166
+ }
1167
+ return transaction;
1168
+ }
1169
+ async commitTransaction(transaction) {
1170
+ const tx = transaction;
1171
+ if (this.transactionDepth === 1) {
1172
+ await this.query('COMMIT');
1173
+ }
1174
+ else {
1175
+ await this.query(`RELEASE SAVEPOINT sp_${tx.id}`);
1176
+ }
1177
+ this.transactionDepth = Math.max(0, this.transactionDepth - 1);
1178
+ }
1179
+ async rollbackTransaction(transaction) {
1180
+ const tx = transaction;
1181
+ if (this.transactionDepth === 1) {
1182
+ await this.query('ROLLBACK');
1183
+ }
1184
+ else {
1185
+ await this.query(`ROLLBACK TO SAVEPOINT sp_${tx.id}`);
1186
+ }
1187
+ this.transactionDepth = Math.max(0, this.transactionDepth - 1);
1188
+ }
1189
+ // ==================== Data types ====================
1190
+ getDataTypeSql(dataType, columnName) {
1191
+ if (typeof dataType === 'string') {
1192
+ return dataType;
1193
+ }
1194
+ if (typeof dataType === 'function') {
1195
+ try {
1196
+ dataType = dataType();
1197
+ }
1198
+ catch {
1199
+ return 'VARCHAR';
1200
+ }
1201
+ }
1202
+ if (!dataType || typeof dataType !== 'object') {
1203
+ return 'VARCHAR';
1204
+ }
1205
+ const dt = dataType;
1206
+ switch (dt.key) {
1207
+ case 'STRING':
1208
+ return `VARCHAR(${dt.length || 255})`;
1209
+ case 'CHAR':
1210
+ return `VARCHAR(${dt.length || 1})`;
1211
+ case 'TEXT':
1212
+ return 'VARCHAR';
1213
+ case 'INTEGER':
1214
+ return dt.unsigned ? 'UINTEGER' : 'INTEGER';
1215
+ case 'BIGINT':
1216
+ return dt.unsigned ? 'UBIGINT' : 'BIGINT';
1217
+ case 'FLOAT':
1218
+ return 'REAL';
1219
+ case 'DOUBLE':
1220
+ return 'DOUBLE';
1221
+ case 'DECIMAL':
1222
+ return `DECIMAL(${dt.precision || 18},${dt.scale || 0})`;
1223
+ case 'BOOLEAN':
1224
+ return 'BOOLEAN';
1225
+ case 'DATE':
1226
+ return 'TIMESTAMP';
1227
+ case 'DATEONLY':
1228
+ return 'DATE';
1229
+ case 'TIME':
1230
+ return 'TIME';
1231
+ case 'BLOB':
1232
+ return 'BLOB';
1233
+ case 'ENUM': {
1234
+ // DuckDB supports a native ENUM type, but only via a separately named
1235
+ // type (`CREATE TYPE mood AS ENUM (...)`), which would require a
1236
+ // second DDL statement per enum column and out-of-band lifecycle
1237
+ // management. To keep column definitions self-contained (matching
1238
+ // how every other option here works as a single inline column def),
1239
+ // this uses the same VARCHAR + CHECK emulation SQLite uses rather
1240
+ // than a named DuckDB ENUM type.
1241
+ const colRef = columnName ? this.escapeId(columnName) : 'value';
1242
+ const values = dt.values;
1243
+ return `VARCHAR CHECK(${values ? `${colRef} IN (${values.map((v) => `'${this.escapeString(String(v))}'`).join(',')})` : '1=1'})`;
1244
+ }
1245
+ case 'JSON':
1246
+ case 'JSONB':
1247
+ return 'JSON';
1248
+ case 'UUID':
1249
+ return 'UUID';
1250
+ case 'GEOMETRY':
1251
+ // Requires the 'spatial' extension's GEOMETRY type; falls back to
1252
+ // BLOB when unloaded so schema creation doesn't hard-fail.
1253
+ return 'BLOB';
1254
+ case 'ARRAY':
1255
+ case 'LIST': {
1256
+ // DuckDB LIST columns are written as `<elementType>[]`. Accept both
1257
+ // DataTypes.LIST(elementType)/DataTypes.ARRAY(elementType) (which
1258
+ // expose the element type as `.type`) and a plain
1259
+ // `{ type: 'LIST', subtype: ... }` shape for parity with how other
1260
+ // dialects (e.g. Postgres' ARRAY handling above) accept `.subtype`.
1261
+ const elementType = dt.type ?? dt.subtype;
1262
+ if (elementType) {
1263
+ return `${this.getDataTypeSql(elementType, columnName)}[]`;
1264
+ }
1265
+ return 'VARCHAR[]';
1266
+ }
1267
+ case 'STRUCT': {
1268
+ // DuckDB STRUCT columns are written as `STRUCT(field TYPE, ...)`.
1269
+ const fields = dt.fields;
1270
+ if (fields && Object.keys(fields).length > 0) {
1271
+ const parts = Object.entries(fields).map(([name, type]) => `${this.escapeId(name)} ${this.getDataTypeSql(type, name)}`);
1272
+ return `STRUCT(${parts.join(', ')})`;
1273
+ }
1274
+ return 'STRUCT()';
1275
+ }
1276
+ case 'MAP': {
1277
+ // DuckDB MAP columns are written as `MAP(keyType, valueType)`.
1278
+ const keyType = dt.keyType;
1279
+ const valueType = dt.valueType;
1280
+ if (keyType && valueType) {
1281
+ return `MAP(${this.getDataTypeSql(keyType, columnName)}, ${this.getDataTypeSql(valueType, columnName)})`;
1282
+ }
1283
+ return 'MAP(VARCHAR, VARCHAR)';
1284
+ }
1285
+ case 'UNION': {
1286
+ // DuckDB UNION columns are written as `UNION(tag TYPE, ...)`.
1287
+ const fields = dt.fields;
1288
+ if (fields && Object.keys(fields).length > 0) {
1289
+ const parts = Object.entries(fields).map(([tag, type]) => `${this.escapeId(tag)} ${this.getDataTypeSql(type, tag)}`);
1290
+ return `UNION(${parts.join(', ')})`;
1291
+ }
1292
+ return 'VARCHAR';
1293
+ }
1294
+ case 'VIRTUAL':
1295
+ return '';
1296
+ default:
1297
+ return 'VARCHAR';
1298
+ }
1299
+ }
1300
+ // ==================== Query builders (WHERE/ORDER/LIMIT/INSERT/UPDATE/DELETE/SELECT) ====================
1301
+ // These mirror SQLite's builder implementations closely - DuckDB shares the
1302
+ // same `?` positional placeholder convention and a very similar SQL dialect
1303
+ // for basic DML - but use DuckDB-correct literals (TRUE/FALSE, etc.) via
1304
+ // `escape()`/`escapeId()` above.
1305
+ buildWhereClause(where, _options) {
1306
+ const values = [];
1307
+ if (!where || (Object.keys(where).length === 0 && Object.getOwnPropertySymbols(where).length === 0)) {
1308
+ return { sql: '', values };
1309
+ }
1310
+ const buildCondition = (condition) => {
1311
+ if (!condition)
1312
+ return { sql: '' };
1313
+ if (typeof condition !== 'object') {
1314
+ values.push(condition);
1315
+ return { sql: '?' };
1316
+ }
1317
+ const rawCond = condition;
1318
+ const topSymbols = Object.getOwnPropertySymbols(rawCond);
1319
+ const normalizedCond = { ...rawCond };
1320
+ for (const sym of topSymbols) {
1321
+ const strKey = (0, operators_1.operatorToWhereKey)(sym);
1322
+ if (strKey)
1323
+ normalizedCond[strKey] = rawCond[sym];
1324
+ }
1325
+ const cond = normalizedCond;
1326
+ if (cond.$and || cond.$or || cond.$not) {
1327
+ const conditions = [];
1328
+ if (cond.$and) {
1329
+ const parts = cond.$and.map((c) => buildCondition(c).sql);
1330
+ conditions.push(`(${parts.join(' AND ')})`);
1331
+ }
1332
+ if (cond.$or) {
1333
+ const parts = cond.$or.map((c) => buildCondition(c).sql);
1334
+ conditions.push(`(${parts.join(' OR ')})`);
1335
+ }
1336
+ if (cond.$not) {
1337
+ conditions.push(`NOT (${buildCondition(cond.$not).sql})`);
1338
+ }
1339
+ return { sql: conditions.join(' AND ') };
1340
+ }
1341
+ const fieldConditions = [];
1342
+ for (const [key, value] of Object.entries(cond)) {
1343
+ if (key.startsWith('$'))
1344
+ continue;
1345
+ if (value && typeof value === 'object' && !(value instanceof Date) && !Array.isArray(value)) {
1346
+ const rawValueObj = value;
1347
+ const symbolKeys = Object.getOwnPropertySymbols(rawValueObj);
1348
+ const normalizedObj = { ...rawValueObj };
1349
+ for (const sym of symbolKeys) {
1350
+ const strKey = (0, operators_1.operatorToWhereKey)(sym);
1351
+ if (strKey)
1352
+ normalizedObj[strKey] = rawValueObj[sym];
1353
+ }
1354
+ const valueObj = normalizedObj;
1355
+ if (valueObj.$eq !== undefined) {
1356
+ values.push(valueObj.$eq);
1357
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
1358
+ }
1359
+ else if (valueObj.$ne !== undefined) {
1360
+ values.push(valueObj.$ne);
1361
+ fieldConditions.push(`${this.escapeId(key)} != ?`);
1362
+ }
1363
+ else if (valueObj.$gt !== undefined) {
1364
+ values.push(valueObj.$gt);
1365
+ fieldConditions.push(`${this.escapeId(key)} > ?`);
1366
+ }
1367
+ else if (valueObj.$gte !== undefined) {
1368
+ values.push(valueObj.$gte);
1369
+ fieldConditions.push(`${this.escapeId(key)} >= ?`);
1370
+ }
1371
+ else if (valueObj.$lt !== undefined) {
1372
+ values.push(valueObj.$lt);
1373
+ fieldConditions.push(`${this.escapeId(key)} < ?`);
1374
+ }
1375
+ else if (valueObj.$lte !== undefined) {
1376
+ values.push(valueObj.$lte);
1377
+ fieldConditions.push(`${this.escapeId(key)} <= ?`);
1378
+ }
1379
+ else if (valueObj.$like !== undefined) {
1380
+ values.push(valueObj.$like);
1381
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
1382
+ }
1383
+ else if (valueObj.$notLike !== undefined) {
1384
+ values.push(valueObj.$notLike);
1385
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
1386
+ }
1387
+ else if (valueObj.$startsWith !== undefined) {
1388
+ values.push(`${valueObj.$startsWith}%`);
1389
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
1390
+ }
1391
+ else if (valueObj.$notStartsWith !== undefined) {
1392
+ values.push(`${valueObj.$notStartsWith}%`);
1393
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
1394
+ }
1395
+ else if (valueObj.$endsWith !== undefined) {
1396
+ values.push(`%${valueObj.$endsWith}`);
1397
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
1398
+ }
1399
+ else if (valueObj.$notEndsWith !== undefined) {
1400
+ values.push(`%${valueObj.$notEndsWith}`);
1401
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
1402
+ }
1403
+ else if (valueObj.$substring !== undefined) {
1404
+ values.push(`%${valueObj.$substring}%`);
1405
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
1406
+ }
1407
+ else if (valueObj.$notSubstring !== undefined) {
1408
+ values.push(`%${valueObj.$notSubstring}%`);
1409
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
1410
+ }
1411
+ else if (valueObj.$in) {
1412
+ const inValues = valueObj.$in;
1413
+ values.push(...inValues);
1414
+ fieldConditions.push(`${this.escapeId(key)} IN (${inValues.map(() => '?').join(',')})`);
1415
+ }
1416
+ else if (valueObj.$notIn) {
1417
+ const notInValues = valueObj.$notIn;
1418
+ values.push(...notInValues);
1419
+ fieldConditions.push(`${this.escapeId(key)} NOT IN (${notInValues.map(() => '?').join(',')})`);
1420
+ }
1421
+ else if (valueObj.$between) {
1422
+ const between = valueObj.$between;
1423
+ values.push(between[0], between[1]);
1424
+ fieldConditions.push(`${this.escapeId(key)} BETWEEN ? AND ?`);
1425
+ }
1426
+ else if (valueObj.$notBetween) {
1427
+ const notBetween = valueObj.$notBetween;
1428
+ values.push(notBetween[0], notBetween[1]);
1429
+ fieldConditions.push(`${this.escapeId(key)} NOT BETWEEN ? AND ?`);
1430
+ }
1431
+ else if (valueObj.$isNull !== undefined) {
1432
+ fieldConditions.push(valueObj.$isNull ? `${this.escapeId(key)} IS NULL` : `${this.escapeId(key)} IS NOT NULL`);
1433
+ }
1434
+ else if (valueObj.$isNotNull !== undefined) {
1435
+ fieldConditions.push(valueObj.$isNotNull ? `${this.escapeId(key)} IS NOT NULL` : `${this.escapeId(key)} IS NULL`);
1436
+ }
1437
+ else if (valueObj.$col !== undefined) {
1438
+ fieldConditions.push(`${this.escapeId(key)} = ${this.escapeId(String(valueObj.$col))}`);
1439
+ }
1440
+ else {
1441
+ values.push(value);
1442
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
1443
+ }
1444
+ }
1445
+ else {
1446
+ values.push(value);
1447
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
1448
+ }
1449
+ }
1450
+ return { sql: fieldConditions.join(' AND ') };
1451
+ };
1452
+ const result = buildCondition(where);
1453
+ return { sql: result.sql, values };
1454
+ }
1455
+ buildOrderClause(order) {
1456
+ const orderArray = order;
1457
+ if (!orderArray || !Array.isArray(orderArray) || orderArray.length === 0) {
1458
+ return '';
1459
+ }
1460
+ const orderParts = [];
1461
+ for (const item of orderArray) {
1462
+ if (Array.isArray(item)) {
1463
+ const field = typeof item[0] === 'string' ? this.escapeId(item[0]) : item[0];
1464
+ const direction = item[1] ? ` ${item[1]}` : '';
1465
+ orderParts.push(`${field}${direction}`);
1466
+ }
1467
+ else if (typeof item === 'string') {
1468
+ orderParts.push(item);
1469
+ }
1470
+ else if (item && typeof item === 'object') {
1471
+ const itemArr = item;
1472
+ const [modelOrString, field] = itemArr;
1473
+ const model = typeof modelOrString === 'string' ? modelOrString : modelOrString.tableName || '';
1474
+ const fieldStr = typeof field === 'string' ? field : '';
1475
+ orderParts.push(`${model ? `${this.escapeId(model)}.` : ''}${this.escapeId(fieldStr)}`);
1476
+ }
1477
+ }
1478
+ return orderParts.length > 0 ? `ORDER BY ${orderParts.join(', ')}` : '';
1479
+ }
1480
+ buildLimitOffset(limit, offset) {
1481
+ let sql = '';
1482
+ if (limit !== undefined) {
1483
+ sql += ` LIMIT ${Number(limit)}`;
1484
+ if (offset !== undefined)
1485
+ sql += ` OFFSET ${Number(offset)}`;
1486
+ }
1487
+ else if (offset !== undefined) {
1488
+ sql += ` OFFSET ${Number(offset)}`;
1489
+ }
1490
+ return sql;
1491
+ }
1492
+ buildInsertQuery(tableName, values, options) {
1493
+ const columns = Object.keys(values);
1494
+ if (columns.length === 0) {
1495
+ return { sql: `INSERT INTO ${this.quoteTable(tableName, options?.schema)} DEFAULT VALUES`, values: [] };
1496
+ }
1497
+ const processedValues = [];
1498
+ const placeholders = [];
1499
+ for (const value of Object.values(values)) {
1500
+ if (value instanceof prorm_1.Literal) {
1501
+ placeholders.push(value.val);
1502
+ }
1503
+ else {
1504
+ placeholders.push('?');
1505
+ processedValues.push(value);
1506
+ }
1507
+ }
1508
+ let sql = `INSERT INTO ${this.quoteTable(tableName, options?.schema)} (${columns
1509
+ .map((c) => this.escapeId(c))
1510
+ .join(', ')}) VALUES (${placeholders.join(', ')})`;
1511
+ if (options?.upsert) {
1512
+ sql += this.buildOnConflictClause(columns, options);
1513
+ }
1514
+ if (options?.returning) {
1515
+ sql +=
1516
+ options.returning === true
1517
+ ? ' RETURNING *'
1518
+ : ` RETURNING ${options.returning.map((c) => this.escapeId(c)).join(', ')}`;
1519
+ }
1520
+ return { sql, values: processedValues };
1521
+ }
1522
+ buildOnConflictClause(insertedColumns, options) {
1523
+ if (options.conflictTargets && options.conflictTargets.length > 0) {
1524
+ return this.buildConflictTargetsSql(options.conflictTargets, insertedColumns);
1525
+ }
1526
+ if (options.doNothing) {
1527
+ return options.conflictFields && options.conflictFields.length > 0
1528
+ ? ` ON CONFLICT (${options.conflictFields.map((c) => this.escapeId(c)).join(', ')}) DO NOTHING`
1529
+ : ' ON CONFLICT DO NOTHING';
1530
+ }
1531
+ if (!options.conflictFields || options.conflictFields.length === 0) {
1532
+ throw new Error('DuckDB upsert requires `conflictFields` naming the PRIMARY KEY or UNIQUE columns to ' +
1533
+ 'resolve conflicts against (e.g. { conflictFields: ["id"] }), unless `doNothing` is set.');
1534
+ }
1535
+ const updateColumns = options.updateOnDuplicate && options.updateOnDuplicate.length > 0
1536
+ ? options.updateOnDuplicate
1537
+ : insertedColumns;
1538
+ const setClauses = updateColumns.map((c) => `${this.escapeId(c)} = excluded.${this.escapeId(c)}`).join(', ');
1539
+ return ` ON CONFLICT (${options.conflictFields.map((c) => this.escapeId(c)).join(', ')}) DO UPDATE SET ${setClauses}`;
1540
+ }
1541
+ buildConflictTargetsSql(targets, insertedColumns) {
1542
+ let clause = '';
1543
+ for (const target of targets) {
1544
+ if (!target.fields || target.fields.length === 0) {
1545
+ throw new Error('Each entry in `conflictTargets` requires a non-empty `fields` array naming the ' +
1546
+ 'PRIMARY KEY or UNIQUE columns to resolve conflicts against.');
1547
+ }
1548
+ const targetSql = `ON CONFLICT (${target.fields.map((c) => this.escapeId(c)).join(', ')})`;
1549
+ const action = target.action || 'DO UPDATE';
1550
+ if (action === 'DO NOTHING') {
1551
+ clause += ` ${targetSql} DO NOTHING`;
1552
+ }
1553
+ else {
1554
+ const updateColumns = target.updateColumns && target.updateColumns.length > 0 ? target.updateColumns : insertedColumns;
1555
+ const setClauses = updateColumns.map((c) => `${this.escapeId(c)} = excluded.${this.escapeId(c)}`).join(', ');
1556
+ clause += ` ${targetSql} DO UPDATE SET ${setClauses}`;
1557
+ if (target.where)
1558
+ clause += ` WHERE ${target.where}`;
1559
+ }
1560
+ }
1561
+ return clause;
1562
+ }
1563
+ buildUpsertQuery(tableName, values, options) {
1564
+ const columns = Object.keys(values);
1565
+ const processedValues = [];
1566
+ const placeholders = [];
1567
+ for (const value of Object.values(values)) {
1568
+ if (value instanceof prorm_1.Literal) {
1569
+ placeholders.push(value.val);
1570
+ }
1571
+ else {
1572
+ placeholders.push('?');
1573
+ processedValues.push(value);
1574
+ }
1575
+ }
1576
+ let sql = `INSERT INTO ${this.quoteTable(tableName, options?.schema)} (${columns
1577
+ .map((c) => this.escapeId(c))
1578
+ .join(', ')}) VALUES (${placeholders.join(', ')})`;
1579
+ if (options) {
1580
+ sql += this.buildOnConflictClause(columns, options);
1581
+ }
1582
+ if (options?.returning) {
1583
+ sql +=
1584
+ options.returning === true
1585
+ ? ' RETURNING *'
1586
+ : ` RETURNING ${options.returning.map((c) => this.escapeId(c)).join(', ')}`;
1587
+ }
1588
+ return { sql, values: processedValues };
1589
+ }
1590
+ buildIncrementQuery(tableName, fields, where, options) {
1591
+ const by = options?.by ?? 1;
1592
+ const setClauses = [];
1593
+ const queryValues = [];
1594
+ if (typeof fields === 'string') {
1595
+ setClauses.push(`${this.escapeId(fields)} = ${this.escapeId(fields)} + ?`);
1596
+ queryValues.push(by);
1597
+ }
1598
+ else if (Array.isArray(fields)) {
1599
+ for (const field of fields) {
1600
+ setClauses.push(`${this.escapeId(field)} = ${this.escapeId(field)} + ?`);
1601
+ queryValues.push(by);
1602
+ }
1603
+ }
1604
+ else {
1605
+ for (const [field, value] of Object.entries(fields)) {
1606
+ setClauses.push(`${this.escapeId(field)} = ${this.escapeId(field)} + ?`);
1607
+ queryValues.push(value);
1608
+ }
1609
+ }
1610
+ const whereClause = this.buildWhereClause(where);
1611
+ queryValues.push(...whereClause.values);
1612
+ const sql = `UPDATE ${this.escapeId(tableName)} SET ${setClauses.join(', ')} WHERE ${whereClause.sql}`;
1613
+ return { sql, values: queryValues };
1614
+ }
1615
+ buildUpdateQuery(tableName, values, where, options) {
1616
+ const setClauses = [];
1617
+ const queryValues = [];
1618
+ for (const [key, value] of Object.entries(values)) {
1619
+ if (value instanceof prorm_1.Literal) {
1620
+ setClauses.push(`${this.escapeId(key)} = ${value.val}`);
1621
+ }
1622
+ else {
1623
+ setClauses.push(`${this.escapeId(key)} = ?`);
1624
+ queryValues.push(value);
1625
+ }
1626
+ }
1627
+ const whereClause = this.buildWhereClause(where);
1628
+ let sql = `UPDATE ${this.escapeId(tableName)} SET ${setClauses.join(', ')}`;
1629
+ if (whereClause.sql)
1630
+ sql += ` WHERE ${whereClause.sql}`;
1631
+ if (options?.returning) {
1632
+ sql +=
1633
+ options.returning === true
1634
+ ? ' RETURNING *'
1635
+ : ` RETURNING ${options.returning.map((c) => this.escapeId(c)).join(', ')}`;
1636
+ }
1637
+ return { sql, values: [...queryValues, ...whereClause.values] };
1638
+ }
1639
+ buildDeleteQuery(tableName, where, options) {
1640
+ const whereClause = this.buildWhereClause(where);
1641
+ let sql = `DELETE FROM ${this.escapeId(tableName)}`;
1642
+ if (whereClause.sql)
1643
+ sql += ` WHERE ${whereClause.sql}`;
1644
+ if (options?.returning) {
1645
+ sql +=
1646
+ options.returning === true
1647
+ ? ' RETURNING *'
1648
+ : ` RETURNING ${options.returning.map((c) => this.escapeId(c)).join(', ')}`;
1649
+ }
1650
+ return { sql, values: whereClause.values };
1651
+ }
1652
+ buildSelectQuery(options) {
1653
+ const queryValues = [];
1654
+ let selectSql = '*';
1655
+ if (options.attributes) {
1656
+ if (Array.isArray(options.attributes)) {
1657
+ selectSql = options.attributes
1658
+ .map((a) => {
1659
+ if (Array.isArray(a)) {
1660
+ const [expr, alias] = a;
1661
+ const exprSql = typeof expr === 'string' ? this.formatMaybeExpression(expr) : String(expr);
1662
+ return `${exprSql} AS ${this.escapeId(alias)}`;
1663
+ }
1664
+ return typeof a === 'string' ? this.formatMaybeExpression(a) : String(a);
1665
+ })
1666
+ .join(', ');
1667
+ }
1668
+ else if (options.attributes.include) {
1669
+ selectSql = options.attributes.include.map((a) => this.escapeId(a)).join(', ');
1670
+ }
1671
+ else if (options.attributes.exclude) {
1672
+ // DuckDB's `SELECT * EXCLUDE (...)` syntax
1673
+ selectSql = `* EXCLUDE (${options.attributes.exclude.map((a) => this.escapeId(a)).join(', ')})`;
1674
+ }
1675
+ }
1676
+ let distinctClause = '';
1677
+ if (options.distinct) {
1678
+ distinctClause = options.col ? `DISTINCT ${this.escapeId(options.col)} ` : 'DISTINCT ';
1679
+ }
1680
+ let sql = `SELECT ${distinctClause}${selectSql} FROM ${this.quoteTable(options.tableName, options.schema)}`;
1681
+ if (options.include && options.include.length > 0) {
1682
+ for (const include of options.include) {
1683
+ const joinType = include.required ? 'INNER JOIN' : 'LEFT JOIN';
1684
+ const includeSchema = include.schema || include.model?.schema;
1685
+ const includeTableName = include.model.tableName;
1686
+ const tableName = includeTableName ? includeTableName : include.model.toString();
1687
+ const alias = include.as || tableName;
1688
+ sql += ` ${joinType} ${this.quoteTable(tableName, includeSchema)} ${this.escapeId(alias)}`;
1689
+ if (include.where) {
1690
+ const whereClause = this.buildWhereClause(include.where);
1691
+ sql += ` ON ${whereClause.sql}`;
1692
+ queryValues.push(...whereClause.values);
1693
+ }
1694
+ }
1695
+ }
1696
+ if (options.where) {
1697
+ const whereClause = this.buildWhereClause(options.where);
1698
+ if (whereClause.sql) {
1699
+ sql += ` WHERE ${whereClause.sql}`;
1700
+ queryValues.push(...whereClause.values);
1701
+ }
1702
+ }
1703
+ if (options.group) {
1704
+ const groupArray = Array.isArray(options.group) ? options.group : [options.group];
1705
+ if (groupArray.length > 0) {
1706
+ sql += ` GROUP BY ${groupArray.map((f) => (f === 'ALL' ? 'ALL' : this.escapeId(f))).join(', ')}`;
1707
+ }
1708
+ }
1709
+ if (options.having) {
1710
+ const havingClause = this.buildWhereClause(options.having);
1711
+ if (havingClause.sql) {
1712
+ sql += ` HAVING ${havingClause.sql}`;
1713
+ queryValues.push(...havingClause.values);
1714
+ }
1715
+ }
1716
+ if (options.order) {
1717
+ const orderClause = this.buildOrderClause(options.order);
1718
+ if (orderClause)
1719
+ sql += ` ${orderClause}`;
1720
+ }
1721
+ sql += this.buildLimitOffset(options.limit, options.offset);
1722
+ if (options.union && options.union.length > 0) {
1723
+ const unionType = options.unionType || 'UNION';
1724
+ for (const unionQuery of options.union) {
1725
+ const unionModel = unionQuery.model;
1726
+ const unionTableName = unionModel.tableName || unionModel.name || '';
1727
+ let unionSql = `SELECT * FROM ${this.escapeId(unionTableName)}`;
1728
+ if (unionQuery.where) {
1729
+ const whereClause = this.buildWhereClause(unionQuery.where);
1730
+ if (whereClause.sql) {
1731
+ unionSql += ` WHERE ${whereClause.sql}`;
1732
+ queryValues.push(...whereClause.values);
1733
+ }
1734
+ }
1735
+ if (unionQuery.order) {
1736
+ const orderClause = this.buildOrderClause(unionQuery.order);
1737
+ if (orderClause)
1738
+ unionSql += ` ${orderClause}`;
1739
+ }
1740
+ if (unionQuery.limit)
1741
+ unionSql += ` LIMIT ${unionQuery.limit}`;
1742
+ if (unionQuery.offset)
1743
+ unionSql += ` OFFSET ${unionQuery.offset}`;
1744
+ sql += ` ${unionType} ${unionSql}`;
1745
+ }
1746
+ }
1747
+ return { sql, values: queryValues };
1748
+ }
1749
+ /**
1750
+ * Formats a SELECT attribute string: bare identifiers get quoted, anything
1751
+ * that already looks like an expression (function call, `*`, operators,
1752
+ * DuckDB's `EXCLUDE`/`REPLACE` column-transform syntax) passes through
1753
+ * verbatim so callers can supply raw analytical SQL.
1754
+ */
1755
+ formatMaybeExpression(attr) {
1756
+ if (/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(attr)) {
1757
+ return attr
1758
+ .split('.')
1759
+ .map((part) => this.escapeId(part))
1760
+ .join('.');
1761
+ }
1762
+ return attr;
1763
+ }
1764
+ // ==================== JSON operations ====================
1765
+ buildJsonExtract(column, path, asText = true) {
1766
+ const columnRef = this.escapeId(column);
1767
+ const normalizedPath = path.startsWith('$.') || path === '$' ? path : `$.${path}`;
1768
+ const extract = `json_extract(${columnRef}, '${this.escapeStringForJsonPath(normalizedPath)}')`;
1769
+ return asText ? `json_extract_string(${columnRef}, '${this.escapeStringForJsonPath(normalizedPath)}')` : extract;
1770
+ }
1771
+ escapeStringForJsonPath(path) {
1772
+ return path.replace(/'/g, "''");
1773
+ }
1774
+ buildJsonQuery(column, path, value, operator = '=') {
1775
+ const values = [];
1776
+ const extract = this.buildJsonExtract(column, path, true);
1777
+ if (value !== undefined) {
1778
+ values.push(value);
1779
+ return { sql: `${extract} ${operator} ?`, values };
1780
+ }
1781
+ return { sql: extract, values };
1782
+ }
1783
+ // ==================== DuckDB-specific value-add: file-as-table functions ====================
1784
+ // DuckDB's signature capability: query Parquet/CSV/JSON files directly as
1785
+ // tables via table functions usable in a FROM clause, and export query
1786
+ // results straight back out to those formats via COPY.
1787
+ /**
1788
+ * Build a `read_parquet(...)` table-function expression usable in a FROM
1789
+ * clause, e.g. `FROM ${dialect.readParquet('data/*.parquet')}`.
1790
+ */
1791
+ readParquet(path, options) {
1792
+ const pathArg = Array.isArray(path)
1793
+ ? `[${path.map((p) => `'${this.escapeString(p)}'`).join(', ')}]`
1794
+ : `'${this.escapeString(path)}'`;
1795
+ const namedArgs = [];
1796
+ if (options?.hivePartitioning !== undefined)
1797
+ namedArgs.push(`hive_partitioning=${options.hivePartitioning}`);
1798
+ if (options?.unionByName !== undefined)
1799
+ namedArgs.push(`union_by_name=${options.unionByName}`);
1800
+ if (options?.filename !== undefined)
1801
+ namedArgs.push(`filename=${options.filename}`);
1802
+ const argsSql = namedArgs.length > 0 ? `, ${namedArgs.join(', ')}` : '';
1803
+ return `read_parquet(${pathArg}${argsSql})`;
1804
+ }
1805
+ /**
1806
+ * Build a `read_csv_auto(...)` (or `read_csv(...)` with explicit options)
1807
+ * table-function expression usable in a FROM clause.
1808
+ */
1809
+ readCsv(path, options) {
1810
+ const pathArg = Array.isArray(path)
1811
+ ? `[${path.map((p) => `'${this.escapeString(p)}'`).join(', ')}]`
1812
+ : `'${this.escapeString(path)}'`;
1813
+ const namedArgs = [];
1814
+ if (options?.header !== undefined)
1815
+ namedArgs.push(`header=${options.header}`);
1816
+ if (options?.delim !== undefined)
1817
+ namedArgs.push(`delim='${this.escapeString(options.delim)}'`);
1818
+ if (options?.allVarchar !== undefined)
1819
+ namedArgs.push(`all_varchar=${options.allVarchar}`);
1820
+ if (options?.columns) {
1821
+ const cols = Object.entries(options.columns)
1822
+ .map(([col, type]) => `'${col}': '${type}'`)
1823
+ .join(', ');
1824
+ namedArgs.push(`columns={${cols}}`);
1825
+ }
1826
+ const fnName = options?.autoDetect === false ? 'read_csv' : 'read_csv_auto';
1827
+ const argsSql = namedArgs.length > 0 ? `, ${namedArgs.join(', ')}` : '';
1828
+ return `${fnName}(${pathArg}${argsSql})`;
1829
+ }
1830
+ /**
1831
+ * Build a `read_json_auto(...)` (or `read_json(...)` with explicit options)
1832
+ * table-function expression usable in a FROM clause.
1833
+ */
1834
+ readJson(path, options) {
1835
+ const pathArg = Array.isArray(path)
1836
+ ? `[${path.map((p) => `'${this.escapeString(p)}'`).join(', ')}]`
1837
+ : `'${this.escapeString(path)}'`;
1838
+ const namedArgs = [];
1839
+ if (options?.format)
1840
+ namedArgs.push(`format='${options.format}'`);
1841
+ if (options?.columns) {
1842
+ const cols = Object.entries(options.columns)
1843
+ .map(([col, type]) => `'${col}': '${type}'`)
1844
+ .join(', ');
1845
+ namedArgs.push(`columns={${cols}}`);
1846
+ }
1847
+ const fnName = options?.autoDetect === false ? 'read_json' : 'read_json_auto';
1848
+ const argsSql = namedArgs.length > 0 ? `, ${namedArgs.join(', ')}` : '';
1849
+ return `${fnName}(${pathArg}${argsSql})`;
1850
+ }
1851
+ /**
1852
+ * Export the results of a query straight to a Parquet/CSV/JSON file via
1853
+ * DuckDB's `COPY (query) TO 'file' (FORMAT ...)`.
1854
+ */
1855
+ async copyTo(query, path, format, options) {
1856
+ const opts = [`FORMAT ${format}`];
1857
+ if (options?.partitionBy && options.partitionBy.length > 0) {
1858
+ opts.push(`PARTITION_BY (${options.partitionBy.map((c) => this.escapeId(c)).join(', ')})`);
1859
+ }
1860
+ if (options?.overwrite)
1861
+ opts.push('OVERWRITE_OR_IGNORE');
1862
+ if (options?.compression)
1863
+ opts.push(`COMPRESSION ${options.compression.toUpperCase()}`);
1864
+ if (format === 'CSV' && options?.header !== undefined)
1865
+ opts.push(`HEADER ${options.header}`);
1866
+ await this.query(`COPY (${query}) TO '${this.escapeString(path)}' (${opts.join(', ')})`);
1867
+ }
1868
+ // ==================== S3 / httpfs credential configuration ====================
1869
+ // `createExtension('httpfs')` only INSTALLs/LOADs the extension - it does not
1870
+ // configure credentials, so `read_parquet('s3://...')` etc. would still fail
1871
+ // with a missing-credentials error against a real bucket. DuckDB supports two
1872
+ // ways to supply S3 credentials, and both are exposed here rather than
1873
+ // picking one:
1874
+ //
1875
+ // - `SET s3_...` variables (`configureS3Credentials`): simplest, but global
1876
+ // to the DuckDB instance/connection - one set of credentials at a time,
1877
+ // with no per-query or per-bucket scoping. This is the original httpfs
1878
+ // mechanism and still fully supported.
1879
+ // - `CREATE SECRET ... (TYPE S3, ...)` (`createSecret`/`dropSecret`): the
1880
+ // newer, DuckDB-recommended mechanism. Secrets are named, can be scoped to
1881
+ // a specific path prefix (`SCOPE`), can coexist (multiple buckets/
1882
+ // credentials at once), and can be persisted. Prefer this for anything
1883
+ // beyond a single quick script.
1884
+ //
1885
+ // See https://duckdb.org/docs/extensions/httpfs/s3api and
1886
+ // https://duckdb.org/docs/configuration/secrets_manager for the underlying
1887
+ // SET variable names (`s3_region`, `s3_access_key_id`, `s3_secret_access_key`,
1888
+ // `s3_session_token`, `s3_endpoint`, `s3_url_style`, `s3_use_ssl`) and
1889
+ // `CREATE SECRET` grammar (`TYPE S3`, `KEY_ID`, `SECRET`, `REGION`,
1890
+ // `SESSION_TOKEN`, `ENDPOINT`, `URL_STYLE`, `USE_SSL`, `SCOPE`) this wraps.
1891
+ /**
1892
+ * Configure S3 access via DuckDB httpfs `SET s3_...` session/global
1893
+ * variables. Only emits a `SET` statement for options that are actually
1894
+ * provided. Simpler than `createSecret()` but global to the instance (later
1895
+ * calls overwrite earlier ones; there is no per-bucket scoping) - prefer
1896
+ * `createSecret()` when multiple credentials/buckets are in play.
1897
+ *
1898
+ * Requires the `httpfs` extension to be installed/loaded first (see
1899
+ * `createExtension('httpfs')`).
1900
+ */
1901
+ async configureS3Credentials(options) {
1902
+ const statements = [];
1903
+ if (options.region !== undefined) {
1904
+ statements.push(`SET s3_region='${this.escapeString(options.region)}'`);
1905
+ }
1906
+ if (options.accessKeyId !== undefined) {
1907
+ statements.push(`SET s3_access_key_id='${this.escapeString(options.accessKeyId)}'`);
1908
+ }
1909
+ if (options.secretAccessKey !== undefined) {
1910
+ statements.push(`SET s3_secret_access_key='${this.escapeString(options.secretAccessKey)}'`);
1911
+ }
1912
+ if (options.sessionToken !== undefined) {
1913
+ statements.push(`SET s3_session_token='${this.escapeString(options.sessionToken)}'`);
1914
+ }
1915
+ if (options.endpoint !== undefined) {
1916
+ statements.push(`SET s3_endpoint='${this.escapeString(options.endpoint)}'`);
1917
+ }
1918
+ if (options.urlStyle !== undefined) {
1919
+ statements.push(`SET s3_url_style='${this.escapeString(options.urlStyle)}'`);
1920
+ }
1921
+ if (options.useSsl !== undefined) {
1922
+ statements.push(`SET s3_use_ssl=${options.useSsl ? 'true' : 'false'}`);
1923
+ }
1924
+ for (const sql of statements) {
1925
+ await this.query(sql);
1926
+ }
1927
+ }
1928
+ /**
1929
+ * Create a named DuckDB Secrets Manager secret of `TYPE S3` for use with
1930
+ * `read_parquet`/`read_csv`/`read_json`/`copyTo` against `s3://` paths.
1931
+ * This is the newer, DuckDB-recommended alternative to
1932
+ * `configureS3Credentials()`'s global `SET s3_...` variables: secrets are
1933
+ * named, can be scoped to a path prefix, and multiple secrets (for
1934
+ * different buckets/credentials) can coexist.
1935
+ *
1936
+ * Requires the `httpfs` extension to be installed/loaded first (see
1937
+ * `createExtension('httpfs')`).
1938
+ */
1939
+ async createSecret(name, options = {}) {
1940
+ const params = ['TYPE S3'];
1941
+ if (options.keyId !== undefined)
1942
+ params.push(`KEY_ID '${this.escapeString(options.keyId)}'`);
1943
+ if (options.secret !== undefined)
1944
+ params.push(`SECRET '${this.escapeString(options.secret)}'`);
1945
+ if (options.region !== undefined)
1946
+ params.push(`REGION '${this.escapeString(options.region)}'`);
1947
+ if (options.sessionToken !== undefined) {
1948
+ params.push(`SESSION_TOKEN '${this.escapeString(options.sessionToken)}'`);
1949
+ }
1950
+ if (options.endpoint !== undefined)
1951
+ params.push(`ENDPOINT '${this.escapeString(options.endpoint)}'`);
1952
+ if (options.urlStyle !== undefined)
1953
+ params.push(`URL_STYLE '${this.escapeString(options.urlStyle)}'`);
1954
+ if (options.useSsl !== undefined)
1955
+ params.push(`USE_SSL ${options.useSsl ? 'true' : 'false'}`);
1956
+ if (options.scope !== undefined) {
1957
+ const scopes = Array.isArray(options.scope) ? options.scope : [options.scope];
1958
+ params.push(`SCOPE '${scopes.map((s) => this.escapeString(s)).join(', ')}'`);
1959
+ }
1960
+ let sql = 'CREATE';
1961
+ if (options.replace)
1962
+ sql += ' OR REPLACE';
1963
+ sql += options.persistent ? ' PERSISTENT SECRET' : ' SECRET';
1964
+ sql += ` ${this.escapeId(name)} (${params.join(', ')})`;
1965
+ await this.query(sql);
1966
+ }
1967
+ /**
1968
+ * Drop a previously created `CREATE SECRET` (see `createSecret()`).
1969
+ */
1970
+ async dropSecret(name, options) {
1971
+ let sql = 'DROP SECRET';
1972
+ if (options?.ifExists)
1973
+ sql += ' IF EXISTS';
1974
+ sql += ` ${this.escapeId(name)}`;
1975
+ await this.query(sql);
1976
+ }
1977
+ }
1978
+ exports.DuckDBDialect = DuckDBDialect;
1979
+ /**
1980
+ * DuckDB-specific transaction class. DuckDB supports SAVEPOINT for nested
1981
+ * transactions (in addition to top-level BEGIN/COMMIT/ROLLBACK).
1982
+ */
1983
+ class DuckDBTransaction {
1984
+ constructor(depth, options, dialect) {
1985
+ this.dialect = dialect;
1986
+ this.finished = false;
1987
+ this.parent = null;
1988
+ this.savepoints = [];
1989
+ this.id = `${depth}_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
1990
+ this.options = options || {};
1991
+ }
1992
+ async commit() {
1993
+ if (this.finished)
1994
+ return;
1995
+ if (this.dialect)
1996
+ await this.dialect.commitTransaction(this);
1997
+ this.finished = true;
1998
+ }
1999
+ async rollback() {
2000
+ if (this.finished)
2001
+ return;
2002
+ if (this.dialect)
2003
+ await this.dialect.rollbackTransaction(this);
2004
+ this.finished = true;
2005
+ }
2006
+ }
2007
+ exports.DuckDBTransaction = DuckDBTransaction;
2008
+ /**
2009
+ * Create a new DuckDB dialect instance.
2010
+ */
2011
+ function createDuckDBDialect(options) {
2012
+ return new DuckDBDialect(options || {});
2013
+ }
2014
+ exports.default = DuckDBDialect;