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,1047 @@
1
+ "use strict";
2
+ /**
3
+ * SQLite Advanced Features Module
4
+ *
5
+ * Comprehensive support for SQLite advanced features including:
6
+ * - Views (Standard and updatable)
7
+ * - Triggers (BEFORE/AFTER INSERT/UPDATE/DELETE, recursive triggers)
8
+ * - Foreign Tables (FTS5, carray, etc.)
9
+ * - Full-Text Search (FTS5) - MATCH, rank, highlighting
10
+ * - JSON JSON1 extension - JSON each, extract
11
+ * - Generated Columns - VIRTUAL, STORED
12
+ * - Indexes - Partial, UNIQUE, expression indexes
13
+ * - Window Functions - SQLite 3.25+ (ROW_NUMBER, etc.)
14
+ * - CTEs - WITH RECURSIVE
15
+ * - Common Table Expressions
16
+ * - VACUUM and ANALYZE - Optimization
17
+ * - Savepoints - Transaction management
18
+ * - UPSERT - INSERT ... ON CONFLICT
19
+ * - Table-valued Functions - json_each, fts5, etc.
20
+ * - Extensions - Loadable extensions
21
+ * - SQLite-specific Types - INTEGER PRIMARY KEY, affinity
22
+ * - Constraints - CHECK, UNIQUE, NOT NULL with ON CONFLICT
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.SQLiteAdvanced = void 0;
26
+ exports.createSQLiteAdvanced = createSQLiteAdvanced;
27
+ /**
28
+ * SQLite Advanced Features Class
29
+ * Provides comprehensive SQLite-specific features beyond standard SQL
30
+ */
31
+ class SQLiteAdvanced {
32
+ constructor(dialect) {
33
+ this.dialect = dialect;
34
+ this.connection = dialect.getConnection();
35
+ }
36
+ // ============================================
37
+ // VIEW MANAGEMENT
38
+ // ============================================
39
+ /**
40
+ * Create a standard or updatable view
41
+ * SQLite views are read-only by default, but can be made updatable via INSTEAD OF triggers
42
+ */
43
+ async createView(viewName, query, options) {
44
+ let sql = 'CREATE ';
45
+ if (options?.temporary) {
46
+ sql += 'TEMPORARY ';
47
+ }
48
+ if (options?.replace) {
49
+ sql = 'CREATE OR REPLACE ';
50
+ }
51
+ sql += 'VIEW ';
52
+ if (options?.columns) {
53
+ sql += `${this.escapeId(viewName)} (${options.columns.join(', ')}) `;
54
+ }
55
+ else {
56
+ sql += `${this.escapeId(viewName)} `;
57
+ }
58
+ sql += `AS ${query}`;
59
+ await this.dialect.query(sql);
60
+ }
61
+ /**
62
+ * Drop a view
63
+ */
64
+ async dropView(viewName, options) {
65
+ let sql = 'DROP VIEW';
66
+ if (options?.ifExists) {
67
+ sql += ' IF EXISTS';
68
+ }
69
+ if (options?.cascade) {
70
+ sql += ' CASCADE';
71
+ }
72
+ sql += ` ${this.escapeId(viewName)}`;
73
+ await this.dialect.query(sql);
74
+ }
75
+ /**
76
+ * List all views in the database
77
+ */
78
+ async listViews(schema) {
79
+ const sql = schema
80
+ ? "SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%' AND name LIKE ?"
81
+ : "SELECT name FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%'";
82
+ const result = await this.dialect.query(sql, schema ? { replacements: [schema] } : undefined);
83
+ return result.rows.map((row) => row.name);
84
+ }
85
+ /**
86
+ * Get view definition
87
+ */
88
+ async getViewDefinition(viewName) {
89
+ const sql = "SELECT sql FROM sqlite_master WHERE type='view' AND name = ?";
90
+ const result = await this.dialect.query(sql, { replacements: [viewName] });
91
+ return result.rows[0]?.sql || null;
92
+ }
93
+ // ============================================
94
+ // TRIGGER MANAGEMENT
95
+ // ============================================
96
+ /**
97
+ * Create a trigger
98
+ */
99
+ async createTrigger(triggerName, timing, events, tableName, body, options) {
100
+ // SQLite does not support `CREATE OR REPLACE TRIGGER` (unlike MySQL/Postgres).
101
+ // Emulate "replace" semantics by dropping any existing trigger with the same
102
+ // name first, then creating the new one.
103
+ if (options?.replace) {
104
+ await this.dropTrigger(triggerName, { ifExists: true });
105
+ }
106
+ let sql = 'CREATE TRIGGER ';
107
+ sql += this.escapeId(triggerName);
108
+ sql += ` ${timing} ${(events || []).join(' OR ')} ON ${this.escapeId(tableName)}`;
109
+ if (options?.forEachRow) {
110
+ sql += ' FOR EACH ROW';
111
+ }
112
+ if (options?.whenCondition) {
113
+ sql += ` WHEN ${options.whenCondition}`;
114
+ }
115
+ sql += `\nBEGIN\n${body}\nEND`;
116
+ await this.dialect.query(sql);
117
+ }
118
+ /**
119
+ * Create an AFTER INSERT trigger
120
+ */
121
+ async createAfterInsertTrigger(triggerName, tableName, body) {
122
+ await this.createTrigger(triggerName, 'AFTER', ['INSERT'], tableName, body, {
123
+ forEachRow: true,
124
+ });
125
+ }
126
+ /**
127
+ * Create an AFTER UPDATE trigger
128
+ */
129
+ async createAfterUpdateTrigger(triggerName, tableName, body, columns) {
130
+ const events = columns ? ['UPDATE OF ' + columns.join(', ')] : ['UPDATE'];
131
+ await this.createTrigger(triggerName, 'AFTER', events, tableName, body, {
132
+ forEachRow: true,
133
+ });
134
+ }
135
+ /**
136
+ * Create an AFTER DELETE trigger
137
+ */
138
+ async createAfterDeleteTrigger(triggerName, tableName, body) {
139
+ await this.createTrigger(triggerName, 'AFTER', ['DELETE'], tableName, body, {
140
+ forEachRow: true,
141
+ });
142
+ }
143
+ /**
144
+ * Create a BEFORE INSERT trigger
145
+ */
146
+ async createBeforeInsertTrigger(triggerName, tableName, body) {
147
+ await this.createTrigger(triggerName, 'BEFORE', ['INSERT'], tableName, body, {
148
+ forEachRow: true,
149
+ });
150
+ }
151
+ /**
152
+ * Create a BEFORE UPDATE trigger
153
+ */
154
+ async createBeforeUpdateTrigger(triggerName, tableName, body) {
155
+ await this.createTrigger(triggerName, 'BEFORE', ['UPDATE'], tableName, body, {
156
+ forEachRow: true,
157
+ });
158
+ }
159
+ /**
160
+ * Create an INSTEAD OF trigger (for views)
161
+ */
162
+ async createInsteadOfTrigger(triggerName, viewName, events, body) {
163
+ await this.createTrigger(triggerName, 'INSTEAD OF', events, viewName, body, {
164
+ forEachRow: true,
165
+ });
166
+ }
167
+ /**
168
+ * Create a recursive trigger
169
+ */
170
+ async createRecursiveTrigger(triggerName, tableName, insertBody, updateBody, deleteBody) {
171
+ // Create INSERT trigger
172
+ await this.createTrigger(`${triggerName}_insert`, 'AFTER', ['INSERT'], tableName, insertBody, {
173
+ forEachRow: true,
174
+ });
175
+ // Create UPDATE trigger
176
+ await this.createTrigger(`${triggerName}_update`, 'AFTER', ['UPDATE'], tableName, updateBody, {
177
+ forEachRow: true,
178
+ });
179
+ // Create DELETE trigger if provided
180
+ if (deleteBody) {
181
+ await this.createTrigger(`${triggerName}_delete`, 'AFTER', ['DELETE'], tableName, deleteBody, { forEachRow: true });
182
+ }
183
+ }
184
+ /**
185
+ * Drop a trigger
186
+ */
187
+ async dropTrigger(triggerName, options) {
188
+ let sql = 'DROP TRIGGER';
189
+ if (options?.ifExists) {
190
+ sql += ' IF EXISTS';
191
+ }
192
+ sql += ` ${this.escapeId(triggerName)}`;
193
+ await this.dialect.query(sql);
194
+ }
195
+ /**
196
+ * List all triggers
197
+ */
198
+ async listTriggers(tableName) {
199
+ const sql = tableName
200
+ ? "SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name = ?"
201
+ : "SELECT name FROM sqlite_master WHERE type='trigger' AND name NOT LIKE 'sqlite_%'";
202
+ const finalSql = tableName ? this.dialect.replaceReplacements(sql, [tableName]) : sql;
203
+ const result = await this.dialect.query(finalSql);
204
+ return result.rows.map((row) => row.name);
205
+ }
206
+ /**
207
+ * Get trigger definition
208
+ */
209
+ async getTriggerDefinition(triggerName) {
210
+ const sql = "SELECT sql FROM sqlite_master WHERE type='trigger' AND name = ?";
211
+ const result = await this.dialect.query(sql, { replacements: [triggerName] });
212
+ return result.rows[0]?.sql || null;
213
+ }
214
+ // ============================================
215
+ // FULL-TEXT SEARCH (FTS5)
216
+ // ============================================
217
+ /**
218
+ * Create an FTS5 virtual table for full-text search
219
+ */
220
+ async createFTS5Table(tableName, columns, options) {
221
+ const columnDefs = columns
222
+ .map((col) => {
223
+ let def = this.escapeId(col.name);
224
+ if (col.type) {
225
+ def += ` ${col.type}`;
226
+ }
227
+ if (col.notindexed) {
228
+ def += ' NOTINDEXED';
229
+ }
230
+ return def;
231
+ })
232
+ .join(', ');
233
+ let sql = `CREATE VIRTUAL TABLE ${this.escapeId(tableName)} USING fts5(${columnDefs}`;
234
+ if (options?.tokenizer) {
235
+ if (typeof options.tokenizer === 'string') {
236
+ sql += `, tokenize='${options.tokenizer}'`;
237
+ }
238
+ else {
239
+ const opts = options.tokenizer.options
240
+ ? `, ${Object.entries(options.tokenizer.options)
241
+ .map(([k, v]) => `${k}=${v}`)
242
+ .join(', ')}`
243
+ : '';
244
+ sql += `, tokenize='${options.tokenizer.name}${opts}'`;
245
+ }
246
+ }
247
+ if (options?.content) {
248
+ sql += `, content='${options.content}'`;
249
+ }
250
+ if (options?.contentRowId) {
251
+ sql += `, content_rowid='${options.contentRowId}'`;
252
+ }
253
+ if (options?.prefix) {
254
+ if (Array.isArray(options.prefix)) {
255
+ sql += `, prefix='${options.prefix.join(' ')}'`;
256
+ }
257
+ else {
258
+ sql += `, prefix='${options.prefix}'`;
259
+ }
260
+ }
261
+ sql += ')';
262
+ await this.dialect.query(sql);
263
+ }
264
+ /**
265
+ * Create FTS5 table with existing content table
266
+ */
267
+ async createFTS5FromTable(ftsTableName, sourceTable, sourceColumns, options) {
268
+ // Create FTS5 virtual table
269
+ await this.createFTS5Table(ftsTableName, sourceColumns.map((col) => ({ name: col })), options);
270
+ // Populate with data from source table
271
+ const columnsStr = sourceColumns.map((c) => this.escapeId(c)).join(', ');
272
+ const insertSql = `INSERT INTO ${this.escapeId(ftsTableName)} (${columnsStr}) SELECT ${columnsStr} FROM ${this.escapeId(sourceTable)}`;
273
+ await this.dialect.query(insertSql);
274
+ // Create triggers to keep FTS table in sync
275
+ await this.createTrigger(`${ftsTableName}_ai`, 'AFTER', ['INSERT'], sourceTable, `BEGIN\nINSERT INTO ${this.escapeId(ftsTableName)} (${columnsStr}) VALUES (new.${sourceColumns.join(', new.')});\nEND`);
276
+ await this.createTrigger(`${ftsTableName}_ad`, 'AFTER', ['DELETE'], sourceTable, `BEGIN\nDELETE FROM ${this.escapeId(ftsTableName)} WHERE rowid = old.rowid;\nEND`);
277
+ const setClauses = sourceColumns.map((col) => `${col} = new.${col}`).join(', ');
278
+ await this.createTrigger(`${ftsTableName}_au`, 'AFTER', ['UPDATE'], sourceTable, `BEGIN\nUPDATE ${this.escapeId(ftsTableName)} SET ${setClauses} WHERE rowid = old.rowid;\nEND`);
279
+ }
280
+ /**
281
+ * Drop an FTS5 table
282
+ */
283
+ async dropFTS5Table(tableName, options) {
284
+ let sql = 'DROP TABLE';
285
+ if (options?.ifExists) {
286
+ sql += ' IF EXISTS';
287
+ }
288
+ sql += ` ${this.escapeId(tableName)}`;
289
+ await this.dialect.query(sql);
290
+ }
291
+ /**
292
+ * Perform full-text search
293
+ */
294
+ async fullTextSearch(ftsTable, searchQuery, options) {
295
+ let sql = 'SELECT';
296
+ if (options?.bm25) {
297
+ sql += ` bm25(${this.escapeId(ftsTable)}) AS score, `;
298
+ }
299
+ else {
300
+ sql += ' 0 AS score, ';
301
+ }
302
+ if (options?.highlight) {
303
+ const col = options.highlight.column || '*';
304
+ sql += ` highlight(${this.escapeId(ftsTable)}, ${options.columns?.indexOf(col) ?? 0}${options.highlight.before ? `, '${options.highlight.before}'` : ''}${options.highlight.after ? `, '${options.highlight.after}'` : ''}) AS highlighted, `;
305
+ }
306
+ sql += options?.columns?.map((c) => this.escapeId(c)).join(', ') || '*';
307
+ sql += ` FROM ${this.escapeId(ftsTable)}`;
308
+ sql += ` WHERE ${this.escapeId(ftsTable)} MATCH ?`;
309
+ if (options?.orderBy) {
310
+ sql += ` ORDER BY ${options.orderBy.map((o) => `${this.escapeId(o.column)}${o.desc ? ' DESC' : ''}`).join(', ')}`;
311
+ }
312
+ if (options?.limit) {
313
+ sql += ` LIMIT ${options.limit}`;
314
+ }
315
+ if (options?.offset) {
316
+ sql += ` OFFSET ${options.offset}`;
317
+ }
318
+ return this.dialect.query(sql, { replacements: [searchQuery] });
319
+ }
320
+ /**
321
+ * Rebuild FTS5 index
322
+ */
323
+ async rebuildFTS5(tableName) {
324
+ await this.dialect.query(`INSERT INTO ${this.escapeId(tableName)}(${this.escapeId(tableName)}) VALUES('rebuild')`);
325
+ }
326
+ /**
327
+ * Optimize FTS5 index
328
+ */
329
+ async optimizeFTS5(tableName) {
330
+ await this.dialect.query(`INSERT INTO ${this.escapeId(tableName)}(${this.escapeId(tableName)}) VALUES('optimize')`);
331
+ }
332
+ // ============================================
333
+ // JSON JSON1 EXTENSION
334
+ // ============================================
335
+ /**
336
+ * Parse JSON string
337
+ */
338
+ parseJson(jsonString) {
339
+ try {
340
+ return JSON.parse(jsonString);
341
+ }
342
+ catch {
343
+ return null;
344
+ }
345
+ }
346
+ /**
347
+ * Extract value from JSON
348
+ */
349
+ jsonExtract(jsonColumn, path) {
350
+ return `json_extract(${jsonColumn}, '${this.escapePathLiteral(path)}')`;
351
+ }
352
+ /**
353
+ * Set value in JSON
354
+ */
355
+ jsonSet(jsonColumn, path, value) {
356
+ return `json_set(${jsonColumn}, '${this.escapePathLiteral(path)}', ${this.escape(value)})`;
357
+ }
358
+ /**
359
+ * Insert into JSON array
360
+ */
361
+ jsonArrayAppend(jsonColumn, path, value) {
362
+ return `json_array_append(${jsonColumn}, '${this.escapePathLiteral(path)}', ${this.escape(value)})`;
363
+ }
364
+ /**
365
+ * Remove from JSON
366
+ */
367
+ jsonRemove(jsonColumn, path) {
368
+ return `json_remove(${jsonColumn}, '${this.escapePathLiteral(path)}')`;
369
+ }
370
+ /**
371
+ * Get JSON array length
372
+ */
373
+ jsonArrayLength(jsonColumn, path) {
374
+ return path
375
+ ? `json_array_length(${jsonColumn}, '${this.escapePathLiteral(path)}')`
376
+ : `json_array_length(${jsonColumn})`;
377
+ }
378
+ /**
379
+ * Check if JSON contains value
380
+ *
381
+ * Note: SQLite's JSON1 extension has no `json_contains` function (that name is
382
+ * MySQL-specific); calling this would fail at runtime with "no such function:
383
+ * json_contains". Emulate it with json_extract() equality instead.
384
+ */
385
+ jsonContains(jsonColumn, value, path) {
386
+ const target = path ? `json_extract(${jsonColumn}, '${this.escapePathLiteral(path)}')` : jsonColumn;
387
+ return `${target} = ${this.escape(typeof value === 'string' ? value : JSON.stringify(value))}`;
388
+ }
389
+ /**
390
+ * Query JSON array as table
391
+ */
392
+ jsonEach(jsonColumn, path) {
393
+ return path ? `json_each(${jsonColumn}, '${this.escapePathLiteral(path)}')` : `json_each(${jsonColumn})`;
394
+ }
395
+ /**
396
+ * Query JSON object as table
397
+ */
398
+ jsonTree(jsonColumn, path) {
399
+ return path ? `json_tree(${jsonColumn}, '${this.escapePathLiteral(path)}')` : `json_tree(${jsonColumn})`;
400
+ }
401
+ /**
402
+ * Get JSON type
403
+ */
404
+ jsonType(jsonColumn, path) {
405
+ return path ? `json_type(${jsonColumn}, '${this.escapePathLiteral(path)}')` : `json_type(${jsonColumn})`;
406
+ }
407
+ /**
408
+ * Valid JSON check
409
+ */
410
+ jsonValid(jsonColumn) {
411
+ return `json_valid(${jsonColumn})`;
412
+ }
413
+ /**
414
+ * Query using json_each (table-valued function)
415
+ */
416
+ async jsonEachQuery(jsonColumn, path, options) {
417
+ const tableRef = path ? `json_each(${jsonColumn}, '${path}')` : `json_each(${jsonColumn})`;
418
+ let sql = `SELECT * FROM ${tableRef}`;
419
+ if (options?.key || options?.value || options?.type) {
420
+ const cols = [];
421
+ if (options?.key)
422
+ cols.push(`key AS ${options.key}`);
423
+ if (options?.value)
424
+ cols.push(`value AS ${options.value}`);
425
+ if (options?.type)
426
+ cols.push(`type AS ${options.type}`);
427
+ if (options?.id)
428
+ cols.push(`id AS ${options.id}`);
429
+ if (options?.parent)
430
+ cols.push(`parent AS ${options.parent}`);
431
+ sql = `SELECT ${cols.join(', ')} FROM ${tableRef}`;
432
+ }
433
+ return this.dialect.query(sql);
434
+ }
435
+ // ============================================
436
+ // GENERATED COLUMNS
437
+ // ============================================
438
+ /**
439
+ * Create table with generated columns
440
+ */
441
+ createTableWithGeneratedColumns(tableName, columns) {
442
+ const columnDefs = columns
443
+ .map((col) => {
444
+ let def = `${this.escapeId(col.name)} ${col.type}`;
445
+ if (col.primaryKey) {
446
+ def += ' PRIMARY KEY';
447
+ }
448
+ if (col.notNull && !col.generated) {
449
+ def += ' NOT NULL';
450
+ }
451
+ if (col.defaultValue !== undefined && !col.generated) {
452
+ def += ` DEFAULT ${this.escape(col.defaultValue)}`;
453
+ }
454
+ if (col.generated) {
455
+ def += ` GENERATED ALWAYS AS (${col.generated.expression}) ${col.generated.type}`;
456
+ }
457
+ return def;
458
+ })
459
+ .join(', ');
460
+ return `CREATE TABLE ${this.escapeId(tableName)} (${columnDefs})`;
461
+ }
462
+ /**
463
+ * Create a VIRTUAL generated column
464
+ */
465
+ createVirtualColumn(name, type, expression) {
466
+ return `${this.escapeId(name)} ${type} GENERATED ALWAYS AS (${expression}) VIRTUAL`;
467
+ }
468
+ /**
469
+ * Create a STORED generated column
470
+ */
471
+ createStoredColumn(name, type, expression) {
472
+ return `${this.escapeId(name)} ${type} GENERATED ALWAYS AS (${expression}) STORED`;
473
+ }
474
+ // ============================================
475
+ // INDEXES - ADVANCED
476
+ // ============================================
477
+ /**
478
+ * Create a UNIQUE index
479
+ */
480
+ async createUniqueIndex(tableName, indexName, columns) {
481
+ const sql = `CREATE UNIQUE INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')})`;
482
+ await this.dialect.query(sql);
483
+ }
484
+ /**
485
+ * Create a partial index (with WHERE clause)
486
+ */
487
+ async createPartialIndex(tableName, indexName, columns, whereCondition) {
488
+ const sql = `CREATE INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) WHERE ${whereCondition}`;
489
+ await this.dialect.query(sql);
490
+ }
491
+ /**
492
+ * Create an expression index
493
+ */
494
+ async createExpressionIndex(tableName, indexName, expression) {
495
+ const sql = `CREATE INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${expression})`;
496
+ await this.dialect.query(sql);
497
+ }
498
+ /**
499
+ * Create an index with IF NOT EXISTS
500
+ */
501
+ async createIndexIfNotExists(tableName, indexName, columns, options) {
502
+ const sql = `CREATE INDEX IF NOT EXISTS ${this.escapeId(indexName)} ON ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')})${options?.where ? ` WHERE ${options.where}` : ''}`;
503
+ await this.dialect.query(sql);
504
+ }
505
+ /**
506
+ * List all indexes for a table
507
+ */
508
+ async listIndexes(tableName) {
509
+ const sql = `SELECT name, sql FROM sqlite_master WHERE type='index' AND tbl_name=? AND name NOT LIKE 'sqlite_%'`;
510
+ const result = await this.dialect.query(sql, { replacements: [tableName] });
511
+ return result.rows.map((row) => ({
512
+ name: row.name,
513
+ unique: row.sql?.toUpperCase().includes('UNIQUE') || false,
514
+ partial: row.sql?.includes('WHERE') || false,
515
+ }));
516
+ }
517
+ /**
518
+ * Reindex a table or database
519
+ */
520
+ async reindex(target) {
521
+ const sql = target ? `REINDEX ${this.escapeId(target)}` : 'REINDEX';
522
+ await this.dialect.query(sql);
523
+ }
524
+ // ============================================
525
+ // WINDOW FUNCTIONS
526
+ // ============================================
527
+ /**
528
+ * Build a window function query
529
+ */
530
+ buildWindowFunction(functionName, options) {
531
+ let sql = functionName;
532
+ if (options?.distinct) {
533
+ sql = `DISTINCT ${sql}`;
534
+ }
535
+ if (options?.filter) {
536
+ sql += ` FILTER (WHERE ${options.filter})`;
537
+ }
538
+ if (options?.over) {
539
+ sql += ' OVER (';
540
+ if (options.over.partitionBy?.length) {
541
+ sql += `PARTITION BY ${options.over.partitionBy.map((c) => this.escapeId(c)).join(', ')}`;
542
+ }
543
+ if (options.over.orderBy?.length) {
544
+ if (options.over.partitionBy?.length)
545
+ sql += ' ';
546
+ sql += `ORDER BY ${options.over.orderBy.map((o) => `${this.escapeId(o.column)}${o.desc ? ' DESC' : ''}`).join(', ')}`;
547
+ }
548
+ if (options.over.frame) {
549
+ const frame = options.over.frame;
550
+ const rows = options.over.rows;
551
+ const range = options.over.range;
552
+ sql += ` ${frame} BETWEEN `;
553
+ if (rows?.start !== undefined) {
554
+ sql += rows.start < 0 ? `CURRENT ROW + ${rows.start}` : `${rows.start}`;
555
+ }
556
+ else if (range?.start !== undefined) {
557
+ sql += range.start < 0 ? `CURRENT ROW + ${range.start}` : `${range.start}`;
558
+ }
559
+ else {
560
+ sql += 'UNBOUNDED PRECEDING';
561
+ }
562
+ sql += ' AND ';
563
+ if (rows?.end !== undefined) {
564
+ sql += rows.end < 0 ? `CURRENT ROW + ${rows.end}` : `${rows.end}`;
565
+ }
566
+ else if (range?.end !== undefined) {
567
+ sql += range.end < 0 ? `CURRENT ROW + ${range.end}` : `${range.end}`;
568
+ }
569
+ else {
570
+ sql += 'UNBOUNDED FOLLOWING';
571
+ }
572
+ }
573
+ sql += ')';
574
+ }
575
+ return sql;
576
+ }
577
+ /**
578
+ * ROW_NUMBER() window function
579
+ */
580
+ rowNumber(options) {
581
+ return this.buildWindowFunction('ROW_NUMBER()', { over: options });
582
+ }
583
+ /**
584
+ * RANK() window function
585
+ */
586
+ rank(options) {
587
+ return this.buildWindowFunction('RANK()', { over: options });
588
+ }
589
+ /**
590
+ * DENSE_RANK() window function
591
+ */
592
+ denseRank(options) {
593
+ return this.buildWindowFunction('DENSE_RANK()', { over: options });
594
+ }
595
+ /**
596
+ * NTILE(n) window function
597
+ */
598
+ ntile(n, options) {
599
+ return this.buildWindowFunction(`NTILE(${n})`, { over: options });
600
+ }
601
+ /**
602
+ * LAG() window function
603
+ */
604
+ lag(column, offset, defaultValue) {
605
+ const func = `LAG(${this.escapeId(column)}${offset ? `, ${offset}` : ''}${defaultValue !== undefined ? `, ${this.escape(defaultValue)}` : ''})`;
606
+ return this.buildWindowFunction(func);
607
+ }
608
+ /**
609
+ * LEAD() window function
610
+ */
611
+ lead(column, offset, defaultValue) {
612
+ const func = `LEAD(${this.escapeId(column)}${offset ? `, ${offset}` : ''}${defaultValue !== undefined ? `, ${this.escape(defaultValue)}` : ''})`;
613
+ return this.buildWindowFunction(func);
614
+ }
615
+ /**
616
+ * FIRST_VALUE() window function
617
+ */
618
+ firstValue(column) {
619
+ return this.buildWindowFunction(`FIRST_VALUE(${this.escapeId(column)})`);
620
+ }
621
+ /**
622
+ * LAST_VALUE() window function
623
+ */
624
+ lastValue(column) {
625
+ return this.buildWindowFunction(`LAST_VALUE(${this.escapeId(column)})`);
626
+ }
627
+ /**
628
+ * NTH_VALUE() window function
629
+ */
630
+ nthValue(column, n) {
631
+ return this.buildWindowFunction(`NTH_VALUE(${this.escapeId(column)}, ${n})`);
632
+ }
633
+ // ============================================
634
+ // COMMON TABLE EXPRESSIONS (CTEs)
635
+ // ============================================
636
+ /**
637
+ * Build a CTE query
638
+ */
639
+ buildCTE(cteName, columns, query, recursive) {
640
+ const cols = columns.length ? `(${columns.join(', ')})` : '';
641
+ return `WITH ${recursive ? 'RECURSIVE ' : ''}${this.escapeId(cteName)}${cols} AS (${query})`;
642
+ }
643
+ /**
644
+ * Build multiple CTEs
645
+ */
646
+ buildMultipleCTEs(ctes, mainQuery, recursive) {
647
+ const cteSqlParts = [];
648
+ for (const cte of ctes) {
649
+ const cols = cte.columns?.length ? `(${cte.columns.join(', ')})` : '';
650
+ const escapedName = this.escapeId(cte.name);
651
+ cteSqlParts.push(`${escapedName}${cols} AS (${cte.query})`);
652
+ }
653
+ const cteSql = cteSqlParts.join(', ');
654
+ const withPrefix = recursive ? 'WITH RECURSIVE ' : 'WITH ';
655
+ return `${withPrefix}${cteSql} ${mainQuery}`;
656
+ }
657
+ /**
658
+ * Execute a recursive CTE query
659
+ */
660
+ async executeRecursiveCTE(cteName, baseQuery, recursiveQuery, finalQuery) {
661
+ const sql = `WITH RECURSIVE ${this.escapeId(cteName)} AS (${baseQuery} UNION ALL ${recursiveQuery}) ${finalQuery}`;
662
+ return this.dialect.query(sql);
663
+ }
664
+ /**
665
+ * Recursive CTE example: Generate sequence
666
+ */
667
+ generateSequence(start, end) {
668
+ return `WITH RECURSIVE cnt(x) AS (
669
+ SELECT ${start}
670
+ UNION ALL
671
+ SELECT x + 1 FROM cnt WHERE x < ${end}
672
+ ) SELECT x FROM cnt`;
673
+ }
674
+ // ============================================
675
+ // VACUUM AND ANALYZE
676
+ // ============================================
677
+ /**
678
+ * VACUUM the database
679
+ * Reclaims disk space and defragments the database
680
+ */
681
+ async vacuum(options) {
682
+ let sql = 'VACUUM';
683
+ if (options?.schema) {
684
+ sql += ` ${options.schema}`;
685
+ }
686
+ if (options?.shrinkTo !== undefined) {
687
+ sql += ` INTO 'shrink_${options.shrinkTo}.db'`;
688
+ }
689
+ await this.dialect.query(sql);
690
+ if (options?.analyze) {
691
+ await this.analyze();
692
+ }
693
+ }
694
+ /**
695
+ * ANALYZE the database
696
+ * Updates statistics for the query optimizer
697
+ */
698
+ async analyze(target) {
699
+ const sql = target ? `ANALYZE ${this.escapeId(target)}` : 'ANALYZE';
700
+ await this.dialect.query(sql);
701
+ }
702
+ /**
703
+ * ANALYZE with detailed information
704
+ */
705
+ async analyzeDetailed(tableName) {
706
+ const sql = tableName ? `ANALYZE ${this.escapeId(tableName)}` : 'ANALYZE';
707
+ await this.dialect.query(sql);
708
+ if (tableName) {
709
+ const statSql = this.dialect.replaceReplacements(`SELECT * FROM sqlite_stat1 WHERE tbl = ?`, [tableName]);
710
+ return this.dialect.query(statSql);
711
+ }
712
+ return this.dialect.query(`SELECT * FROM sqlite_stat1`);
713
+ }
714
+ /**
715
+ * Get table statistics
716
+ */
717
+ async getTableStats(tableName) {
718
+ const sql = `SELECT * FROM sqlite_stat1 WHERE tbl = ?`;
719
+ const finalSql = this.dialect.replaceReplacements(sql, [tableName]);
720
+ return this.dialect.query(finalSql);
721
+ }
722
+ /**
723
+ * Get index statistics
724
+ */
725
+ async getIndexStats(indexName) {
726
+ const sql = `SELECT * FROM sqlite_stat1 WHERE idx = ?`;
727
+ const finalSql = this.dialect.replaceReplacements(sql, [indexName]);
728
+ return this.dialect.query(finalSql);
729
+ }
730
+ // ============================================
731
+ // SAVEPOINTS AND TRANSACTION MANAGEMENT
732
+ // ============================================
733
+ /**
734
+ * Create a savepoint
735
+ */
736
+ async savepoint(savepointName) {
737
+ await this.dialect.query(`SAVEPOINT ${this.escapeId(savepointName)}`);
738
+ }
739
+ /**
740
+ * Release a savepoint
741
+ */
742
+ async releaseSavepoint(savepointName) {
743
+ await this.dialect.query(`RELEASE SAVEPOINT ${this.escapeId(savepointName)}`);
744
+ }
745
+ /**
746
+ * Rollback to a savepoint
747
+ */
748
+ async rollbackToSavepoint(savepointName) {
749
+ await this.dialect.query(`ROLLBACK TO SAVEPOINT ${this.escapeId(savepointName)}`);
750
+ }
751
+ /**
752
+ * Execute within a savepoint
753
+ */
754
+ async withSavepoint(savepointName, fn) {
755
+ try {
756
+ await this.savepoint(savepointName);
757
+ return await fn();
758
+ }
759
+ catch (error) {
760
+ await this.rollbackToSavepoint(savepointName);
761
+ throw error;
762
+ }
763
+ finally {
764
+ try {
765
+ await this.releaseSavepoint(savepointName);
766
+ }
767
+ catch {
768
+ // Ignore release errors
769
+ }
770
+ }
771
+ }
772
+ // ============================================
773
+ // UPSERT (INSERT ON CONFLICT)
774
+ // ============================================
775
+ /**
776
+ * Build an UPSERT query
777
+ */
778
+ buildUpsertQuery(tableName, columns, conflictColumns, updateColumns, options) {
779
+ if (options?.ignore) {
780
+ const sql = `INSERT OR IGNORE INTO ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
781
+ return sql;
782
+ }
783
+ if (options?.replace) {
784
+ const sql = `INSERT OR REPLACE INTO ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
785
+ return sql;
786
+ }
787
+ const conflictCols = conflictColumns.map((c) => this.escapeId(c)).join(', ');
788
+ const setClause = (updateColumns || columns.filter((c) => !conflictColumns.includes(c)))
789
+ .map((c) => `${this.escapeId(c)} = excluded.${this.escapeId(c)}`)
790
+ .join(', ');
791
+ return `INSERT INTO ${this.escapeId(tableName)} (${columns.map((c) => this.escapeId(c)).join(', ')}) VALUES (${columns.map(() => '?').join(', ')}) ON CONFLICT (${conflictCols}) DO UPDATE SET ${setClause}`;
792
+ }
793
+ /**
794
+ * Perform an upsert
795
+ */
796
+ async upsert(tableName, values, conflictColumns, updateColumns) {
797
+ const columns = Object.keys(values);
798
+ const sql = this.buildUpsertQuery(tableName, columns, conflictColumns, updateColumns);
799
+ const finalSql = this.dialect.replaceReplacements(sql, Object.values(values));
800
+ return this.dialect.query(finalSql);
801
+ }
802
+ // ============================================
803
+ // TABLE-VALUED FUNCTIONS
804
+ // ============================================
805
+ /**
806
+ * Use json_each as table-valued function
807
+ */
808
+ async jsonEachAsTable(jsonColumn, path) {
809
+ const tableRef = path ? `json_each(${jsonColumn}, '${path}')` : `json_each(${jsonColumn})`;
810
+ return this.dialect.query(`SELECT * FROM ${tableRef}`);
811
+ }
812
+ /**
813
+ * Use json_tree as table-valued function
814
+ */
815
+ async jsonTreeAsTable(jsonColumn, path) {
816
+ const tableRef = path ? `json_tree(${jsonColumn}, '${path}')` : `json_tree(${jsonColumn})`;
817
+ return this.dialect.query(`SELECT * FROM ${tableRef}`);
818
+ }
819
+ /**
820
+ * Generate series using recursive CTE
821
+ */
822
+ async generateSeries(start, end, step) {
823
+ const stepStr = step ? `, ${step}` : '';
824
+ return this.dialect.query(`WITH RECURSIVE range(x) AS (SELECT ${start} UNION ALL SELECT x + ${step || 1} FROM range WHERE x < ${end}) SELECT x FROM range`);
825
+ }
826
+ /**
827
+ * Use fts5 as table-valued function
828
+ */
829
+ async fts5Search(ftsTable, query) {
830
+ const sql = `SELECT * FROM ${this.escapeId(ftsTable)} WHERE ${this.escapeId(ftsTable)} MATCH ?`;
831
+ const finalSql = this.dialect.replaceReplacements(sql, [query]);
832
+ return this.dialect.query(finalSql);
833
+ }
834
+ // ============================================
835
+ // EXTENSIONS
836
+ // ============================================
837
+ /**
838
+ * Load a SQLite extension
839
+ */
840
+ loadExtension(extensionPath) {
841
+ // Note: entryPoint parameter may not be supported by all extensions
842
+ this.connection.loadExtension(extensionPath);
843
+ }
844
+ /**
845
+ * Enable/disable extension loading
846
+ */
847
+ setExtensionLoading(enabled) {
848
+ this.connection.pragma(`enable_load_extension = ${enabled ? 1 : 0}`);
849
+ }
850
+ /**
851
+ * Get list of loaded extensions
852
+ */
853
+ getLoadedExtensions() {
854
+ const stmt = this.connection.prepare('SELECT * FROM pragma_compile_options WHERE compile_options LIKE "%ENABLE%"');
855
+ const rows = stmt.all();
856
+ return rows.map((row) => row.compile_options);
857
+ }
858
+ // ============================================
859
+ // SQLITE-SPECIFIC TYPES
860
+ // ============================================
861
+ /**
862
+ * Create INTEGER PRIMARY KEY column definition
863
+ */
864
+ createIntegerPrimaryKey(columnName, autoIncrement) {
865
+ return autoIncrement
866
+ ? `${this.escapeId(columnName)} INTEGER PRIMARY KEY AUTOINCREMENT`
867
+ : `${this.escapeId(columnName)} INTEGER PRIMARY KEY`;
868
+ }
869
+ /**
870
+ * Create column with specific affinity
871
+ */
872
+ createColumnWithAffinity(columnName, affinity) {
873
+ return `${this.escapeId(columnName)} ${affinity}`;
874
+ }
875
+ /**
876
+ * Get column affinity
877
+ */
878
+ getColumnAffinity(type) {
879
+ const upperType = type.toUpperCase();
880
+ if (upperType.includes('INT'))
881
+ return 'INTEGER';
882
+ if (upperType.includes('REAL') || upperType.includes('DOUBLE') || upperType.includes('FLOAT'))
883
+ return 'REAL';
884
+ if (upperType.includes('BLOB'))
885
+ return 'BLOB';
886
+ if (upperType.includes('CHAR') || upperType.includes('CLOB') || upperType.includes('TEXT'))
887
+ return 'TEXT';
888
+ return 'NUMERIC';
889
+ }
890
+ // ============================================
891
+ // CONSTRAINTS WITH ON CONFLICT
892
+ // ============================================
893
+ /**
894
+ * Create CHECK constraint
895
+ */
896
+ createCheckConstraint(columnName, condition) {
897
+ return `CHECK (${columnName} ${condition})`;
898
+ }
899
+ /**
900
+ * Create table with ON CONFLICT clause
901
+ */
902
+ createTableWithOnConflict(tableName, columns, conflictAlgorithm) {
903
+ const columnDefs = columns
904
+ .map((col) => {
905
+ let def = `${this.escapeId(col.name)} ${col.type}`;
906
+ if (col.constraints?.length) {
907
+ def += ` ${col.constraints.join(', ')}`;
908
+ }
909
+ return def;
910
+ })
911
+ .join(', ');
912
+ let sql = `CREATE TABLE ${this.escapeId(tableName)} (${columnDefs})`;
913
+ if (conflictAlgorithm) {
914
+ sql += ` ON CONFLICT ${conflictAlgorithm}`;
915
+ }
916
+ return sql;
917
+ }
918
+ /**
919
+ * Add ON CONFLICT to column definition
920
+ */
921
+ addOnConflictClause(constraint, conflictAlgorithm) {
922
+ return `${constraint} ON CONFLICT ${conflictAlgorithm}`;
923
+ }
924
+ /**
925
+ * Create UNIQUE constraint
926
+ */
927
+ createUniqueConstraint(columns) {
928
+ return `UNIQUE (${columns.map((c) => this.escapeId(c)).join(', ')})`;
929
+ }
930
+ /**
931
+ * Create NOT NULL constraint with ON CONFLICT
932
+ */
933
+ createNotNullConstraint(columnName, conflictAlgorithm) {
934
+ return conflictAlgorithm ? `NOT NULL ON CONFLICT ${conflictAlgorithm}` : 'NOT NULL';
935
+ }
936
+ // ============================================
937
+ // PRAGMA COMMANDS
938
+ // ============================================
939
+ /**
940
+ * Execute a PRAGMA command
941
+ */
942
+ async pragma(pragmaName, value) {
943
+ const sql = value !== undefined ? `PRAGMA ${pragmaName} = ${value}` : `PRAGMA ${pragmaName}`;
944
+ return this.dialect.query(sql);
945
+ }
946
+ /**
947
+ * Get database info
948
+ */
949
+ async getDatabaseInfo() {
950
+ const result = await Promise.all([
951
+ this.pragma('schema_version'),
952
+ this.pragma('user_version'),
953
+ this.pragma('foreign_keys'),
954
+ this.pragma('journal_mode'),
955
+ this.pragma('synchronous'),
956
+ this.pragma('cache_size'),
957
+ this.pragma('page_size'),
958
+ ]);
959
+ return {
960
+ schemaVersion: result[0].rows[0]?.schema_version,
961
+ userVersion: result[1].rows[0]?.user_version,
962
+ foreignKeys: result[2].rows[0]?.foreign_keys === 1,
963
+ journalingMode: result[3].rows[0]?.journal_mode,
964
+ synchronous: result[4].rows[0]?.synchronous,
965
+ cacheSize: result[5].rows[0]?.cache_size,
966
+ pageSize: result[6].rows[0]?.page_size,
967
+ };
968
+ }
969
+ /**
970
+ * Set database options
971
+ */
972
+ async setDatabaseOptions(options) {
973
+ if (options.foreignKeys !== undefined) {
974
+ await this.pragma('foreign_keys', options.foreignKeys ? 1 : 0);
975
+ }
976
+ if (options.journalingMode) {
977
+ await this.pragma('journal_mode', options.journalingMode);
978
+ }
979
+ if (options.synchronous) {
980
+ await this.pragma('synchronous', options.synchronous);
981
+ }
982
+ if (options.cacheSize !== undefined) {
983
+ await this.pragma('cache_size', options.cacheSize);
984
+ }
985
+ }
986
+ /**
987
+ * Get table info using PRAGMA
988
+ */
989
+ async getTableInfo(tableName) {
990
+ // Note: PRAGMA table_info(x) takes its argument in parentheses, not as
991
+ // part of the pragma name - this.pragma() only builds `PRAGMA name` /
992
+ // `PRAGMA name = value`, so it's built directly here instead.
993
+ return this.dialect.query(`PRAGMA table_info(${this.escapeId(tableName)})`);
994
+ }
995
+ /**
996
+ * Get index list for table
997
+ */
998
+ async getIndexList(tableName) {
999
+ return this.dialect.query(`PRAGMA index_list(${this.escapeId(tableName)})`);
1000
+ }
1001
+ /**
1002
+ * Get foreign key list for table
1003
+ */
1004
+ async getForeignKeyList(tableName) {
1005
+ return this.dialect.query(`PRAGMA foreign_key_list(${this.escapeId(tableName)})`);
1006
+ }
1007
+ // ============================================
1008
+ // UTILITY METHODS
1009
+ // ============================================
1010
+ /**
1011
+ * Escape identifier
1012
+ */
1013
+ escapeId(identifier) {
1014
+ const id = String(identifier ?? '');
1015
+ return `"${id.replace(/"/g, '""')}"`;
1016
+ }
1017
+ /**
1018
+ * Escape a JSON path (or any other value) for safe interpolation inside a
1019
+ * single-quoted SQL string literal, by doubling embedded single quotes.
1020
+ */
1021
+ escapePathLiteral(value) {
1022
+ return value.replace(/'/g, "''");
1023
+ }
1024
+ /**
1025
+ * Escape value
1026
+ */
1027
+ escape(value) {
1028
+ if (value === null)
1029
+ return 'NULL';
1030
+ if (typeof value === 'number')
1031
+ return String(value);
1032
+ if (typeof value === 'boolean')
1033
+ return value ? '1' : '0';
1034
+ if (typeof value === 'string')
1035
+ return `'${value.replace(/'/g, "''")}'`;
1036
+ if (value instanceof Date)
1037
+ return `'${value.toISOString()}'`;
1038
+ return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
1039
+ }
1040
+ }
1041
+ exports.SQLiteAdvanced = SQLiteAdvanced;
1042
+ /**
1043
+ * Create SQLiteAdvanced instance from dialect
1044
+ */
1045
+ function createSQLiteAdvanced(dialect) {
1046
+ return new SQLiteAdvanced(dialect);
1047
+ }