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,695 @@
1
+ "use strict";
2
+ /**
3
+ * Typed date/time function builder API.
4
+ *
5
+ * Date truncation, date arithmetic (add/subtract), date-difference, "current
6
+ * date/time" helpers, and best-effort date formatting differ WILDLY across
7
+ * the 11 SQL dialects this ORM supports (Postgres `DATE_TRUNC`, MySQL
8
+ * `DATE_ADD`/`DATEDIFF`, MSSQL `DATEADD`/`DATEDIFF`, Oracle `TRUNC`/
9
+ * `ADD_MONTHS`, SQLite `strftime`/`julianday`, etc). This module gives callers
10
+ * a single typed entry point per capability so they never have to hand-write
11
+ * dialect-specific SQL strings themselves.
12
+ *
13
+ * This module is intentionally self-contained, mirroring the approach taken
14
+ * in `src/query-builders/functions/window.ts`: it does NOT add any new
15
+ * dispatch branches to the shared SQL compiler (`src/query-builders/
16
+ * sql-compiler.ts`) or to `src/operators.ts`. Every function below computes
17
+ * a final, dialect-correct SQL fragment string directly and wraps it with the
18
+ * existing `literal()` helper from `src/operators.ts` so it slots into
19
+ * `attributes: [...]` / `where` clauses exactly the way `fn()`/`col()`/
20
+ * `cast()`/`extract()` do today.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import { dateTrunc, dateAdd, dateDiff, now, formatDate } from './query-builders/functions/datetime';
25
+ * import { col } from '../../operators';
26
+ *
27
+ * User.findAll({
28
+ * attributes: [
29
+ * 'id',
30
+ * dateTrunc('postgres', 'month', col('createdAt')).as('signupMonth'),
31
+ * dateAdd('postgres', col('createdAt'), 30, 'day').as('trialEnds'),
32
+ * dateDiff('mysql', 'day', col('startDate'), col('endDate')).as('durationDays'),
33
+ * now('sqlite').as('rightNow'),
34
+ * formatDate('postgres', col('createdAt'), 'YYYY-MM-DD').as('createdAtStr'),
35
+ * ],
36
+ * });
37
+ * ```
38
+ */
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.dateTrunc = dateTrunc;
41
+ exports.dateAdd = dateAdd;
42
+ exports.dateSub = dateSub;
43
+ exports.dateDiff = dateDiff;
44
+ exports.now = now;
45
+ exports.currentDate = currentDate;
46
+ exports.currentTimestamp = currentTimestamp;
47
+ exports.formatDate = formatDate;
48
+ const operators_1 = require("../../operators");
49
+ function makeExpression(sql) {
50
+ const lit = (0, operators_1.literal)(sql);
51
+ return {
52
+ __type: 'literal',
53
+ sql,
54
+ $literal: lit.$literal,
55
+ as(alias) {
56
+ return [this, alias];
57
+ },
58
+ };
59
+ }
60
+ function isDateTimeExpression(value) {
61
+ return value && value.__type === 'literal' && typeof value.sql === 'string' && typeof value.as === 'function';
62
+ }
63
+ /**
64
+ * Escape a plain identifier (or `table.column` dotted path) using ANSI
65
+ * double-quote convention. This mirrors `escapeIdentifier()` in
66
+ * `window.ts` for consistency across the `functions/` family; the shared
67
+ * compiler normalizes/re-quotes identifiers per dialect downstream the same
68
+ * way it already does for `col()`/`fn()` output.
69
+ */
70
+ function escapeIdentifier(name) {
71
+ if (name.includes('.')) {
72
+ return name
73
+ .split('.')
74
+ .map((part) => `"${part.replace(/"/g, '""')}"`)
75
+ .join('.');
76
+ }
77
+ return `"${name.replace(/"/g, '""')}"`;
78
+ }
79
+ /** Format a JS Date as a portable `'YYYY-MM-DD HH:MM:SS'` SQL string literal. */
80
+ function formatDateLiteral(date) {
81
+ const iso = date.toISOString(); // e.g. 2024-01-02T03:04:05.000Z
82
+ const value = iso.replace('T', ' ').replace('Z', '').replace(/\.\d+$/, '');
83
+ return `'${value}'`;
84
+ }
85
+ /**
86
+ * Flatten a date/time argument (string column name, ColExpression,
87
+ * FnExpression, LiteralExpression, DateTimeExpression, Date, number) into a
88
+ * raw SQL fragment string. Plain strings are treated as column names (same
89
+ * convention as `extract()`/`fn()`/`col()` in `src/operators.ts`).
90
+ */
91
+ function toSqlFragment(arg) {
92
+ if (arg === null || arg === undefined)
93
+ return 'NULL';
94
+ if (typeof arg === 'number')
95
+ return String(arg);
96
+ if (arg instanceof Date)
97
+ return formatDateLiteral(arg);
98
+ if (typeof arg === 'string')
99
+ return escapeIdentifier(arg);
100
+ if (isDateTimeExpression(arg))
101
+ return arg.sql;
102
+ if ((0, operators_1.isFnExpression)(arg)) {
103
+ const args = arg.args.map((a) => toSqlFragment(a)).join(', ');
104
+ return `${arg.fn}(${args})`;
105
+ }
106
+ if ((0, operators_1.isColExpression)(arg))
107
+ return escapeIdentifier(arg.col);
108
+ if ((0, operators_1.isLiteralExpression)(arg)) {
109
+ return '$literal' in arg ? arg.$literal : arg.sql;
110
+ }
111
+ // Fallback: stringify
112
+ return String(arg);
113
+ }
114
+ function isPostgresLikeTrunc(dialect) {
115
+ // Postgres, CockroachDB, and Redshift all support the real DATE_TRUNC('unit', expr)
116
+ // function with identical syntax/unit-name semantics (Redshift and CockroachDB are
117
+ // both Postgres-wire/SQL-derived and inherit this function verbatim).
118
+ return dialect === 'postgres' || dialect === 'cockroachdb' || dialect === 'redshift';
119
+ }
120
+ // ============================================================================
121
+ // dateTrunc
122
+ // ============================================================================
123
+ /**
124
+ * Truncate a date/timestamp expression down to the start of the given unit
125
+ * boundary (year/quarter/month/week/day/hour/minute/second).
126
+ *
127
+ * Dialect notes:
128
+ * - postgres / cockroachdb / redshift: native `DATE_TRUNC('unit', expr)`.
129
+ * - mysql / mariadb: NO native DATE_TRUNC exists (verified) — built via
130
+ * `DATE_FORMAT`/string-concat + `STR_TO_DATE` workarounds.
131
+ * - mssql: uses `DATETRUNC(datepart, expr)`, added in SQL Server 2022. For
132
+ * older MSSQL versions there is no native equivalent; this targets modern
133
+ * SQL Server.
134
+ * - oracle: `TRUNC(expr, fmt)`. Oracle's TRUNC only truncates down to
135
+ * hour/minute ('HH24'/'MI'); truncating to 'second' is a no-op since DATE
136
+ * values already carry second precision, so `expr` is returned unchanged.
137
+ * - sqlite: no native function — built via `strftime()`/string-concat.
138
+ * - db2: no native DATE_TRUNC — built via `VARCHAR_FORMAT`/`TIMESTAMP_FORMAT`
139
+ * round-tripping (Db2's TO_CHAR/TO_DATE equivalents) plus `DAYOFWEEK()`
140
+ * for week truncation.
141
+ * - snowflake: native `DATE_TRUNC('unit', expr)`.
142
+ * - clickhouse: dedicated `toStartOfYear`/`toStartOfQuarter`/`toStartOfMonth`/
143
+ * `toStartOfWeek`/`toStartOfDay`/`toStartOfHour`/`toStartOfMinute`/
144
+ * `toStartOfSecond` functions (genuinely different function-per-unit API,
145
+ * not a single parameterized function).
146
+ */
147
+ function dateTrunc(dialect, unit, expr) {
148
+ const e = toSqlFragment(expr);
149
+ if (isPostgresLikeTrunc(dialect) || dialect === 'snowflake') {
150
+ return makeExpression(`DATE_TRUNC('${unit}', ${e})`);
151
+ }
152
+ switch (dialect) {
153
+ case 'mysql':
154
+ case 'mariadb': {
155
+ switch (unit) {
156
+ case 'year':
157
+ return makeExpression(`DATE_FORMAT(${e}, '%Y-01-01 00:00:00')`);
158
+ case 'quarter':
159
+ return makeExpression(`STR_TO_DATE(CONCAT(DATE_FORMAT(${e}, '%Y-'), LPAD((QUARTER(${e}) - 1) * 3 + 1, 2, '0'), '-01 00:00:00'), '%Y-%m-%d %H:%i:%s')`);
160
+ case 'month':
161
+ return makeExpression(`DATE_FORMAT(${e}, '%Y-%m-01 00:00:00')`);
162
+ case 'week':
163
+ return makeExpression(`DATE_SUB(DATE(${e}), INTERVAL WEEKDAY(${e}) DAY)`);
164
+ case 'day':
165
+ return makeExpression(`DATE_FORMAT(${e}, '%Y-%m-%d 00:00:00')`);
166
+ case 'hour':
167
+ return makeExpression(`DATE_FORMAT(${e}, '%Y-%m-%d %H:00:00')`);
168
+ case 'minute':
169
+ return makeExpression(`DATE_FORMAT(${e}, '%Y-%m-%d %H:%i:00')`);
170
+ case 'second':
171
+ return makeExpression(`DATE_FORMAT(${e}, '%Y-%m-%d %H:%i:%s')`);
172
+ }
173
+ break;
174
+ }
175
+ case 'mssql': {
176
+ // SQL Server 2022+ DATETRUNC(datepart, date) accepts these unit names directly.
177
+ return makeExpression(`DATETRUNC(${unit}, ${e})`);
178
+ }
179
+ case 'oracle': {
180
+ switch (unit) {
181
+ case 'year':
182
+ return makeExpression(`TRUNC(${e}, 'YYYY')`);
183
+ case 'quarter':
184
+ return makeExpression(`TRUNC(${e}, 'Q')`);
185
+ case 'month':
186
+ return makeExpression(`TRUNC(${e}, 'MM')`);
187
+ case 'week':
188
+ return makeExpression(`TRUNC(${e}, 'IW')`);
189
+ case 'day':
190
+ return makeExpression(`TRUNC(${e}, 'DD')`);
191
+ case 'hour':
192
+ return makeExpression(`TRUNC(${e}, 'HH24')`);
193
+ case 'minute':
194
+ return makeExpression(`TRUNC(${e}, 'MI')`);
195
+ case 'second':
196
+ // Oracle DATE has only second precision already; no smaller truncation exists.
197
+ return makeExpression(`${e}`);
198
+ }
199
+ break;
200
+ }
201
+ case 'sqlite': {
202
+ switch (unit) {
203
+ case 'year':
204
+ return makeExpression(`strftime('%Y-01-01 00:00:00', ${e})`);
205
+ case 'quarter':
206
+ return makeExpression(`(strftime('%Y-', ${e}) || printf('%02d', ((CAST(strftime('%m', ${e}) AS INTEGER) - 1) / 3) * 3 + 1) || '-01 00:00:00')`);
207
+ case 'month':
208
+ return makeExpression(`strftime('%Y-%m-01 00:00:00', ${e})`);
209
+ case 'week':
210
+ return makeExpression(`date(${e}, '-' || strftime('%w', ${e}) || ' days')`);
211
+ case 'day':
212
+ return makeExpression(`strftime('%Y-%m-%d 00:00:00', ${e})`);
213
+ case 'hour':
214
+ return makeExpression(`strftime('%Y-%m-%d %H:00:00', ${e})`);
215
+ case 'minute':
216
+ return makeExpression(`strftime('%Y-%m-%d %H:%M:00', ${e})`);
217
+ case 'second':
218
+ return makeExpression(`strftime('%Y-%m-%d %H:%M:%S', ${e})`);
219
+ }
220
+ break;
221
+ }
222
+ case 'db2': {
223
+ switch (unit) {
224
+ case 'year':
225
+ return makeExpression(`TIMESTAMP_FORMAT(VARCHAR_FORMAT(${e}, 'YYYY') || '-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')`);
226
+ case 'quarter':
227
+ return makeExpression(`TIMESTAMP_FORMAT(VARCHAR_FORMAT(${e}, 'YYYY') || '-' || LPAD((QUARTER(${e}) - 1) * 3 + 1, 2, '0') || '-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')`);
228
+ case 'month':
229
+ return makeExpression(`TIMESTAMP_FORMAT(VARCHAR_FORMAT(${e}, 'YYYY-MM') || '-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')`);
230
+ case 'week':
231
+ return makeExpression(`(CAST(${e} AS DATE) - (DAYOFWEEK(${e}) - 1) DAYS)`);
232
+ case 'day':
233
+ return makeExpression(`TIMESTAMP_FORMAT(VARCHAR_FORMAT(${e}, 'YYYY-MM-DD') || ' 00:00:00', 'YYYY-MM-DD HH24:MI:SS')`);
234
+ case 'hour':
235
+ return makeExpression(`TIMESTAMP_FORMAT(VARCHAR_FORMAT(${e}, 'YYYY-MM-DD HH24') || ':00:00', 'YYYY-MM-DD HH24:MI:SS')`);
236
+ case 'minute':
237
+ return makeExpression(`TIMESTAMP_FORMAT(VARCHAR_FORMAT(${e}, 'YYYY-MM-DD HH24:MI') || ':00', 'YYYY-MM-DD HH24:MI:SS')`);
238
+ case 'second':
239
+ return makeExpression(`TIMESTAMP_FORMAT(VARCHAR_FORMAT(${e}, 'YYYY-MM-DD HH24:MI:SS'), 'YYYY-MM-DD HH24:MI:SS')`);
240
+ }
241
+ break;
242
+ }
243
+ case 'clickhouse': {
244
+ switch (unit) {
245
+ case 'year':
246
+ return makeExpression(`toStartOfYear(${e})`);
247
+ case 'quarter':
248
+ return makeExpression(`toStartOfQuarter(${e})`);
249
+ case 'month':
250
+ return makeExpression(`toStartOfMonth(${e})`);
251
+ case 'week':
252
+ return makeExpression(`toStartOfWeek(${e})`);
253
+ case 'day':
254
+ return makeExpression(`toStartOfDay(${e})`);
255
+ case 'hour':
256
+ return makeExpression(`toStartOfHour(${e})`);
257
+ case 'minute':
258
+ return makeExpression(`toStartOfMinute(${e})`);
259
+ case 'second':
260
+ return makeExpression(`toStartOfSecond(${e})`);
261
+ }
262
+ break;
263
+ }
264
+ }
265
+ // Should be unreachable for supported dialects, but keep a safe fallback.
266
+ return makeExpression(`DATE_TRUNC('${unit}', ${e})`);
267
+ }
268
+ // ============================================================================
269
+ // dateAdd / dateSub
270
+ // ============================================================================
271
+ function dateAddInternal(dialect, expr, signedAmount, unit) {
272
+ const e = toSqlFragment(expr);
273
+ const amount = signedAmount;
274
+ switch (dialect) {
275
+ case 'postgres':
276
+ case 'cockroachdb': {
277
+ // Native interval arithmetic.
278
+ const sign = amount < 0 ? '-' : '+';
279
+ return makeExpression(`${e} ${sign} INTERVAL '${Math.abs(amount)} ${unit}'`);
280
+ }
281
+ case 'redshift': {
282
+ // Redshift has a genuine DATEADD(datepart, number, date) function (SQL-Server-like),
283
+ // distinct from Postgres's interval-literal arithmetic even though Redshift is
284
+ // otherwise Postgres-derived.
285
+ return makeExpression(`DATEADD(${unit}, ${amount}, ${e})`);
286
+ }
287
+ case 'mysql':
288
+ case 'mariadb': {
289
+ const fn = amount < 0 ? 'DATE_SUB' : 'DATE_ADD';
290
+ return makeExpression(`${fn}(${e}, INTERVAL ${Math.abs(amount)} ${unit.toUpperCase()})`);
291
+ }
292
+ case 'mssql': {
293
+ return makeExpression(`DATEADD(${unit}, ${amount}, ${e})`);
294
+ }
295
+ case 'oracle': {
296
+ switch (unit) {
297
+ case 'year':
298
+ return makeExpression(`ADD_MONTHS(${e}, ${amount * 12})`);
299
+ case 'month':
300
+ return makeExpression(`ADD_MONTHS(${e}, ${amount})`);
301
+ case 'day':
302
+ return makeExpression(`(${e} + ${amount})`);
303
+ case 'hour':
304
+ return makeExpression(`(${e} + ${amount} / 24)`);
305
+ case 'minute':
306
+ return makeExpression(`(${e} + ${amount} / 1440)`);
307
+ case 'second':
308
+ return makeExpression(`(${e} + ${amount} / 86400)`);
309
+ }
310
+ break;
311
+ }
312
+ case 'sqlite': {
313
+ const plural = `${unit}${Math.abs(amount) === 1 ? '' : 's'}`;
314
+ const sign = amount < 0 ? '-' : '+';
315
+ return makeExpression(`datetime(${e}, '${sign}${Math.abs(amount)} ${plural}')`);
316
+ }
317
+ case 'db2': {
318
+ const sign = amount < 0 ? '-' : '+';
319
+ const unitWord = `${unit.toUpperCase()}${Math.abs(amount) === 1 ? '' : 'S'}`;
320
+ return makeExpression(`(${e} ${sign} ${Math.abs(amount)} ${unitWord})`);
321
+ }
322
+ case 'snowflake': {
323
+ return makeExpression(`DATEADD(${unit}, ${amount}, ${e})`);
324
+ }
325
+ case 'clickhouse': {
326
+ // ClickHouse supports ANSI-style INTERVAL arithmetic directly.
327
+ const sign = amount < 0 ? '-' : '+';
328
+ return makeExpression(`${e} ${sign} INTERVAL ${Math.abs(amount)} ${unit.toUpperCase()}`);
329
+ }
330
+ }
331
+ // Fallback (unreachable for supported dialects).
332
+ return makeExpression(`${e} + INTERVAL '${amount} ${unit}'`);
333
+ }
334
+ /**
335
+ * Add `amount` units of time to a date/timestamp expression.
336
+ * See dialect notes on {@link dateTrunc} / module doc for the general
337
+ * per-dialect divergence rationale; per-function notes:
338
+ * - postgres/cockroachdb: `expr + INTERVAL 'n unit'`.
339
+ * - redshift: `DATEADD(unit, n, expr)` (SQL-Server-style function, NOT interval
340
+ * arithmetic, despite being Postgres-derived).
341
+ * - mysql/mariadb: `DATE_ADD(expr, INTERVAL n UNIT)`.
342
+ * - mssql/snowflake: `DATEADD(unit, n, expr)`.
343
+ * - oracle: day-level uses plain `+`/`-` arithmetic (Oracle DATE math is in
344
+ * days); month/year use `ADD_MONTHS`; hour/minute/second are expressed as
345
+ * fractional-day arithmetic.
346
+ * - sqlite: `datetime(expr, '+n units')`.
347
+ * - db2: labeled-duration arithmetic, e.g. `expr + n DAYS`.
348
+ * - clickhouse: ANSI `expr + INTERVAL n UNIT`.
349
+ */
350
+ function dateAdd(dialect, expr, amount, unit) {
351
+ return dateAddInternal(dialect, expr, amount, unit);
352
+ }
353
+ /**
354
+ * Subtract `amount` units of time from a date/timestamp expression.
355
+ * Equivalent to `dateAdd(dialect, expr, -amount, unit)` — see {@link dateAdd}
356
+ * for per-dialect SQL notes.
357
+ */
358
+ function dateSub(dialect, expr, amount, unit) {
359
+ return dateAddInternal(dialect, expr, -amount, unit);
360
+ }
361
+ // ============================================================================
362
+ // dateDiff
363
+ // ============================================================================
364
+ /**
365
+ * Compute the difference between two date/timestamp expressions
366
+ * (`end - start`) expressed in the given unit.
367
+ *
368
+ * Dialect notes (this is the function with the widest genuine divergence):
369
+ * - mysql/mariadb: `TIMESTAMPDIFF(UNIT, start, end)` (native, all units).
370
+ * - mssql/snowflake: `DATEDIFF(unit, start, end)` (native, all units).
371
+ * - redshift: also has a genuine native `DATEDIFF(datepart, start, end)`
372
+ * function (SQL-Server-style), unlike Postgres which has no such function.
373
+ * - clickhouse: native `dateDiff('unit', start, end)`.
374
+ * - postgres/cockroachdb: NO native DATEDIFF — day uses date subtraction
375
+ * (`end::date - start::date`), sub-day units use
376
+ * `EXTRACT(EPOCH FROM (end - start))` scaled, and year/month are computed
377
+ * from `EXTRACT(YEAR/MONTH FROM ...)` components.
378
+ * - oracle: NO native DATEDIFF — DATE subtraction yields a day count
379
+ * (`TRUNC(end) - TRUNC(start)` for whole days, scaled for other sub-day
380
+ * units); month/year use `MONTHS_BETWEEN`.
381
+ * - sqlite: NO native DATEDIFF — built on `julianday()` differences (day and
382
+ * sub-day units) and `strftime('%Y'/'%m', ...)` component differences
383
+ * (month/year, since julianday-based month math is not calendar-exact).
384
+ * - db2: NO native DATEDIFF — built from `DAYS()` (whole-day difference) and
385
+ * `MIDNIGHT_SECONDS()` (intra-day seconds) for day/sub-day units, and
386
+ * `YEAR()`/`MONTH()` component differences for month/year.
387
+ */
388
+ function dateDiff(dialect, unit, start, end) {
389
+ const s = toSqlFragment(start);
390
+ const e = toSqlFragment(end);
391
+ switch (dialect) {
392
+ case 'mysql':
393
+ case 'mariadb': {
394
+ return makeExpression(`TIMESTAMPDIFF(${unit.toUpperCase()}, ${s}, ${e})`);
395
+ }
396
+ case 'mssql':
397
+ case 'snowflake':
398
+ case 'redshift': {
399
+ return makeExpression(`DATEDIFF(${unit}, ${s}, ${e})`);
400
+ }
401
+ case 'clickhouse': {
402
+ return makeExpression(`dateDiff('${unit}', ${s}, ${e})`);
403
+ }
404
+ case 'postgres':
405
+ case 'cockroachdb': {
406
+ switch (unit) {
407
+ case 'year':
408
+ return makeExpression(`(EXTRACT(YEAR FROM ${e}) - EXTRACT(YEAR FROM ${s}))`);
409
+ case 'quarter':
410
+ return makeExpression(`(((EXTRACT(YEAR FROM ${e}) - EXTRACT(YEAR FROM ${s})) * 4) + (EXTRACT(QUARTER FROM ${e}) - EXTRACT(QUARTER FROM ${s})))`);
411
+ case 'month':
412
+ return makeExpression(`(((EXTRACT(YEAR FROM ${e}) - EXTRACT(YEAR FROM ${s})) * 12) + (EXTRACT(MONTH FROM ${e}) - EXTRACT(MONTH FROM ${s})))`);
413
+ case 'week':
414
+ return makeExpression(`FLOOR((CAST(${e} AS DATE) - CAST(${s} AS DATE)) / 7)`);
415
+ case 'day':
416
+ return makeExpression(`(CAST(${e} AS DATE) - CAST(${s} AS DATE))`);
417
+ case 'hour':
418
+ return makeExpression(`(EXTRACT(EPOCH FROM (${e} - ${s})) / 3600)`);
419
+ case 'minute':
420
+ return makeExpression(`(EXTRACT(EPOCH FROM (${e} - ${s})) / 60)`);
421
+ case 'second':
422
+ return makeExpression(`EXTRACT(EPOCH FROM (${e} - ${s}))`);
423
+ }
424
+ break;
425
+ }
426
+ case 'oracle': {
427
+ switch (unit) {
428
+ case 'year':
429
+ return makeExpression(`(MONTHS_BETWEEN(${e}, ${s}) / 12)`);
430
+ case 'quarter':
431
+ return makeExpression(`(MONTHS_BETWEEN(${e}, ${s}) / 3)`);
432
+ case 'month':
433
+ return makeExpression(`MONTHS_BETWEEN(${e}, ${s})`);
434
+ case 'week':
435
+ return makeExpression(`((${e} - ${s}) / 7)`);
436
+ case 'day':
437
+ return makeExpression(`(TRUNC(${e}) - TRUNC(${s}))`);
438
+ case 'hour':
439
+ return makeExpression(`((${e} - ${s}) * 24)`);
440
+ case 'minute':
441
+ return makeExpression(`((${e} - ${s}) * 1440)`);
442
+ case 'second':
443
+ return makeExpression(`((${e} - ${s}) * 86400)`);
444
+ }
445
+ break;
446
+ }
447
+ case 'sqlite': {
448
+ switch (unit) {
449
+ case 'year':
450
+ return makeExpression(`(CAST(strftime('%Y', ${e}) AS INTEGER) - CAST(strftime('%Y', ${s}) AS INTEGER))`);
451
+ case 'quarter':
452
+ return makeExpression(`(((CAST(strftime('%Y', ${e}) AS INTEGER) - CAST(strftime('%Y', ${s}) AS INTEGER)) * 4) + ((CAST(strftime('%m', ${e}) AS INTEGER) - 1) / 3 - (CAST(strftime('%m', ${s}) AS INTEGER) - 1) / 3))`);
453
+ case 'month':
454
+ return makeExpression(`(((CAST(strftime('%Y', ${e}) AS INTEGER) - CAST(strftime('%Y', ${s}) AS INTEGER)) * 12) + (CAST(strftime('%m', ${e}) AS INTEGER) - CAST(strftime('%m', ${s}) AS INTEGER)))`);
455
+ case 'week':
456
+ return makeExpression(`CAST((julianday(${e}) - julianday(${s})) / 7 AS INTEGER)`);
457
+ case 'day':
458
+ return makeExpression(`CAST(julianday(${e}) - julianday(${s}) AS INTEGER)`);
459
+ case 'hour':
460
+ return makeExpression(`((julianday(${e}) - julianday(${s})) * 24)`);
461
+ case 'minute':
462
+ return makeExpression(`((julianday(${e}) - julianday(${s})) * 1440)`);
463
+ case 'second':
464
+ return makeExpression(`((julianday(${e}) - julianday(${s})) * 86400)`);
465
+ }
466
+ break;
467
+ }
468
+ case 'db2': {
469
+ const totalSeconds = `((DAYS(${e}) - DAYS(${s})) * 86400 + (MIDNIGHT_SECONDS(${e}) - MIDNIGHT_SECONDS(${s})))`;
470
+ switch (unit) {
471
+ case 'year':
472
+ return makeExpression(`(YEAR(${e}) - YEAR(${s}))`);
473
+ case 'quarter':
474
+ return makeExpression(`(((YEAR(${e}) - YEAR(${s})) * 4) + (QUARTER(${e}) - QUARTER(${s})))`);
475
+ case 'month':
476
+ return makeExpression(`(((YEAR(${e}) - YEAR(${s})) * 12) + (MONTH(${e}) - MONTH(${s})))`);
477
+ case 'week':
478
+ return makeExpression(`((DAYS(${e}) - DAYS(${s})) / 7)`);
479
+ case 'day':
480
+ return makeExpression(`(DAYS(${e}) - DAYS(${s}))`);
481
+ case 'hour':
482
+ return makeExpression(`(${totalSeconds} / 3600)`);
483
+ case 'minute':
484
+ return makeExpression(`(${totalSeconds} / 60)`);
485
+ case 'second':
486
+ return makeExpression(totalSeconds);
487
+ }
488
+ break;
489
+ }
490
+ }
491
+ // Fallback (unreachable for supported dialects).
492
+ return makeExpression(`DATEDIFF(${unit}, ${s}, ${e})`);
493
+ }
494
+ // ============================================================================
495
+ // now / currentDate / currentTimestamp
496
+ // ============================================================================
497
+ /**
498
+ * Current date-and-time (instant), dialect-appropriate.
499
+ * - mysql/mariadb: `NOW()`
500
+ * - postgres/cockroachdb/redshift: `NOW()`
501
+ * - mssql: `GETDATE()`
502
+ * - oracle: `SYSTIMESTAMP`
503
+ * - sqlite: `datetime('now')`
504
+ * - db2: `CURRENT TIMESTAMP` (special register, no parentheses)
505
+ * - snowflake: `CURRENT_TIMESTAMP()`
506
+ * - clickhouse: `now()`
507
+ */
508
+ function now(dialect) {
509
+ switch (dialect) {
510
+ case 'mysql':
511
+ case 'mariadb':
512
+ case 'postgres':
513
+ case 'cockroachdb':
514
+ case 'redshift':
515
+ return makeExpression('NOW()');
516
+ case 'mssql':
517
+ return makeExpression('GETDATE()');
518
+ case 'oracle':
519
+ return makeExpression('SYSTIMESTAMP');
520
+ case 'sqlite':
521
+ return makeExpression(`datetime('now')`);
522
+ case 'db2':
523
+ return makeExpression('CURRENT TIMESTAMP');
524
+ case 'snowflake':
525
+ return makeExpression('CURRENT_TIMESTAMP()');
526
+ case 'clickhouse':
527
+ return makeExpression('now()');
528
+ }
529
+ return makeExpression('NOW()');
530
+ }
531
+ /**
532
+ * Current date (no time component), dialect-appropriate.
533
+ * - mysql/mariadb: `CURDATE()`
534
+ * - postgres/cockroachdb/redshift: `CURRENT_DATE`
535
+ * - mssql: `CAST(GETDATE() AS DATE)`
536
+ * - oracle: `TRUNC(SYSDATE)`
537
+ * - sqlite: `date('now')`
538
+ * - db2: `CURRENT DATE` (special register)
539
+ * - snowflake: `CURRENT_DATE()`
540
+ * - clickhouse: `today()`
541
+ */
542
+ function currentDate(dialect) {
543
+ switch (dialect) {
544
+ case 'mysql':
545
+ case 'mariadb':
546
+ return makeExpression('CURDATE()');
547
+ case 'postgres':
548
+ case 'cockroachdb':
549
+ case 'redshift':
550
+ return makeExpression('CURRENT_DATE');
551
+ case 'mssql':
552
+ return makeExpression('CAST(GETDATE() AS DATE)');
553
+ case 'oracle':
554
+ return makeExpression('TRUNC(SYSDATE)');
555
+ case 'sqlite':
556
+ return makeExpression(`date('now')`);
557
+ case 'db2':
558
+ return makeExpression('CURRENT DATE');
559
+ case 'snowflake':
560
+ return makeExpression('CURRENT_DATE()');
561
+ case 'clickhouse':
562
+ return makeExpression('today()');
563
+ }
564
+ return makeExpression('CURRENT_DATE');
565
+ }
566
+ /**
567
+ * Current timestamp (ANSI-standard spelling where available), dialect-appropriate.
568
+ * - mysql/mariadb/postgres/cockroachdb/redshift/db2: `CURRENT_TIMESTAMP`
569
+ * - mssql: `CURRENT_TIMESTAMP` (ANSI synonym for `GETDATE()`)
570
+ * - oracle: `CURRENT_TIMESTAMP` (session-timezone-aware; distinct from `SYSTIMESTAMP`)
571
+ * - sqlite: `CURRENT_TIMESTAMP` (SQLite keyword, UTC)
572
+ * - snowflake: `CURRENT_TIMESTAMP()`
573
+ * - clickhouse: `now()` (no distinct `CURRENT_TIMESTAMP`; `now()` is the canonical form)
574
+ */
575
+ function currentTimestamp(dialect) {
576
+ switch (dialect) {
577
+ case 'snowflake':
578
+ return makeExpression('CURRENT_TIMESTAMP()');
579
+ case 'clickhouse':
580
+ return makeExpression('now()');
581
+ default:
582
+ return makeExpression('CURRENT_TIMESTAMP');
583
+ }
584
+ }
585
+ // ============================================================================
586
+ // formatDate
587
+ // ============================================================================
588
+ /**
589
+ * Format a date/timestamp expression as a string.
590
+ *
591
+ * IMPORTANT: format-token translation here is BEST-EFFORT for a common
592
+ * subset of tokens, NOT a full strftime-compatible formatting engine. Only
593
+ * the following tokens are recognized in the input `format` string (case
594
+ * sensitive, matched longest-first so e.g. `HH24` is not partially consumed
595
+ * by `HH`):
596
+ * - `YYYY` - 4-digit year
597
+ * - `MM` - 2-digit month (01-12)
598
+ * - `DD` - 2-digit day of month (01-31)
599
+ * - `HH24` - 2-digit hour, 24-hour clock (00-23)
600
+ * - `HH` - 2-digit hour, 12-hour clock (01-12)
601
+ * - `MI` - 2-digit minute (00-59)
602
+ * - `SS` - 2-digit second (00-59)
603
+ * Any other characters in `format` (literal separators like `-`, `/`, `:`,
604
+ * spaces, etc) are passed through unchanged, best-effort, per dialect.
605
+ *
606
+ * Dialect notes:
607
+ * - postgres/cockroachdb/redshift/oracle: `TO_CHAR(expr, format)` — these
608
+ * tokens map onto TO_CHAR's native template natively, so the format string
609
+ * passes through unchanged.
610
+ * - mysql/mariadb: `DATE_FORMAT(expr, format)`, tokens translated to `%Y`,
611
+ * `%m`, `%d`, `%H`, `%h`, `%i`, `%s`.
612
+ * - mssql: `FORMAT(expr, format)`, tokens translated to .NET-style `yyyy`,
613
+ * `MM`, `dd`, `HH`, `hh`, `mm`, `ss`.
614
+ * - sqlite: `strftime(format, expr)`, tokens translated to `%Y`, `%m`, `%d`,
615
+ * `%H`, `%I`, `%M`, `%S`.
616
+ * - db2: `VARCHAR_FORMAT(expr, format)` (Db2's TO_CHAR-equivalent); tokens
617
+ * pass through unchanged like Oracle/Postgres.
618
+ * - snowflake: `TO_VARCHAR(expr, format)`; tokens pass through unchanged.
619
+ * - clickhouse: `formatDateTime(expr, format)`, tokens translated to `%Y`,
620
+ * `%m`, `%d`, `%H`, `%I`, `%i`, `%S` (ClickHouse's directives are
621
+ * MySQL-`strftime`-like, notably using `%i` for minutes like MySQL).
622
+ */
623
+ function formatDate(dialect, expr, format) {
624
+ const e = toSqlFragment(expr);
625
+ const translate = (tokenMap) => {
626
+ // Replace longest tokens first (HH24 before HH) to avoid partial matches.
627
+ const tokens = Object.keys(tokenMap).sort((a, b) => b.length - a.length);
628
+ const pattern = new RegExp(tokens.join('|'), 'g');
629
+ return format.replace(pattern, (match) => tokenMap[match]);
630
+ };
631
+ switch (dialect) {
632
+ case 'postgres':
633
+ case 'cockroachdb':
634
+ case 'redshift':
635
+ case 'oracle': {
636
+ return makeExpression(`TO_CHAR(${e}, '${format}')`);
637
+ }
638
+ case 'mysql':
639
+ case 'mariadb': {
640
+ const translated = translate({
641
+ YYYY: '%Y',
642
+ MM: '%m',
643
+ DD: '%d',
644
+ HH24: '%H',
645
+ HH: '%h',
646
+ MI: '%i',
647
+ SS: '%s',
648
+ });
649
+ return makeExpression(`DATE_FORMAT(${e}, '${translated}')`);
650
+ }
651
+ case 'mssql': {
652
+ const translated = translate({
653
+ YYYY: 'yyyy',
654
+ MM: 'MM',
655
+ DD: 'dd',
656
+ HH24: 'HH',
657
+ HH: 'hh',
658
+ MI: 'mm',
659
+ SS: 'ss',
660
+ });
661
+ return makeExpression(`FORMAT(${e}, '${translated}')`);
662
+ }
663
+ case 'sqlite': {
664
+ const translated = translate({
665
+ YYYY: '%Y',
666
+ MM: '%m',
667
+ DD: '%d',
668
+ HH24: '%H',
669
+ HH: '%I',
670
+ MI: '%M',
671
+ SS: '%S',
672
+ });
673
+ return makeExpression(`strftime('${translated}', ${e})`);
674
+ }
675
+ case 'db2': {
676
+ return makeExpression(`VARCHAR_FORMAT(${e}, '${format}')`);
677
+ }
678
+ case 'snowflake': {
679
+ return makeExpression(`TO_VARCHAR(${e}, '${format}')`);
680
+ }
681
+ case 'clickhouse': {
682
+ const translated = translate({
683
+ YYYY: '%Y',
684
+ MM: '%m',
685
+ DD: '%d',
686
+ HH24: '%H',
687
+ HH: '%I',
688
+ MI: '%i',
689
+ SS: '%S',
690
+ });
691
+ return makeExpression(`formatDateTime(${e}, '${translated}')`);
692
+ }
693
+ }
694
+ return makeExpression(`TO_CHAR(${e}, '${format}')`);
695
+ }