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,3382 @@
1
+ "use strict";
2
+ /**
3
+ * SQLite dialect implementation
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SQLiteTransaction = exports.SQLiteDialect = void 0;
10
+ exports.createSQLiteDialect = createSQLiteDialect;
11
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
12
+ const stream_1 = require("stream");
13
+ const prorm_1 = require("../../prorm");
14
+ const operators_1 = require("../../operators");
15
+ const sqlite_advanced_1 = require("../../sqlite-advanced");
16
+ /**
17
+ * SQLite dialect class that implements the Dialect interface
18
+ */
19
+ class SQLiteDialect {
20
+ constructor(config) {
21
+ this.name = 'sqlite';
22
+ this.library = 'better-sqlite3';
23
+ this.connection = null;
24
+ this._isConnected = false;
25
+ this.transactionDepth = 0;
26
+ this._advanced = null;
27
+ this.config = {
28
+ storage: ':memory:',
29
+ ...config,
30
+ };
31
+ }
32
+ /**
33
+ * Connect to the SQLite database
34
+ */
35
+ async connect() {
36
+ try {
37
+ const options = {
38
+ readonly: this.config.readonly || false,
39
+ fileMustExist: this.config.fileMustExist || false,
40
+ timeout: this.config.timeout || 5000,
41
+ verbose: this.config.verbose || undefined,
42
+ };
43
+ this.connection = new better_sqlite3_1.default(this.config.storage, options);
44
+ this._isConnected = true;
45
+ // Enable foreign keys by default
46
+ this.connection.pragma('foreign_keys = ON');
47
+ }
48
+ catch (error) {
49
+ throw new Error(`Failed to connect to SQLite database: ${error}`);
50
+ }
51
+ }
52
+ /**
53
+ * Disconnect from the SQLite database
54
+ */
55
+ async disconnect() {
56
+ if (this.connection) {
57
+ this.connection.close();
58
+ this.connection = null;
59
+ this._isConnected = false;
60
+ }
61
+ }
62
+ /**
63
+ * Get the current database connection
64
+ */
65
+ getConnection() {
66
+ return this.connection;
67
+ }
68
+ /**
69
+ * Check if connected
70
+ */
71
+ isConnected() {
72
+ return this._isConnected && this.connection !== null;
73
+ }
74
+ /**
75
+ * Get SQLite advanced features instance
76
+ */
77
+ get advanced() {
78
+ if (!this._advanced) {
79
+ this._advanced = (0, sqlite_advanced_1.createSQLiteAdvanced)(this);
80
+ }
81
+ return this._advanced;
82
+ }
83
+ /**
84
+ * Replace placeholders in SQL with actual values
85
+ * Supports both named (:param) and positional (?) placeholders
86
+ */
87
+ replaceReplacements(sql, replacements) {
88
+ if (!replacements) {
89
+ return sql;
90
+ }
91
+ // Handle positional replacements (?)
92
+ if (Array.isArray(replacements)) {
93
+ let result = sql;
94
+ for (const value of replacements) {
95
+ result = result.replace(/\?/, () => this.escape(value));
96
+ }
97
+ return result;
98
+ }
99
+ // Handle named replacements (:param)
100
+ let result = sql;
101
+ for (const [key, value] of Object.entries(replacements)) {
102
+ // Handle array values for IN clauses
103
+ if (Array.isArray(value)) {
104
+ const escapedValues = value.map((v) => this.escape(v)).join(', ');
105
+ // Replace :paramName (word boundary) with the array values
106
+ const regex = new RegExp(`:${key}(?![\\w])`, 'g');
107
+ result = result.replace(regex, escapedValues);
108
+ }
109
+ else if (value === null) {
110
+ // Handle null values
111
+ const regex = new RegExp(`:${key}(?![\\w])`, 'g');
112
+ result = result.replace(regex, 'NULL');
113
+ }
114
+ else {
115
+ // Handle scalar values
116
+ const regex = new RegExp(`:${key}(?![\\w])`, 'g');
117
+ result = result.replace(regex, () => this.escape(value));
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ /**
123
+ * Execute a raw SQL query with retry support
124
+ */
125
+ async query(sql, options) {
126
+ // Default retry options for queries
127
+ const defaultQueryRetryOptions = {
128
+ max: 3,
129
+ timeout: 1000,
130
+ match: ['SQLITE_BUSY', 'SQLITE_LOCKED', 'database is locked', 'database table is locked'],
131
+ backoff: false,
132
+ backoffMultiplier: 2,
133
+ backoffMax: 10000,
134
+ };
135
+ const retryOptions = options?.retry;
136
+ const effectiveRetry = retryOptions
137
+ ? {
138
+ ...defaultQueryRetryOptions,
139
+ ...retryOptions,
140
+ match: retryOptions.match || defaultQueryRetryOptions.match,
141
+ }
142
+ : null;
143
+ const executeQuery = async () => {
144
+ if (!this.connection) {
145
+ throw new Error('Not connected to database');
146
+ }
147
+ const sqlUpper = sql.trim().toUpperCase();
148
+ // EXPLAIN / EXPLAIN QUERY PLAN and WITH (CTE) statements also return rows.
149
+ const isSelect = sqlUpper.startsWith('SELECT') ||
150
+ sqlUpper.startsWith('PRAGMA') ||
151
+ sqlUpper.startsWith('EXPLAIN') ||
152
+ sqlUpper.startsWith('WITH');
153
+ const stmt = this.connection.prepare(sql);
154
+ // Bind parameters for `?` / named placeholders. Several call sites in this
155
+ // dialect (bulkInsert, getCreateTable, getTableStatus, sqlite-advanced.ts
156
+ // listViews/getViewDefinition/etc.) build parameterized SQL and pass the
157
+ // values via `replacements` (array) or `bind` (array or named object) -
158
+ // previously these were silently dropped and better-sqlite3 would throw
159
+ // "Too few parameter values were provided" whenever placeholders were used.
160
+ const rawParams = options?.replacements ?? options?.bind ?? options?.bindings;
161
+ // better-sqlite3 only binds numbers/strings/bigints/buffers/null. Coerce
162
+ // the JS types the query/where builders commonly produce (boolean, Date,
163
+ // plain object/array) so those bindings don't throw at execution time.
164
+ const coerceBind = (v) => {
165
+ if (typeof v === 'boolean')
166
+ return v ? 1 : 0;
167
+ if (v instanceof Date)
168
+ return v.toISOString();
169
+ if (v !== null && v !== undefined && typeof v === 'object' && !Buffer.isBuffer(v)) {
170
+ return JSON.stringify(v);
171
+ }
172
+ return v;
173
+ };
174
+ const params = Array.isArray(rawParams)
175
+ ? rawParams.map(coerceBind)
176
+ : rawParams && typeof rawParams === 'object'
177
+ ? Object.fromEntries(Object.entries(rawParams).map(([k, v]) => [
178
+ k,
179
+ coerceBind(v),
180
+ ]))
181
+ : rawParams;
182
+ try {
183
+ if (isSelect) {
184
+ const rows = params === undefined
185
+ ? stmt.all()
186
+ : Array.isArray(params)
187
+ ? stmt.all(...params)
188
+ : stmt.all(params);
189
+ return {
190
+ rows,
191
+ rowCount: rows.length,
192
+ fields: [],
193
+ };
194
+ }
195
+ else {
196
+ const result = params === undefined
197
+ ? stmt.run()
198
+ : Array.isArray(params)
199
+ ? stmt.run(...params)
200
+ : stmt.run(params);
201
+ return {
202
+ rows: [],
203
+ rowCount: result.changes,
204
+ fields: [],
205
+ lastInsertRowid: result.lastInsertRowid !== undefined ? Number(result.lastInsertRowid) : undefined,
206
+ };
207
+ }
208
+ }
209
+ catch (error) {
210
+ throw new Error(`Query error: ${error}`);
211
+ }
212
+ };
213
+ // If retry options are provided, use retry logic
214
+ if (effectiveRetry && effectiveRetry.max > 0) {
215
+ return this.executeWithRetry(executeQuery, effectiveRetry);
216
+ }
217
+ return executeQuery();
218
+ }
219
+ /**
220
+ * Stream query results using better-sqlite3's native `Statement#iterate()`
221
+ * — a real in-process cursor (no round trips at all, since sqlite is
222
+ * embedded), so this is more efficient than the generic paginated
223
+ * LIMIT/OFFSET fallback other dialects use.
224
+ * @param sql - The SELECT statement to stream
225
+ * @param options - Streaming options (batch size only affects backpressure batching, not fetch round trips)
226
+ */
227
+ queryStream(sql, options) {
228
+ const stream = new stream_1.Readable({ objectMode: true, highWaterMark: options?.highWaterMark || 1000, read() { } });
229
+ if (!this.connection) {
230
+ process.nextTick(() => stream.destroy(new Error('Not connected to database')));
231
+ return stream;
232
+ }
233
+ let iterator;
234
+ try {
235
+ iterator = this.connection.prepare(sql).iterate();
236
+ }
237
+ catch (error) {
238
+ process.nextTick(() => stream.destroy(error instanceof Error ? error : new Error(String(error))));
239
+ return stream;
240
+ }
241
+ // Pull rows out of the synchronous iterator on a microtask loop so we
242
+ // still respect Readable backpressure (stop pulling once push() returns
243
+ // false, resume from _read()).
244
+ const pump = () => {
245
+ let canContinue = true;
246
+ while (canContinue) {
247
+ const next = iterator.next();
248
+ if (next.done) {
249
+ stream.push(null);
250
+ return;
251
+ }
252
+ const row = next.value;
253
+ if (options?.mapToModel && options?.model) {
254
+ const instance = new options.model();
255
+ Object.assign(instance, row);
256
+ canContinue = stream.push(instance);
257
+ }
258
+ else {
259
+ canContinue = stream.push(row);
260
+ }
261
+ }
262
+ };
263
+ stream._read = pump;
264
+ return stream;
265
+ }
266
+ /**
267
+ * Execute a function with retry logic for query execution
268
+ */
269
+ async executeWithRetry(fn, retryOptions) {
270
+ let lastError;
271
+ const { backoff, backoffMultiplier, backoffMax } = retryOptions;
272
+ for (let attempt = 0; attempt <= retryOptions.max; attempt++) {
273
+ try {
274
+ return await fn();
275
+ }
276
+ catch (error) {
277
+ lastError = error;
278
+ const errorMessage = lastError.message;
279
+ // Check if this error should be retried
280
+ const shouldRetry = retryOptions.match.some((pattern) => errorMessage.toLowerCase().includes(pattern.toLowerCase()));
281
+ if (!shouldRetry || attempt === retryOptions.max) {
282
+ throw lastError;
283
+ }
284
+ // Calculate delay - use exponential backoff if enabled
285
+ let delay;
286
+ if (backoff) {
287
+ delay = retryOptions.timeout * Math.pow(backoffMultiplier || 2, attempt);
288
+ delay = Math.min(delay, backoffMax || 10000);
289
+ }
290
+ else {
291
+ delay = retryOptions.timeout;
292
+ }
293
+ // Add some jitter to avoid thundering herd
294
+ const jitter = Math.random() * 100;
295
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
296
+ }
297
+ }
298
+ throw lastError;
299
+ }
300
+ /**
301
+ * Escape a value for use in a query
302
+ */
303
+ escape(value) {
304
+ if (value === null) {
305
+ return 'NULL';
306
+ }
307
+ // Handle undefined - return NULL (similar to null)
308
+ if (value === undefined) {
309
+ return 'NULL';
310
+ }
311
+ // Handle Literal (raw SQL) values
312
+ if (value instanceof prorm_1.Literal) {
313
+ return value.val;
314
+ }
315
+ if (typeof value === 'string') {
316
+ return `'${this.escapeString(value)}'`;
317
+ }
318
+ if (typeof value === 'number') {
319
+ return String(value);
320
+ }
321
+ if (typeof value === 'bigint') {
322
+ return value.toString();
323
+ }
324
+ if (typeof value === 'boolean') {
325
+ return value ? '1' : '0';
326
+ }
327
+ if (value instanceof Date) {
328
+ return `'${value.toISOString()}'`;
329
+ }
330
+ if (Buffer.isBuffer(value)) {
331
+ return `X'${value.toString('hex')}'`;
332
+ }
333
+ // JSON stringify objects
334
+ return `'${this.escapeString(JSON.stringify(value))}'`;
335
+ }
336
+ /**
337
+ * Build a JSON extraction query for SQLite
338
+ * SQLite: json_extract(column, '$.path')
339
+ */
340
+ buildJsonExtract(column, path, asText = true) {
341
+ const columnRef = this.escapeId(column);
342
+ const normalizedPath = this.escapeString(path.startsWith('$.') ? path : `$.${path}`);
343
+ // SQLite's json_extract returns JSON by default. When comparing to text values,
344
+ // we need to CAST to TEXT to ensure proper comparison (equivalent to PostgreSQL's ->> operator)
345
+ if (asText) {
346
+ return `CAST(json_extract(${columnRef}, '${normalizedPath}') AS TEXT)`;
347
+ }
348
+ return `json_extract(${columnRef}, '${normalizedPath}')`;
349
+ }
350
+ /**
351
+ * Build a JSON contains query for SQLite
352
+ * SQLite: uses json_each and json_extract for containment checks
353
+ */
354
+ buildJsonContains(column, value, path) {
355
+ const columnRef = this.escapeId(column);
356
+ // json_extract() returns scalars (string/number/boolean) unwrapped, but returns
357
+ // objects/arrays as compact JSON text. JSON.stringify()-ing a scalar (e.g. the
358
+ // string "foo" -> '"foo"') would never match the unwrapped value SQLite returns,
359
+ // so only stringify non-scalar values here.
360
+ const isScalar = value === null || (typeof value !== 'object' && typeof value !== 'function');
361
+ const comparisonValue = isScalar ? value : JSON.stringify(value);
362
+ if (path) {
363
+ const normalizedPath = path.startsWith('$.') ? path : `$.${path}`;
364
+ // SQLite: check if the path contains the value
365
+ return {
366
+ sql: `json_extract(${columnRef}, '${this.escapeString(normalizedPath)}') = ?`,
367
+ values: [comparisonValue],
368
+ };
369
+ }
370
+ // For JSON objects, compare as stringified JSON
371
+ return {
372
+ sql: `${columnRef} = ?`,
373
+ values: [JSON.stringify(value)],
374
+ };
375
+ }
376
+ /**
377
+ * Build a JSON has key query for SQLite
378
+ * SQLite: json_each or json_extract for key existence
379
+ */
380
+ buildJsonHasKey(column, key, type = 'one') {
381
+ const columnRef = this.escapeId(column);
382
+ const normalizedKey = this.escapeString(key.startsWith('$.') ? key.substring(2) : key);
383
+ // SQLite: check if key exists by checking if json_extract returns non-null
384
+ return `json_extract(${columnRef}, '$.${normalizedKey}') IS NOT NULL`;
385
+ }
386
+ /**
387
+ * Build a JSON path query for SQLite
388
+ */
389
+ buildJsonPathQuery(column, path, value, operator = '=') {
390
+ const values = [];
391
+ const columnRef = this.escapeId(column);
392
+ const normalizedPath = this.escapeString(path.startsWith('$.') ? path : `$.${path}`);
393
+ const jsonExtract = `json_extract(${columnRef}, '${normalizedPath}')`;
394
+ if (value !== undefined) {
395
+ values.push(value);
396
+ return { sql: `${jsonExtract} ${operator} ?`, values };
397
+ }
398
+ return { sql: jsonExtract, values };
399
+ }
400
+ /**
401
+ * Build a `json_group_array()` aggregate expression for SQLite.
402
+ *
403
+ * `json_group_array()` is SQLite's JSON1 aggregate for collecting the
404
+ * values of `valueExpr` (evaluated once per row in the current group) into
405
+ * a single JSON array. Commonly used for "hasMany as nested JSON" style
406
+ * queries, e.g. rolling up child rows into a JSON array column of a
407
+ * grouped parent query.
408
+ *
409
+ * @param valueExpr - a column name or SQL expression to collect per row.
410
+ * Bare identifiers are escaped as columns; anything containing SQL
411
+ * syntax (parens, function calls, `.`) is passed through as-is so
412
+ * callers can pass expressions like `json_object('id', id)`.
413
+ * @param options.distinct - emit `json_group_array(DISTINCT ...)`
414
+ */
415
+ buildJsonGroupArray(valueExpr, options) {
416
+ const expr = this.escapeAggregateExpr(valueExpr);
417
+ const distinct = options?.distinct ? 'DISTINCT ' : '';
418
+ return `json_group_array(${distinct}${expr})`;
419
+ }
420
+ /**
421
+ * Build a `json_group_object()` aggregate expression for SQLite.
422
+ *
423
+ * `json_group_object()` collects `(keyExpr, valueExpr)` pairs from the
424
+ * rows of the current group into a single JSON object, keyed by
425
+ * `keyExpr`. Useful for building a JSON map of `{ [key]: value }` from
426
+ * grouped rows (e.g. nested-relation queries keyed by id).
427
+ */
428
+ buildJsonGroupObject(keyExpr, valueExpr) {
429
+ const key = this.escapeAggregateExpr(keyExpr);
430
+ const value = this.escapeAggregateExpr(valueExpr);
431
+ return `json_group_object(${key}, ${value})`;
432
+ }
433
+ /**
434
+ * Escape a column/expression argument passed to an aggregate builder.
435
+ * Bare identifiers (letters, digits, underscore, dot for table.column)
436
+ * are quoted as an identifier; anything else (function calls, literals,
437
+ * `*`, etc.) is passed through untouched so callers can supply arbitrary
438
+ * SQL expressions.
439
+ */
440
+ escapeAggregateExpr(expr) {
441
+ if (/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(expr)) {
442
+ return expr
443
+ .split('.')
444
+ .map((part) => this.escapeId(part))
445
+ .join('.');
446
+ }
447
+ return expr;
448
+ }
449
+ /**
450
+ * Build a `json_patch()` expression for SQLite (RFC 7396 JSON Merge Patch).
451
+ *
452
+ * `json_patch(target, patch)` applies an RFC 7396 merge patch to the JSON
453
+ * stored in `column`, returning the patched JSON document. Unlike
454
+ * `json_set()` (which sets a single path) this can add, replace, and
455
+ * remove multiple keys in one call - keys in `patch` set to `null` are
456
+ * removed from the result.
457
+ *
458
+ * @param column - the JSON column to patch
459
+ * @param patch - a JS object/value to be JSON-stringified and applied as
460
+ * the merge patch; passed as a bound parameter, not interpolated.
461
+ */
462
+ buildJsonPatch(column, patch) {
463
+ const columnRef = this.escapeId(column);
464
+ return {
465
+ sql: `json_patch(${columnRef}, json(?))`,
466
+ values: [JSON.stringify(patch)],
467
+ };
468
+ }
469
+ /**
470
+ * Escape a string for SQL
471
+ */
472
+ escapeString(str) {
473
+ return str.replace(/'/g, "''");
474
+ }
475
+ /**
476
+ * Escape an identifier (table name, column name, etc.)
477
+ */
478
+ escapeId(identifier) {
479
+ const id = String(identifier ?? '');
480
+ return `"${id.replace(/"/g, '""')}"`;
481
+ }
482
+ /**
483
+ * Quote an identifier (column name, table name)
484
+ * Same as escapeId for SQLite
485
+ */
486
+ quoteIdentifier(identifier) {
487
+ return this.escapeId(identifier);
488
+ }
489
+ /**
490
+ * Quote a table name
491
+ * Same as escapeId for SQLite
492
+ * If schema is provided, returns schema.tableName (SQLite treats schemas as prefixes)
493
+ */
494
+ quoteTable(tableName, schema) {
495
+ if (schema) {
496
+ return `${this.escapeId(schema)}.${this.escapeId(tableName)}`;
497
+ }
498
+ return this.escapeId(tableName);
499
+ }
500
+ /**
501
+ * Get the database version
502
+ */
503
+ async getDatabaseVersion() {
504
+ if (!this.connection) {
505
+ throw new Error('Not connected to database');
506
+ }
507
+ const result = this.connection.pragma('user_version', { simple: true });
508
+ return String(result);
509
+ }
510
+ /**
511
+ * Generate SQL for creating a database
512
+ * SQLite uses ATTACH DATABASE for creating new database files
513
+ */
514
+ createDatabaseSQL(options) {
515
+ const sql = `CREATE DATABASE "${options.name}"`;
516
+ const optionsList = [];
517
+ if (options.encoding)
518
+ optionsList.push(`ENCODING = '${options.encoding}'`);
519
+ if (options.lcCollate)
520
+ optionsList.push(`LC_COLLATE = '${options.lcCollate}'`);
521
+ if (options.lcCtype)
522
+ optionsList.push(`LC_CTYPE = '${options.lcCtype}'`);
523
+ if (options.collate)
524
+ optionsList.push(`COLLATE = '${options.collate}'`);
525
+ if (optionsList.length > 0) {
526
+ return sql + ' ' + optionsList.join(' ');
527
+ }
528
+ return sql;
529
+ }
530
+ /**
531
+ * Generate SQL for dropping a database
532
+ * SQLite uses DETACH DATABASE for dropping database files
533
+ */
534
+ dropDatabaseSQL(name) {
535
+ return `DROP DATABASE "${name}"`;
536
+ }
537
+ /**
538
+ * Attach an external SQLite database file to the current connection under
539
+ * `alias`, making its tables reachable as `alias.tableName` in subsequent
540
+ * queries (cross-database queries/joins). Executes a real
541
+ * `ATTACH DATABASE ? AS <alias>` statement rather than just documenting the
542
+ * capability.
543
+ *
544
+ * @param path - filesystem path to the database file to attach (or
545
+ * `:memory:` for an attached in-memory database)
546
+ * @param alias - schema name subsequent SQL will use to refer to the
547
+ * attached database (e.g. `alias.users`)
548
+ */
549
+ async attachDatabase(path, alias) {
550
+ if (!this.connection) {
551
+ throw new Error('Not connected to database');
552
+ }
553
+ if (!alias || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(alias)) {
554
+ throw new Error(`Invalid database alias: ${alias}`);
555
+ }
556
+ const aliasId = this.escapeId(alias);
557
+ this.connection.prepare(`ATTACH DATABASE ? AS ${aliasId}`).run(path);
558
+ }
559
+ /**
560
+ * Detach a previously-attached database from the current connection.
561
+ * Executes a real `DETACH DATABASE <alias>` statement.
562
+ *
563
+ * @param alias - the schema name the database was attached under via
564
+ * `attachDatabase()`
565
+ */
566
+ async detachDatabase(alias) {
567
+ if (!this.connection) {
568
+ throw new Error('Not connected to database');
569
+ }
570
+ if (!alias || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(alias)) {
571
+ throw new Error(`Invalid database alias: ${alias}`);
572
+ }
573
+ const aliasId = this.escapeId(alias);
574
+ this.connection.exec(`DETACH DATABASE ${aliasId}`);
575
+ }
576
+ /**
577
+ * Generate SQL for creating a savepoint
578
+ */
579
+ createSavepointSQL(name) {
580
+ const savepointName = name || `sp_${Date.now()}`;
581
+ return `SAVEPOINT ${savepointName}`;
582
+ }
583
+ /**
584
+ * Generate SQL for releasing a savepoint
585
+ */
586
+ releaseSavepointSQL(name) {
587
+ return `RELEASE SAVEPOINT ${name}`;
588
+ }
589
+ /**
590
+ * Generate SQL for rolling back to a savepoint
591
+ */
592
+ rollbackToSavepointSQL(name) {
593
+ return `ROLLBACK TO SAVEPOINT ${name}`;
594
+ }
595
+ /**
596
+ * Create a new table
597
+ */
598
+ async createTable(tableName, columns, options) {
599
+ const columnDefs = [];
600
+ for (const [columnName, definition] of Object.entries(columns)) {
601
+ // Skip virtual fields - they don't create database columns
602
+ const typeObj = definition.type;
603
+ if (typeObj && typeObj.key === 'VIRTUAL') {
604
+ continue;
605
+ }
606
+ columnDefs.push(this.getColumnDefinitionSql(columnName, definition));
607
+ }
608
+ // Add constraints from options
609
+ if (options?.constraints) {
610
+ for (const constraint of options.constraints) {
611
+ const constraintSql = this.buildConstraintSql(constraint);
612
+ if (constraintSql) {
613
+ columnDefs.push(constraintSql);
614
+ }
615
+ }
616
+ }
617
+ let sql = `CREATE TABLE`;
618
+ if (options?.ifNotExists) {
619
+ sql += ' IF NOT EXISTS';
620
+ }
621
+ sql += ` ${this.escapeId(tableName)} (${columnDefs.join(', ')})`;
622
+ // SQLite 3.37+ STRICT tables and WITHOUT ROWID tables are both table-level
623
+ // modifiers that go after the closing paren of the column-def list, and
624
+ // can be combined as `) STRICT, WITHOUT ROWID`.
625
+ const tableModifiers = [];
626
+ if (options?.strict) {
627
+ tableModifiers.push('STRICT');
628
+ }
629
+ if (options?.withoutRowid) {
630
+ tableModifiers.push('WITHOUT ROWID');
631
+ }
632
+ if (tableModifiers.length > 0) {
633
+ sql += ` ${tableModifiers.join(', ')}`;
634
+ }
635
+ // SQLite does not support ENGINE, CHARSET, COLLATE in CREATE TABLE
636
+ // These options are ignored for SQLite but can be logged for debugging
637
+ if (options?.engine) {
638
+ console.warn(`SQLite: engine option '${options.engine}' is not supported and will be ignored`);
639
+ }
640
+ if (options?.charset) {
641
+ console.warn(`SQLite: charset option '${options.charset}' is not supported and will be ignored`);
642
+ }
643
+ if (options?.collate) {
644
+ // SQLite supports COLLATE in column definitions, not table-level
645
+ // Add COLLATE to text columns instead
646
+ console.warn(`SQLite: table-level collate option is not supported, apply collate to text columns instead`);
647
+ }
648
+ if (options?.rowFormat) {
649
+ console.warn(`SQLite: rowFormat option '${options.rowFormat}' is not supported and will be ignored`);
650
+ }
651
+ // Handle uniqueKeys for SQLite by creating unique indexes after table creation
652
+ let uniqueKeysToCreate = [];
653
+ if (options?.uniqueKeys) {
654
+ if (Array.isArray(options.uniqueKeys)) {
655
+ // New format: UniqueKeyOptions[]
656
+ uniqueKeysToCreate = options.uniqueKeys.map((uk) => ({
657
+ name: uk.name || `${tableName}_${uk.fields.join('_')}_key`,
658
+ fields: uk.fields,
659
+ }));
660
+ }
661
+ else {
662
+ // Legacy format: Record<string, string[]>
663
+ uniqueKeysToCreate = Object.entries(options.uniqueKeys).map(([keyName, fields]) => ({
664
+ name: keyName || `uq_${tableName}_${fields.join('_')}`,
665
+ fields,
666
+ }));
667
+ }
668
+ }
669
+ if (options?.initialAutoIncrement) {
670
+ console.warn(`SQLite: initialAutoIncrement option '${options.initialAutoIncrement}' is not supported and will be ignored`);
671
+ }
672
+ // Handle comment - SQLite doesn't support table comments natively
673
+ // Store in sqlite_master as a comment (extended feature)
674
+ let tableComment;
675
+ if (options?.comment) {
676
+ tableComment = options.comment;
677
+ }
678
+ await this.query(sql);
679
+ // Store table comment as extended property if provided
680
+ // SQLite has no native mechanism to attach a comment to a table, so this
681
+ // is a documented no-op kept for interface compatibility with other dialects.
682
+ if (tableComment) {
683
+ try {
684
+ // no-op: SQLite does not support table comments
685
+ }
686
+ catch (e) {
687
+ // Ignore if extended comments aren't supported
688
+ }
689
+ }
690
+ // SQLite doesn't support initialAutoIncrement in CREATE TABLE
691
+ // It's controlled by AUTOINCREMENT keyword on column definitions
692
+ if (options?.initialAutoIncrement) {
693
+ console.warn(`SQLite: initialAutoIncrement option is not supported in CREATE TABLE`);
694
+ }
695
+ // Create indexes after table creation
696
+ if (options?.indexes) {
697
+ for (const index of options.indexes) {
698
+ await this.addIndex(tableName, index.name || `idx_${tableName}_${index.fields.join('_')}`, index.fields, {
699
+ unique: index.unique,
700
+ type: index.type,
701
+ using: index.using,
702
+ where: index.where,
703
+ expression: index.expression,
704
+ });
705
+ }
706
+ }
707
+ }
708
+ /**
709
+ * Build constraint SQL for table creation
710
+ */
711
+ buildConstraintSql(constraint) {
712
+ const name = constraint.name ? `CONSTRAINT "${constraint.name}" ` : '';
713
+ const fields = constraint.fields?.map((f) => this.escapeId(f)).join(', ') || '';
714
+ switch (constraint.type) {
715
+ case 'PRIMARY KEY':
716
+ return `${name}PRIMARY KEY (${fields})`;
717
+ case 'UNIQUE':
718
+ return `${name}UNIQUE (${fields})`;
719
+ case 'FOREIGN KEY':
720
+ if (!constraint.references)
721
+ return null;
722
+ let fkSql = `${name}FOREIGN KEY (${fields}) `;
723
+ // Handle composite foreign keys (string | string[])
724
+ const refField = constraint.references.field;
725
+ const refFieldSql = Array.isArray(refField)
726
+ ? `(${refField.map((f) => this.escapeId(f)).join(', ')})`
727
+ : this.escapeId(refField);
728
+ fkSql += `REFERENCES ${this.escapeId(constraint.references.table)}${refFieldSql}`;
729
+ if (constraint.references.onDelete) {
730
+ fkSql += ` ON DELETE ${constraint.references.onDelete}`;
731
+ }
732
+ if (constraint.references.onUpdate) {
733
+ fkSql += ` ON UPDATE ${constraint.references.onUpdate}`;
734
+ }
735
+ return fkSql;
736
+ case 'CHECK':
737
+ if (!constraint.check)
738
+ return null;
739
+ return `${name}CHECK (${constraint.check})`;
740
+ default:
741
+ return null;
742
+ }
743
+ }
744
+ /**
745
+ * Generate column definition SQL
746
+ */
747
+ getColumnDefinitionSql(columnName, definition) {
748
+ let sql = `${this.escapeId(columnName)} ${this.getDataTypeSql(definition.type, columnName)}`;
749
+ if (definition.allowNull === false) {
750
+ sql += ' NOT NULL';
751
+ }
752
+ if (definition.defaultValue !== undefined) {
753
+ sql += ` DEFAULT ${this.getDefaultValue(definition.defaultValue)}`;
754
+ }
755
+ if (definition.primaryKey) {
756
+ sql += ' PRIMARY KEY';
757
+ // SQLite AUTOINCREMENT is only valid on INTEGER PRIMARY KEY columns
758
+ if (definition.autoIncrement) {
759
+ const resolvedType = this.getDataTypeSql(definition.type).toUpperCase();
760
+ if (resolvedType === 'INTEGER') {
761
+ sql += ' AUTOINCREMENT';
762
+ }
763
+ }
764
+ }
765
+ if (definition.unique) {
766
+ sql += ' UNIQUE';
767
+ }
768
+ if (definition.references) {
769
+ // Handle composite foreign keys (string | string[])
770
+ const refField5 = definition.references.field;
771
+ const refFieldSql5 = Array.isArray(refField5)
772
+ ? `(${refField5.map((f) => this.escapeId(f)).join(', ')})`
773
+ : `(${this.escapeId(refField5)})`;
774
+ sql += ` REFERENCES ${this.escapeId(definition.references.table)}${refFieldSql5}`;
775
+ if (definition.references.onDelete) {
776
+ sql += ` ON DELETE ${definition.references.onDelete}`;
777
+ }
778
+ if (definition.references.onUpdate) {
779
+ sql += ` ON UPDATE ${definition.references.onUpdate}`;
780
+ }
781
+ }
782
+ // SQLite does not support column-level comments in CREATE TABLE
783
+ // Comments are stored separately and not part of the column definition
784
+ return sql;
785
+ }
786
+ /**
787
+ * Get default value SQL
788
+ */
789
+ getDefaultValue(value) {
790
+ // Handle Literal (raw SQL) values - insert directly without escaping
791
+ if (value instanceof prorm_1.Literal) {
792
+ return value.val;
793
+ }
794
+ if (value === null) {
795
+ return 'NULL';
796
+ }
797
+ if (typeof value === 'string') {
798
+ return `'${this.escapeString(value)}'`;
799
+ }
800
+ if (typeof value === 'number' || typeof value === 'boolean') {
801
+ return String(value);
802
+ }
803
+ if (value instanceof Date) {
804
+ return `'${value.toISOString()}'`;
805
+ }
806
+ // For functions like CURRENT_TIMESTAMP
807
+ if (typeof value === 'string' && value.toUpperCase().includes('CURRENT_')) {
808
+ return value;
809
+ }
810
+ // Handle DataType instances with toDefaultValue method (e.g., DataTypes.NOW)
811
+ if (typeof value === 'object' &&
812
+ value !== null &&
813
+ typeof value.toDefaultValue === 'function') {
814
+ return value.toDefaultValue();
815
+ }
816
+ return `'${this.escapeString(String(value))}'`;
817
+ }
818
+ /**
819
+ * Drop a table
820
+ */
821
+ async dropTable(tableName, options) {
822
+ let sql = 'DROP TABLE';
823
+ if (options?.ifExists) {
824
+ sql += ' IF EXISTS';
825
+ }
826
+ sql += ` ${this.escapeId(tableName)}`;
827
+ // SQLite has no `DROP TABLE ... CASCADE` syntax - appending it produces a
828
+ // syntax error, which made every `cascade: true` drop fail (silently
829
+ // swallowed by callers like `Prorm.syncForce()`, which only logs a
830
+ // warning and moves on). That meant `sync({ force: true })` never
831
+ // actually dropped/recreated tables that already existed, since
832
+ // `CREATE TABLE IF NOT EXISTS` is then a no-op against the stale table.
833
+ // SQLite doesn't need an explicit CASCADE: dropping a table just removes
834
+ // its schema entry - it doesn't cascade-delete rows in other tables the
835
+ // way `ON DELETE CASCADE` does, so `cascade` is a no-op here rather than
836
+ // an error.
837
+ await this.query(sql);
838
+ }
839
+ /**
840
+ * Create a partitioned table (SQLite emulation)
841
+ * Creates base table + child partition tables + union view
842
+ */
843
+ async createPartitionedTable(tableName, columns, options) {
844
+ const partitionConfig = options?.partitionBy;
845
+ // Handle partitions being a number (e.g., partitions: 2 for KEY partitioning)
846
+ // or undefined, or an array
847
+ const partitions = Array.isArray(options?.partitions)
848
+ ? options.partitions
849
+ : typeof options?.partitions === 'number'
850
+ ? []
851
+ : [];
852
+ // Create the base table (used as template)
853
+ await this.createTable(tableName, columns, {
854
+ ...options,
855
+ ifNotExists: options?.ifNotExists ?? true,
856
+ });
857
+ // Store partition metadata
858
+ const metadataTable = `${tableName}_partitions`;
859
+ await this.query(`CREATE TABLE IF NOT EXISTS ${this.escapeId(metadataTable)} (
860
+ partition_name TEXT PRIMARY KEY,
861
+ parent_table TEXT NOT NULL,
862
+ bound TEXT,
863
+ is_detached INTEGER DEFAULT 0,
864
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
865
+ )`);
866
+ // Create partitions if specified
867
+ for (const partition of partitions) {
868
+ await this.createPartition({
869
+ parentTable: tableName,
870
+ name: partition.name,
871
+ bound: partition.bound,
872
+ tablespace: partition.tablespace,
873
+ storageParameters: partition.storageParameters,
874
+ });
875
+ }
876
+ // Create a UNION ALL view to provide a unified interface
877
+ if (partitions.length > 0) {
878
+ const partitionSelects = partitions.map((p) => `SELECT * FROM ${this.escapeId(p.name)}`).join('\n UNION ALL\n ');
879
+ const viewName = `${tableName}_union`;
880
+ await this.createView(viewName, partitionSelects, { replace: true });
881
+ }
882
+ }
883
+ /**
884
+ * Create a partition for an existing partitioned table (SQLite emulation)
885
+ */
886
+ async createPartition(options) {
887
+ const { parentTable, name, bound } = options;
888
+ // Get the column structure from the parent table
889
+ const parentColumns = await this.describeTable(parentTable);
890
+ // Create the partition table with the same structure
891
+ const partitionColumns = {};
892
+ for (const [colName, colDef] of Object.entries(parentColumns)) {
893
+ partitionColumns[colName] = {
894
+ type: colDef.type,
895
+ allowNull: colDef.allowNull,
896
+ defaultValue: colDef.defaultValue,
897
+ autoIncrement: colDef.autoIncrement,
898
+ };
899
+ }
900
+ // Create the partition table
901
+ await this.createTable(name, partitionColumns, { ifNotExists: true });
902
+ // Insert partition metadata
903
+ const metadataTable = `${parentTable}_partitions`;
904
+ const boundStr = bound ? JSON.stringify(bound) : 'NULL';
905
+ await this.query(`INSERT OR IGNORE INTO ${this.escapeId(metadataTable)} (partition_name, parent_table, bound) VALUES (${this.escape(name)}, ${this.escape(parentTable)}, ${boundStr})`);
906
+ }
907
+ /**
908
+ * Attach a partition to a partitioned table (SQLite emulation)
909
+ */
910
+ async attachPartition(options) {
911
+ const { parentTable, partitionName } = options;
912
+ // Verify the partition table exists
913
+ const tables = await this.showTables();
914
+ if (!tables.includes(partitionName)) {
915
+ throw new Error(`Partition table '${partitionName}' does not exist`);
916
+ }
917
+ // Update partition metadata to mark as attached
918
+ const metadataTable = `${parentTable}_partitions`;
919
+ await this.query(`INSERT OR REPLACE INTO ${this.escapeId(metadataTable)} (partition_name, parent_table, is_detached) VALUES (${this.escape(partitionName)}, ${this.escape(parentTable)}, 0)`);
920
+ }
921
+ /**
922
+ * Detach a partition from a partitioned table (SQLite emulation)
923
+ * Marks the partition as detached but keeps the table
924
+ */
925
+ async detachPartition(options) {
926
+ const { partitionName } = options;
927
+ // Find the parent table from metadata
928
+ const partitions = await this.listPartitions(partitionName);
929
+ if (partitions.length === 0) {
930
+ throw new Error(`Partition '${partitionName}' not found`);
931
+ }
932
+ const partitionInfo = partitions[0];
933
+ const metadataTable = `${partitionInfo.parent_table}_partitions`;
934
+ // Mark partition as detached
935
+ await this.query(`UPDATE ${this.escapeId(metadataTable)} SET is_detached = 1 WHERE partition_name = ${this.escape(partitionName)}`);
936
+ }
937
+ /**
938
+ * Drop a partition (SQLite emulation)
939
+ */
940
+ async dropPartition(partitionName, options) {
941
+ const ifExists = options?.ifExists ?? false;
942
+ const cascade = options?.cascade ?? false;
943
+ // Find the parent table from metadata
944
+ const partitions = await this.listPartitions(partitionName);
945
+ let parentTable = null;
946
+ if (partitions.length > 0) {
947
+ parentTable = partitions[0].parent_table;
948
+ }
949
+ // Drop the partition table
950
+ let sql = 'DROP TABLE';
951
+ if (ifExists) {
952
+ sql += ' IF EXISTS';
953
+ }
954
+ sql += ` ${this.escapeId(partitionName)}`;
955
+ if (cascade) {
956
+ sql += ' CASCADE';
957
+ }
958
+ await this.query(sql);
959
+ // Remove partition metadata if parent table exists
960
+ if (parentTable) {
961
+ const metadataTable = `${parentTable}_partitions`;
962
+ try {
963
+ await this.query(`DELETE FROM ${this.escapeId(metadataTable)} WHERE partition_name = ${this.escape(partitionName)}`);
964
+ }
965
+ catch {
966
+ // Ignore if metadata table doesn't exist
967
+ }
968
+ }
969
+ }
970
+ /**
971
+ * Add a partition to an existing partitioned table (SQLite emulation)
972
+ * @param tableName - Name of the partitioned table
973
+ * @param partitionName - Name for the new partition
974
+ * @param partitionSpec - Partition specification
975
+ */
976
+ async addPartition(tableName, partitionName, partitionSpec) {
977
+ // SQLite does not support table partitioning
978
+ throw new Error(`Table partitioning is not supported by SQLite. ` +
979
+ `Please use PostgreSQL, MySQL, or another database that supports partitioning.`);
980
+ }
981
+ /**
982
+ * List partitions for a table (SQLite emulation)
983
+ */
984
+ async listPartitions(tableName) {
985
+ const metadataTable = `${tableName}_partitions`;
986
+ try {
987
+ const result = await this.query(`SELECT * FROM ${this.escapeId(metadataTable)}`);
988
+ return result.rows.map((row) => ({
989
+ name: row.partition_name,
990
+ parent_table: row.parent_table,
991
+ bound: row.bound,
992
+ is_detached: row.is_detached,
993
+ }));
994
+ }
995
+ catch {
996
+ // If metadata table doesn't exist, return empty array
997
+ return [];
998
+ }
999
+ }
1000
+ /**
1001
+ * Check if a table has partitions (SQLite emulation)
1002
+ */
1003
+ async hasPartition(tableName) {
1004
+ const partitions = await this.listPartitions(tableName);
1005
+ return partitions.length > 0;
1006
+ }
1007
+ // ==================== User Management Stubs ====================
1008
+ /**
1009
+ * Create a user (stub - not supported by SQLite)
1010
+ */
1011
+ async createUser(_username, _options) {
1012
+ throw new Error('User management is not supported by SQLite');
1013
+ }
1014
+ /**
1015
+ * Drop a user (stub - not supported by SQLite)
1016
+ */
1017
+ async dropUser(_username, _options) {
1018
+ throw new Error('User management is not supported by SQLite');
1019
+ }
1020
+ /**
1021
+ * List users (stub - not supported by SQLite)
1022
+ */
1023
+ async listUsers() {
1024
+ throw new Error('User management is not supported by SQLite');
1025
+ }
1026
+ /**
1027
+ * Create a role (stub - not supported by SQLite)
1028
+ */
1029
+ async createRole(_roleName, _options) {
1030
+ throw new Error('Role management is not supported by SQLite');
1031
+ }
1032
+ /**
1033
+ * Drop a role (stub - not supported by SQLite)
1034
+ */
1035
+ async dropRole(_roleName, _options) {
1036
+ throw new Error('Role management is not supported by SQLite');
1037
+ }
1038
+ /**
1039
+ * List roles (stub - not supported by SQLite)
1040
+ */
1041
+ async listRoles() {
1042
+ throw new Error('Role management is not supported by SQLite');
1043
+ }
1044
+ /**
1045
+ * Grant privileges (stub - not supported by SQLite)
1046
+ */
1047
+ async grant(_privilege, _on, _to) {
1048
+ throw new Error('User management is not supported by SQLite');
1049
+ }
1050
+ /**
1051
+ * Revoke privileges (stub - not supported by SQLite)
1052
+ */
1053
+ async revoke(_privilege, _on, _from) {
1054
+ throw new Error('User management is not supported by SQLite');
1055
+ }
1056
+ /**
1057
+ * Add a column to a table
1058
+ */
1059
+ async addColumn(tableName, columnName, definition) {
1060
+ const sql = `ALTER TABLE ${this.escapeId(tableName)} ADD COLUMN ${this.getColumnDefinitionSql(columnName, definition)}`;
1061
+ await this.query(sql);
1062
+ }
1063
+ /**
1064
+ * Remove a column from a table
1065
+ */
1066
+ async removeColumn(tableName, columnName) {
1067
+ // SQLite doesn't support DROP COLUMN directly, need to recreate table
1068
+ await this.recreateTableWithoutColumn(tableName, columnName);
1069
+ }
1070
+ /**
1071
+ * Change a column definition
1072
+ */
1073
+ async changeColumn(tableName, columnName, definition) {
1074
+ // SQLite doesn't support ALTER COLUMN, need to recreate table
1075
+ await this.recreateTableModifyColumn(tableName, columnName, definition);
1076
+ }
1077
+ /**
1078
+ * Show all tables in the database
1079
+ */
1080
+ async showTables() {
1081
+ const sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
1082
+ const result = await this.query(sql);
1083
+ return result.rows.map((row) => row.name);
1084
+ }
1085
+ /**
1086
+ * Get table status (SQLite implementation)
1087
+ */
1088
+ async getTableStatus(tableName) {
1089
+ const sql = tableName
1090
+ ? "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name = ?"
1091
+ : "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'";
1092
+ const result = tableName
1093
+ ? await this.query(sql, { replacements: [tableName] })
1094
+ : await this.query(sql);
1095
+ return result.rows;
1096
+ }
1097
+ /**
1098
+ * Get table create statement (SQLite implementation)
1099
+ */
1100
+ async getCreateTable(tableName) {
1101
+ const sql = "SELECT sql FROM sqlite_master WHERE type='table' AND name = ?";
1102
+ const result = await this.query(sql, { replacements: [tableName] });
1103
+ return result.rows[0]?.sql || '';
1104
+ }
1105
+ /**
1106
+ * Get MariaDB-specific version info (not supported in SQLite)
1107
+ * This method exists for compatibility when tests fall back to SQLite
1108
+ */
1109
+ async getMariaDBVersionInfo() {
1110
+ throw new Error('getMariaDBVersionInfo is not supported in SQLite. Use MariaDB dialect for this feature.');
1111
+ }
1112
+ /**
1113
+ * Show constraints for a table (SQLite)
1114
+ */
1115
+ async showConstraints(tableName) {
1116
+ const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
1117
+ const result = await this.query(sql);
1118
+ return result.rows.map((row) => ({
1119
+ name: row.name,
1120
+ type: row.type,
1121
+ notnull: row.notnull === 1,
1122
+ dflt_value: row.dflt_value,
1123
+ pk: row.pk === 1,
1124
+ }));
1125
+ }
1126
+ /**
1127
+ * Show indexes for a table
1128
+ */
1129
+ async showIndexes(tableName) {
1130
+ const sql = `PRAGMA index_list(${this.escapeId(tableName)})`;
1131
+ const result = await this.query(sql);
1132
+ return result.rows.map((row) => ({
1133
+ name: row.name,
1134
+ unique: row.unique === 1,
1135
+ origin: row.origin,
1136
+ }));
1137
+ }
1138
+ /**
1139
+ * Create a schema
1140
+ * SQLite doesn't have native schema support, but we can use ATTACH DATABASE
1141
+ * For now, this is a no-op for compatibility with other dialects
1142
+ * @param schema - The schema name to create
1143
+ */
1144
+ async createSchema(_schema) {
1145
+ // SQLite doesn't have native schema support
1146
+ // Schemas are typically simulated using database files or naming conventions
1147
+ // For compatibility, this is a no-op - there is no SQL statement to execute
1148
+ return;
1149
+ }
1150
+ /**
1151
+ * Drop a schema
1152
+ * SQLite doesn't have native schema support
1153
+ * For now, this is a no-op for compatibility with other dialects
1154
+ * @param schema - The schema name to drop
1155
+ * @param options - Drop options
1156
+ */
1157
+ async dropSchema(_schema, _options) {
1158
+ // SQLite doesn't have native schema support
1159
+ // For compatibility, this is a no-op - there is no SQL statement to execute
1160
+ return;
1161
+ }
1162
+ /**
1163
+ * Show all schemas in the database
1164
+ * SQLite doesn't have native schema support, returns default schema
1165
+ */
1166
+ async showAllSchemas() {
1167
+ // SQLite only has the main schema
1168
+ return ['main'];
1169
+ }
1170
+ /**
1171
+ * List all schemas (SQLite only has main)
1172
+ */
1173
+ async listSchemas() {
1174
+ return this.showAllSchemas();
1175
+ }
1176
+ /**
1177
+ * VACUUM the database - reclaims disk space and defragments
1178
+ * @param options - VACUUM options
1179
+ */
1180
+ async vacuum(options) {
1181
+ return this.advanced.vacuum(options);
1182
+ }
1183
+ /**
1184
+ * ANALYZE the database - updates statistics for query optimizer
1185
+ * @param target - Optional table or index name
1186
+ */
1187
+ async analyze(target) {
1188
+ return this.advanced.analyze(target);
1189
+ }
1190
+ /**
1191
+ * Get database information using PRAGMA
1192
+ */
1193
+ async getDatabaseInfo() {
1194
+ return this.advanced.getDatabaseInfo();
1195
+ }
1196
+ /**
1197
+ * Create an FTS5 virtual table for full-text search
1198
+ */
1199
+ async createFTS5Table(tableName, columns, options) {
1200
+ return this.advanced.createFTS5Table(tableName, columns, options);
1201
+ }
1202
+ /**
1203
+ * Perform full-text search
1204
+ */
1205
+ async fullTextSearch(ftsTable, searchQuery, options) {
1206
+ return this.advanced.fullTextSearch(ftsTable, searchQuery, options);
1207
+ }
1208
+ // ==================== Stored Procedures ====================
1209
+ // SQLite does not support stored procedures natively
1210
+ // These methods provide helpful messages explaining limitations and workarounds
1211
+ /**
1212
+ * Create a stored procedure (SQLite - Not Supported)
1213
+ *
1214
+ * SQLite does not support stored procedures natively. However, you can achieve
1215
+ * similar functionality using:
1216
+ *
1217
+ * 1. Application-level code: Move business logic into your application code
1218
+ * 2. Views: For read-only operations
1219
+ * 3. Triggers: For automated responses to data changes
1220
+ * 4. FTS5 virtual tables: For full-text search operations
1221
+ * 5. SQLite ATTACH: Connect to another database that supports stored procedures
1222
+ *
1223
+ * Example workaround using application code:
1224
+ * ```typescript
1225
+ * // Instead of:
1226
+ * await db.executeStoredProcedure({ procedureName: 'calculateTotal', params: { orderId: 1 } });
1227
+ *
1228
+ * // Use application-level code:
1229
+ * const total = await calculateTotal(orderId);
1230
+ * ```
1231
+ */
1232
+ async createStoredProcedure(options) {
1233
+ const procName = options.name;
1234
+ throw new Error(`SQLite does not support stored procedures. Cannot create '${procName}'.\n\n` +
1235
+ `Workarounds:\n` +
1236
+ `- Move logic to application code\n` +
1237
+ `- Use views for read-only operations\n` +
1238
+ `- Use triggers for automated data changes\n` +
1239
+ `- Use FTS5 for full-text search\n` +
1240
+ `- Use ATTACH DATABASE to connect to PostgreSQL/MySQL for stored procedures\n\n` +
1241
+ `Example: If using this in an app, create a TypeScript function that executes ` +
1242
+ `the same SQL logic and call that instead.`);
1243
+ }
1244
+ /**
1245
+ * Drop a stored procedure (SQLite - Not Supported)
1246
+ *
1247
+ * Since SQLite does not support stored procedures, there is nothing to drop.
1248
+ * If you previously created a stored procedure in an attached database,
1249
+ * drop it from that database instead.
1250
+ */
1251
+ async dropStoredProcedure(procedureName, _options) {
1252
+ throw new Error(`SQLite does not support stored procedures. Cannot drop '${procedureName}'.\n\n` +
1253
+ `If you created this procedure in an attached database using ATTACH, ` +
1254
+ `drop it from that database instead:\n` +
1255
+ ` await db.query("DETACH DATABASE alias");\n` +
1256
+ `Then recreate the procedure in the target database.`);
1257
+ }
1258
+ /**
1259
+ * Drop a stored procedure (alias for dropStoredProcedure)
1260
+ * SQLite does not support stored procedures - use application code instead.
1261
+ */
1262
+ async dropProcedure(procedureName, options) {
1263
+ return this.dropStoredProcedure(procedureName, options);
1264
+ }
1265
+ /**
1266
+ * Execute a stored procedure (SQLite - Not Supported)
1267
+ *
1268
+ * SQLite does not support stored procedures. To execute the equivalent logic,
1269
+ * move the procedure's SQL statements into your application code.
1270
+ *
1271
+ * Example:
1272
+ * ```typescript
1273
+ * // Instead of:
1274
+ * const result = await db.executeStoredProcedure({
1275
+ * procedureName: 'getUserOrders',
1276
+ * params: { userId: 123 }
1277
+ * });
1278
+ *
1279
+ * // Use a query directly:
1280
+ * const result = await db.query(
1281
+ * 'SELECT * FROM orders WHERE user_id = ?',
1282
+ * [123]
1283
+ * );
1284
+ * ```
1285
+ */
1286
+ async executeStoredProcedure(options) {
1287
+ const procName = options.procedureName;
1288
+ throw new Error(`SQLite does not support stored procedures. Cannot execute '${procName}'.\n\n` +
1289
+ `Workaround: Execute the SQL logic directly in your application:\n` +
1290
+ ` const result = await db.query('YOUR_SQL_HERE', [params]);\n\n` +
1291
+ `If you need to reuse this logic, create a TypeScript/JavaScript function ` +
1292
+ `that wraps the SQL query.`);
1293
+ }
1294
+ /**
1295
+ * Check if a stored procedure exists (SQLite - Always Returns False)
1296
+ *
1297
+ * Since SQLite does not support stored procedures, this always returns false.
1298
+ * If you are checking for a procedure in an attached database, use that
1299
+ * database's native method instead.
1300
+ */
1301
+ async hasStoredProcedure(_procedureName, _schema) {
1302
+ // SQLite does not support stored procedures, so none can exist
1303
+ return false;
1304
+ }
1305
+ /**
1306
+ * Create a stored procedure (alias for createStoredProcedure)
1307
+ * SQLite does not support stored procedures - use application code instead.
1308
+ */
1309
+ async createProcedure(options) {
1310
+ return this.createStoredProcedure(options);
1311
+ }
1312
+ /**
1313
+ * Create a foreign data wrapper (SQLite - not supported)
1314
+ * @param fdwName - Foreign data wrapper name
1315
+ * @param options - Options including handler function
1316
+ */
1317
+ async createForeignDataWrapper(_fdwName, _options) {
1318
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1319
+ }
1320
+ async dropForeignDataWrapper(_fdwName, _options) {
1321
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1322
+ }
1323
+ async createForeignServer(_serverName, _fdwName, _options) {
1324
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1325
+ }
1326
+ async dropForeignServer(_serverName, _options) {
1327
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1328
+ }
1329
+ async createForeignTable(_tableName, _columns, _options) {
1330
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1331
+ }
1332
+ async changeOwner(_newOwner, _tableName) {
1333
+ throw new Error('changeOwner is not supported in SQLite');
1334
+ }
1335
+ async addConstraint(_tableName, _options) {
1336
+ // SQLite cannot ALTER TABLE ADD CONSTRAINT, but a UNIQUE constraint is
1337
+ // equivalent to a UNIQUE INDEX, which we can create on an existing table.
1338
+ const type = String(_options.type || '').toUpperCase();
1339
+ const name = _options.name ||
1340
+ `${_tableName}_${(_options.fields || []).join('_')}_${type.toLowerCase().replace(/\s+/g, '_')}`;
1341
+ if (type === 'UNIQUE') {
1342
+ await this.addIndex(_tableName, name, _options.fields, { unique: true });
1343
+ return;
1344
+ }
1345
+ throw new Error(`addConstraint type "${type}" cannot be added to an existing SQLite table; ` +
1346
+ `define it when the table is created (SQLite only supports UNIQUE via a unique index post-hoc).`);
1347
+ }
1348
+ async removeConstraint(_tableName, _constraintName) {
1349
+ // The only post-hoc constraint we create is a unique index; drop it if present.
1350
+ await this.query(`DROP INDEX IF EXISTS ${this.escapeId(_constraintName)}`);
1351
+ }
1352
+ async createSecurityPolicy(_policyName, _tableName, _options) {
1353
+ throw new Error('Security policies are not supported in SQLite');
1354
+ }
1355
+ async dropSecurityPolicy(_policyName, _tableName) {
1356
+ throw new Error('Security policies are not supported in SQLite');
1357
+ }
1358
+ // ==================== Triggers ====================
1359
+ /**
1360
+ * Create a trigger
1361
+ */
1362
+ async createTrigger(triggerName, timing, events, tableName, body, options) {
1363
+ return this.advanced.createTrigger(triggerName, timing, events, tableName, body, options);
1364
+ }
1365
+ /**
1366
+ * Drop a trigger
1367
+ */
1368
+ async dropTrigger(triggerName, options) {
1369
+ return this.advanced.dropTrigger(triggerName, options);
1370
+ }
1371
+ async hasTrigger(_triggerName, _tableName) {
1372
+ // Check if trigger exists
1373
+ const sql = `SELECT name FROM sqlite_master WHERE type='trigger' AND name=?`;
1374
+ const result = await this.query(sql, { plain: true });
1375
+ return !!result;
1376
+ }
1377
+ // ==================== Sequences ====================
1378
+ // SQLite does not have native sequences, but they can be simulated
1379
+ // using INTEGER PRIMARY KEY AUTOINCREMENT tables
1380
+ /**
1381
+ * Create a sequence (simulated using AUTOINCREMENT table)
1382
+ */
1383
+ async createSequence(options) {
1384
+ const seqName = options.name;
1385
+ const schema = options.schema;
1386
+ // Use a special prefix to distinguish sequence tables
1387
+ const tableName = this.quoteTable(this._getSequenceTableName(seqName, schema));
1388
+ // Check if sequence already exists
1389
+ if (options.ifNotExists) {
1390
+ const exists = await this.hasSequence(seqName, schema);
1391
+ if (exists) {
1392
+ return;
1393
+ }
1394
+ }
1395
+ // Build the CREATE TABLE statement
1396
+ let sql = `CREATE TABLE ${tableName} (value INTEGER PRIMARY KEY AUTOINCREMENT)`;
1397
+ // Handle startWith - SQLite AUTOINCREMENT starts at 1 by default
1398
+ // To set a different starting value, we need to insert a row first
1399
+ if (options.startWith !== undefined && options.startWith > 1) {
1400
+ // Create the table first
1401
+ await this.query(sql);
1402
+ // Insert a row with value = startWith - 1 so next value will be startWith
1403
+ const insertSql = `INSERT INTO ${tableName} (value) VALUES (?)`;
1404
+ await this.query(insertSql, { bind: [options.startWith - 1] });
1405
+ // Delete that row so the sequence starts at the correct value
1406
+ const deleteSql = `DELETE FROM ${tableName}`;
1407
+ await this.query(deleteSql);
1408
+ }
1409
+ else {
1410
+ await this.query(sql);
1411
+ }
1412
+ }
1413
+ /**
1414
+ * Drop a sequence (simulated by dropping the sequence table)
1415
+ */
1416
+ async dropSequence(sequenceName, options) {
1417
+ const schema = options?.schema;
1418
+ const tableName = this._getSequenceTableName(sequenceName, schema);
1419
+ const ifExists = options?.ifExists ? 'IF EXISTS ' : '';
1420
+ const sql = `DROP TABLE ${ifExists}${this.quoteTable(tableName)}`;
1421
+ await this.query(sql);
1422
+ }
1423
+ /**
1424
+ * Get next value from a sequence
1425
+ * (simulated by inserting and getting last_insert_rowid)
1426
+ */
1427
+ async nextSequenceValue(sequenceName, schema) {
1428
+ const tableName = this._getSequenceTableName(sequenceName, schema);
1429
+ const sql = `INSERT INTO ${this.quoteTable(tableName)} (value) VALUES (NULL)`;
1430
+ await this.query(sql);
1431
+ // Get the last inserted rowid (the auto-generated value)
1432
+ const result = await this.query('SELECT last_insert_rowid() as value', { raw: true });
1433
+ return result.rows?.[0]?.value;
1434
+ }
1435
+ /**
1436
+ * Check if a sequence exists
1437
+ * (simulated by checking if the sequence table exists)
1438
+ */
1439
+ async hasSequence(sequenceName, schema) {
1440
+ const tableName = this._getSequenceTableName(sequenceName, schema);
1441
+ const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name=?`;
1442
+ const result = await this.query(sql, { bind: [tableName], plain: true });
1443
+ return !!result;
1444
+ }
1445
+ /**
1446
+ * List all sequences in the database (SQLite - simulated via tables)
1447
+ * @returns Array of sequence names
1448
+ */
1449
+ async listSequences() {
1450
+ const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '_sequence_%'`;
1451
+ const result = await this.query(sql);
1452
+ return result.rows.map((row) => row.name.replace(/^_sequence_/, ''));
1453
+ }
1454
+ /**
1455
+ * Get the internal table name for a sequence
1456
+ */
1457
+ _getSequenceTableName(sequenceName, schema) {
1458
+ // Prefix with _sequence_ to avoid name collisions with regular tables
1459
+ const prefix = '_sequence_';
1460
+ const schemaPrefix = schema ? `${schema}_` : '';
1461
+ return `${prefix}${schemaPrefix}${sequenceName}`;
1462
+ }
1463
+ // ==================== Row-Level Security (RLS) ====================
1464
+ async createPolicy(_options) {
1465
+ throw new Error('Row-level security is not supported by SQLite');
1466
+ }
1467
+ async dropPolicy(_policyName, _tableName, _options) {
1468
+ throw new Error('Row-level security is not supported by SQLite');
1469
+ }
1470
+ async enableRLS(_tableName, _schema) {
1471
+ throw new Error('Row-level security is not supported by SQLite');
1472
+ }
1473
+ async enableRowLevelSecurity(_tableName, _schema) {
1474
+ throw new Error('Row-level security is not supported by SQLite');
1475
+ }
1476
+ async disableRLS(_tableName, _schema) {
1477
+ throw new Error('Row-level security is not supported by SQLite');
1478
+ }
1479
+ async hasPolicy(_policyName, _tableName) {
1480
+ return false;
1481
+ }
1482
+ // ==================== Comments ====================
1483
+ async commentTable(_tableName, _comment) {
1484
+ // SQLite does not support table comments natively
1485
+ // Could use SQLITE_SQLITE_MASTER comment extension or pragma
1486
+ }
1487
+ async commentColumn(_tableName, _columnName, _comment) {
1488
+ // SQLite does not support column comments natively
1489
+ }
1490
+ // ==================== Advanced Indexes ====================
1491
+ async createPartialIndex(tableName, indexName, fields, where, options) {
1492
+ // SQLite supports partial indexes via WHERE clause
1493
+ const fieldsSql = fields.map((f) => this.quoteIdentifier(f)).join(', ');
1494
+ let sql = `CREATE INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)} (${fieldsSql}) WHERE ${where}`;
1495
+ if (options?.unique)
1496
+ sql = sql.replace('CREATE INDEX', 'CREATE UNIQUE INDEX');
1497
+ if (options?.ifNotExists)
1498
+ sql = sql.replace('CREATE INDEX', 'CREATE INDEX IF NOT EXISTS');
1499
+ await this.query(sql);
1500
+ }
1501
+ async createExpressionIndex(tableName, indexName, expression, options) {
1502
+ // SQLite supports expression indexes
1503
+ let sql = `CREATE INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)} (${expression})`;
1504
+ if (options?.unique)
1505
+ sql = sql.replace('CREATE INDEX', 'CREATE UNIQUE INDEX');
1506
+ if (options?.ifNotExists)
1507
+ sql = sql.replace('CREATE INDEX', 'CREATE INDEX IF NOT EXISTS');
1508
+ await this.query(sql);
1509
+ }
1510
+ // ==================== Identity & Computed Columns ====================
1511
+ async createIdentityColumn(_tableName, _columnName, _options) {
1512
+ throw new Error('Identity columns are not supported by SQLite. Use AUTOINCREMENT instead.');
1513
+ }
1514
+ async createComputedColumn(tableName, columnName, expression, options) {
1515
+ // SQLite supports generated columns (computed)
1516
+ const persistedSql = options?.persisted ? 'STORED' : 'VIRTUAL';
1517
+ const typeSql = options?.type || 'ANY';
1518
+ const sql = `ALTER TABLE ${this.quoteTable(tableName)} ADD COLUMN ${this.quoteIdentifier(columnName)} ${typeSql} GENERATED ALWAYS AS (${expression}) ${persistedSql}`;
1519
+ await this.query(sql);
1520
+ }
1521
+ /**
1522
+ * Create a PostgreSQL extension (not supported in SQLite)
1523
+ */
1524
+ async createExtension(_extensionName, _options) {
1525
+ throw new Error('Extensions are not supported by SQLite. This is a PostgreSQL-specific feature.');
1526
+ }
1527
+ /**
1528
+ * Drop a PostgreSQL extension (not supported in SQLite)
1529
+ */
1530
+ async dropExtension(_extensionName, _options) {
1531
+ throw new Error('Extensions are not supported by SQLite. This is a PostgreSQL-specific feature.');
1532
+ }
1533
+ /**
1534
+ * Get all installed PostgreSQL extensions (not supported in SQLite)
1535
+ */
1536
+ async getExtensions() {
1537
+ throw new Error('Extensions are not supported by SQLite. This is a PostgreSQL-specific feature.');
1538
+ }
1539
+ /**
1540
+ * Check if a PostgreSQL extension is installed (not supported in SQLite)
1541
+ */
1542
+ async hasExtension(_extensionName) {
1543
+ throw new Error('Extensions are not supported by SQLite. This is a PostgreSQL-specific feature.');
1544
+ }
1545
+ // ---------------------------------------------------------------------------
1546
+ // Foreign Data Wrapper – not supported in SQLite
1547
+ // ---------------------------------------------------------------------------
1548
+ buildCreateServerQuery(_name, _opts) {
1549
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1550
+ }
1551
+ buildAlterServerQuery(_name, _opts) {
1552
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1553
+ }
1554
+ buildDropServerQuery(_name, _opts) {
1555
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1556
+ }
1557
+ buildCreateUserMappingQuery(_opts) {
1558
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1559
+ }
1560
+ buildAlterUserMappingQuery(_opts) {
1561
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1562
+ }
1563
+ buildDropUserMappingQuery(_serverName, _user, _opts) {
1564
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1565
+ }
1566
+ buildCreateForeignTableQuery(_tableName, _opts) {
1567
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1568
+ }
1569
+ buildDropForeignTableQuery(_tableName, _opts) {
1570
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1571
+ }
1572
+ buildImportForeignSchemaQuery(_remoteSchema, _serverName, _opts) {
1573
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1574
+ }
1575
+ getServersQuery() {
1576
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
1577
+ }
1578
+ /**
1579
+ * Create a database view (SQLite uses CREATE VIEW)
1580
+ * @param viewName - Name of the view to create
1581
+ * @param query - The SELECT query for the view
1582
+ * @param options - View options
1583
+ */
1584
+ async createView(viewName, query, options) {
1585
+ let sql = 'CREATE VIEW';
1586
+ if (options?.replace) {
1587
+ sql = 'CREATE OR REPLACE VIEW';
1588
+ }
1589
+ sql += ` ${this.escapeId(viewName)} AS ${query}`;
1590
+ await this.query(sql);
1591
+ }
1592
+ /**
1593
+ * Drop a database view
1594
+ * @param viewName - Name of the view to drop
1595
+ * @param options - Drop options
1596
+ */
1597
+ async dropView(viewName, options) {
1598
+ let sql = 'DROP VIEW';
1599
+ if (options?.ifExists) {
1600
+ sql += ' IF EXISTS';
1601
+ }
1602
+ sql += ` ${this.escapeId(viewName)}`;
1603
+ await this.query(sql);
1604
+ }
1605
+ /**
1606
+ * Show all views in the database
1607
+ */
1608
+ async showViews() {
1609
+ const sql = "SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%'";
1610
+ const result = await this.query(sql);
1611
+ return result.rows.map((row) => row.name);
1612
+ }
1613
+ /**
1614
+ * Create a materialized view - Not supported by SQLite
1615
+ */
1616
+ async createMaterializedView(_options) {
1617
+ throw new Error('Materialized views are not supported in SQLite. This feature is only available in PostgreSQL.');
1618
+ }
1619
+ /**
1620
+ * Refresh a materialized view - Not supported by SQLite
1621
+ */
1622
+ async refreshMaterializedView(_viewName, _options) {
1623
+ throw new Error('Materialized views are not supported in SQLite. This feature is only available in PostgreSQL.');
1624
+ }
1625
+ /**
1626
+ * Drop a materialized view - Not supported by SQLite
1627
+ */
1628
+ async dropMaterializedView(_viewName, _options) {
1629
+ throw new Error('Materialized views are not supported in SQLite. This feature is only available in PostgreSQL.');
1630
+ }
1631
+ /**
1632
+ * Check if a materialized view exists - Not supported by SQLite
1633
+ */
1634
+ async hasMaterializedView(_viewName) {
1635
+ throw new Error('Materialized views are not supported in SQLite. This feature is only available in PostgreSQL.');
1636
+ }
1637
+ /**
1638
+ * Show all materialized views - Not supported by SQLite
1639
+ */
1640
+ async showMaterializedViews() {
1641
+ throw new Error('Materialized views are not supported in SQLite. This feature is only available in PostgreSQL.');
1642
+ }
1643
+ /**
1644
+ * Describe a table (get column information)
1645
+ */
1646
+ async describeTable(tableName) {
1647
+ const sql = `PRAGMA table_info(${this.escapeId(tableName)})`;
1648
+ const result = await this.query(sql);
1649
+ // PRAGMA table_info doesn't expose AUTOINCREMENT directly, but SQLite only
1650
+ // allows a single AUTOINCREMENT column per table and it must be the INTEGER
1651
+ // PRIMARY KEY, so we can detect it by checking whether the table's own
1652
+ // CREATE TABLE statement contains the AUTOINCREMENT keyword and attribute it
1653
+ // to the (single) primary key column. Without this, table-rebuild operations
1654
+ // (removeColumn/changeColumn/createConstraint) would silently drop
1655
+ // AUTOINCREMENT from the primary key every time they run.
1656
+ let tableHasAutoIncrement = false;
1657
+ try {
1658
+ const createTableSql = await this.getCreateTable(tableName);
1659
+ tableHasAutoIncrement = /\bAUTOINCREMENT\b/i.test(createTableSql);
1660
+ }
1661
+ catch {
1662
+ // If we can't determine this (e.g. table doesn't exist yet), default to false
1663
+ }
1664
+ const description = {};
1665
+ for (const row of result.rows) {
1666
+ const rowData = row;
1667
+ const isPrimaryKey = rowData.pk === 1;
1668
+ description[rowData.name] = {
1669
+ type: rowData.type || 'TEXT',
1670
+ allowNull: rowData.notnull === 0,
1671
+ defaultValue: rowData.dflt_value,
1672
+ primaryKey: isPrimaryKey,
1673
+ autoIncrement: isPrimaryKey && tableHasAutoIncrement,
1674
+ };
1675
+ }
1676
+ return description;
1677
+ }
1678
+ /**
1679
+ * Convert a column description (as returned by describeTable, sourced from
1680
+ * PRAGMA table_info) into a ColumnDefinition suitable for getColumnDefinitionSql.
1681
+ *
1682
+ * PRAGMA table_info's `dflt_value` is already a raw SQL literal exactly as SQLite
1683
+ * stored it (e.g. the string column default `'foo'` comes back as the 3-character
1684
+ * string `'foo'`, quotes included). It must be wrapped in a Literal so it is
1685
+ * emitted verbatim instead of being re-escaped and quoted a second time.
1686
+ */
1687
+ toColumnDefinition(colInfo) {
1688
+ const rawDefault = colInfo.defaultValue;
1689
+ return {
1690
+ ...colInfo,
1691
+ defaultValue: typeof rawDefault === 'string' ? new prorm_1.Literal(rawDefault) : rawDefault,
1692
+ };
1693
+ }
1694
+ /**
1695
+ * Rename a table
1696
+ */
1697
+ async renameTable(oldName, newName) {
1698
+ const sql = `ALTER TABLE ${this.escapeId(oldName)} RENAME TO ${this.escapeId(newName)}`;
1699
+ await this.query(sql);
1700
+ }
1701
+ /**
1702
+ * Add an index to a table
1703
+ * Supports partial indexes (WHERE) and expression indexes (SQLite 3.9.0+)
1704
+ */
1705
+ async addIndex(tableName, indexName, fields = [], options) {
1706
+ // Support the `(tableName, fields, options)` shorthand: when the second
1707
+ // argument is an array, it holds the fields and the index name is derived.
1708
+ if (Array.isArray(indexName)) {
1709
+ if (fields && !Array.isArray(fields)) {
1710
+ options = fields;
1711
+ }
1712
+ fields = indexName;
1713
+ indexName = `${tableName}_${fields.join('_')}_idx`;
1714
+ }
1715
+ let sql = 'CREATE';
1716
+ if (options?.unique) {
1717
+ sql += ' UNIQUE';
1718
+ }
1719
+ sql += ' INDEX';
1720
+ // Use IF NOT EXISTS to avoid errors when recreating indexes
1721
+ sql += ' IF NOT EXISTS';
1722
+ if (options?.type) {
1723
+ sql += ` ${options.type}`;
1724
+ }
1725
+ sql += ` ${this.escapeId(indexName)} ON ${this.escapeId(tableName)}`;
1726
+ // Handle expression index (SQLite 3.9.0+)
1727
+ if (options?.expression) {
1728
+ sql += ` ((${options.expression}))`;
1729
+ }
1730
+ else {
1731
+ sql += ` (${fields.map((f) => this.escapeId(f)).join(', ')})`;
1732
+ }
1733
+ // Add partial index WHERE clause (SQLite 3.8.0+)
1734
+ if (options?.where) {
1735
+ const whereClause = this.buildWhereClause(options.where);
1736
+ sql += ` WHERE ${whereClause.sql}`;
1737
+ }
1738
+ await this.query(sql);
1739
+ }
1740
+ /**
1741
+ * Remove an index from a table
1742
+ */
1743
+ async removeIndex(tableName, indexName) {
1744
+ // Drop by the exact name if present, otherwise fall back to the name the
1745
+ // `(tableName, fields)` addIndex shorthand generates (`${table}_${col}_idx`),
1746
+ // so callers can remove an index by either the explicit or derived name.
1747
+ await this.query(`DROP INDEX IF EXISTS ${this.escapeId(indexName)}`);
1748
+ const derived = `${tableName}_${indexName}_idx`;
1749
+ await this.query(`DROP INDEX IF EXISTS ${this.escapeId(derived)}`);
1750
+ }
1751
+ /**
1752
+ * Create an index on a table with full options support
1753
+ * Supports partial indexes (WHERE) and expression indexes (SQLite 3.9.0+)
1754
+ */
1755
+ async createIndex(tableName, indexDef) {
1756
+ const fields = indexDef.fields || [];
1757
+ let sql = 'CREATE';
1758
+ if (indexDef.unique) {
1759
+ sql += ' UNIQUE';
1760
+ }
1761
+ sql += ' INDEX IF NOT EXISTS';
1762
+ if (indexDef.type) {
1763
+ sql += ` ${indexDef.type}`;
1764
+ }
1765
+ sql += ` ${this.escapeId(indexDef.name)} ON ${this.escapeId(tableName)}`;
1766
+ // Handle expression index (SQLite 3.9.0+)
1767
+ if (indexDef.expression) {
1768
+ sql += ` ((${indexDef.expression}))`;
1769
+ }
1770
+ else {
1771
+ sql += ` (${fields.map((f) => this.escapeId(f)).join(', ')})`;
1772
+ }
1773
+ if (indexDef.using) {
1774
+ sql += ` USING ${indexDef.using}`;
1775
+ }
1776
+ if (indexDef.where && Object.keys(indexDef.where).length > 0) {
1777
+ const whereClause = this.buildWhereClause(indexDef.where);
1778
+ sql += ` WHERE ${whereClause.sql}`;
1779
+ }
1780
+ await this.query(sql);
1781
+ }
1782
+ /**
1783
+ * Drop an index from a table
1784
+ */
1785
+ async dropIndex(tableName, indexName, _options) {
1786
+ const sql = `DROP INDEX ${this.escapeId(indexName)}`;
1787
+ await this.query(sql);
1788
+ }
1789
+ /**
1790
+ * Create a constraint on a table
1791
+ * Note: SQLite has limited ALTER TABLE support for constraints
1792
+ */
1793
+ async createConstraint(tableName, constraintDef) {
1794
+ // SQLite doesn't support ADD CONSTRAINT via ALTER TABLE in most versions
1795
+ // We need to recreate the table with the new constraint
1796
+ await this.recreateTableWithConstraint(tableName, constraintDef);
1797
+ }
1798
+ /**
1799
+ * Drop a constraint from a table
1800
+ * Note: SQLite has limited ALTER TABLE support for constraints
1801
+ */
1802
+ async dropConstraint(tableName, constraintName, _options) {
1803
+ // SQLite doesn't support DROP CONSTRAINT via ALTER TABLE
1804
+ // We would need to recreate the table without the constraint
1805
+ throw new Error('SQLite does not support dropping constraints with ALTER TABLE. Recreate the table instead.');
1806
+ }
1807
+ /**
1808
+ * Recreate table with a new constraint (for createConstraint)
1809
+ */
1810
+ async recreateTableWithConstraint(tableName, constraintDef) {
1811
+ const tableInfo = await this.describeTable(tableName);
1812
+ // Build column definitions, preserving existing NOT NULL / DEFAULT / PRIMARY KEY /
1813
+ // AUTOINCREMENT / UNIQUE constraints (not just the bare data type) since SQLite
1814
+ // requires a full table rebuild rather than in-place ALTER for constraint changes.
1815
+ const columnDefs = [];
1816
+ for (const [colName, colInfo] of Object.entries(tableInfo)) {
1817
+ columnDefs.push(this.getColumnDefinitionSql(colName, this.toColumnDefinition(colInfo)));
1818
+ }
1819
+ // Add constraint to column definitions
1820
+ if (constraintDef.fields && constraintDef.fields.length > 0) {
1821
+ const escapedFields = constraintDef.fields.map((f) => this.escapeId(f)).join(', ');
1822
+ switch (constraintDef.type) {
1823
+ case 'PRIMARY KEY':
1824
+ columnDefs.push(`PRIMARY KEY (${escapedFields})`);
1825
+ break;
1826
+ case 'UNIQUE':
1827
+ columnDefs.push(`UNIQUE (${escapedFields})`);
1828
+ break;
1829
+ case 'FOREIGN KEY':
1830
+ if (constraintDef.references) {
1831
+ // Handle composite foreign keys (string | string[])
1832
+ const refField2 = constraintDef.references.field;
1833
+ const refFieldSql2 = Array.isArray(refField2)
1834
+ ? `(${refField2.map((f) => this.escapeId(f)).join(', ')})`
1835
+ : this.escapeId(refField2);
1836
+ let fkDef = `FOREIGN KEY (${escapedFields}) REFERENCES ${this.escapeId(constraintDef.references.table)}${refFieldSql2}`;
1837
+ if (constraintDef.references.onDelete) {
1838
+ fkDef += ` ON DELETE ${constraintDef.references.onDelete}`;
1839
+ }
1840
+ if (constraintDef.references.onUpdate) {
1841
+ fkDef += ` ON UPDATE ${constraintDef.references.onUpdate}`;
1842
+ }
1843
+ columnDefs.push(fkDef);
1844
+ }
1845
+ break;
1846
+ case 'CHECK':
1847
+ if (constraintDef.check) {
1848
+ columnDefs.push(`CHECK (${constraintDef.check})`);
1849
+ }
1850
+ break;
1851
+ }
1852
+ }
1853
+ const tempTable = `${tableName}_temp_${Date.now()}`;
1854
+ // Rename old table
1855
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} RENAME TO ${this.escapeId(tempTable)}`);
1856
+ // Create new table with constraint
1857
+ await this.query(`CREATE TABLE ${this.escapeId(tableName)} (${columnDefs.join(', ')})`);
1858
+ // Copy data
1859
+ const colNames = Object.keys(tableInfo)
1860
+ .map((c) => this.escapeId(c))
1861
+ .join(', ');
1862
+ await this.query(`INSERT INTO ${this.escapeId(tableName)} SELECT ${colNames} FROM ${this.escapeId(tempTable)}`);
1863
+ // Drop old table
1864
+ await this.query(`DROP TABLE ${this.escapeId(tempTable)}`);
1865
+ }
1866
+ /**
1867
+ * Begin a new transaction
1868
+ */
1869
+ async startTransaction(options) {
1870
+ if (!this.connection) {
1871
+ throw new Error('Not connected to database');
1872
+ }
1873
+ const transaction = new SQLiteTransaction(this.connection, this.transactionDepth++, options, this);
1874
+ if (transaction.isOuter) {
1875
+ this.connection.exec('BEGIN TRANSACTION');
1876
+ }
1877
+ else {
1878
+ this.connection.exec(`SAVEPOINT ${transaction.id}`);
1879
+ }
1880
+ return transaction;
1881
+ }
1882
+ /**
1883
+ * Commit a transaction
1884
+ */
1885
+ async commitTransaction(transaction) {
1886
+ if (!this.connection) {
1887
+ throw new Error('Not connected to database');
1888
+ }
1889
+ const sqliteTx = transaction;
1890
+ if (sqliteTx.isOuter) {
1891
+ this.connection.exec('COMMIT');
1892
+ }
1893
+ else {
1894
+ this.connection.exec(`RELEASE SAVEPOINT ${sqliteTx.id}`);
1895
+ }
1896
+ this.transactionDepth--;
1897
+ }
1898
+ /**
1899
+ * Rollback a transaction
1900
+ */
1901
+ async rollbackTransaction(transaction) {
1902
+ if (!this.connection) {
1903
+ throw new Error('Not connected to database');
1904
+ }
1905
+ const sqliteTx = transaction;
1906
+ if (sqliteTx.isOuter) {
1907
+ this.connection.exec('ROLLBACK');
1908
+ }
1909
+ else {
1910
+ this.connection.exec(`ROLLBACK TO SAVEPOINT ${sqliteTx.id}`);
1911
+ }
1912
+ this.transactionDepth--;
1913
+ }
1914
+ /**
1915
+ * Get the SQL for a data type
1916
+ */
1917
+ getDataTypeSql(dataType, columnName) {
1918
+ if (typeof dataType === 'string') {
1919
+ // SQLite has no native ENUM; emulate a string ENUM(...) type as TEXT with
1920
+ // a CHECK on the allowed values (matching the object-form ENUM below).
1921
+ const enumMatch = dataType.match(/^\s*ENUM\s*\((.*)\)\s*$/i);
1922
+ if (enumMatch) {
1923
+ const colRef = columnName ? this.escapeId(columnName) : 'value';
1924
+ return `TEXT CHECK(${colRef} IN (${enumMatch[1]}))`;
1925
+ }
1926
+ return dataType;
1927
+ }
1928
+ // If passed a factory function (e.g. DataTypes.INTEGER without calling it), call it to get an instance
1929
+ if (typeof dataType === 'function') {
1930
+ try {
1931
+ dataType = dataType();
1932
+ }
1933
+ catch {
1934
+ return 'TEXT';
1935
+ }
1936
+ }
1937
+ if (!dataType || typeof dataType !== 'object') {
1938
+ return 'TEXT';
1939
+ }
1940
+ const dt = dataType;
1941
+ switch (dt.key) {
1942
+ case 'STRING':
1943
+ return `VARCHAR(${dt.length || 255})`;
1944
+ case 'CHAR':
1945
+ return `CHAR(${dt.length || 1})`;
1946
+ case 'TEXT':
1947
+ return dt.length ? `TEXT(${dt.length})` : 'TEXT';
1948
+ case 'INTEGER':
1949
+ return dt.unsigned ? 'INTEGER UNSIGNED' : 'INTEGER';
1950
+ case 'BIGINT':
1951
+ return dt.unsigned ? 'BIGINT UNSIGNED' : 'BIGINT';
1952
+ case 'FLOAT':
1953
+ return `FLOAT(${dt.length || 10},${dt.decimals || 2})`;
1954
+ case 'DOUBLE':
1955
+ return `DOUBLE(${dt.length || 10},${dt.decimals || 2})`;
1956
+ case 'DECIMAL':
1957
+ return `DECIMAL(${dt.precision || 10},${dt.scale || 0})`;
1958
+ case 'BOOLEAN':
1959
+ return 'INTEGER'; // SQLite uses INTEGER for booleans
1960
+ case 'DATE':
1961
+ return 'DATETIME';
1962
+ case 'DATEONLY':
1963
+ return 'DATE';
1964
+ case 'TIME':
1965
+ return 'TIME';
1966
+ case 'BLOB':
1967
+ return 'BLOB';
1968
+ case 'ENUM': {
1969
+ // Reference the actual column being defined, not a hardcoded literal "col"
1970
+ // (SQLite would otherwise fail with "no such column: col", or worse silently
1971
+ // reference an unrelated column that happens to be named "col").
1972
+ const colRef = columnName ? this.escapeId(columnName) : 'value';
1973
+ return `TEXT CHECK(${dt.values ? `${colRef} IN (${dt.values.map((v) => `'${this.escapeString(String(v))}'`).join(',')})` : '1=1'})`;
1974
+ }
1975
+ case 'JSON':
1976
+ return 'TEXT'; // SQLite stores JSON as text
1977
+ case 'JSONB':
1978
+ return 'TEXT';
1979
+ case 'UUID':
1980
+ return 'TEXT';
1981
+ case 'GEOMETRY':
1982
+ return 'BLOB';
1983
+ case 'VIRTUAL':
1984
+ // Virtual fields don't create a database column - return empty string
1985
+ return '';
1986
+ default:
1987
+ return 'TEXT';
1988
+ }
1989
+ }
1990
+ /**
1991
+ * Build a WHERE clause from a WhereOptions object
1992
+ */
1993
+ buildWhereClause(where, options) {
1994
+ const values = [];
1995
+ if (!where ||
1996
+ (Object.keys(where).length === 0 && Object.getOwnPropertySymbols(where).length === 0)) {
1997
+ return { sql: '', values };
1998
+ }
1999
+ const buildCondition = (condition, parentKey) => {
2000
+ if (!condition) {
2001
+ return { sql: '', values: [] };
2002
+ }
2003
+ if (typeof condition !== 'object') {
2004
+ values.push(condition);
2005
+ return { sql: '?', values: [condition] };
2006
+ }
2007
+ // Normalize Symbol operator keys at the top level too
2008
+ const rawCond = condition;
2009
+ const topSymbols = Object.getOwnPropertySymbols(rawCond);
2010
+ const normalizedCond = { ...rawCond };
2011
+ for (const sym of topSymbols) {
2012
+ const strKey = (0, operators_1.operatorToWhereKey)(sym);
2013
+ if (strKey)
2014
+ normalizedCond[strKey] = rawCond[sym];
2015
+ }
2016
+ const cond = normalizedCond;
2017
+ // Handle logical operators. These are collected (NOT returned early) so
2018
+ // that sibling field conditions on the same object — e.g.
2019
+ // { active: 1, $or: [...] } — are still AND-ed in rather than dropped.
2020
+ const logicalConditions = [];
2021
+ if (cond.$and || cond.$or || cond.$not) {
2022
+ if (cond.$and) {
2023
+ const andConditions = cond.$and
2024
+ .map((c) => buildCondition(c).sql)
2025
+ .filter(Boolean);
2026
+ if (andConditions.length)
2027
+ logicalConditions.push(`(${andConditions.join(' AND ')})`);
2028
+ }
2029
+ if (cond.$or) {
2030
+ const orConditions = cond.$or
2031
+ .map((c) => buildCondition(c).sql)
2032
+ .filter(Boolean);
2033
+ if (orConditions.length)
2034
+ logicalConditions.push(`(${orConditions.join(' OR ')})`);
2035
+ }
2036
+ if (cond.$not) {
2037
+ const result = buildCondition(cond.$not);
2038
+ if (result.sql)
2039
+ logicalConditions.push(`NOT (${result.sql})`);
2040
+ }
2041
+ }
2042
+ // Handle field conditions
2043
+ const fieldConditions = [...logicalConditions];
2044
+ for (const [key, value] of Object.entries(cond)) {
2045
+ if (key.startsWith('$'))
2046
+ continue;
2047
+ // A bare array value (e.g. `where: { id: [1, 2, 3] }`, the common
2048
+ // Sequelize-style shorthand for "IN") used to fall through to the
2049
+ // generic object-condition branch below, which spreads the array
2050
+ // into a plain object (`{0: 1, 1: 2, 2: 3}`), matches none of the
2051
+ // `$eq`/`$in`/etc. operator keys, and ends up binding the whole
2052
+ // array as a single `= ?` parameter - better-sqlite3 silently
2053
+ // matches nothing for that (no rows have an id equal to an array),
2054
+ // so `where: { id: [...] }` matched zero rows instead of the
2055
+ // intended "any of these ids".
2056
+ if (Array.isArray(value)) {
2057
+ values.push(...value);
2058
+ fieldConditions.push(`${this.escapeId(key)} IN (${value.map(() => '?').join(',') || 'NULL'})`);
2059
+ continue;
2060
+ }
2061
+ if (value && typeof value === 'object') {
2062
+ // Normalize Symbol operator keys to string keys ($gt, $eq, etc.)
2063
+ const rawValueObj = value;
2064
+ const symbolKeys = Object.getOwnPropertySymbols(rawValueObj);
2065
+ const normalizedObj = { ...rawValueObj };
2066
+ for (const sym of symbolKeys) {
2067
+ const strKey = (0, operators_1.operatorToWhereKey)(sym);
2068
+ if (strKey)
2069
+ normalizedObj[strKey] = rawValueObj[sym];
2070
+ }
2071
+ const valueObj = normalizedObj;
2072
+ // When several operators target the same field (e.g. { age: { $gte, $lte } }),
2073
+ // the single-operator dispatch below would only emit the first one. Split
2074
+ // into one condition per operator and AND them together.
2075
+ const opKeys = Object.keys(valueObj).filter((k) => k.startsWith('$'));
2076
+ if (opKeys.length > 1) {
2077
+ const parts = opKeys
2078
+ .map((ok) => buildCondition({ [key]: { [ok]: valueObj[ok] } }).sql)
2079
+ .filter(Boolean);
2080
+ if (parts.length > 0) {
2081
+ fieldConditions.push(parts.length > 1 ? `(${parts.join(' AND ')})` : parts[0]);
2082
+ }
2083
+ continue;
2084
+ }
2085
+ if (valueObj.$eq !== undefined) {
2086
+ values.push(valueObj.$eq);
2087
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
2088
+ }
2089
+ else if (valueObj.$ne !== undefined) {
2090
+ values.push(valueObj.$ne);
2091
+ fieldConditions.push(`${this.escapeId(key)} != ?`);
2092
+ }
2093
+ else if (valueObj.$gt !== undefined) {
2094
+ values.push(valueObj.$gt);
2095
+ fieldConditions.push(`${this.escapeId(key)} > ?`);
2096
+ }
2097
+ else if (valueObj.$gte !== undefined) {
2098
+ values.push(valueObj.$gte);
2099
+ fieldConditions.push(`${this.escapeId(key)} >= ?`);
2100
+ }
2101
+ else if (valueObj.$lt !== undefined) {
2102
+ values.push(valueObj.$lt);
2103
+ fieldConditions.push(`${this.escapeId(key)} < ?`);
2104
+ }
2105
+ else if (valueObj.$lte !== undefined) {
2106
+ values.push(valueObj.$lte);
2107
+ fieldConditions.push(`${this.escapeId(key)} <= ?`);
2108
+ }
2109
+ else if (valueObj.$like !== undefined) {
2110
+ values.push(valueObj.$like);
2111
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
2112
+ }
2113
+ else if (valueObj.$notLike !== undefined) {
2114
+ values.push(valueObj.$notLike);
2115
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
2116
+ }
2117
+ else if (valueObj.$startsWith !== undefined) {
2118
+ values.push(valueObj.$startsWith + '%');
2119
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
2120
+ }
2121
+ else if (valueObj.$notStartsWith !== undefined) {
2122
+ values.push(valueObj.$notStartsWith + '%');
2123
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
2124
+ }
2125
+ else if (valueObj.$endsWith !== undefined) {
2126
+ values.push('%' + valueObj.$endsWith);
2127
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
2128
+ }
2129
+ else if (valueObj.$notEndsWith !== undefined) {
2130
+ values.push('%' + valueObj.$notEndsWith);
2131
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
2132
+ }
2133
+ else if (valueObj.$substring !== undefined) {
2134
+ values.push('%' + valueObj.$substring + '%');
2135
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
2136
+ }
2137
+ else if (valueObj.$notSubstring !== undefined) {
2138
+ values.push('%' + valueObj.$notSubstring + '%');
2139
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
2140
+ }
2141
+ else if (valueObj.$in) {
2142
+ const inValues = valueObj.$in;
2143
+ values.push(...inValues);
2144
+ fieldConditions.push(`${this.escapeId(key)} IN (${inValues.map(() => '?').join(',')})`);
2145
+ }
2146
+ else if (valueObj.$notIn) {
2147
+ const notInValues = valueObj.$notIn;
2148
+ values.push(...notInValues);
2149
+ fieldConditions.push(`${this.escapeId(key)} NOT IN (${notInValues.map(() => '?').join(',')})`);
2150
+ }
2151
+ else if (valueObj.$between) {
2152
+ const between = valueObj.$between;
2153
+ values.push(between[0], between[1]);
2154
+ fieldConditions.push(`${this.escapeId(key)} BETWEEN ? AND ?`);
2155
+ }
2156
+ else if (valueObj.$notBetween) {
2157
+ const notBetween = valueObj.$notBetween;
2158
+ values.push(notBetween[0], notBetween[1]);
2159
+ fieldConditions.push(`${this.escapeId(key)} NOT BETWEEN ? AND ?`);
2160
+ }
2161
+ else if (valueObj.$isNull !== undefined) {
2162
+ if (valueObj.$isNull) {
2163
+ fieldConditions.push(`${this.escapeId(key)} IS NULL`);
2164
+ }
2165
+ else {
2166
+ fieldConditions.push(`${this.escapeId(key)} IS NOT NULL`);
2167
+ }
2168
+ }
2169
+ else if (valueObj.$isNotNull !== undefined) {
2170
+ if (valueObj.$isNotNull) {
2171
+ fieldConditions.push(`${this.escapeId(key)} IS NOT NULL`);
2172
+ }
2173
+ else {
2174
+ fieldConditions.push(`${this.escapeId(key)} IS NULL`);
2175
+ }
2176
+ }
2177
+ else if (valueObj.$iLike !== undefined) {
2178
+ // SQLite's LIKE is case-insensitive for ASCII, so it serves as ILIKE.
2179
+ values.push(valueObj.$iLike);
2180
+ fieldConditions.push(`${this.escapeId(key)} LIKE ?`);
2181
+ }
2182
+ else if (valueObj.$notILike !== undefined) {
2183
+ values.push(valueObj.$notILike);
2184
+ fieldConditions.push(`${this.escapeId(key)} NOT LIKE ?`);
2185
+ }
2186
+ else if (valueObj.$is !== undefined) {
2187
+ if (valueObj.$is === null) {
2188
+ fieldConditions.push(`${this.escapeId(key)} IS NULL`);
2189
+ }
2190
+ else {
2191
+ values.push(valueObj.$is);
2192
+ fieldConditions.push(`${this.escapeId(key)} IS ?`);
2193
+ }
2194
+ }
2195
+ else if (valueObj.$not !== undefined) {
2196
+ if (valueObj.$not === null) {
2197
+ fieldConditions.push(`${this.escapeId(key)} IS NOT NULL`);
2198
+ }
2199
+ else {
2200
+ values.push(valueObj.$not);
2201
+ fieldConditions.push(`${this.escapeId(key)} != ?`);
2202
+ }
2203
+ }
2204
+ else if (valueObj.$contains !== undefined) {
2205
+ // Array containment: the JSON-array column must contain every given
2206
+ // element. Emulated with json_each since SQLite has no array type.
2207
+ const contained = Array.isArray(valueObj.$contains)
2208
+ ? valueObj.$contains
2209
+ : [valueObj.$contains];
2210
+ const placeholders = contained.map(() => '?').join(', ');
2211
+ values.push(...contained);
2212
+ fieldConditions.push(`(SELECT COUNT(DISTINCT value) FROM json_each(${this.escapeId(key)}) WHERE value IN (${placeholders})) = ${contained.length}`);
2213
+ }
2214
+ else if (valueObj.$col !== undefined) {
2215
+ fieldConditions.push(`${this.escapeId(key)} = ${this.escapeId(String(valueObj.$col))}`);
2216
+ }
2217
+ else {
2218
+ values.push(value);
2219
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
2220
+ }
2221
+ }
2222
+ else {
2223
+ values.push(value);
2224
+ fieldConditions.push(`${this.escapeId(key)} = ?`);
2225
+ }
2226
+ }
2227
+ return { sql: fieldConditions.join(' AND '), values };
2228
+ };
2229
+ const result = buildCondition(where);
2230
+ return { sql: result.sql, values: result.values };
2231
+ }
2232
+ /**
2233
+ * Build an ORDER BY clause
2234
+ */
2235
+ buildOrderClause(order, options) {
2236
+ const orderArray = order;
2237
+ if (!orderArray || !Array.isArray(orderArray) || orderArray.length === 0) {
2238
+ return '';
2239
+ }
2240
+ const orderParts = [];
2241
+ for (const item of orderArray) {
2242
+ if (Array.isArray(item)) {
2243
+ const field = typeof item[0] === 'string' ? this.escapeId(item[0]) : item[0];
2244
+ const direction = item[1] ? ` ${item[1]}` : '';
2245
+ orderParts.push(`${field}${direction}`);
2246
+ }
2247
+ else if (typeof item === 'string') {
2248
+ orderParts.push(item);
2249
+ }
2250
+ else if (item && typeof item === 'object') {
2251
+ const itemArr = item;
2252
+ const [modelOrString, field] = itemArr;
2253
+ const model = typeof modelOrString === 'string'
2254
+ ? modelOrString
2255
+ : modelOrString.tableName || '';
2256
+ const fieldStr = typeof field === 'string' ? field : '';
2257
+ orderParts.push(`${model ? `${this.escapeId(model)}.` : ''}${this.escapeId(fieldStr)}`);
2258
+ }
2259
+ }
2260
+ return orderParts.length > 0 ? `ORDER BY ${orderParts.join(', ')}` : '';
2261
+ }
2262
+ /**
2263
+ * Build a LIMIT/OFFSET clause
2264
+ */
2265
+ buildLimitOffset(limit, offset) {
2266
+ let sql = '';
2267
+ if (limit !== undefined) {
2268
+ sql += ` LIMIT ${Number(limit)}`;
2269
+ if (offset !== undefined) {
2270
+ sql += ` OFFSET ${Number(offset)}`;
2271
+ }
2272
+ }
2273
+ else if (offset !== undefined) {
2274
+ // SQLite requires LIMIT to use OFFSET; use -1 for "all rows"
2275
+ sql += ` LIMIT -1 OFFSET ${Number(offset)}`;
2276
+ }
2277
+ return sql;
2278
+ }
2279
+ /**
2280
+ * Build an INSERT query
2281
+ */
2282
+ buildInsertQuery(tableName, values, options) {
2283
+ const columns = Object.keys(values);
2284
+ // If no values provided, return a minimal valid INSERT (for defaults only)
2285
+ if (columns.length === 0) {
2286
+ return {
2287
+ sql: `INSERT INTO ${this.quoteTable(tableName, options?.schema)} DEFAULT VALUES`,
2288
+ values: [],
2289
+ };
2290
+ }
2291
+ const processedValues = [];
2292
+ const placeholders = [];
2293
+ // Process each value - handle Literal differently
2294
+ for (const value of Object.values(values)) {
2295
+ if (value instanceof prorm_1.Literal) {
2296
+ // For Literal values, inline the SQL directly
2297
+ placeholders.push(value.val);
2298
+ }
2299
+ else {
2300
+ // For regular values, use parameterized placeholder
2301
+ placeholders.push('?');
2302
+ processedValues.push(value);
2303
+ }
2304
+ }
2305
+ let sql = `INSERT INTO ${this.quoteTable(tableName, options?.schema)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${placeholders.join(', ')})`;
2306
+ if (options?.upsert) {
2307
+ if (options.conflictTargets && options.conflictTargets.length > 0) {
2308
+ sql += this.buildConflictClausesSql(options.conflictTargets, columns);
2309
+ }
2310
+ else if (options.doNothing) {
2311
+ // `DO NOTHING` is the one form of `ON CONFLICT` SQLite allows without an
2312
+ // explicit conflict target - it applies to any constraint violation.
2313
+ sql +=
2314
+ options.conflictFields && options.conflictFields.length > 0
2315
+ ? ` ON CONFLICT (${options.conflictFields.map((c) => this.escapeId(c)).join(', ')}) DO NOTHING`
2316
+ : ' ON CONFLICT DO NOTHING';
2317
+ }
2318
+ else {
2319
+ const updateColumns = columns
2320
+ .map((c) => `${this.escapeId(c)} = excluded.${this.escapeId(c)}`)
2321
+ .join(', ');
2322
+ // SQLite's `ON CONFLICT (...) DO UPDATE` requires an explicit conflict target
2323
+ // that matches a real PRIMARY KEY/UNIQUE index on the table - unlike
2324
+ // `DO NOTHING`, it cannot be omitted. Defaulting to "all inserted columns"
2325
+ // (the previous behavior) almost never matches a real constraint and fails
2326
+ // at runtime with a confusing "ON CONFLICT clause does not match any
2327
+ // PRIMARY KEY or UNIQUE constraint" error, so fail fast with a clear message.
2328
+ if (!options.conflictFields || options.conflictFields.length === 0) {
2329
+ throw new Error('SQLite upsert requires `conflictFields` naming the PRIMARY KEY or UNIQUE ' +
2330
+ 'columns to resolve conflicts against (e.g. { conflictFields: ["id"] }), ' +
2331
+ 'unless `doNothing` is set (which allows a bare `ON CONFLICT DO NOTHING`).');
2332
+ }
2333
+ sql += ` ON CONFLICT (${options.conflictFields.map((c) => this.escapeId(c)).join(', ')}) DO UPDATE SET ${updateColumns}`;
2334
+ }
2335
+ }
2336
+ return { sql, values: processedValues };
2337
+ }
2338
+ /**
2339
+ * Build one or more chained `ON CONFLICT (...) DO ...` clauses. SQLite allows
2340
+ * multiple `ON CONFLICT` clauses in a single INSERT, each targeting a
2341
+ * different PRIMARY KEY/UNIQUE constraint and evaluated in order until one
2342
+ * matches, e.g.:
2343
+ * INSERT INTO t (...) VALUES (...)
2344
+ * ON CONFLICT (a) DO NOTHING
2345
+ * ON CONFLICT (b) DO UPDATE SET ...
2346
+ */
2347
+ buildConflictClausesSql(targets, insertedColumns) {
2348
+ let clause = '';
2349
+ for (const target of targets) {
2350
+ if (!target.fields || target.fields.length === 0) {
2351
+ throw new Error('Each entry in `conflictTargets` requires a non-empty `fields` array naming the ' +
2352
+ 'PRIMARY KEY or UNIQUE columns to resolve conflicts against.');
2353
+ }
2354
+ const targetSql = `ON CONFLICT (${target.fields.map((c) => this.escapeId(c)).join(', ')})`;
2355
+ const action = target.action || 'DO UPDATE';
2356
+ if (action === 'DO NOTHING') {
2357
+ clause += ` ${targetSql} DO NOTHING`;
2358
+ }
2359
+ else {
2360
+ const updateColumns = target.updateColumns && target.updateColumns.length > 0
2361
+ ? target.updateColumns
2362
+ : insertedColumns;
2363
+ const setClauses = updateColumns
2364
+ .map((c) => `${this.escapeId(c)} = excluded.${this.escapeId(c)}`)
2365
+ .join(', ');
2366
+ clause += ` ${targetSql} DO UPDATE SET ${setClauses}`;
2367
+ if (target.where) {
2368
+ clause += ` WHERE ${target.where}`;
2369
+ }
2370
+ }
2371
+ }
2372
+ return clause;
2373
+ }
2374
+ /**
2375
+ * Build an UPSERT query (INSERT ... ON CONFLICT DO UPDATE)
2376
+ * SQLite supports: INSERT OR REPLACE, INSERT OR IGNORE, and ON CONFLICT DO UPDATE
2377
+ */
2378
+ buildUpsertQuery(tableName, values, options) {
2379
+ const columns = Object.keys(values);
2380
+ const processedValues = [];
2381
+ const placeholders = [];
2382
+ // Process each value - handle Literal differently
2383
+ for (const value of Object.values(values)) {
2384
+ if (value instanceof prorm_1.Literal) {
2385
+ // For Literal values, inline the SQL directly
2386
+ placeholders.push(value.val);
2387
+ }
2388
+ else {
2389
+ // For regular values, use parameterized placeholder
2390
+ placeholders.push('?');
2391
+ processedValues.push(value);
2392
+ }
2393
+ }
2394
+ let sql = `INSERT INTO ${this.quoteTable(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${placeholders.join(', ')})`;
2395
+ // Handle upsert (ON CONFLICT)
2396
+ if (options) {
2397
+ if (options.conflictTargets && options.conflictTargets.length > 0) {
2398
+ sql += this.buildConflictClausesSql(options.conflictTargets, columns);
2399
+ }
2400
+ else if (options.doNothing) {
2401
+ // `DO NOTHING` is the one form of `ON CONFLICT` SQLite allows without an
2402
+ // explicit conflict target - it applies to any constraint violation.
2403
+ sql +=
2404
+ options.conflictFields && options.conflictFields.length > 0
2405
+ ? ` ON CONFLICT (${options.conflictFields.map((c) => this.escapeId(c)).join(', ')}) DO NOTHING`
2406
+ : ' ON CONFLICT DO NOTHING';
2407
+ }
2408
+ else {
2409
+ // SQLite's `ON CONFLICT (...) DO UPDATE` requires an explicit conflict target
2410
+ // that matches a real PRIMARY KEY/UNIQUE index; it cannot be inferred from the
2411
+ // set of inserted columns (see buildInsertQuery for the same issue).
2412
+ if (!options.conflictFields || options.conflictFields.length === 0) {
2413
+ throw new Error('SQLite upsert requires `conflictFields` naming the PRIMARY KEY or UNIQUE ' +
2414
+ 'columns to resolve conflicts against (e.g. { conflictFields: ["id"] }), ' +
2415
+ 'unless `doNothing` is set (which allows a bare `ON CONFLICT DO NOTHING`).');
2416
+ }
2417
+ // Determine which columns to update
2418
+ const updateColumns = options.updateOnDuplicate && options.updateOnDuplicate.length > 0
2419
+ ? options.updateOnDuplicate
2420
+ : columns;
2421
+ const setClauses = updateColumns
2422
+ .map((c) => `${this.escapeId(c)} = excluded.${this.escapeId(c)}`)
2423
+ .join(', ');
2424
+ sql += ` ON CONFLICT (${options.conflictFields.map((c) => this.escapeId(c)).join(', ')}) DO UPDATE SET ${setClauses}`;
2425
+ }
2426
+ }
2427
+ // Handle RETURNING clause (SQLite 3.35+)
2428
+ if (options?.returning) {
2429
+ if (options.returning === true) {
2430
+ sql += ' RETURNING *';
2431
+ }
2432
+ else if (Array.isArray(options.returning)) {
2433
+ sql += ` RETURNING ${options.returning.map((c) => this.escapeId(c)).join(', ')}`;
2434
+ }
2435
+ }
2436
+ return { sql, values: processedValues };
2437
+ }
2438
+ /**
2439
+ * Build an increment query
2440
+ * @param tableName - Table name
2441
+ * @param fields - Fields to increment
2442
+ * @param where - Where clause
2443
+ * @param options - Query options (by: number)
2444
+ */
2445
+ buildIncrementQuery(tableName, fields, where, options) {
2446
+ const by = options?.by ?? 1;
2447
+ const setClauses = [];
2448
+ const queryValues = [];
2449
+ // Handle different field formats
2450
+ if (typeof fields === 'string') {
2451
+ // Single field: increment('count')
2452
+ setClauses.push(`${this.escapeId(fields)} = ${this.escapeId(fields)} + ?`);
2453
+ queryValues.push(by);
2454
+ }
2455
+ else if (Array.isArray(fields)) {
2456
+ // Array of fields: increment(['count', 'value'], { by: 5 })
2457
+ for (const field of fields) {
2458
+ setClauses.push(`${this.escapeId(field)} = ${this.escapeId(field)} + ?`);
2459
+ queryValues.push(by);
2460
+ }
2461
+ }
2462
+ else {
2463
+ // Object: increment({ count: 1, value: 10 })
2464
+ for (const [field, value] of Object.entries(fields)) {
2465
+ setClauses.push(`${this.escapeId(field)} = ${this.escapeId(field)} + ?`);
2466
+ queryValues.push(value);
2467
+ }
2468
+ }
2469
+ // Build WHERE clause
2470
+ const whereClause = this.buildWhereClause(where);
2471
+ queryValues.push(...whereClause.values);
2472
+ const sql = `UPDATE ${this.escapeId(tableName)} SET ${setClauses.join(', ')} WHERE ${whereClause.sql}`;
2473
+ return { sql, values: queryValues };
2474
+ }
2475
+ /**
2476
+ * Build an UPDATE query
2477
+ */
2478
+ buildUpdateQuery(tableName, values, where, options) {
2479
+ const setClauses = [];
2480
+ const queryValues = [];
2481
+ for (const [key, value] of Object.entries(values)) {
2482
+ if (value instanceof prorm_1.Literal) {
2483
+ // For Literal values, inline the SQL directly
2484
+ setClauses.push(`${this.escapeId(key)} = ${value.val}`);
2485
+ }
2486
+ else {
2487
+ // For regular values, use parameterized placeholder
2488
+ setClauses.push(`${this.escapeId(key)} = ?`);
2489
+ queryValues.push(value);
2490
+ }
2491
+ }
2492
+ const whereClause = this.buildWhereClause(where);
2493
+ let sql = `UPDATE ${this.escapeId(tableName)} SET ${setClauses.join(', ')}`;
2494
+ if (whereClause.sql) {
2495
+ sql += ` WHERE ${whereClause.sql}`;
2496
+ }
2497
+ if (options?.limit) {
2498
+ sql += ` LIMIT ${options.limit}`;
2499
+ }
2500
+ // Handle RETURNING clause (SQLite 3.35+)
2501
+ if (options?.returning) {
2502
+ if (options.returning === true) {
2503
+ sql += ' RETURNING *';
2504
+ }
2505
+ else if (Array.isArray(options.returning)) {
2506
+ sql += ` RETURNING ${options.returning.map((c) => this.escapeId(c)).join(', ')}`;
2507
+ }
2508
+ }
2509
+ return { sql, values: [...queryValues, ...whereClause.values] };
2510
+ }
2511
+ /**
2512
+ * Build a DELETE query
2513
+ */
2514
+ buildDeleteQuery(tableName, where, options) {
2515
+ const whereClause = this.buildWhereClause(where);
2516
+ let sql = `DELETE FROM ${this.escapeId(tableName)}`;
2517
+ if (whereClause.sql) {
2518
+ sql += ` WHERE ${whereClause.sql}`;
2519
+ }
2520
+ if (options?.limit) {
2521
+ sql += ` LIMIT ${options.limit}`;
2522
+ }
2523
+ // Handle RETURNING clause (SQLite 3.35+)
2524
+ if (options?.returning) {
2525
+ if (options.returning === true) {
2526
+ sql += ' RETURNING *';
2527
+ }
2528
+ else if (Array.isArray(options.returning)) {
2529
+ sql += ` RETURNING ${options.returning.map((c) => this.escapeId(c)).join(', ')}`;
2530
+ }
2531
+ }
2532
+ return { sql, values: whereClause.values };
2533
+ }
2534
+ /**
2535
+ * Check if a value is a function expression (fn('COUNT', ...))
2536
+ */
2537
+ isFnExpression(value) {
2538
+ return value !== null && typeof value === 'object' && value.__type === 'fn';
2539
+ }
2540
+ /**
2541
+ * Check if a value is a column expression (col('name'))
2542
+ */
2543
+ isColExpression(value) {
2544
+ return value !== null && typeof value === 'object' && value.__type === 'col';
2545
+ }
2546
+ /**
2547
+ * Check if a value is a literal expression (literal('sql'))
2548
+ */
2549
+ isLiteralExpression(value) {
2550
+ return value !== null && typeof value === 'object' && value.__type === 'literal';
2551
+ }
2552
+ /**
2553
+ * Compile a function expression to SQL
2554
+ */
2555
+ compileFnExpression(fnExpr) {
2556
+ const args = fnExpr.args.map((arg) => {
2557
+ if (this.isColExpression(arg)) {
2558
+ return this.escapeId(arg.col);
2559
+ }
2560
+ else if (typeof arg === 'string') {
2561
+ return this.escape(arg);
2562
+ }
2563
+ else if (this.isFnExpression(arg)) {
2564
+ return this.compileFnExpression(arg);
2565
+ }
2566
+ else {
2567
+ return String(arg);
2568
+ }
2569
+ });
2570
+ return `${fnExpr.fn}(${args.join(', ')})`;
2571
+ }
2572
+ /**
2573
+ * Build SELECT clause with support for function expressions and aliases
2574
+ * Handles: 'field', ['field'], ['field', 'alias'], [fn('COUNT', 'id'), 'count']
2575
+ */
2576
+ buildSelectClause(attributes) {
2577
+ if (!attributes)
2578
+ return '*';
2579
+ if (Array.isArray(attributes)) {
2580
+ const parts = attributes.map((attr) => {
2581
+ // Handle array format: [expression, alias] or [fn, alias]
2582
+ if (Array.isArray(attr)) {
2583
+ const [expr, alias] = attr;
2584
+ let sql;
2585
+ if (this.isFnExpression(expr)) {
2586
+ sql = this.compileFnExpression(expr);
2587
+ }
2588
+ else if (this.isColExpression(expr)) {
2589
+ sql = this.escapeId(expr.col);
2590
+ }
2591
+ else if (this.isLiteralExpression(expr)) {
2592
+ sql = expr.sql;
2593
+ }
2594
+ else if (typeof expr === 'string') {
2595
+ // Check if it's a column reference or literal SQL
2596
+ if (expr.includes('(') || expr.includes(' ')) {
2597
+ sql = expr; // Literal SQL
2598
+ }
2599
+ else {
2600
+ sql = this.escapeId(expr);
2601
+ }
2602
+ }
2603
+ else {
2604
+ sql = String(expr);
2605
+ }
2606
+ return `${sql} AS ${this.escapeId(alias)}`;
2607
+ }
2608
+ else if (this.isFnExpression(attr)) {
2609
+ // Function without alias - let DB assign a name
2610
+ return this.compileFnExpression(attr);
2611
+ }
2612
+ else if (this.isColExpression(attr)) {
2613
+ return this.escapeId(attr.col);
2614
+ }
2615
+ else if (this.isLiteralExpression(attr)) {
2616
+ return attr.sql;
2617
+ }
2618
+ else if (typeof attr === 'string') {
2619
+ // Plain column name
2620
+ return this.escapeId(attr);
2621
+ }
2622
+ return String(attr);
2623
+ });
2624
+ return parts.join(', ');
2625
+ }
2626
+ else if (attributes.include) {
2627
+ return attributes.include.map((a) => this.escapeId(a)).join(', ');
2628
+ }
2629
+ else if (attributes.exclude) {
2630
+ return `* EXCEPT (${attributes.exclude.map((a) => this.escapeId(a)).join(', ')})`;
2631
+ }
2632
+ return '*';
2633
+ }
2634
+ /**
2635
+ * Build GROUP BY clause with support for function expressions and aliases
2636
+ * Handles: 'field', ['field1', 'field2'], [[fn('COUNT', 'id'), 'count']]
2637
+ */
2638
+ buildGroupByClause(group) {
2639
+ if (!group)
2640
+ return '';
2641
+ const groupArray = Array.isArray(group) ? group : [group];
2642
+ if (groupArray.length === 0)
2643
+ return '';
2644
+ const parts = groupArray.map((field) => {
2645
+ if (Array.isArray(field)) {
2646
+ // Array format: [expression, alias] or [fn, alias]
2647
+ const [expr, alias] = field;
2648
+ let sql;
2649
+ if (this.isFnExpression(expr)) {
2650
+ sql = this.compileFnExpression(expr);
2651
+ }
2652
+ else if (this.isColExpression(expr)) {
2653
+ sql = this.escapeId(expr.col);
2654
+ }
2655
+ else if (this.isLiteralExpression(expr)) {
2656
+ sql = expr.sql;
2657
+ }
2658
+ else if (typeof expr === 'string') {
2659
+ sql = this.escapeId(expr);
2660
+ }
2661
+ else {
2662
+ sql = String(expr);
2663
+ }
2664
+ // Alias is optional in GROUP BY
2665
+ if (alias) {
2666
+ return `${sql} AS ${this.escapeId(alias)}`;
2667
+ }
2668
+ return sql;
2669
+ }
2670
+ else if (this.isFnExpression(field)) {
2671
+ return this.compileFnExpression(field);
2672
+ }
2673
+ else if (this.isColExpression(field)) {
2674
+ return this.escapeId(field.col);
2675
+ }
2676
+ else if (this.isLiteralExpression(field)) {
2677
+ return field.sql;
2678
+ }
2679
+ else if (typeof field === 'string') {
2680
+ if (field.includes('.')) {
2681
+ return field
2682
+ .split('.')
2683
+ .map((part) => this.escapeId(part))
2684
+ .join('.');
2685
+ }
2686
+ return this.escapeId(field);
2687
+ }
2688
+ return String(field);
2689
+ });
2690
+ return `GROUP BY ${parts.join(', ')}`;
2691
+ }
2692
+ /**
2693
+ * Build a WHERE clause condition using standard operators (Op.gt, Op.eq, etc.)
2694
+ * Uses SQLite ? placeholders
2695
+ */
2696
+ buildCondition(condition, values, paramChar = '?') {
2697
+ if (!condition || (typeof condition === 'object' && Object.keys(condition).length === 0)) {
2698
+ return '';
2699
+ }
2700
+ // Handle logical operators
2701
+ if ('$and' in condition) {
2702
+ const andParts = condition.$and.map((c) => this.buildCondition(c, values, paramChar));
2703
+ return `(${andParts.join(' AND ')})`;
2704
+ }
2705
+ if ('$or' in condition) {
2706
+ const orParts = condition.$or.map((c) => this.buildCondition(c, values, paramChar));
2707
+ return `(${orParts.join(' OR ')})`;
2708
+ }
2709
+ if ('$not' in condition) {
2710
+ return `NOT (${this.buildCondition(condition.$not, values, paramChar)})`;
2711
+ }
2712
+ // Handle regular conditions
2713
+ const conditions = [];
2714
+ for (const [key, value] of Object.entries(condition)) {
2715
+ if (key.startsWith('$'))
2716
+ continue; // Skip logical operators
2717
+ let sql = '';
2718
+ // Check if value is an operator object
2719
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
2720
+ const opKeys = Object.keys(value);
2721
+ if (opKeys.length === 1) {
2722
+ const opKey = opKeys[0];
2723
+ const opValue = value[opKey];
2724
+ // Map Op symbol to string
2725
+ if (opKey === 'Symbol(eq)' || opKey === 'Symbol(operators.eq)') {
2726
+ sql = `${this.escapeId(key)} = ${paramChar}`;
2727
+ values.push(opValue);
2728
+ }
2729
+ else if (opKey === 'Symbol(ne)' || opKey === 'Symbol(operators.ne)') {
2730
+ sql = `${this.escapeId(key)} != ${paramChar}`;
2731
+ values.push(opValue);
2732
+ }
2733
+ else if (opKey === 'Symbol(gt)' || opKey === 'Symbol(operators.gt)') {
2734
+ sql = `${this.escapeId(key)} > ${paramChar}`;
2735
+ values.push(opValue);
2736
+ }
2737
+ else if (opKey === 'Symbol(gte)' || opKey === 'Symbol(operators.gte)') {
2738
+ sql = `${this.escapeId(key)} >= ${paramChar}`;
2739
+ values.push(opValue);
2740
+ }
2741
+ else if (opKey === 'Symbol(lt)' || opKey === 'Symbol(operators.lt)') {
2742
+ sql = `${this.escapeId(key)} < ${paramChar}`;
2743
+ values.push(opValue);
2744
+ }
2745
+ else if (opKey === 'Symbol(lte)' || opKey === 'Symbol(operators.lte)') {
2746
+ sql = `${this.escapeId(key)} <= ${paramChar}`;
2747
+ values.push(opValue);
2748
+ }
2749
+ else if (opKey === 'Symbol(in)' || opKey === 'Symbol(operators.in)') {
2750
+ sql = `${this.escapeId(key)} IN (${paramChar})`;
2751
+ values.push(opValue);
2752
+ }
2753
+ else if (opKey === 'Symbol(notIn)' || opKey === 'Symbol(operators.notIn)') {
2754
+ sql = `${this.escapeId(key)} NOT IN (${paramChar})`;
2755
+ values.push(opValue);
2756
+ }
2757
+ else if (opKey === 'Symbol(between)' || opKey === 'Symbol(operators.between)') {
2758
+ sql = `${this.escapeId(key)} BETWEEN ${paramChar} AND ${paramChar}`;
2759
+ values.push(...opValue);
2760
+ }
2761
+ else if (opKey === 'Symbol(notBetween)' || opKey === 'Symbol(operators.notBetween)') {
2762
+ sql = `${this.escapeId(key)} NOT BETWEEN ${paramChar} AND ${paramChar}`;
2763
+ values.push(...opValue);
2764
+ }
2765
+ else if (opKey === 'Symbol(isNull)' || opKey === 'Symbol(operators.isNull)') {
2766
+ sql = `${this.escapeId(key)} IS NULL`;
2767
+ }
2768
+ else if (opKey === 'Symbol(isNotNull)' || opKey === 'Symbol(operators.isNotNull)') {
2769
+ sql = `${this.escapeId(key)} IS NOT NULL`;
2770
+ }
2771
+ else if (opKey === 'Symbol(like)' || opKey === 'Symbol(operators.like)') {
2772
+ sql = `${this.escapeId(key)} LIKE ${paramChar}`;
2773
+ values.push(opValue);
2774
+ }
2775
+ else if (opKey === 'Symbol(notLike)' || opKey === 'Symbol(operators.notLike)') {
2776
+ sql = `${this.escapeId(key)} NOT LIKE ${paramChar}`;
2777
+ values.push(opValue);
2778
+ }
2779
+ else if (opKey === 'Symbol(iLike)' || opKey === 'Symbol(operators.iLike)') {
2780
+ sql = `${this.escapeId(key)} LIKE ${paramChar}`; // SQLite doesn't have ILIKE
2781
+ values.push(opValue);
2782
+ }
2783
+ else if (opKey === 'Symbol(notILike)' || opKey === 'Symbol(operators.notILike)') {
2784
+ sql = `${this.escapeId(key)} NOT LIKE ${paramChar}`;
2785
+ values.push(opValue);
2786
+ }
2787
+ }
2788
+ if (!sql) {
2789
+ // Fallback: treat as equals
2790
+ sql = `${this.escapeId(key)} = ${paramChar}`;
2791
+ values.push(value);
2792
+ }
2793
+ }
2794
+ else if (Array.isArray(value)) {
2795
+ // Handle IN arrays
2796
+ sql = `${this.escapeId(key)} IN (${value.map(() => paramChar).join(', ')})`;
2797
+ values.push(...value);
2798
+ }
2799
+ else {
2800
+ // Simple equality
2801
+ sql = `${this.escapeId(key)} = ${paramChar}`;
2802
+ values.push(value);
2803
+ }
2804
+ if (sql) {
2805
+ conditions.push(sql);
2806
+ }
2807
+ }
2808
+ return conditions.join(' AND ');
2809
+ }
2810
+ /**
2811
+ * Build a HAVING clause with support for standard Op operators
2812
+ */
2813
+ buildHavingClause(having) {
2814
+ const values = [];
2815
+ if (!having || (typeof having === 'object' && Object.keys(having).length === 0)) {
2816
+ return { sql: '', values };
2817
+ }
2818
+ // Use the condition builder with Op operator support
2819
+ const sql = this.buildCondition(having, values);
2820
+ return { sql, values };
2821
+ }
2822
+ /**
2823
+ * Build a `WITH [RECURSIVE] name [(cols)] AS (query), ...` clause from
2824
+ * `SelectOptions.cte`. Nested `SelectOptions` queries are compiled via a
2825
+ * recursive call to `buildSelectQuery`; raw SQL strings are used verbatim.
2826
+ *
2827
+ * SQLite uses unnumbered `?` placeholders (unlike Postgres/MSSQL's
2828
+ * numbered `$n`/`@pN`), so no placeholder renumbering is needed here -
2829
+ * each CTE's `values` are simply concatenated in the order their `?`
2830
+ * markers appear in the rendered SQL, and the caller prepends the whole
2831
+ * batch ahead of the main query's own values.
2832
+ */
2833
+ buildWithClause(ctes) {
2834
+ if (!ctes || ctes.length === 0) {
2835
+ return { sql: '', values: [] };
2836
+ }
2837
+ const values = [];
2838
+ const anyRecursive = ctes.some((cte) => cte.recursive === true);
2839
+ const parts = ctes.map((cte) => {
2840
+ const columnsSql = cte.columns && cte.columns.length > 0
2841
+ ? ` (${cte.columns.map((c) => this.escapeId(c)).join(', ')})`
2842
+ : '';
2843
+ let bodySql;
2844
+ if (typeof cte.query === 'string') {
2845
+ bodySql = cte.query;
2846
+ }
2847
+ else {
2848
+ const built = this.buildSelectQuery(cte.query);
2849
+ bodySql = built.sql;
2850
+ values.push(...built.values);
2851
+ }
2852
+ return `${this.escapeId(cte.name)}${columnsSql} AS (${bodySql})`;
2853
+ });
2854
+ const withKeyword = anyRecursive ? 'WITH RECURSIVE' : 'WITH';
2855
+ return { sql: `${withKeyword} ${parts.join(', ')} `, values };
2856
+ }
2857
+ /**
2858
+ * Build a SELECT query
2859
+ */
2860
+ buildSelectQuery(options) {
2861
+ const queryValues = [];
2862
+ // Build WITH clause (Common Table Expressions), if provided
2863
+ let withSql = '';
2864
+ if (options.cte && options.cte.length > 0) {
2865
+ const withClause = this.buildWithClause(options.cte);
2866
+ withSql = withClause.sql;
2867
+ queryValues.push(...withClause.values);
2868
+ }
2869
+ // Helper function to format a column/expression for SELECT
2870
+ const formatSelectAttribute = (attr) => {
2871
+ // Handle Col class instance (has toSql method)
2872
+ if (attr && typeof attr === 'object' && typeof attr.toSql === 'function') {
2873
+ return attr.toSql();
2874
+ }
2875
+ // Handle the modern typed-expression shapes from src/operators.ts / the
2876
+ // src/query-builders/functions/** builder library: { __type: 'literal', sql },
2877
+ // { __type: 'fn', fn, args }, { __type: 'col', col }. Without this, these
2878
+ // objects fall through to the generic branches below (which look for the
2879
+ // older { val }/{ fn }/{ col }/{ literal } shapes) and end up stringified
2880
+ // as "[object Object]", producing invalid SQL like "[object Object] AS alias".
2881
+ if (attr && typeof attr === 'object' && attr.__type === 'literal' && typeof attr.sql === 'string') {
2882
+ return attr.sql;
2883
+ }
2884
+ if (attr && typeof attr === 'object' && attr.__type === 'col' && typeof attr.col === 'string') {
2885
+ return this.escapeId(attr.col);
2886
+ }
2887
+ if (attr && typeof attr === 'object' && attr.__type === 'fn' && Array.isArray(attr.args)) {
2888
+ const formattedArgs = attr.args.map((arg) => formatSelectAttribute(arg));
2889
+ return `${attr.fn}(${formattedArgs.join(', ')})`;
2890
+ }
2891
+ // Handle Literal class instance (has val property)
2892
+ if (attr && typeof attr === 'object' && 'val' in attr) {
2893
+ return attr.val;
2894
+ }
2895
+ // Handle Fn type: { fn: 'COUNT', args: [...] }
2896
+ if (attr && typeof attr === 'object' && 'fn' in attr) {
2897
+ const fn = attr;
2898
+ const formattedArgs = fn.args.map((arg) => {
2899
+ // Handle Col class or object
2900
+ if (arg && typeof arg === 'object') {
2901
+ if (typeof arg.toSql === 'function') {
2902
+ return arg.toSql();
2903
+ }
2904
+ // Handle Col type object: { col: 'name' }
2905
+ if ('col' in arg) {
2906
+ return this.escapeId(arg.col);
2907
+ }
2908
+ // Handle Fn type (nested)
2909
+ if ('fn' in arg) {
2910
+ return formatSelectAttribute(arg);
2911
+ }
2912
+ // Handle Literal type object: { literal: 'sql' }
2913
+ if ('literal' in arg) {
2914
+ return arg.literal;
2915
+ }
2916
+ }
2917
+ // Handle string column names
2918
+ if (typeof arg === 'string') {
2919
+ // Check if it looks like a function call already
2920
+ if (arg.includes('(') && arg.includes(')')) {
2921
+ return arg;
2922
+ }
2923
+ return this.escapeId(arg);
2924
+ }
2925
+ // Handle literals
2926
+ return String(arg);
2927
+ });
2928
+ return `${fn.fn}(${formattedArgs.join(', ')})`;
2929
+ }
2930
+ // Handle Col type object: { col: 'name' }
2931
+ if (attr && typeof attr === 'object' && 'col' in attr) {
2932
+ return this.escapeId(attr.col);
2933
+ }
2934
+ // Handle Literal type object: { literal: 'sql' }
2935
+ if (attr && typeof attr === 'object' && 'literal' in attr) {
2936
+ return attr.literal;
2937
+ }
2938
+ // Handle string column names
2939
+ if (typeof attr === 'string') {
2940
+ return this.escapeId(attr);
2941
+ }
2942
+ // Fallback
2943
+ return String(attr);
2944
+ };
2945
+ // Helper function to format an attribute with optional alias
2946
+ // Handles [expression, alias] or [fn, alias] format
2947
+ const formatAttributeWithAlias = (attr) => {
2948
+ // Check if it's an array with alias [expression, alias]
2949
+ if (Array.isArray(attr) && attr.length === 2) {
2950
+ const [expr, alias] = attr;
2951
+ return `${formatSelectAttribute(expr)} AS ${this.escapeId(alias)}`;
2952
+ }
2953
+ return formatSelectAttribute(attr);
2954
+ };
2955
+ // Build SELECT clause
2956
+ let selectSql = '*';
2957
+ if (options.attributes) {
2958
+ if (Array.isArray(options.attributes)) {
2959
+ selectSql = options.attributes.map((a) => formatAttributeWithAlias(a)).join(', ');
2960
+ }
2961
+ else if (options.attributes.include) {
2962
+ selectSql = options.attributes.include.map((a) => this.escapeId(a)).join(', ');
2963
+ }
2964
+ else if (options.attributes.exclude) {
2965
+ selectSql = `* EXCEPT (${options.attributes.exclude.map((a) => this.escapeId(a)).join(', ')})`;
2966
+ }
2967
+ }
2968
+ // Handle DISTINCT (for COUNT(DISTINCT col))
2969
+ let distinctClause = '';
2970
+ if (options.distinct) {
2971
+ if (options.col) {
2972
+ distinctClause = `DISTINCT ${this.escapeId(options.col)} `;
2973
+ }
2974
+ else {
2975
+ distinctClause = 'DISTINCT ';
2976
+ }
2977
+ }
2978
+ let sql = `${withSql}SELECT ${distinctClause}${selectSql} FROM ${this.quoteTable(options.tableName, options.schema)}`;
2979
+ // Build JOIN clause (for includes)
2980
+ if (options.include && options.include.length > 0) {
2981
+ for (const include of options.include) {
2982
+ const joinType = include.required ? 'INNER JOIN' : 'LEFT JOIN';
2983
+ const includeSchema = include.schema || include.model?.schema;
2984
+ const includeTableName = include.model.tableName;
2985
+ const tableName = includeTableName ? includeTableName : include.model.toString();
2986
+ const alias = include.as || tableName;
2987
+ sql += ` ${joinType} ${this.quoteTable(tableName, includeSchema)} ${this.escapeId(alias)}`;
2988
+ if (include.where) {
2989
+ const whereClause = this.buildWhereClause(include.where);
2990
+ sql += ` ON ${whereClause.sql}`;
2991
+ queryValues.push(...whereClause.values);
2992
+ }
2993
+ }
2994
+ }
2995
+ // Build WHERE clause
2996
+ if (options.where) {
2997
+ const whereClause = this.buildWhereClause(options.where);
2998
+ if (whereClause.sql) {
2999
+ sql += ` WHERE ${whereClause.sql}`;
3000
+ queryValues.push(...whereClause.values);
3001
+ }
3002
+ }
3003
+ // Build GROUP BY clause
3004
+ if (options.group) {
3005
+ const groupByClause = this.buildGroupByClause(options.group);
3006
+ if (groupByClause) {
3007
+ sql += ` ${groupByClause}`;
3008
+ }
3009
+ }
3010
+ // Build HAVING clause
3011
+ if (options.having) {
3012
+ const havingClause = this.buildHavingClause(options.having);
3013
+ if (havingClause.sql) {
3014
+ sql += ` HAVING ${havingClause.sql}`;
3015
+ queryValues.push(...havingClause.values);
3016
+ }
3017
+ }
3018
+ // Build ORDER BY clause
3019
+ if (options.order) {
3020
+ const orderClause = this.buildOrderClause(options.order);
3021
+ if (orderClause) {
3022
+ sql += ` ${orderClause}`;
3023
+ }
3024
+ }
3025
+ // Build LIMIT/OFFSET clause
3026
+ sql += this.buildLimitOffset(options.limit, options.offset);
3027
+ // Build UNION clause if provided
3028
+ if (options.union && options.union.length > 0) {
3029
+ const unionType = options.unionType || 'UNION';
3030
+ for (const unionQuery of options.union) {
3031
+ const unionModel = unionQuery.model;
3032
+ const unionTableName = unionModel.tableName || unionModel.name || '';
3033
+ // Build the union query
3034
+ let unionSql = `SELECT * FROM ${this.escapeId(unionTableName)}`;
3035
+ // Add WHERE clause for union query
3036
+ if (unionQuery.where) {
3037
+ const whereClause = this.buildWhereClause(unionQuery.where);
3038
+ if (whereClause.sql) {
3039
+ unionSql += ` WHERE ${whereClause.sql}`;
3040
+ queryValues.push(...whereClause.values);
3041
+ }
3042
+ }
3043
+ // Add ORDER BY for union query
3044
+ if (unionQuery.order) {
3045
+ const orderClause = this.buildOrderClause(unionQuery.order);
3046
+ if (orderClause) {
3047
+ unionSql += ` ${orderClause}`;
3048
+ }
3049
+ }
3050
+ // Add LIMIT for union query
3051
+ if (unionQuery.limit) {
3052
+ unionSql += ` LIMIT ${unionQuery.limit}`;
3053
+ }
3054
+ // Add OFFSET for union query
3055
+ if (unionQuery.offset) {
3056
+ unionSql += ` OFFSET ${unionQuery.offset}`;
3057
+ }
3058
+ sql += ` ${unionType} ${unionSql}`;
3059
+ }
3060
+ }
3061
+ return { sql, values: queryValues };
3062
+ }
3063
+ /**
3064
+ * Recreate table without a column (for removeColumn)
3065
+ */
3066
+ async recreateTableWithoutColumn(tableName, columnName) {
3067
+ const tableInfo = await this.describeTable(tableName);
3068
+ const columns = Object.keys(tableInfo).filter((col) => col !== columnName);
3069
+ // Preserve NOT NULL / DEFAULT / PRIMARY KEY / AUTOINCREMENT / UNIQUE constraints
3070
+ // of the remaining columns during the rebuild (see recreateTableWithConstraint).
3071
+ const columnsDef = columns.map((col) => this.getColumnDefinitionSql(col, this.toColumnDefinition(tableInfo[col])));
3072
+ const tempTable = `${tableName}_temp`;
3073
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} RENAME TO ${this.escapeId(tempTable)}`);
3074
+ await this.query(`CREATE TABLE ${this.escapeId(tableName)} (${columnsDef.join(', ')})`);
3075
+ await this.query(`INSERT INTO ${this.escapeId(tableName)} SELECT ${columns.map((c) => this.escapeId(c)).join(', ')} FROM ${this.escapeId(tempTable)}`);
3076
+ await this.query(`DROP TABLE ${this.escapeId(tempTable)}`);
3077
+ }
3078
+ /**
3079
+ * Recreate table to modify a column
3080
+ */
3081
+ async recreateTableModifyColumn(tableName, columnName, newDefinition) {
3082
+ const tableInfo = await this.describeTable(tableName);
3083
+ const columns = [];
3084
+ for (const [colName, colInfo] of Object.entries(tableInfo)) {
3085
+ if (colName === columnName) {
3086
+ columns.push(this.getColumnDefinitionSql(colName, newDefinition));
3087
+ }
3088
+ else {
3089
+ // Preserve NOT NULL / DEFAULT / PRIMARY KEY / AUTOINCREMENT / UNIQUE constraints
3090
+ // of unaffected columns during the rebuild.
3091
+ columns.push(this.getColumnDefinitionSql(colName, this.toColumnDefinition(colInfo)));
3092
+ }
3093
+ }
3094
+ const tempTable = `${tableName}_temp`;
3095
+ await this.query(`ALTER TABLE ${this.escapeId(tableName)} RENAME TO ${this.escapeId(tempTable)}`);
3096
+ await this.query(`CREATE TABLE ${this.escapeId(tableName)} (${columns.join(', ')})`);
3097
+ const colNames = Object.keys(tableInfo)
3098
+ .map((c) => this.escapeId(c))
3099
+ .join(', ');
3100
+ await this.query(`INSERT INTO ${this.escapeId(tableName)} SELECT ${colNames} FROM ${this.escapeId(tempTable)}`);
3101
+ await this.query(`DROP TABLE ${this.escapeId(tempTable)}`);
3102
+ }
3103
+ // ── User-management stubs (not supported by SQLite) ─────────────────────
3104
+ buildCreateUserQuery(_username, _options) {
3105
+ throw new Error('User management is not supported by SQLite');
3106
+ }
3107
+ buildAlterUserQuery(_username, _options) {
3108
+ throw new Error('User management is not supported by SQLite');
3109
+ }
3110
+ buildDropUserQuery(_username, _options) {
3111
+ throw new Error('User management is not supported by SQLite');
3112
+ }
3113
+ getUsersQuery() {
3114
+ throw new Error('User management is not supported by SQLite');
3115
+ }
3116
+ buildGrantQuery(_options) {
3117
+ throw new Error('GRANT is not supported by SQLite');
3118
+ }
3119
+ buildRevokeQuery(_options) {
3120
+ throw new Error('REVOKE is not supported by SQLite');
3121
+ }
3122
+ buildShowGrantsQuery(_username, _host) {
3123
+ throw new Error('SHOW GRANTS is not supported by SQLite');
3124
+ }
3125
+ buildFlushPrivilegesQuery() {
3126
+ throw new Error('FLUSH PRIVILEGES is not supported by SQLite');
3127
+ }
3128
+ buildCreateRoleQuery(_roleName, _options) {
3129
+ throw new Error('Role management is not supported by SQLite');
3130
+ }
3131
+ buildDropRoleQuery(_roleName, _options) {
3132
+ throw new Error('Role management is not supported by SQLite');
3133
+ }
3134
+ buildGrantRoleQuery(_role, _to, _options) {
3135
+ throw new Error('Role management is not supported by SQLite');
3136
+ }
3137
+ buildRevokeRoleQuery(_role, _from, _options) {
3138
+ throw new Error('Role management is not supported by SQLite');
3139
+ }
3140
+ getRolesQuery() {
3141
+ throw new Error('Role management is not supported by SQLite');
3142
+ }
3143
+ /**
3144
+ * Bulk insert records into a table
3145
+ */
3146
+ async bulkInsert(tableName, records, _options) {
3147
+ if (records.length === 0) {
3148
+ return { rows: [], rowCount: 0, fields: [] };
3149
+ }
3150
+ const columns = Object.keys(records[0]);
3151
+ const placeholders = records.map(() => `(${columns.map(() => '?').join(', ')})`);
3152
+ const values = records.flatMap((record) => columns.map((col) => record[col]));
3153
+ const sql = `INSERT INTO ${this.quoteTable(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES ${placeholders.join(', ')}`;
3154
+ return this.query(sql, { replacements: values });
3155
+ }
3156
+ /**
3157
+ * Update every row matching `where` with `values` in a single statement.
3158
+ */
3159
+ async bulkUpdate(tableName, values, where = {}) {
3160
+ const cols = Object.keys(values);
3161
+ if (cols.length === 0)
3162
+ return undefined;
3163
+ const setValues = [];
3164
+ const setClause = cols
3165
+ .map((c) => {
3166
+ setValues.push(values[c]);
3167
+ return `${this.escapeId(c)} = ?`;
3168
+ })
3169
+ .join(', ');
3170
+ let sql = `UPDATE ${this.quoteTable(tableName)} SET ${setClause}`;
3171
+ const whereClause = this.buildWhereClause(where);
3172
+ if (whereClause.sql)
3173
+ sql += ` WHERE ${whereClause.sql}`;
3174
+ return this.query(sql, { replacements: [...setValues, ...whereClause.values] });
3175
+ }
3176
+ /**
3177
+ * Delete every row matching `where` (deletes all rows when `where` is empty).
3178
+ */
3179
+ async bulkDelete(tableName, where = {}) {
3180
+ let sql = `DELETE FROM ${this.quoteTable(tableName)}`;
3181
+ const whereClause = this.buildWhereClause(where);
3182
+ if (whereClause.sql)
3183
+ sql += ` WHERE ${whereClause.sql}`;
3184
+ return this.query(sql, { replacements: whereClause.values });
3185
+ }
3186
+ /**
3187
+ * Add a foreign key to a table
3188
+ * Note: SQLite does not support ALTER TABLE ADD CONSTRAINT.
3189
+ * Foreign keys must be defined when the table is created.
3190
+ */
3191
+ async addForeignKey(tableName, columnName, referencedTableName, referencedColumnName, options) {
3192
+ // SQLite has no ALTER TABLE ADD CONSTRAINT, so adding a foreign key to an
3193
+ // existing table requires the documented 12-step table rebuild: recreate the
3194
+ // table with the FK baked into its definition, copy the rows over, then swap.
3195
+ let createSql;
3196
+ try {
3197
+ createSql = await this.getCreateTable(tableName);
3198
+ }
3199
+ catch {
3200
+ throw new Error(`Cannot add foreign key: table "${tableName}" not found`);
3201
+ }
3202
+ if (!createSql) {
3203
+ throw new Error(`Cannot add foreign key: table "${tableName}" not found`);
3204
+ }
3205
+ let fkClause = `FOREIGN KEY (${this.escapeId(columnName)}) REFERENCES ${this.quoteTable(referencedTableName)} (${this.escapeId(referencedColumnName)})`;
3206
+ if (options?.onDelete)
3207
+ fkClause += ` ON DELETE ${options.onDelete}`;
3208
+ if (options?.onUpdate)
3209
+ fkClause += ` ON UPDATE ${options.onUpdate}`;
3210
+ // Insert the FK clause just before the closing paren of the CREATE TABLE body.
3211
+ const lastParen = createSql.lastIndexOf(')');
3212
+ const newCreateSql = createSql.slice(0, lastParen).trimEnd() + `,\n ${fkClause}\n` + createSql.slice(lastParen);
3213
+ const tmpName = `${tableName}__fk_rebuild`;
3214
+ // Setter PRAGMAs don't return rows, so run them via exec() rather than query()
3215
+ // (which would try stmt.all() and throw "statement does not return data").
3216
+ this.connection?.exec('PRAGMA foreign_keys=OFF');
3217
+ try {
3218
+ await this.query(`ALTER TABLE ${this.quoteTable(tableName)} RENAME TO ${this.escapeId(tmpName)}`);
3219
+ await this.query(newCreateSql);
3220
+ await this.query(`INSERT INTO ${this.quoteTable(tableName)} SELECT * FROM ${this.escapeId(tmpName)}`);
3221
+ await this.query(`DROP TABLE ${this.escapeId(tmpName)}`);
3222
+ }
3223
+ finally {
3224
+ this.connection?.exec('PRAGMA foreign_keys=ON');
3225
+ }
3226
+ }
3227
+ /**
3228
+ * Rename a column
3229
+ */
3230
+ async renameColumn(tableName, oldColumnName, newColumnName) {
3231
+ const sql = `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${this.escapeId(oldColumnName)} TO ${this.escapeId(newColumnName)}`;
3232
+ await this.query(sql);
3233
+ }
3234
+ /**
3235
+ * Create a fulltext index (SQLite supports FTS5)
3236
+ */
3237
+ async createFulltextIndex(_tableName, indexName, fields, _options) {
3238
+ // SQLite uses FTS5 for fulltext search
3239
+ const sql = `CREATE VIRTUAL TABLE ${this.escapeId(indexName)} USING fts5(${fields.map((f) => this.escapeId(f)).join(', ')})`;
3240
+ await this.query(sql);
3241
+ }
3242
+ /**
3243
+ * Create a spatial index (SQLite supports spatial indexing via RTree)
3244
+ */
3245
+ async createSpatialIndex(_tableName, indexName, fields, _options) {
3246
+ // SQLite uses RTree for spatial indexing
3247
+ const sql = `CREATE VIRTUAL TABLE ${this.escapeId(indexName)} USING rtree(${fields.map((f) => this.escapeId(f)).join(', ')})`;
3248
+ await this.query(sql);
3249
+ }
3250
+ /**
3251
+ * Create user mapping (SQLite doesn't support user mappings)
3252
+ */
3253
+ async createUserMapping(_userName, _serverName, _options) {
3254
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
3255
+ }
3256
+ /**
3257
+ * Drop user mapping (SQLite doesn't support user mappings)
3258
+ */
3259
+ async dropUserMapping(_userName, _serverName, _options) {
3260
+ throw new Error('Foreign Data Wrappers are not supported by SQLite. This is a PostgreSQL-specific feature.');
3261
+ }
3262
+ // ==================== Spatial Functions ====================
3263
+ /**
3264
+ * ST_Distance - calculate distance between two geometries
3265
+ * SQLite uses SpatiaLite extension or custom implementation
3266
+ */
3267
+ stDistance(geom1, geom2, _srid) {
3268
+ // SQLite doesn't have built-in ST_Distance, use Haversine approximation for point coordinates
3269
+ // For full spatial support, SpatiaLite extension is required
3270
+ return `ST_Distance(${geom1}, ${geom2})`;
3271
+ }
3272
+ /**
3273
+ * ST_DWithin - check if geometries are within a given distance
3274
+ */
3275
+ stDWithin(geom1, geom2, distance, _srid) {
3276
+ return `ST_DWithin(${geom1}, ${geom2}, ${distance})`;
3277
+ }
3278
+ /**
3279
+ * ST_Within - check if geometry A is within geometry B
3280
+ */
3281
+ stWithin(geom1, geom2, _srid) {
3282
+ return `ST_Within(${geom1}, ${geom2})`;
3283
+ }
3284
+ /**
3285
+ * ST_Contains - check if geometry A contains geometry B
3286
+ */
3287
+ stContains(geom1, geom2, _srid) {
3288
+ return `ST_Contains(${geom1}, ${geom2})`;
3289
+ }
3290
+ /**
3291
+ * ST_Intersects - check if geometries intersect
3292
+ */
3293
+ stIntersects(geom1, geom2, _srid) {
3294
+ return `ST_Intersects(${geom1}, ${geom2})`;
3295
+ }
3296
+ /**
3297
+ * ST_AsText - convert geometry to text representation
3298
+ */
3299
+ stAsText(geom) {
3300
+ return `ST_AsText(${geom})`;
3301
+ }
3302
+ /**
3303
+ * ST_GeomFromText - create geometry from text
3304
+ */
3305
+ stGeomFromText(wkt, srid) {
3306
+ return srid ? `ST_GeomFromText('${wkt}', ${srid})` : `ST_GeomFromText('${wkt}')`;
3307
+ }
3308
+ /**
3309
+ * Build an expression to shred a JSON document/array into relational rows.
3310
+ * SQLite has no `JSON_TABLE` function; its real equivalent is the `json_each`
3311
+ * table-valued function, which yields fixed columns (key, value, type, atom, id,
3312
+ * parent, fullkey, path). To project those into named/typed columns (matching the
3313
+ * other dialects' JSON_TABLE/OPENJSON shape), this wraps json_each in a derived table
3314
+ * that applies `json_extract` per requested column.
3315
+ * SQLite: (SELECT json_extract(je.value, '$.path') AS name, ... FROM
3316
+ * json_each(expr, '$.rowPath') AS je) AS alias
3317
+ */
3318
+ buildJsonTable(jsonExpression, rowPath, columns, alias) {
3319
+ const pathArg = rowPath && rowPath !== '$' ? `, '${rowPath}'` : '';
3320
+ const selectCols = columns
3321
+ .map((col) => {
3322
+ if (col.forOrdinality) {
3323
+ return `je."key" AS ${this.escapeId(col.name)}`;
3324
+ }
3325
+ return `json_extract(je.value, '${col.path}') AS ${this.escapeId(col.name)}`;
3326
+ })
3327
+ .join(', ');
3328
+ return `(SELECT ${selectCols} FROM json_each(${jsonExpression}${pathArg}) AS je) AS ${this.escapeId(alias)}`;
3329
+ }
3330
+ }
3331
+ exports.SQLiteDialect = SQLiteDialect;
3332
+ /**
3333
+ * SQLite-specific transaction class
3334
+ */
3335
+ class SQLiteTransaction {
3336
+ constructor(connection, depth, options,
3337
+ // Owning dialect, used so commit()/rollback() called directly on the
3338
+ // transaction object (rather than via dialect.commitTransaction/
3339
+ // rollbackTransaction) still actually execute COMMIT/ROLLBACK/SAVEPOINT SQL.
3340
+ // Previously these were no-ops that only flipped `finished`, leaving the
3341
+ // underlying SQLite transaction open forever if a caller used this API.
3342
+ dialect) {
3343
+ this.connection = connection;
3344
+ this.dialect = dialect;
3345
+ this.finished = false;
3346
+ this.parent = null;
3347
+ this.savepoints = [];
3348
+ // `depth` is the pre-increment transactionDepth at creation time (0 for
3349
+ // the outermost transaction). The id itself is used verbatim as the
3350
+ // SAVEPOINT name for nested transactions, so it must not also get a
3351
+ // separate "sp_" prefix prepended at the call site (that produced
3352
+ // "SAVEPOINT sp_sp_0_..." double-prefixed names that never matched what
3353
+ // was actually created).
3354
+ this.id = `sp_${depth}_${Date.now()}`;
3355
+ this.isOuter = depth === 0;
3356
+ this.options = options || {};
3357
+ }
3358
+ async commit() {
3359
+ if (this.finished)
3360
+ return;
3361
+ if (this.dialect) {
3362
+ await this.dialect.commitTransaction(this);
3363
+ }
3364
+ this.finished = true;
3365
+ }
3366
+ async rollback() {
3367
+ if (this.finished)
3368
+ return;
3369
+ if (this.dialect) {
3370
+ await this.dialect.rollbackTransaction(this);
3371
+ }
3372
+ this.finished = true;
3373
+ }
3374
+ }
3375
+ exports.SQLiteTransaction = SQLiteTransaction;
3376
+ /**
3377
+ * Create a new SQLite dialect instance
3378
+ */
3379
+ function createSQLiteDialect(options) {
3380
+ return new SQLiteDialect(options || {});
3381
+ }
3382
+ exports.default = SQLiteDialect;