turbine-orm 0.81.0 → 0.90.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 (295) hide show
  1. package/README.md +26 -25
  2. package/dist/checkout.d.ts +27 -2
  3. package/dist/checkout.js +75 -4
  4. package/dist/cjs/checkout.d.ts +27 -2
  5. package/dist/cjs/checkout.js +76 -3
  6. package/dist/cjs/cli/code-first-tags.d.ts +62 -0
  7. package/dist/cjs/cli/code-first-tags.js +124 -0
  8. package/dist/cjs/cli/compile-query.js +9 -0
  9. package/dist/cjs/cli/config.d.ts +35 -6
  10. package/dist/cjs/cli/config.js +17 -1
  11. package/dist/cjs/cli/destructive.d.ts +7 -4
  12. package/dist/cjs/cli/destructive.js +224 -20
  13. package/dist/cjs/cli/error-catalog.js +36 -0
  14. package/dist/cjs/cli/error-text.d.ts +15 -0
  15. package/dist/cjs/cli/error-text.js +40 -0
  16. package/dist/cjs/cli/index.d.ts +62 -4
  17. package/dist/cjs/cli/index.js +568 -108
  18. package/dist/cjs/cli/local-server-guard.d.ts +53 -0
  19. package/dist/cjs/cli/local-server-guard.js +119 -0
  20. package/dist/cjs/cli/local-server-output.d.ts +41 -0
  21. package/dist/cjs/cli/local-server-output.js +103 -0
  22. package/dist/cjs/cli/mcp.d.ts +8 -0
  23. package/dist/cjs/cli/mcp.js +121 -32
  24. package/dist/cjs/cli/migrate.d.ts +113 -5
  25. package/dist/cjs/cli/migrate.js +270 -21
  26. package/dist/cjs/cli/observe.d.ts +6 -1
  27. package/dist/cjs/cli/observe.js +85 -55
  28. package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
  29. package/dist/cjs/cli/pii-predicate-guard.js +98 -37
  30. package/dist/cjs/cli/prisma-resolve.js +17 -13
  31. package/dist/cjs/cli/prisma-schema.js +12 -4
  32. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  33. package/dist/cjs/cli/studio.d.ts +14 -2
  34. package/dist/cjs/cli/studio.js +244 -69
  35. package/dist/cjs/cli/ui.d.ts +1 -1
  36. package/dist/cjs/client.d.ts +558 -85
  37. package/dist/cjs/client.js +1270 -395
  38. package/dist/cjs/connection-guard.d.ts +13 -0
  39. package/dist/cjs/connection-guard.js +28 -0
  40. package/dist/cjs/connection-url.d.ts +51 -16
  41. package/dist/cjs/connection-url.js +67 -34
  42. package/dist/cjs/diagnostics.d.ts +81 -0
  43. package/dist/cjs/diagnostics.js +147 -0
  44. package/dist/cjs/dialect.d.ts +449 -20
  45. package/dist/cjs/dialect.js +117 -76
  46. package/dist/cjs/engine-config.d.ts +7 -6
  47. package/dist/cjs/errors.d.ts +126 -6
  48. package/dist/cjs/errors.js +256 -13
  49. package/dist/cjs/generate.d.ts +57 -7
  50. package/dist/cjs/generate.js +635 -279
  51. package/dist/cjs/global-filters-config.d.ts +59 -0
  52. package/dist/cjs/global-filters-config.js +367 -0
  53. package/dist/cjs/index.d.ts +9 -4
  54. package/dist/cjs/index.js +10 -2
  55. package/dist/cjs/introspect.d.ts +31 -6
  56. package/dist/cjs/introspect.js +108 -50
  57. package/dist/cjs/log-codes.d.ts +12 -0
  58. package/dist/cjs/log-codes.js +77 -0
  59. package/dist/cjs/logger.d.ts +87 -0
  60. package/dist/cjs/logger.js +98 -0
  61. package/dist/cjs/mssql.d.ts +8 -5
  62. package/dist/cjs/mssql.js +352 -65
  63. package/dist/cjs/mysql.d.ts +3 -5
  64. package/dist/cjs/mysql.js +420 -70
  65. package/dist/cjs/nested-write.d.ts +3 -0
  66. package/dist/cjs/nested-write.js +179 -114
  67. package/dist/cjs/pipeline-submittable.d.ts +12 -0
  68. package/dist/cjs/pipeline-submittable.js +118 -44
  69. package/dist/cjs/pipeline.js +21 -26
  70. package/dist/cjs/powdb-introspect.d.ts +5 -2
  71. package/dist/cjs/powdb-introspect.js +20 -15
  72. package/dist/cjs/powdb-shared.d.ts +36 -5
  73. package/dist/cjs/powdb-shared.js +67 -8
  74. package/dist/cjs/powdb.d.ts +4 -0
  75. package/dist/cjs/powdb.js +183 -51
  76. package/dist/cjs/powql.d.ts +394 -51
  77. package/dist/cjs/powql.js +1829 -284
  78. package/dist/cjs/prisma-compat.d.ts +23 -15
  79. package/dist/cjs/prisma-compat.js +507 -372
  80. package/dist/cjs/query/aggregates.d.ts +17 -1
  81. package/dist/cjs/query/aggregates.js +261 -29
  82. package/dist/cjs/query/batched-loader.d.ts +25 -6
  83. package/dist/cjs/query/batched-loader.js +103 -20
  84. package/dist/cjs/query/builder.d.ts +325 -184
  85. package/dist/cjs/query/builder.js +1452 -490
  86. package/dist/cjs/query/column-ref.d.ts +61 -0
  87. package/dist/cjs/query/column-ref.js +73 -0
  88. package/dist/cjs/query/compound-unique.d.ts +21 -0
  89. package/dist/cjs/query/compound-unique.js +98 -18
  90. package/dist/cjs/query/deferred.d.ts +53 -16
  91. package/dist/cjs/query/expr.d.ts +217 -0
  92. package/dist/cjs/query/expr.js +859 -0
  93. package/dist/cjs/query/filters.d.ts +155 -53
  94. package/dist/cjs/query/filters.js +613 -76
  95. package/dist/cjs/query/index.d.ts +2 -0
  96. package/dist/cjs/query/index.js +7 -1
  97. package/dist/cjs/query/json-value.d.ts +43 -0
  98. package/dist/cjs/query/json-value.js +59 -0
  99. package/dist/cjs/query/normalize-args.d.ts +87 -0
  100. package/dist/cjs/query/normalize-args.js +527 -0
  101. package/dist/cjs/query/option-surface.d.ts +16 -4
  102. package/dist/cjs/query/option-surface.js +57 -14
  103. package/dist/cjs/query/relation-names.d.ts +14 -0
  104. package/dist/cjs/query/relation-names.js +77 -18
  105. package/dist/cjs/query/relations.d.ts +30 -3
  106. package/dist/cjs/query/relations.js +219 -63
  107. package/dist/cjs/query/row-lock.d.ts +49 -0
  108. package/dist/cjs/query/row-lock.js +138 -0
  109. package/dist/cjs/query/sql-comment.d.ts +161 -0
  110. package/dist/cjs/query/sql-comment.js +381 -0
  111. package/dist/cjs/query/stream-idle.d.ts +132 -0
  112. package/dist/cjs/query/stream-idle.js +212 -0
  113. package/dist/cjs/query/table-client.d.ts +101 -0
  114. package/dist/cjs/query/table-client.js +74 -0
  115. package/dist/cjs/query/types.d.ts +677 -298
  116. package/dist/cjs/query/utils.d.ts +105 -6
  117. package/dist/cjs/query/utils.js +426 -19
  118. package/dist/cjs/query/warn-registry.d.ts +18 -0
  119. package/dist/cjs/query/warn-registry.js +18 -0
  120. package/dist/cjs/query/where-compile.d.ts +138 -14
  121. package/dist/cjs/query/where-compile.js +513 -29
  122. package/dist/cjs/query/where.d.ts +159 -59
  123. package/dist/cjs/query/where.js +716 -246
  124. package/dist/cjs/query/writes.d.ts +112 -50
  125. package/dist/cjs/query/writes.js +1498 -200
  126. package/dist/cjs/query-events.d.ts +4 -2
  127. package/dist/cjs/query-events.js +3 -1
  128. package/dist/cjs/realtime.d.ts +7 -3
  129. package/dist/cjs/realtime.js +13 -2
  130. package/dist/cjs/schema-builder.d.ts +55 -4
  131. package/dist/cjs/schema-builder.js +382 -18
  132. package/dist/cjs/schema-metadata.d.ts +21 -1
  133. package/dist/cjs/schema-metadata.js +29 -7
  134. package/dist/cjs/schema-sql.d.ts +75 -5
  135. package/dist/cjs/schema-sql.js +497 -45
  136. package/dist/cjs/schema.d.ts +26 -5
  137. package/dist/cjs/schema.js +110 -13
  138. package/dist/cjs/sql-placeholders.d.ts +56 -0
  139. package/dist/cjs/sql-placeholders.js +204 -0
  140. package/dist/cjs/sqlite.d.ts +75 -20
  141. package/dist/cjs/sqlite.js +593 -95
  142. package/dist/cjs/supplied-pool-types.d.ts +48 -0
  143. package/dist/cjs/supplied-pool-types.js +256 -0
  144. package/dist/cjs/transaction-control.d.ts +51 -0
  145. package/dist/cjs/transaction-control.js +316 -0
  146. package/dist/cjs/transaction-state.d.ts +342 -0
  147. package/dist/cjs/transaction-state.js +707 -0
  148. package/dist/cjs/typed-sql.d.ts +37 -3
  149. package/dist/cjs/typed-sql.js +102 -8
  150. package/dist/cli/code-first-tags.d.ts +62 -0
  151. package/dist/cli/code-first-tags.js +120 -0
  152. package/dist/cli/compile-query.js +9 -0
  153. package/dist/cli/config.d.ts +35 -6
  154. package/dist/cli/config.js +16 -1
  155. package/dist/cli/destructive.d.ts +7 -4
  156. package/dist/cli/destructive.js +224 -20
  157. package/dist/cli/error-catalog.js +36 -0
  158. package/dist/cli/error-text.d.ts +15 -0
  159. package/dist/cli/error-text.js +37 -0
  160. package/dist/cli/index.d.ts +62 -4
  161. package/dist/cli/index.js +569 -112
  162. package/dist/cli/local-server-guard.d.ts +53 -0
  163. package/dist/cli/local-server-guard.js +113 -0
  164. package/dist/cli/local-server-output.d.ts +41 -0
  165. package/dist/cli/local-server-output.js +98 -0
  166. package/dist/cli/mcp.d.ts +8 -0
  167. package/dist/cli/mcp.js +121 -34
  168. package/dist/cli/migrate.d.ts +113 -5
  169. package/dist/cli/migrate.js +261 -21
  170. package/dist/cli/observe.d.ts +6 -1
  171. package/dist/cli/observe.js +84 -54
  172. package/dist/cli/pii-predicate-guard.d.ts +43 -23
  173. package/dist/cli/pii-predicate-guard.js +100 -35
  174. package/dist/cli/prisma-resolve.js +15 -11
  175. package/dist/cli/prisma-schema.js +12 -4
  176. package/dist/cli/studio-ui.generated.js +1 -1
  177. package/dist/cli/studio.d.ts +14 -2
  178. package/dist/cli/studio.js +246 -71
  179. package/dist/cli/ui.d.ts +1 -1
  180. package/dist/client.d.ts +558 -85
  181. package/dist/client.js +1275 -400
  182. package/dist/connection-guard.d.ts +13 -0
  183. package/dist/connection-guard.js +27 -0
  184. package/dist/connection-url.d.ts +51 -16
  185. package/dist/connection-url.js +63 -34
  186. package/dist/diagnostics.d.ts +81 -0
  187. package/dist/diagnostics.js +144 -0
  188. package/dist/dialect.d.ts +449 -20
  189. package/dist/dialect.js +115 -43
  190. package/dist/engine-config.d.ts +7 -6
  191. package/dist/errors.d.ts +126 -6
  192. package/dist/errors.js +252 -12
  193. package/dist/generate.d.ts +57 -7
  194. package/dist/generate.js +635 -281
  195. package/dist/global-filters-config.d.ts +59 -0
  196. package/dist/global-filters-config.js +361 -0
  197. package/dist/index.d.ts +9 -4
  198. package/dist/index.js +4 -2
  199. package/dist/introspect.d.ts +31 -6
  200. package/dist/introspect.js +106 -50
  201. package/dist/log-codes.d.ts +12 -0
  202. package/dist/log-codes.js +74 -0
  203. package/dist/logger.d.ts +87 -0
  204. package/dist/logger.js +93 -0
  205. package/dist/mssql.d.ts +8 -5
  206. package/dist/mssql.js +354 -67
  207. package/dist/mysql.d.ts +3 -5
  208. package/dist/mysql.js +423 -73
  209. package/dist/nested-write.d.ts +3 -0
  210. package/dist/nested-write.js +177 -112
  211. package/dist/pipeline-submittable.d.ts +12 -0
  212. package/dist/pipeline-submittable.js +117 -44
  213. package/dist/pipeline.js +21 -26
  214. package/dist/powdb-introspect.d.ts +5 -2
  215. package/dist/powdb-introspect.js +21 -16
  216. package/dist/powdb-shared.d.ts +36 -5
  217. package/dist/powdb-shared.js +65 -8
  218. package/dist/powdb.d.ts +4 -0
  219. package/dist/powdb.js +183 -51
  220. package/dist/powql.d.ts +394 -51
  221. package/dist/powql.js +1836 -291
  222. package/dist/prisma-compat.d.ts +23 -15
  223. package/dist/prisma-compat.js +508 -373
  224. package/dist/query/aggregates.d.ts +17 -1
  225. package/dist/query/aggregates.js +263 -32
  226. package/dist/query/batched-loader.d.ts +25 -6
  227. package/dist/query/batched-loader.js +101 -21
  228. package/dist/query/builder.d.ts +325 -184
  229. package/dist/query/builder.js +1458 -495
  230. package/dist/query/column-ref.d.ts +61 -0
  231. package/dist/query/column-ref.js +66 -0
  232. package/dist/query/compound-unique.d.ts +21 -0
  233. package/dist/query/compound-unique.js +97 -19
  234. package/dist/query/deferred.d.ts +53 -16
  235. package/dist/query/expr.d.ts +217 -0
  236. package/dist/query/expr.js +848 -0
  237. package/dist/query/filters.d.ts +155 -53
  238. package/dist/query/filters.js +597 -72
  239. package/dist/query/index.d.ts +2 -0
  240. package/dist/query/index.js +4 -0
  241. package/dist/query/json-value.d.ts +43 -0
  242. package/dist/query/json-value.js +54 -0
  243. package/dist/query/normalize-args.d.ts +87 -0
  244. package/dist/query/normalize-args.js +517 -0
  245. package/dist/query/option-surface.d.ts +16 -4
  246. package/dist/query/option-surface.js +56 -13
  247. package/dist/query/relation-names.d.ts +14 -0
  248. package/dist/query/relation-names.js +77 -19
  249. package/dist/query/relations.d.ts +30 -3
  250. package/dist/query/relations.js +220 -67
  251. package/dist/query/row-lock.d.ts +49 -0
  252. package/dist/query/row-lock.js +131 -0
  253. package/dist/query/sql-comment.d.ts +161 -0
  254. package/dist/query/sql-comment.js +374 -0
  255. package/dist/query/stream-idle.d.ts +132 -0
  256. package/dist/query/stream-idle.js +203 -0
  257. package/dist/query/table-client.d.ts +101 -0
  258. package/dist/query/table-client.js +70 -0
  259. package/dist/query/types.d.ts +677 -298
  260. package/dist/query/utils.d.ts +105 -6
  261. package/dist/query/utils.js +414 -18
  262. package/dist/query/warn-registry.d.ts +18 -0
  263. package/dist/query/warn-registry.js +18 -0
  264. package/dist/query/where-compile.d.ts +138 -14
  265. package/dist/query/where-compile.js +508 -31
  266. package/dist/query/where.d.ts +159 -59
  267. package/dist/query/where.js +711 -248
  268. package/dist/query/writes.d.ts +112 -50
  269. package/dist/query/writes.js +1496 -202
  270. package/dist/query-events.d.ts +4 -2
  271. package/dist/query-events.js +3 -1
  272. package/dist/realtime.d.ts +7 -3
  273. package/dist/realtime.js +13 -2
  274. package/dist/schema-builder.d.ts +55 -4
  275. package/dist/schema-builder.js +381 -18
  276. package/dist/schema-metadata.d.ts +21 -1
  277. package/dist/schema-metadata.js +28 -7
  278. package/dist/schema-sql.d.ts +75 -5
  279. package/dist/schema-sql.js +494 -44
  280. package/dist/schema.d.ts +26 -5
  281. package/dist/schema.js +109 -13
  282. package/dist/sql-placeholders.d.ts +56 -0
  283. package/dist/sql-placeholders.js +200 -0
  284. package/dist/sqlite.d.ts +75 -20
  285. package/dist/sqlite.js +597 -99
  286. package/dist/supplied-pool-types.d.ts +48 -0
  287. package/dist/supplied-pool-types.js +247 -0
  288. package/dist/transaction-control.d.ts +51 -0
  289. package/dist/transaction-control.js +311 -0
  290. package/dist/transaction-state.d.ts +342 -0
  291. package/dist/transaction-state.js +696 -0
  292. package/dist/typed-sql.d.ts +37 -3
  293. package/dist/typed-sql.js +100 -8
  294. package/package.json +7 -4
  295. package/skills/turbine-orm/SKILL.md +56 -6
@@ -0,0 +1,859 @@
1
+ "use strict";
2
+ /**
3
+ * turbine-orm: SQL expressions in update data.
4
+ *
5
+ * `expr.greatest(col('viewCount'), 100)` and friends are branded node objects,
6
+ * and `expr.excluded('events')` names the incoming row of an upsert. Both are
7
+ * written into the SQL TEXT, so both carry a symbol brand: `JSON.parse` cannot
8
+ * produce one, so request data can never become an expression (the same
9
+ * reasoning as `col()` and `UNSAFE`). `Symbol.for` so the ESM and CJS copies of
10
+ * the package recognize each other's nodes. A plain look-alike object
11
+ * (`{ kind: 'greatest', args: [...] }`) is a VALUE, and on a non-JSON column
12
+ * the write path refuses it.
13
+ *
14
+ * The public types live in `query/types.ts` (`SqlExpr`, `ExcludedRef`). This
15
+ * module holds the node constructors, their validation, and
16
+ * {@link compileSetExpr}: the ONE traversal that produces an expression's SQL,
17
+ * its bound parameters and its cache fingerprint together, so the SET build,
18
+ * the cache-hit param collector and the fingerprint cannot disagree (the
19
+ * failure class behind the where-clause cache bugs).
20
+ *
21
+ * Where expressions are accepted: `update`, `updateMany`, `upsert`'s `update`,
22
+ * `createMany`'s `onConflict.update`, and nested update data (which runs
23
+ * through `update()`). Not in `create` / `createMany` data, where the write
24
+ * bind seam refuses a node.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.expr = exports.EXCLUDED_REF = exports.SQL_EXPR = void 0;
28
+ exports.isSqlExpr = isSqlExpr;
29
+ exports.isExcludedRef = isExcludedRef;
30
+ exports.isPlainExprShape = isPlainExprShape;
31
+ exports.plainExprShapeMessage = plainExprShapeMessage;
32
+ exports.exprNotAllowedMessage = exprNotAllowedMessage;
33
+ exports.assertExprTarget = assertExprTarget;
34
+ exports.compileSetExpr = compileSetExpr;
35
+ exports.resolveExcludedFromCreate = resolveExcludedFromCreate;
36
+ const errors_js_1 = require("../errors.js");
37
+ const column_ref_js_1 = require("./column-ref.js");
38
+ const filters_js_1 = require("./filters.js");
39
+ const normalize_args_js_1 = require("./normalize-args.js");
40
+ const utils_js_1 = require("./utils.js");
41
+ const where_js_1 = require("./where.js");
42
+ const where_compile_js_1 = require("./where-compile.js");
43
+ /** The brand every {@link SqlExpr} node carries. */
44
+ exports.SQL_EXPR = Symbol.for('turbine-orm.sqlExpr');
45
+ /** The brand an {@link ExcludedRef} carries (the incoming row of an upsert). */
46
+ exports.EXCLUDED_REF = Symbol.for('turbine-orm.excludedRef');
47
+ const EXPR_KINDS = new Set([
48
+ 'add',
49
+ 'sub',
50
+ 'mul',
51
+ 'div',
52
+ 'coalesce',
53
+ 'greatest',
54
+ 'least',
55
+ 'case',
56
+ 'now',
57
+ 'excluded',
58
+ ]);
59
+ /** Operand count bounds for coalesce / greatest / least. */
60
+ const MIN_VARIADIC = 2;
61
+ const MAX_VARIADIC = 8;
62
+ /** Arm count bounds for case. */
63
+ const MAX_CASE_ARMS = 16;
64
+ /** Nesting bound, so a hostile tree cannot exhaust the stack. */
65
+ const MAX_EXPR_DEPTH = 32;
66
+ // ---------------------------------------------------------------------------
67
+ // Constructors
68
+ // ---------------------------------------------------------------------------
69
+ function makeNode(kind, args, extra) {
70
+ const node = { kind, args: Object.freeze([...args]), ...extra };
71
+ // Non-enumerable, like `col()`'s brand: the node logs and compares as
72
+ // `{ kind, args }`, and no `Object.keys` walker ever sees the symbol.
73
+ Object.defineProperty(node, exports.SQL_EXPR, { value: true, enumerable: false });
74
+ return Object.freeze(node);
75
+ }
76
+ function describeOperand(value) {
77
+ if (Array.isArray(value))
78
+ return 'an array';
79
+ if (typeof value === 'object' && value !== null) {
80
+ if (isPlainExprShape(value))
81
+ return 'a plain { kind, args } object (not built by expr)';
82
+ return (0, filters_js_1.isPlainRecord)(value) ? 'a plain object' : `a ${value.constructor?.name ?? 'object'} instance`;
83
+ }
84
+ if (typeof value === 'number')
85
+ return String(value);
86
+ return typeof value;
87
+ }
88
+ /** Refuse an operand no engine can bind as one value. Validated at construction, so the error names the call. */
89
+ function assertOperand(kind, position, value) {
90
+ const at = `expr.${kind}(): operand ${position + 1}`;
91
+ if (value === undefined) {
92
+ throw new errors_js_1.ValidationError(`${at} is undefined. Pass null for SQL NULL.`);
93
+ }
94
+ if (value === null || (0, column_ref_js_1.isColumnRef)(value) || isSqlExpr(value))
95
+ return;
96
+ switch (typeof value) {
97
+ case 'string':
98
+ case 'boolean':
99
+ case 'bigint':
100
+ return;
101
+ case 'number':
102
+ if (Number.isFinite(value))
103
+ return;
104
+ throw new errors_js_1.ValidationError(`${at} is ${value}; a bound number must be finite.`);
105
+ }
106
+ if (value instanceof Date) {
107
+ if (!Number.isNaN(value.getTime()))
108
+ return;
109
+ throw new errors_js_1.ValidationError(`${at} is an Invalid Date.`);
110
+ }
111
+ if (value instanceof Uint8Array)
112
+ return;
113
+ if ((0, column_ref_js_1.isPlainColumnRefShape)(value)) {
114
+ const name = JSON.stringify(value.col);
115
+ throw new errors_js_1.ValidationError(`${at} is a plain { col: ${name} } object, not a column reference. Build one with col(${name}) ` +
116
+ "(import { col } from 'turbine-orm').");
117
+ }
118
+ throw new errors_js_1.ValidationError(`${at} is ${describeOperand(value)}. An operand is a scalar (string, number, bigint, boolean, Date, ` +
119
+ 'Buffer or null), col(field), or a node built by expr; a plain object is never an expression.');
120
+ }
121
+ /** Arithmetic takes numbers only: a string, boolean or Date is refused at construction. */
122
+ function assertNumericOperand(kind, position, value) {
123
+ assertOperand(kind, position, value);
124
+ const t = typeof value;
125
+ if (t === 'string' || t === 'boolean' || value instanceof Date || value instanceof Uint8Array) {
126
+ throw new errors_js_1.ValidationError(`expr.${kind}(): operand ${position + 1} is a ${value instanceof Date ? 'Date' : t}. Arithmetic takes ` +
127
+ 'numbers, bigints, col() of a numeric column, or numeric expressions. Date arithmetic is not supported.');
128
+ }
129
+ if (isSqlExpr(value) && value.kind === 'now') {
130
+ throw new errors_js_1.ValidationError(`expr.${kind}(): operand ${position + 1} is expr.now(). Date arithmetic is not supported; ` +
131
+ 'compute the timestamp in JavaScript and pass it as a value.');
132
+ }
133
+ }
134
+ function arithmetic(kind, a, b) {
135
+ assertNumericOperand(kind, 0, a);
136
+ assertNumericOperand(kind, 1, b);
137
+ return makeNode(kind, [a, b]);
138
+ }
139
+ function variadic(kind, operands) {
140
+ if (operands.length < MIN_VARIADIC || operands.length > MAX_VARIADIC) {
141
+ throw new errors_js_1.ValidationError(`expr.${kind}() takes ${MIN_VARIADIC} to ${MAX_VARIADIC} operands, got ${operands.length}.`);
142
+ }
143
+ operands.forEach((v, i) => {
144
+ assertOperand(kind, i, v);
145
+ });
146
+ return makeNode(kind, operands);
147
+ }
148
+ function caseNode(arms, otherwise) {
149
+ if (!Array.isArray(arms) || arms.length === 0 || arms.length > MAX_CASE_ARMS) {
150
+ throw new errors_js_1.ValidationError(`expr.case() takes an array of 1 to ${MAX_CASE_ARMS} arms ({ when, then }), got ` +
151
+ `${Array.isArray(arms) ? `${arms.length} arms` : describeOperand(arms)}.`);
152
+ }
153
+ const frozen = arms.map((arm, i) => {
154
+ if (!(0, filters_js_1.isPlainRecord)(arm)) {
155
+ throw new errors_js_1.ValidationError(`expr.case(): arm ${i + 1} is ${describeOperand(arm)}; write { when, then }.`);
156
+ }
157
+ const extra = Object.keys(arm).filter((k) => k !== 'when' && k !== 'then');
158
+ if (extra.length > 0) {
159
+ throw new errors_js_1.ValidationError(`expr.case(): arm ${i + 1} has unknown key "${extra[0]}"; an arm is { when, then }.`);
160
+ }
161
+ if (!(0, filters_js_1.isPlainRecord)(arm.when)) {
162
+ throw new errors_js_1.ValidationError(`expr.case(): arm ${i + 1}'s when is ${describeOperand(arm.when)}; write a predicate object like \`where\`.`);
163
+ }
164
+ if (!('then' in arm)) {
165
+ throw new errors_js_1.ValidationError(`expr.case(): arm ${i + 1} has no then. Pass null for SQL NULL.`);
166
+ }
167
+ assertOperand('case', i, arm.then);
168
+ // biome-ignore lint/suspicious/noThenProperty: SQL's CASE WHEN ... THEN; a value operand, never a function, so never thenable
169
+ return Object.freeze({ when: arm.when, then: arm.then });
170
+ });
171
+ if (otherwise !== undefined)
172
+ assertOperand('case', arms.length, otherwise);
173
+ return makeNode('case', frozen, otherwise === undefined ? undefined : { else: otherwise });
174
+ }
175
+ /**
176
+ * Builders for SQL expressions in update data. Each returns a frozen, branded
177
+ * node; the SQL is generated per engine when the update compiles.
178
+ *
179
+ * ```ts
180
+ * import { col, expr } from 'turbine-orm';
181
+ * await db.posts.update({
182
+ * where: { id },
183
+ * data: {
184
+ * viewCount: expr.greatest(col('viewCount'), 100),
185
+ * score: expr.add(col('score'), expr.mul(col('bonus'), 2)),
186
+ * editedAt: expr.now(),
187
+ * },
188
+ * });
189
+ * ```
190
+ */
191
+ exports.expr = Object.freeze({
192
+ /** `a + b`. Numbers, bigints, numeric columns and numeric expressions. */
193
+ add(a, b) {
194
+ return arithmetic('add', a, b);
195
+ },
196
+ /** `a - b`. */
197
+ sub(a, b) {
198
+ return arithmetic('sub', a, b);
199
+ },
200
+ /** `a * b`. */
201
+ mul(a, b) {
202
+ return arithmetic('mul', a, b);
203
+ },
204
+ /**
205
+ * `a / b`. When both operands are integers (an integer column, an integer
206
+ * number or bigint, or an integer expression) the division truncates toward
207
+ * zero on every engine, as the `divide` update operator does on an integer
208
+ * column. With a non-integer operand it is a decimal division.
209
+ */
210
+ div(a, b) {
211
+ return arithmetic('div', a, b);
212
+ },
213
+ /** The first non-NULL operand (2 to 8). */
214
+ coalesce(a, b, ...rest) {
215
+ return variadic('coalesce', [a, b, ...rest]);
216
+ },
217
+ /**
218
+ * The largest operand (2 to 8), IGNORING NULLs on every engine (PostgreSQL's
219
+ * meaning; MySQL and SQLite are rewritten to match). NULL only when every
220
+ * operand is NULL.
221
+ */
222
+ greatest(a, b, ...rest) {
223
+ return variadic('greatest', [a, b, ...rest]);
224
+ },
225
+ /** The smallest operand (2 to 8), ignoring NULLs on every engine. */
226
+ least(a, b, ...rest) {
227
+ return variadic('least', [a, b, ...rest]);
228
+ },
229
+ /**
230
+ * `CASE WHEN ... THEN ... ELSE ... END`. Each arm's `when` is a predicate on
231
+ * the current row's columns written like `where` (no relation filters); the
232
+ * first matching arm wins. Without `otherwise` an unmatched row gets NULL.
233
+ */
234
+ case(arms, otherwise) {
235
+ return caseNode(arms, otherwise);
236
+ },
237
+ /**
238
+ * The database server's current time, stored in the zone convention Turbine
239
+ * reads with: a zone-less `timestamp` / `date` gets the UTC wall clock (unless
240
+ * the client sets `utcTimestamps: false`). On PostgreSQL it is the
241
+ * TRANSACTION's start time; on MySQL, the statement's. Temporal columns only.
242
+ */
243
+ now() {
244
+ return makeNode('now', []);
245
+ },
246
+ /**
247
+ * The incoming row's value for `field`: inside `upsert`'s `update` (the value
248
+ * `create` supplies) and `createMany`'s `onConflict.update` (each row's own
249
+ * value). Refused anywhere else.
250
+ */
251
+ excluded(field) {
252
+ if (typeof field !== 'string' || field.length === 0) {
253
+ throw new errors_js_1.ValidationError('expr.excluded() takes the name of a field on the table, as a non-empty string.');
254
+ }
255
+ const node = { kind: 'excluded', args: Object.freeze([]), field };
256
+ Object.defineProperty(node, exports.SQL_EXPR, { value: true, enumerable: false });
257
+ Object.defineProperty(node, exports.EXCLUDED_REF, { value: true, enumerable: false });
258
+ return Object.freeze(node);
259
+ },
260
+ });
261
+ /** True only for a node built by {@link expr} (any loaded copy of the package). */
262
+ function isSqlExpr(value) {
263
+ return (typeof value === 'object' &&
264
+ value !== null &&
265
+ value[exports.SQL_EXPR] === true &&
266
+ typeof value.kind === 'string');
267
+ }
268
+ /** True only for `expr.excluded(...)`. */
269
+ function isExcludedRef(value) {
270
+ return isSqlExpr(value) && value[exports.EXCLUDED_REF] === true;
271
+ }
272
+ /**
273
+ * True for an UNBRANDED object shaped like a node (`{ kind: 'greatest', args }`),
274
+ * which is what parsed request data produces. The write path refuses it on a
275
+ * non-JSON column rather than storing it as JSON text.
276
+ */
277
+ function isPlainExprShape(value) {
278
+ if (!(0, filters_js_1.isPlainRecord)(value) || isSqlExpr(value))
279
+ return false;
280
+ const kind = value.kind;
281
+ return (typeof kind === 'string' && EXPR_KINDS.has(kind) && (Array.isArray(value.args) || typeof value.field === 'string'));
282
+ }
283
+ /** The refusal for {@link isPlainExprShape}. */
284
+ function plainExprShapeMessage(table, key, kind) {
285
+ return (`A plain { kind: ${JSON.stringify(kind)}, ... } object written to "${table}.${key}" is not an expression. ` +
286
+ `Build one with expr.${EXPR_KINDS.has(kind) ? kind : 'add'}(...) (import { expr } from 'turbine-orm'): ` +
287
+ 'expressions carry a symbol brand so that parsed request data can never become SQL. ' +
288
+ 'To store JSON text in this column, pass `JSON.stringify(value)` or `jsonValue(value)`.');
289
+ }
290
+ /** The refusal for a node at a position that takes values only (create data, `{ set }`). */
291
+ function exprNotAllowedMessage(table, key, value) {
292
+ const what = (0, column_ref_js_1.isColumnRef)(value) ? `col(${JSON.stringify(value.col)})` : `expr.${value.kind}()`;
293
+ return (`${what} cannot be written to "${table}.${key}" here: expressions and column references are accepted as the ` +
294
+ "direct value of a field in update, updateMany, upsert's update and createMany's onConflict.update, " +
295
+ 'not in create / createMany data and not inside an operator such as { set }.');
296
+ }
297
+ const NUMERIC_TYPES = new Set([
298
+ 'int2',
299
+ 'int4',
300
+ 'int8',
301
+ 'smallint',
302
+ 'integer',
303
+ 'int',
304
+ 'bigint',
305
+ 'tinyint',
306
+ 'mediumint',
307
+ 'serial',
308
+ 'bigserial',
309
+ 'smallserial',
310
+ 'numeric',
311
+ 'decimal',
312
+ 'dec',
313
+ 'fixed',
314
+ 'float4',
315
+ 'float8',
316
+ 'real',
317
+ 'double precision',
318
+ 'double',
319
+ 'float',
320
+ 'money',
321
+ 'smallmoney',
322
+ 'number',
323
+ ]);
324
+ /** The integer subset of {@link NUMERIC_TYPES}. */
325
+ const INTEGER_TYPES = new Set([
326
+ 'int2',
327
+ 'int4',
328
+ 'int8',
329
+ 'smallint',
330
+ 'integer',
331
+ 'int',
332
+ 'bigint',
333
+ 'tinyint',
334
+ 'mediumint',
335
+ 'serial',
336
+ 'bigserial',
337
+ 'smallserial',
338
+ ]);
339
+ const TEMPORAL_TYPES = {
340
+ timestamptz: 'tz',
341
+ 'timestamp with time zone': 'tz',
342
+ datetimeoffset: 'tz',
343
+ timestamp: 'timestamp',
344
+ 'timestamp without time zone': 'timestamp',
345
+ datetime: 'timestamp',
346
+ datetime2: 'timestamp',
347
+ smalldatetime: 'timestamp',
348
+ date: 'date',
349
+ time: 'time',
350
+ timetz: 'time',
351
+ 'time with time zone': 'time',
352
+ 'time without time zone': 'time',
353
+ };
354
+ /** A column type token, lowercased, without precision or `unsigned`. */
355
+ function normalizeType(dbType) {
356
+ return dbType
357
+ .toLowerCase()
358
+ .replace(/\(\s*\d+(\s*,\s*\d+)?\s*\)/g, '')
359
+ .replace(/\bunsigned\b|\bzerofill\b/g, '')
360
+ .replace(/\s+/g, ' ')
361
+ .trim();
362
+ }
363
+ function columnClass(dbType) {
364
+ if (dbType === undefined)
365
+ return 'unknown';
366
+ const t = normalizeType(dbType);
367
+ if (t === 'json' || t === 'jsonb')
368
+ return 'json';
369
+ if (t.startsWith('_') || t.endsWith('[]'))
370
+ return 'array';
371
+ if (NUMERIC_TYPES.has(t))
372
+ return 'numeric';
373
+ if (Object.hasOwn(TEMPORAL_TYPES, t))
374
+ return 'temporal';
375
+ return 'other';
376
+ }
377
+ /** The {@link NumClass} of a column of this type. */
378
+ function numClassOfType(dbType) {
379
+ if (dbType === undefined)
380
+ return 'unknown';
381
+ const t = normalizeType(dbType);
382
+ if (INTEGER_TYPES.has(t))
383
+ return 'int';
384
+ return NUMERIC_TYPES.has(t) ? 'dec' : 'other';
385
+ }
386
+ /** The {@link NumClass} of a bound value. */
387
+ function numClassOfValue(value) {
388
+ if (value === null)
389
+ return 'null';
390
+ if (typeof value === 'bigint')
391
+ return 'int';
392
+ if (typeof value === 'number')
393
+ return Number.isInteger(value) ? 'int' : 'dec';
394
+ return 'other';
395
+ }
396
+ /**
397
+ * The class of a result computed from these operands: anything non-numeric
398
+ * wins, then a non-integer, then an untyped column; NULL is neutral.
399
+ */
400
+ function combineClasses(classes) {
401
+ let out = 'null';
402
+ for (const c of classes) {
403
+ if (c === 'other')
404
+ return 'other';
405
+ if (c === 'dec' || (c === 'unknown' && out !== 'dec') || (c === 'int' && out === 'null'))
406
+ out = c;
407
+ }
408
+ return out;
409
+ }
410
+ function temporalKind(dbType) {
411
+ if (dbType === undefined)
412
+ return undefined;
413
+ const t = normalizeType(dbType);
414
+ return Object.hasOwn(TEMPORAL_TYPES, t) ? TEMPORAL_TYPES[t] : undefined;
415
+ }
416
+ function columnType(qi, column) {
417
+ return qi.columnPgTypeMap.get(column);
418
+ }
419
+ /**
420
+ * Refuse an expression assigned to a column it cannot target in this release
421
+ * (json/jsonb and array columns). Shared by the walker and the single-row
422
+ * upsert's `excluded` resolution, so both paths refuse the same columns.
423
+ */
424
+ function assertExprTarget(qi, key, value, operation) {
425
+ const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, key);
426
+ if (column === undefined) {
427
+ // The SET builder's own unknown-column error names the key; reach it.
428
+ return qi.toColumn(key);
429
+ }
430
+ const cls = columnClass(columnType(qi, column));
431
+ if (cls === 'json' || cls === 'array') {
432
+ // A bare copy of the incoming row's same-typed column (the onConflict
433
+ // field list, `update: ['payload']`) applies no operator, so it is a plain
434
+ // assignment on any column type.
435
+ if (isBareStructuredCopy(qi, column, value))
436
+ return column;
437
+ const what = (0, column_ref_js_1.isColumnRef)(value) ? `col(${JSON.stringify(value.col)})` : `expr.${value.kind}()`;
438
+ throw new errors_js_1.ValidationError(`${what} in ${operation} data cannot target "${qi.table}.${key}": it is ${cls === 'json' ? 'a json/jsonb' : 'an array'} ` +
439
+ 'column, and expressions on json and array columns are not supported. Write a plain value instead.');
440
+ }
441
+ return column;
442
+ }
443
+ /** `expr.excluded(f)` assigned whole to a column of exactly f's declared type. */
444
+ function isBareStructuredCopy(qi, column, value) {
445
+ if (!isExcludedRef(value))
446
+ return false;
447
+ const source = (0, utils_js_1.resolveColumnName)(qi.tableMeta, value.field);
448
+ const type = columnType(qi, column);
449
+ return source !== undefined && type !== undefined && columnType(qi, source) === type;
450
+ }
451
+ /**
452
+ * Compile one expression (or `col()` reference) assigned to `env.targetKey`.
453
+ * The single traversal: SQL, params (through `env.bind`, in emission order)
454
+ * and fingerprint come out of one walk.
455
+ *
456
+ * What an expression stores is the same on every engine, by one rule:
457
+ * arithmetic is integer arithmetic when both operands are integers (integer
458
+ * columns, integer numbers, bigints, integer expressions), and integer
459
+ * division truncates toward zero, as `{ divide }` does on an integer column.
460
+ * Any other numeric operand makes it decimal. A non-integer result is refused
461
+ * on an integer column, because the engines disagree on converting one (round
462
+ * half away from zero, round half to even, truncate, or store it unconverted).
463
+ * The class is decided from the operands' types and the bound values' kinds,
464
+ * both part of the fingerprint, so the SQL text never depends on anything the
465
+ * cache key does not capture.
466
+ */
467
+ function compileSetExpr(env, value) {
468
+ const { qi } = env;
469
+ assertEngine(qi);
470
+ const column = assertExprTarget(qi, env.targetKey, value, env.operation);
471
+ const state = { env, targetType: columnType(qi, column), reads: new Set() };
472
+ const part = walkOperand(state, value, false, 0);
473
+ if (part.cls === 'dec' && numClassOfType(state.targetType) === 'int') {
474
+ const what = (0, column_ref_js_1.isColumnRef)(value) ? `col(${JSON.stringify(value.col)})` : `expr.${value.kind}()`;
475
+ throw new errors_js_1.ValidationError(`${what} in ${env.operation} data on "${qi.table}.${env.targetKey}" computes a non-integer value (an operand ` +
476
+ `is a non-integer number or a non-integer column), and "${env.targetKey}" is an integer column ` +
477
+ `(${state.targetType}). Engines convert such a value differently, so it is refused. Use integer operands ` +
478
+ "(expr.div truncates: expr.div(expr.mul(col('n'), 3), 2) is n * 1.5 truncated), or compute the value in " +
479
+ 'JavaScript.');
480
+ }
481
+ return { sql: part.sql, fingerprint: part.fp, reads: state.reads };
482
+ }
483
+ function assertEngine(qi) {
484
+ if (qi.dialect.capabilities.updateExpressions)
485
+ return;
486
+ throw new errors_js_1.UnsupportedFeatureError('update expressions (expr / col() in update data)', qi.dialect.name, 'Compute the value in JavaScript and write it as a plain value.');
487
+ }
488
+ function walkOperand(state, value, inArithmetic, depth) {
489
+ if (depth > MAX_EXPR_DEPTH) {
490
+ throw new errors_js_1.ValidationError(`An update expression is nested more than ${MAX_EXPR_DEPTH} levels deep.`);
491
+ }
492
+ if ((0, column_ref_js_1.isColumnRef)(value)) {
493
+ const column = resolveOperandColumn(state, value.col, 'col', inArithmetic);
494
+ state.reads.add(column);
495
+ return { sql: state.env.column(column), fp: `c:${column}`, cls: numClassOfType(columnType(state.env.qi, column)) };
496
+ }
497
+ if (isSqlExpr(value))
498
+ return walkNode(state, value, inArithmetic, depth);
499
+ // A value leaf. Its shape was validated when the node was built; the bind
500
+ // seam coerces it for the TARGET column, exactly as a plain value would be.
501
+ // The constructors check their own operands, so this catches the one shape
502
+ // they cannot see: a non-number reaching arithmetic through coalesce,
503
+ // greatest, least or a case arm (`expr.add(expr.coalesce('x', 1), 2)`).
504
+ if (inArithmetic &&
505
+ (typeof value === 'string' || typeof value === 'boolean' || value instanceof Date || value instanceof Uint8Array)) {
506
+ throw new errors_js_1.ValidationError(`An arithmetic operand in ${state.env.operation} data on "${state.env.qi.table}.${state.env.targetKey}" is a ` +
507
+ `${value instanceof Date ? 'Date' : value instanceof Uint8Array ? 'Uint8Array' : typeof value}. ` +
508
+ 'Arithmetic takes numbers, bigints, col() of a numeric column, or numeric expressions.');
509
+ }
510
+ // The class is part of the fingerprint: it decides PostgreSQL's cast on an
511
+ // arithmetic operand and MySQL's DIV, so two calls differing only in it must
512
+ // not share a cached statement.
513
+ const cls = numClassOfValue(value);
514
+ const arithmetic = inArithmetic ? (cls === 'dec' ? 'dec' : 'int') : undefined;
515
+ return { sql: state.env.bind(value, arithmetic), fp: `?${cls}`, cls };
516
+ }
517
+ /** Resolve a field an operand names, refusing columns no expression may read. */
518
+ function resolveOperandColumn(state, field, how, inArithmetic) {
519
+ const { qi } = state.env;
520
+ const call = how === 'col' ? `col(${JSON.stringify(field)})` : `expr.excluded(${JSON.stringify(field)})`;
521
+ const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, field);
522
+ if (column === undefined) {
523
+ throw new errors_js_1.ValidationError(`${call} in ${state.env.operation} data on "${qi.table}" names no column. ` +
524
+ `Known fields: ${Object.keys(qi.tableMeta.columnMap).join(', ') || '(none)'}.`);
525
+ }
526
+ const cls = columnClass(columnType(qi, column));
527
+ if (cls === 'json' || cls === 'array') {
528
+ throw new errors_js_1.ValidationError(`${call} on "${qi.table}" reads ${cls === 'json' ? 'a json/jsonb' : 'an array'} column; expressions over json and ` +
529
+ 'array columns are not supported.');
530
+ }
531
+ if (inArithmetic && cls !== 'numeric' && cls !== 'unknown') {
532
+ throw new errors_js_1.ValidationError(`${call} on "${qi.table}" is used in arithmetic, but "${field}" is not a numeric column ` +
533
+ `(${columnType(qi, column)}). Arithmetic takes numeric columns only.`);
534
+ }
535
+ return column;
536
+ }
537
+ const ARITHMETIC_SQL = { add: '+', sub: '-', mul: '*', div: '/' };
538
+ function walkNode(state, node, inArithmetic, depth) {
539
+ const { env } = state;
540
+ const { qi } = env;
541
+ switch (node.kind) {
542
+ case 'add':
543
+ case 'sub':
544
+ case 'mul':
545
+ case 'div': {
546
+ const cls = columnClass(state.targetType);
547
+ if (!inArithmetic && cls !== 'numeric' && cls !== 'unknown') {
548
+ throw new errors_js_1.ValidationError(`expr.${node.kind}() in ${env.operation} data on "${qi.table}.${env.targetKey}": arithmetic needs a numeric ` +
549
+ `column, and this one is ${state.targetType}.`);
550
+ }
551
+ const [a, b] = node.args.map((arg) => walkOperand(state, arg, true, depth + 1));
552
+ const result = combineClasses([a.cls, b.cls]);
553
+ // Integer division is the engine's truncating operator: DIV on MySQL,
554
+ // whose `/` is decimal division even between integers (truncating
555
+ // toward zero, like PostgreSQL's, SQLite's and SQL Server's `/`).
556
+ const op = node.kind === 'div' && result === 'int' ? qi.dialect.capabilities.integerDivision : ARITHMETIC_SQL[node.kind];
557
+ return { sql: `(${a.sql} ${op} ${b.sql})`, fp: `${node.kind}(${a.fp},${b.fp})`, cls: result };
558
+ }
559
+ case 'coalesce': {
560
+ if (node.args.length > MIN_VARIADIC)
561
+ qi.markVariableArity();
562
+ const parts = node.args.map((arg) => walkOperand(state, arg, inArithmetic, depth + 1));
563
+ return {
564
+ sql: `COALESCE(${parts.map((p) => p.sql).join(', ')})`,
565
+ fp: `coalesce(${parts.map((p) => p.fp).join(',')})`,
566
+ cls: combineClasses(parts.map((p) => p.cls)),
567
+ };
568
+ }
569
+ case 'greatest':
570
+ case 'least': {
571
+ if (node.args.length > MIN_VARIADIC)
572
+ qi.markVariableArity();
573
+ const parts = node.args.map((arg) => walkOperand(state, arg, inArithmetic, depth + 1));
574
+ return {
575
+ sql: renderExtreme(qi, node.kind, parts.map((p) => p.sql)),
576
+ fp: `${node.kind}(${parts.map((p) => p.fp).join(',')})`,
577
+ cls: combineClasses(parts.map((p) => p.cls)),
578
+ };
579
+ }
580
+ case 'case':
581
+ return walkCase(state, node, inArithmetic, depth);
582
+ case 'now': {
583
+ if (inArithmetic) {
584
+ throw new errors_js_1.ValidationError('expr.now() cannot be an arithmetic operand: date arithmetic is not supported.');
585
+ }
586
+ const rendered = renderNow(state);
587
+ return { sql: rendered, fp: `now:${rendered}`, cls: 'other' };
588
+ }
589
+ case 'excluded': {
590
+ const field = node.field;
591
+ if (!env.excluded) {
592
+ throw new errors_js_1.ValidationError(`expr.excluded(${JSON.stringify(field)}) names the incoming row of an upsert (upsert's update, or ` +
593
+ `createMany's onConflict.update); it has no meaning in ${env.operation} data.`);
594
+ }
595
+ // Depth 0 is the whole assigned value: a bare copy, which assertExprTarget
596
+ // has already allowed on a same-typed json or array column.
597
+ const column = depth === 0 && isBareStructuredCopy(qi, assertExprTarget(qi, env.targetKey, node, env.operation), node)
598
+ ? (0, utils_js_1.resolveColumnName)(qi.tableMeta, field)
599
+ : resolveOperandColumn(state, field, 'excluded', inArithmetic);
600
+ if (env.insertColumns && !env.insertColumns.has(column)) {
601
+ throw new errors_js_1.ValidationError(`expr.excluded(${JSON.stringify(field)}) on "${qi.table}" names a field the incoming rows do not supply. ` +
602
+ 'Add it to every row of data, or read the existing value with col() instead.');
603
+ }
604
+ return { sql: env.excluded(column), fp: `x:${column}`, cls: numClassOfType(columnType(qi, column)) };
605
+ }
606
+ default:
607
+ throw new errors_js_1.ValidationError(`Unknown expression kind "${String(node.kind)}".`);
608
+ }
609
+ }
610
+ function walkCase(state, node, inArithmetic, depth) {
611
+ const { env } = state;
612
+ const { qi } = env;
613
+ // The arm count is the caller's array length, written into the SQL text, so
614
+ // the statement is not a fixed shape (see BuilderCtx.markVariableArity).
615
+ qi.markVariableArity();
616
+ const sqlParts = [];
617
+ const fpParts = [];
618
+ const classes = [];
619
+ node.args.forEach((arm, i) => {
620
+ const when = caseWhen(state, arm.when, i);
621
+ const predicate = env.predicate(when);
622
+ const then = walkOperand(state, arm.then, inArithmetic, depth + 1);
623
+ sqlParts.push(`WHEN ${predicate} THEN ${then.sql}`);
624
+ classes.push(then.cls);
625
+ fpParts.push(`w:${(0, where_js_1.fingerprintWhere)(qi, when)}=>${then.fp}`);
626
+ });
627
+ const hasElse = node.else !== undefined;
628
+ const otherwise = hasElse ? walkOperand(state, node.else, inArithmetic, depth + 1) : undefined;
629
+ if (otherwise)
630
+ classes.push(otherwise.cls);
631
+ return {
632
+ sql: `CASE ${sqlParts.join(' ')}${otherwise ? ` ELSE ${otherwise.sql}` : ''} END`,
633
+ fp: `case(${fpParts.join(';')}|e:${otherwise ? otherwise.fp : '-'})`,
634
+ cls: combineClasses(classes),
635
+ };
636
+ }
637
+ /**
638
+ * Prune and validate one arm's `when`: `undefined` keys drop out (the C1 rule
639
+ * for every `where` position), an arm left with no predicate is refused, and
640
+ * relation filters are refused. The columns it reads are recorded.
641
+ */
642
+ function caseWhen(state, when, index) {
643
+ const { qi } = state.env;
644
+ const pruned = (0, normalize_args_js_1.pruneUndefinedArgs)(qi.schema, qi.table, { where: when }).where;
645
+ if (!(0, filters_js_1.isPlainRecord)(pruned) || Object.keys(pruned).length === 0) {
646
+ throw new errors_js_1.ValidationError(`expr.case(): arm ${index + 1}'s when on "${qi.table}" has no predicate${pruned === when ? '' : ' (every key was undefined)'}. Every arm needs a condition; use the second argument of expr.case() for the fallback value.`);
647
+ }
648
+ collectPredicateReads(state, pruned, 0);
649
+ return pruned;
650
+ }
651
+ function collectPredicateReads(state, where, depth) {
652
+ const { qi } = state.env;
653
+ if (depth > MAX_EXPR_DEPTH) {
654
+ throw new errors_js_1.ValidationError(`An expr.case() predicate is nested more than ${MAX_EXPR_DEPTH} levels deep.`);
655
+ }
656
+ for (const event of (0, where_compile_js_1.walkWhere)(qi.whereHost, where)) {
657
+ switch (event.kind) {
658
+ case 'or':
659
+ case 'and':
660
+ for (const c of event.conditions)
661
+ collectPredicateReads(state, c, depth + 1);
662
+ break;
663
+ case 'not':
664
+ collectPredicateReads(state, event.condition, depth + 1);
665
+ break;
666
+ case 'relation':
667
+ throw new errors_js_1.ValidationError(`expr.case() on "${qi.table}": "${event.key}" is a relation filter. A case arm's when takes predicates ` +
668
+ "on the row's own columns only.");
669
+ case 'scalar': {
670
+ const column = (0, utils_js_1.resolveColumnName)(qi.tableMeta, event.key);
671
+ if (column !== undefined)
672
+ state.reads.add(column);
673
+ collectRefReads(state, event.value, 0);
674
+ break;
675
+ }
676
+ }
677
+ }
678
+ }
679
+ /** Record the columns `col()` references inside an operator object read. */
680
+ function collectRefReads(state, value, depth) {
681
+ if (depth > 4)
682
+ return;
683
+ if ((0, column_ref_js_1.isColumnRef)(value)) {
684
+ const column = (0, utils_js_1.resolveColumnName)(state.env.qi.tableMeta, value.col);
685
+ if (column !== undefined)
686
+ state.reads.add(column);
687
+ return;
688
+ }
689
+ if ((0, filters_js_1.isPlainRecord)(value))
690
+ for (const v of Object.values(value))
691
+ collectRefReads(state, v, depth + 1);
692
+ }
693
+ // ---------------------------------------------------------------------------
694
+ // Per-engine rendering
695
+ // ---------------------------------------------------------------------------
696
+ /**
697
+ * GREATEST / LEAST that ignore NULLs on every engine. PostgreSQL's ignore NULL
698
+ * arguments natively. MySQL's and SQLite's return NULL when ANY argument is
699
+ * NULL, so each position i becomes COALESCE(arg_i, <the others>): when any
700
+ * argument is non-NULL every position is non-NULL and each non-NULL argument
701
+ * still appears as itself in its own position, so the extreme is the extreme of
702
+ * the non-NULL arguments; when all are NULL the result is NULL. SQL Server
703
+ * before 2022 has no GREATEST, and MAX over a VALUES list ignores NULLs.
704
+ *
705
+ * Each argument was compiled once, so a bound value repeated across positions
706
+ * reuses ONE placeholder (every engine here binds by number or by name).
707
+ */
708
+ function renderExtreme(qi, kind, args) {
709
+ switch (qi.dialect.name) {
710
+ case 'postgresql':
711
+ return `${kind === 'greatest' ? 'GREATEST' : 'LEAST'}(${args.join(', ')})`;
712
+ case 'mssql':
713
+ return (`(SELECT ${kind === 'greatest' ? 'MAX' : 'MIN'}(turbine_v) FROM ` +
714
+ `(VALUES ${args.map((a) => `(${a})`).join(', ')}) AS turbine_ext(turbine_v))`);
715
+ case 'mysql':
716
+ case 'sqlite': {
717
+ const fn = qi.dialect.name === 'sqlite' ? (kind === 'greatest' ? 'max' : 'min') : kind.toUpperCase();
718
+ const positions = args.map((a, i) => `COALESCE(${[a, ...args.filter((_, j) => j !== i)].join(', ')})`);
719
+ return `${fn}(${positions.join(', ')})`;
720
+ }
721
+ default:
722
+ throw unspelledExpression(qi, `expr.${kind}()`);
723
+ }
724
+ }
725
+ /**
726
+ * The server's current time for the target column, in the convention the read
727
+ * path uses. Zone-less `timestamp` / `date` columns take the UTC wall clock
728
+ * (PostgreSQL with `utcTimestamps: false` takes the session's local time, which
729
+ * is what that setting reads back), `timestamptz` takes the instant, and on
730
+ * SQLite, where Turbine binds a Date as ISO-8601 UTC text, the same text (the
731
+ * `YYYY-MM-DD` UTC day on a date column).
732
+ */
733
+ function renderNow(state) {
734
+ const { qi, targetKey, operation } = state.env;
735
+ const kind = temporalKind(state.targetType);
736
+ if (kind === undefined || kind === 'time') {
737
+ throw new errors_js_1.ValidationError(`expr.now() in ${operation} data cannot target "${qi.table}.${targetKey}"` +
738
+ (state.targetType === undefined ? ': the column has no known type. ' : ` (${state.targetType}): `) +
739
+ 'it fits timestamp, timestamptz, datetime and date columns only.');
740
+ }
741
+ const utc = qi.utcTimestamps !== false;
742
+ switch (qi.dialect.name) {
743
+ case 'postgresql':
744
+ if (kind === 'tz')
745
+ return 'now()';
746
+ if (kind === 'date')
747
+ return utc ? "(now() AT TIME ZONE 'UTC')::date" : 'CURRENT_DATE';
748
+ return utc ? "(now() AT TIME ZONE 'UTC')" : 'LOCALTIMESTAMP';
749
+ case 'mysql':
750
+ return kind === 'date' ? 'UTC_DATE()' : 'UTC_TIMESTAMP(3)';
751
+ case 'sqlite':
752
+ // The text a bound Date takes on SQLite: the UTC day on a date column.
753
+ return kind === 'date' ? "strftime('%Y-%m-%d', 'now')" : "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
754
+ case 'mssql':
755
+ if (kind === 'tz')
756
+ return 'SYSDATETIMEOFFSET()';
757
+ if (kind === 'date')
758
+ return 'CAST(SYSUTCDATETIME() AS date)';
759
+ return 'SYSUTCDATETIME()';
760
+ default:
761
+ throw unspelledExpression(qi, 'expr.now()');
762
+ }
763
+ }
764
+ /**
765
+ * GREATEST / LEAST and now() are still spelled per engine NAME (until they
766
+ * become dialect hooks), so a dialect under any other name is refused rather
767
+ * than handed one engine's spelling as a fallback. That includes a dialect
768
+ * spread from a shipped engine dialect under a new name: its capabilities say
769
+ * it takes update expressions, but nothing here knows how it spells these
770
+ * three. Every other expression is rendered from capabilities.
771
+ */
772
+ function unspelledExpression(qi, what) {
773
+ return new errors_js_1.UnsupportedFeatureError(`${what} in update data`, qi.dialect.name, `${what} is spelled per engine, and only postgresql, mysql, sqlite and mssql have a spelling. ` +
774
+ 'Compute the value in JavaScript and write it as a plain value.');
775
+ }
776
+ // ---------------------------------------------------------------------------
777
+ // Single-row upsert: `excluded` is `create`'s value
778
+ // ---------------------------------------------------------------------------
779
+ /**
780
+ * Rewrite every `expr.excluded(f)` in a single-row upsert's `update` to the
781
+ * value `create` supplies for `f`. The incoming row of a one-row upsert IS
782
+ * `create`, so the statement path, the lookup-first path (a plain update) and
783
+ * a nested upsert store the same row. A top-level reference becomes
784
+ * `{ set: value }`, which writes the value exactly as a plain value would be
785
+ * written, on every column type (json and array columns take `set` too). A
786
+ * reference inside an expression becomes a value operand, checked as the
787
+ * node's constructor checks one; the expression is then compiled by the
788
+ * update, with the column-type rules every expression gets. Needs only the
789
+ * table's name and metadata, so the nested-write engine shares it. Returns
790
+ * `update` by reference when it holds no reference.
791
+ */
792
+ function resolveExcludedFromCreate(target, update, create) {
793
+ if (!update || !Object.values(update).some(holdsExcluded))
794
+ return update;
795
+ const createByColumn = new Map();
796
+ for (const [key, value] of Object.entries(create ?? {})) {
797
+ if (value === undefined)
798
+ continue;
799
+ const column = (0, utils_js_1.resolveColumnName)(target.tableMeta, key);
800
+ if (column !== undefined)
801
+ createByColumn.set(column, value);
802
+ }
803
+ const incomingValue = (ref) => {
804
+ const column = (0, utils_js_1.resolveColumnName)(target.tableMeta, ref.field);
805
+ if (column === undefined || !createByColumn.has(column)) {
806
+ throw new errors_js_1.ValidationError(`expr.excluded(${JSON.stringify(ref.field)}) in upsert's update on "${target.table}" names a field ` +
807
+ '`create` does not supply. It reads the incoming row, which is `create`.');
808
+ }
809
+ return createByColumn.get(column);
810
+ };
811
+ const out = {};
812
+ for (const [key, value] of Object.entries(update)) {
813
+ if (!holdsExcluded(value)) {
814
+ out[key] = value;
815
+ continue;
816
+ }
817
+ out[key] = isExcludedRef(value)
818
+ ? { set: incomingValue(value) }
819
+ : replaceExcluded(value, incomingValue, 0);
820
+ }
821
+ return out;
822
+ }
823
+ function holdsExcluded(value, depth = 0) {
824
+ if (depth > MAX_EXPR_DEPTH || !isSqlExpr(value))
825
+ return false;
826
+ if (isExcludedRef(value))
827
+ return true;
828
+ if (value.kind === 'case') {
829
+ const node = value;
830
+ return node.args.some((arm) => holdsExcluded(arm.then, depth + 1)) || holdsExcluded(node.else, depth + 1);
831
+ }
832
+ return value.args.some((arg) => holdsExcluded(arg, depth + 1));
833
+ }
834
+ function replaceExcluded(node, incomingValue, depth) {
835
+ if (depth > MAX_EXPR_DEPTH) {
836
+ throw new errors_js_1.ValidationError(`An update expression is nested more than ${MAX_EXPR_DEPTH} levels deep.`);
837
+ }
838
+ // The value `create` supplies takes the reference's place, so it must pass
839
+ // the check the node's constructor applies to an operand in that position.
840
+ const arithmeticNode = node.kind === 'add' || node.kind === 'sub' || node.kind === 'mul' || node.kind === 'div';
841
+ const swap = (operand, position) => {
842
+ if (isExcludedRef(operand)) {
843
+ const value = incomingValue(operand);
844
+ if (arithmeticNode)
845
+ assertNumericOperand(node.kind, position, value);
846
+ else
847
+ assertOperand(node.kind, position, value);
848
+ return value;
849
+ }
850
+ return isSqlExpr(operand) ? replaceExcluded(operand, incomingValue, depth + 1) : operand;
851
+ };
852
+ if (node.kind === 'case') {
853
+ const c = node;
854
+ // biome-ignore lint/suspicious/noThenProperty: SQL's CASE WHEN ... THEN; a value operand, never a function
855
+ const arms = c.args.map((arm, i) => Object.freeze({ when: arm.when, then: swap(arm.then, i) }));
856
+ return makeNode('case', arms, c.else === undefined ? undefined : { else: swap(c.else, arms.length) });
857
+ }
858
+ return makeNode(node.kind, node.args.map(swap));
859
+ }