litedbmodel 2.0.0 → 2.0.2

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 (163) hide show
  1. package/dist/scp/authoring-sql.d.ts +78 -0
  2. package/dist/scp/authoring-sql.d.ts.map +1 -1
  3. package/dist/scp/authoring-sql.js +110 -2
  4. package/dist/scp/authoring-sql.js.map +1 -1
  5. package/dist/scp/catalog.d.ts +5 -2
  6. package/dist/scp/catalog.d.ts.map +1 -1
  7. package/dist/scp/catalog.js +32 -1
  8. package/dist/scp/catalog.js.map +1 -1
  9. package/dist/scp/codegen.d.ts +143 -85
  10. package/dist/scp/codegen.d.ts.map +1 -1
  11. package/dist/scp/codegen.js +374 -90
  12. package/dist/scp/codegen.js.map +1 -1
  13. package/dist/scp/coltype.d.ts +41 -0
  14. package/dist/scp/coltype.d.ts.map +1 -0
  15. package/dist/scp/coltype.js +188 -0
  16. package/dist/scp/coltype.js.map +1 -0
  17. package/dist/scp/dialect.d.ts +12 -3
  18. package/dist/scp/dialect.d.ts.map +1 -1
  19. package/dist/scp/dialect.js +12 -3
  20. package/dist/scp/dialect.js.map +1 -1
  21. package/dist/scp/find-filter-guard.d.ts +68 -0
  22. package/dist/scp/find-filter-guard.d.ts.map +1 -0
  23. package/dist/scp/find-filter-guard.js +88 -0
  24. package/dist/scp/find-filter-guard.js.map +1 -0
  25. package/dist/scp/fragment/assemble.d.ts +61 -0
  26. package/dist/scp/fragment/assemble.d.ts.map +1 -0
  27. package/dist/scp/fragment/assemble.js +156 -0
  28. package/dist/scp/fragment/assemble.js.map +1 -0
  29. package/dist/scp/fragment/compile-pg.d.ts +107 -0
  30. package/dist/scp/fragment/compile-pg.d.ts.map +1 -0
  31. package/dist/scp/fragment/compile-pg.js +142 -0
  32. package/dist/scp/fragment/compile-pg.js.map +1 -0
  33. package/dist/scp/fragment/index.d.ts +16 -0
  34. package/dist/scp/fragment/index.d.ts.map +1 -0
  35. package/dist/scp/fragment/index.js +34 -0
  36. package/dist/scp/fragment/index.js.map +1 -0
  37. package/dist/scp/fragment/model.d.ts +119 -0
  38. package/dist/scp/fragment/model.d.ts.map +1 -0
  39. package/dist/scp/fragment/model.js +66 -0
  40. package/dist/scp/fragment/model.js.map +1 -0
  41. package/dist/scp/fragment/pg-array-type.d.ts +13 -0
  42. package/dist/scp/fragment/pg-array-type.d.ts.map +1 -0
  43. package/dist/scp/fragment/pg-array-type.js +34 -0
  44. package/dist/scp/fragment/pg-array-type.js.map +1 -0
  45. package/dist/scp/guard.d.ts +2 -6
  46. package/dist/scp/guard.d.ts.map +1 -1
  47. package/dist/scp/guard.js +19 -22
  48. package/dist/scp/guard.js.map +1 -1
  49. package/dist/scp/index.cjs +18695 -3921
  50. package/dist/scp/index.d.ts +41 -24
  51. package/dist/scp/index.d.ts.map +1 -1
  52. package/dist/scp/index.js +134 -49
  53. package/dist/scp/index.js.map +1 -1
  54. package/dist/scp/index.mjs +4830 -1432
  55. package/dist/scp/makesql/authoring-compile.d.ts +90 -0
  56. package/dist/scp/makesql/authoring-compile.d.ts.map +1 -0
  57. package/dist/scp/makesql/authoring-compile.js +494 -0
  58. package/dist/scp/makesql/authoring-compile.js.map +1 -0
  59. package/dist/scp/makesql/compile-crud.d.ts +133 -0
  60. package/dist/scp/makesql/compile-crud.d.ts.map +1 -0
  61. package/dist/scp/makesql/compile-crud.js +288 -0
  62. package/dist/scp/makesql/compile-crud.js.map +1 -0
  63. package/dist/scp/makesql/compile-relation.d.ts +157 -0
  64. package/dist/scp/makesql/compile-relation.d.ts.map +1 -0
  65. package/dist/scp/makesql/compile-relation.js +425 -0
  66. package/dist/scp/makesql/compile-relation.js.map +1 -0
  67. package/dist/scp/makesql/compile-select.d.ts +52 -0
  68. package/dist/scp/makesql/compile-select.d.ts.map +1 -0
  69. package/dist/scp/makesql/compile-select.js +58 -0
  70. package/dist/scp/makesql/compile-select.js.map +1 -0
  71. package/dist/scp/makesql/compile.d.ts +63 -0
  72. package/dist/scp/makesql/compile.d.ts.map +1 -0
  73. package/dist/scp/makesql/compile.js +99 -0
  74. package/dist/scp/makesql/compile.js.map +1 -0
  75. package/dist/scp/makesql/handler.d.ts +58 -0
  76. package/dist/scp/makesql/handler.d.ts.map +1 -0
  77. package/dist/scp/makesql/handler.js +113 -0
  78. package/dist/scp/makesql/handler.js.map +1 -0
  79. package/dist/scp/makesql/index.d.ts +33 -0
  80. package/dist/scp/makesql/index.d.ts.map +1 -0
  81. package/dist/scp/makesql/index.js +114 -0
  82. package/dist/scp/makesql/index.js.map +1 -0
  83. package/dist/scp/makesql/ir.d.ts +42 -0
  84. package/dist/scp/makesql/ir.d.ts.map +1 -0
  85. package/dist/scp/makesql/ir.js +66 -0
  86. package/dist/scp/makesql/ir.js.map +1 -0
  87. package/dist/scp/makesql/json-array.d.ts +84 -0
  88. package/dist/scp/makesql/json-array.d.ts.map +1 -0
  89. package/dist/scp/makesql/json-array.js +118 -0
  90. package/dist/scp/makesql/json-array.js.map +1 -0
  91. package/dist/scp/makesql/json-batch.d.ts +102 -0
  92. package/dist/scp/makesql/json-batch.d.ts.map +1 -0
  93. package/dist/scp/makesql/json-batch.js +269 -0
  94. package/dist/scp/makesql/json-batch.js.map +1 -0
  95. package/dist/scp/makesql/makesql.d.ts +99 -0
  96. package/dist/scp/makesql/makesql.d.ts.map +1 -0
  97. package/dist/scp/makesql/makesql.js +122 -0
  98. package/dist/scp/makesql/makesql.js.map +1 -0
  99. package/dist/scp/makesql/outtype.d.ts +44 -0
  100. package/dist/scp/makesql/outtype.d.ts.map +1 -0
  101. package/dist/scp/makesql/outtype.js +197 -0
  102. package/dist/scp/makesql/outtype.js.map +1 -0
  103. package/dist/scp/makesql/pool-executor.d.ts +47 -0
  104. package/dist/scp/makesql/pool-executor.d.ts.map +1 -0
  105. package/dist/scp/makesql/pool-executor.js +50 -0
  106. package/dist/scp/makesql/pool-executor.js.map +1 -0
  107. package/dist/scp/makesql/static-bundle.d.ts +187 -0
  108. package/dist/scp/makesql/static-bundle.d.ts.map +1 -0
  109. package/dist/scp/makesql/static-bundle.js +1113 -0
  110. package/dist/scp/makesql/static-bundle.js.map +1 -0
  111. package/dist/scp/makesql/tx.d.ts +216 -0
  112. package/dist/scp/makesql/tx.d.ts.map +1 -0
  113. package/dist/scp/makesql/tx.js +788 -0
  114. package/dist/scp/makesql/tx.js.map +1 -0
  115. package/dist/scp/makesql/writeouttype.d.ts +66 -0
  116. package/dist/scp/makesql/writeouttype.d.ts.map +1 -0
  117. package/dist/scp/makesql/writeouttype.js +195 -0
  118. package/dist/scp/makesql/writeouttype.js.map +1 -0
  119. package/dist/scp/relation.d.ts +86 -74
  120. package/dist/scp/relation.d.ts.map +1 -1
  121. package/dist/scp/relation.js +186 -120
  122. package/dist/scp/relation.js.map +1 -1
  123. package/dist/scp/runtime.d.ts +158 -211
  124. package/dist/scp/runtime.d.ts.map +1 -1
  125. package/dist/scp/runtime.js +250 -515
  126. package/dist/scp/runtime.js.map +1 -1
  127. package/dist/scp/typed-object.d.ts +12 -1
  128. package/dist/scp/typed-object.d.ts.map +1 -1
  129. package/dist/scp/typed-object.js +25 -4
  130. package/dist/scp/typed-object.js.map +1 -1
  131. package/docs/proposal/litedbmodel-v2-scp-architecture.md +51 -0
  132. package/docs/proposal/v0-coverage-matrix.md +157 -0
  133. package/docs/proposal/v2-completeness-plan.md +90 -0
  134. package/docs/proposal/v2-sql-parity-checklist.md +162 -0
  135. package/package.json +3 -3
  136. package/dist/scp/bridge.d.ts +0 -64
  137. package/dist/scp/bridge.d.ts.map +0 -1
  138. package/dist/scp/bridge.js +0 -329
  139. package/dist/scp/bridge.js.map +0 -1
  140. package/dist/scp/compile-dialect.d.ts +0 -39
  141. package/dist/scp/compile-dialect.d.ts.map +0 -1
  142. package/dist/scp/compile-dialect.js +0 -62
  143. package/dist/scp/compile-dialect.js.map +0 -1
  144. package/dist/scp/compile-sqlite.d.ts +0 -114
  145. package/dist/scp/compile-sqlite.d.ts.map +0 -1
  146. package/dist/scp/compile-sqlite.js +0 -149
  147. package/dist/scp/compile-sqlite.js.map +0 -1
  148. package/dist/scp/ir.d.ts +0 -92
  149. package/dist/scp/ir.d.ts.map +0 -1
  150. package/dist/scp/ir.js +0 -28
  151. package/dist/scp/ir.js.map +0 -1
  152. package/dist/scp/render.d.ts +0 -40
  153. package/dist/scp/render.d.ts.map +0 -1
  154. package/dist/scp/render.js +0 -170
  155. package/dist/scp/render.js.map +0 -1
  156. package/dist/scp/write-plan.d.ts +0 -161
  157. package/dist/scp/write-plan.d.ts.map +0 -1
  158. package/dist/scp/write-plan.js +0 -528
  159. package/dist/scp/write-plan.js.map +0 -1
  160. package/dist/scp/write-runtime.d.ts +0 -80
  161. package/dist/scp/write-runtime.d.ts.map +0 -1
  162. package/dist/scp/write-runtime.js +0 -167
  163. package/dist/scp/write-runtime.js.map +0 -1
@@ -0,0 +1,84 @@
1
+ /**
2
+ * litedbmodel v2 SCP — the single-JSON-param array forms for MySQL 8 + SQLite
3
+ * (epic #43 / #45).
4
+ *
5
+ * ## Why this exists — the intentional deviation from v1 text
6
+ *
7
+ * v1's MySQL/SQLite builders expand an array into N placeholders — `col IN (?, ?, …)`
8
+ * for an IN-list, `VALUES (?,?),(?,?),…` for a batch INSERT, `VALUES ROW(…)` /
9
+ * `CASE WHEN` for a batch UPDATE — binding ONE param per element. PostgreSQL already
10
+ * avoids that explosion (`= ANY(?::t[])` / `UNNEST(?::t[])` binds the whole array as a
11
+ * SINGLE param). This module gives MySQL/SQLite the same one-param property by moving
12
+ * the expansion SERVER-side: the array (or the batch rows) is encoded as ONE JSON
13
+ * string param and expanded inside the engine with a `JSON_TABLE` subquery
14
+ * (MySQL 8.0.4+) and `json_each` / `json_extract` (SQLite json1, always present via
15
+ * better-sqlite3). The IN-list uses a `col IN (SELECT … JSON_TABLE …)` SUBQUERY (NOT
16
+ * `MEMBER OF`) precisely so it inherits v1's `col IN (list)` type coercion — `MEMBER OF`
17
+ * does strict JSON-type comparison and DIVERGES from v1 on cross-type IN-lists.
18
+ *
19
+ * This is an owner-approved improvement OVER v1 — so the emitted SQL TEXT for
20
+ * MySQL/SQLite array/batch surfaces intentionally differs from v1. The correctness bar
21
+ * is RESULT PARITY (same rows / same post-write state on a real MySQL 8 + SQLite),
22
+ * proven by `test/scp/json-array-parity.test.ts`. **PostgreSQL is untouched** — it keeps
23
+ * `= ANY` / `UNNEST` / LATERAL and stays byte-identical to v1.
24
+ *
25
+ * Everything here is still just TEXT inside a `makeSQL` `sql` port plus ONE JSON param —
26
+ * no new IR kind, no new catalog leaf. The driver-side placeholder-count-expansion that
27
+ * v1 relied on for MySQL/SQLite arrays is GONE: every dialect now binds an array as one
28
+ * param with static SQL text.
29
+ */
30
+ import { DBConditions, type ConditionObject, type ConditionValue } from '../../DBConditions';
31
+ import type { SqlCastFormatter } from '../../DBValues';
32
+ import type { Dialect } from './handler';
33
+ /** Dialects that use the single-JSON-param array forms (everything except PostgreSQL). */
34
+ export type JsonArrayDialect = 'mysql' | 'sqlite';
35
+ /**
36
+ * The MySQL/SQLite IN-list JSON form for a plain `col IN [values]` condition — a
37
+ * SUBQUERY form so it inherits the SAME comparison rules as v1's `col IN (list)`
38
+ * (crucially, MySQL's/SQLite's implicit type coercion), guaranteeing RESULT PARITY.
39
+ *
40
+ * - MySQL: `col IN (SELECT JSON_UNQUOTE(v) FROM JSON_TABLE(?, '$[*]' COLUMNS(v JSON PATH
41
+ * '$')) jt)`. `col IN (subquery)` uses the SAME type-coercion rules as `col IN (list)`
42
+ * — so e.g. an int column compared against JSON string values `["1","10"]` matches v1
43
+ * exactly (whereas `MEMBER OF` does STRICT JSON-type comparison and DIVERGES — proven
44
+ * on real MySQL 8). The `v JSON PATH '$'` + `JSON_UNQUOTE` extraction preserves
45
+ * arbitrary-length text (avoiding CHAR's 255 truncation-to-NULL) and full bigint /
46
+ * decimal precision, while yielding a value MySQL then coerces to the column type just
47
+ * like a literal in the IN-list. NO column type is required (right, since the IN
48
+ * condition path `DBConditions` carries none).
49
+ * - SQLite: `col IN (SELECT value FROM json_each(?))` — `json_each` yields each element
50
+ * with its natural (dynamic) type and the IN-subquery inherits SQLite's affinity
51
+ * comparison, matching v1 (incl. cross-type int-col × string values).
52
+ *
53
+ * The single param is the JSON-encoded array string (`[1,2,3]`). Empty arrays are NOT
54
+ * routed here — the caller keeps v1's `1 = 0` for the empty case (no param, exact v1
55
+ * empty semantics). Cross-type / bigint / decimal parity vs v1 is proven on real
56
+ * MySQL 8 + SQLite in `test/scp/json-array-parity.test.ts`.
57
+ */
58
+ export declare function inListJson(dialect: JsonArrayDialect, col: string, values: unknown[]): {
59
+ sql: string;
60
+ param: string;
61
+ };
62
+ /**
63
+ * A dialect-aware `DBConditions` for MySQL/SQLite that replaces the v1 N-placeholder
64
+ * IN-list (`col IN (?, ?, …)`) with the single-JSON-param form (see {@link inListJson}).
65
+ * Every OTHER construct — eq/ne/cmp, custom-op, IS NULL, boolean literal, raw, subquery,
66
+ * EXISTS, cast, tuple-IN, nested AND/OR — is delegated UNCHANGED to the base
67
+ * `DBConditions`, so only the plain-array membership case deviates from v1.
68
+ *
69
+ * The empty-array case is left to the base class (`1 = 0`), matching v1 exactly.
70
+ */
71
+ export declare class JsonArrayConditions extends DBConditions {
72
+ private readonly _dialect;
73
+ constructor(conditions: ConditionObject, dialect: JsonArrayDialect, operator?: 'AND' | 'OR');
74
+ protected compileCondition(key: string, value: ConditionValue, params: unknown[], formatter?: SqlCastFormatter): string;
75
+ }
76
+ /**
77
+ * Build a `DBConditions` appropriate for the dialect: PostgreSQL gets the base class
78
+ * (v1 byte-identical IN-list), MySQL/SQLite get {@link JsonArrayConditions} (single-JSON
79
+ * IN-list). Nested `DBConditions` inside a condition object stay base-class (they are
80
+ * authored by callers via `new DBConditions`), so only TOP-LEVEL plain-array IN-lists on
81
+ * MySQL/SQLite take the JSON form — which is the surface v1 expanded.
82
+ */
83
+ export declare function conditionsFor(conditions: ConditionObject, dialect: Dialect): DBConditions;
84
+ //# sourceMappingURL=json-array.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-array.d.ts","sourceRoot":"","sources":["../../../src/scp/makesql/json-array.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAapH;AAED;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;gBAEhC,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,GAAE,KAAK,GAAG,IAAY;IAKlG,SAAS,CAAC,gBAAgB,CACxB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,OAAO,EAAE,EACjB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,MAAM;CAiBV;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,GAAG,YAAY,CAGzF"}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — the single-JSON-param array forms for MySQL 8 + SQLite
4
+ * (epic #43 / #45).
5
+ *
6
+ * ## Why this exists — the intentional deviation from v1 text
7
+ *
8
+ * v1's MySQL/SQLite builders expand an array into N placeholders — `col IN (?, ?, …)`
9
+ * for an IN-list, `VALUES (?,?),(?,?),…` for a batch INSERT, `VALUES ROW(…)` /
10
+ * `CASE WHEN` for a batch UPDATE — binding ONE param per element. PostgreSQL already
11
+ * avoids that explosion (`= ANY(?::t[])` / `UNNEST(?::t[])` binds the whole array as a
12
+ * SINGLE param). This module gives MySQL/SQLite the same one-param property by moving
13
+ * the expansion SERVER-side: the array (or the batch rows) is encoded as ONE JSON
14
+ * string param and expanded inside the engine with a `JSON_TABLE` subquery
15
+ * (MySQL 8.0.4+) and `json_each` / `json_extract` (SQLite json1, always present via
16
+ * better-sqlite3). The IN-list uses a `col IN (SELECT … JSON_TABLE …)` SUBQUERY (NOT
17
+ * `MEMBER OF`) precisely so it inherits v1's `col IN (list)` type coercion — `MEMBER OF`
18
+ * does strict JSON-type comparison and DIVERGES from v1 on cross-type IN-lists.
19
+ *
20
+ * This is an owner-approved improvement OVER v1 — so the emitted SQL TEXT for
21
+ * MySQL/SQLite array/batch surfaces intentionally differs from v1. The correctness bar
22
+ * is RESULT PARITY (same rows / same post-write state on a real MySQL 8 + SQLite),
23
+ * proven by `test/scp/json-array-parity.test.ts`. **PostgreSQL is untouched** — it keeps
24
+ * `= ANY` / `UNNEST` / LATERAL and stays byte-identical to v1.
25
+ *
26
+ * Everything here is still just TEXT inside a `makeSQL` `sql` port plus ONE JSON param —
27
+ * no new IR kind, no new catalog leaf. The driver-side placeholder-count-expansion that
28
+ * v1 relied on for MySQL/SQLite arrays is GONE: every dialect now binds an array as one
29
+ * param with static SQL text.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.JsonArrayConditions = void 0;
33
+ exports.inListJson = inListJson;
34
+ exports.conditionsFor = conditionsFor;
35
+ const DBConditions_1 = require("../../DBConditions");
36
+ const DBValues_1 = require("../../DBValues");
37
+ /**
38
+ * The MySQL/SQLite IN-list JSON form for a plain `col IN [values]` condition — a
39
+ * SUBQUERY form so it inherits the SAME comparison rules as v1's `col IN (list)`
40
+ * (crucially, MySQL's/SQLite's implicit type coercion), guaranteeing RESULT PARITY.
41
+ *
42
+ * - MySQL: `col IN (SELECT JSON_UNQUOTE(v) FROM JSON_TABLE(?, '$[*]' COLUMNS(v JSON PATH
43
+ * '$')) jt)`. `col IN (subquery)` uses the SAME type-coercion rules as `col IN (list)`
44
+ * — so e.g. an int column compared against JSON string values `["1","10"]` matches v1
45
+ * exactly (whereas `MEMBER OF` does STRICT JSON-type comparison and DIVERGES — proven
46
+ * on real MySQL 8). The `v JSON PATH '$'` + `JSON_UNQUOTE` extraction preserves
47
+ * arbitrary-length text (avoiding CHAR's 255 truncation-to-NULL) and full bigint /
48
+ * decimal precision, while yielding a value MySQL then coerces to the column type just
49
+ * like a literal in the IN-list. NO column type is required (right, since the IN
50
+ * condition path `DBConditions` carries none).
51
+ * - SQLite: `col IN (SELECT value FROM json_each(?))` — `json_each` yields each element
52
+ * with its natural (dynamic) type and the IN-subquery inherits SQLite's affinity
53
+ * comparison, matching v1 (incl. cross-type int-col × string values).
54
+ *
55
+ * The single param is the JSON-encoded array string (`[1,2,3]`). Empty arrays are NOT
56
+ * routed here — the caller keeps v1's `1 = 0` for the empty case (no param, exact v1
57
+ * empty semantics). Cross-type / bigint / decimal parity vs v1 is proven on real
58
+ * MySQL 8 + SQLite in `test/scp/json-array-parity.test.ts`.
59
+ */
60
+ function inListJson(dialect, col, values) {
61
+ if (dialect === 'mysql') {
62
+ return {
63
+ sql: `${col} IN (SELECT JSON_UNQUOTE(v) FROM JSON_TABLE(?, '$[*]' COLUMNS(v JSON PATH '$')) jt)`,
64
+ // A BOOLEAN element serializes to `1`/`0` in the MySQL JSON param (NOT JSON `true`/`false`):
65
+ // `JSON_UNQUOTE(v)` on JSON `true` yields the STRING `'true'`, which MySQL coerces to `0`
66
+ // against a TINYINT(1) — silently mismatching. `1`/`0` is exactly what v1's `col IN (?)`
67
+ // bound (the mysql2 driver sends a JS bool as `1`/`0`), so this keeps v1 RESULT parity. SQL
68
+ // text + PG are untouched; SQLite's `json_each` coerces JSON booleans natively (no change).
69
+ param: JSON.stringify(values.map((v) => (typeof v === 'boolean' ? (v ? 1 : 0) : v))),
70
+ };
71
+ }
72
+ return { sql: `${col} IN (SELECT value FROM json_each(?))`, param: JSON.stringify(values) };
73
+ }
74
+ /**
75
+ * A dialect-aware `DBConditions` for MySQL/SQLite that replaces the v1 N-placeholder
76
+ * IN-list (`col IN (?, ?, …)`) with the single-JSON-param form (see {@link inListJson}).
77
+ * Every OTHER construct — eq/ne/cmp, custom-op, IS NULL, boolean literal, raw, subquery,
78
+ * EXISTS, cast, tuple-IN, nested AND/OR — is delegated UNCHANGED to the base
79
+ * `DBConditions`, so only the plain-array membership case deviates from v1.
80
+ *
81
+ * The empty-array case is left to the base class (`1 = 0`), matching v1 exactly.
82
+ */
83
+ class JsonArrayConditions extends DBConditions_1.DBConditions {
84
+ _dialect;
85
+ constructor(conditions, dialect, operator = 'AND') {
86
+ super(conditions, operator);
87
+ this._dialect = dialect;
88
+ }
89
+ compileCondition(key, value, params, formatter) {
90
+ // Intercept ONLY the plain-array IN-list case (non-empty, no custom-op `?` in key,
91
+ // not a DBToken). Everything else — including empty array (`1 = 0`), custom-op
92
+ // arrays, tuple-IN, raw — falls through to the base class untouched.
93
+ if (Array.isArray(value) &&
94
+ value.length > 0 &&
95
+ !key.includes('?') &&
96
+ !key.startsWith('__') &&
97
+ !(value instanceof DBValues_1.DBToken)) {
98
+ const { sql, param } = inListJson(this._dialect, key, value);
99
+ params.push(param);
100
+ return sql;
101
+ }
102
+ return super.compileCondition(key, value, params, formatter);
103
+ }
104
+ }
105
+ exports.JsonArrayConditions = JsonArrayConditions;
106
+ /**
107
+ * Build a `DBConditions` appropriate for the dialect: PostgreSQL gets the base class
108
+ * (v1 byte-identical IN-list), MySQL/SQLite get {@link JsonArrayConditions} (single-JSON
109
+ * IN-list). Nested `DBConditions` inside a condition object stay base-class (they are
110
+ * authored by callers via `new DBConditions`), so only TOP-LEVEL plain-array IN-lists on
111
+ * MySQL/SQLite take the JSON form — which is the surface v1 expanded.
112
+ */
113
+ function conditionsFor(conditions, dialect) {
114
+ if (dialect === 'postgres')
115
+ return new DBConditions_1.DBConditions(conditions);
116
+ return new JsonArrayConditions(conditions, dialect);
117
+ }
118
+ //# sourceMappingURL=json-array.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-array.js","sourceRoot":"","sources":["../../../src/scp/makesql/json-array.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAiCH,gCAaC;AAkDD,sCAGC;AAjGD,qDAA6F;AAC7F,6CAAyC;AAOzC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAgB,UAAU,CAAC,OAAyB,EAAE,GAAW,EAAE,MAAiB;IAClF,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,OAAO;YACL,GAAG,EAAE,GAAG,GAAG,qFAAqF;YAChG,6FAA6F;YAC7F,0FAA0F;YAC1F,yFAAyF;YACzF,4FAA4F;YAC5F,4FAA4F;YAC5F,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACrF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,sCAAsC,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAa,mBAAoB,SAAQ,2BAAY;IAClC,QAAQ,CAAmB;IAE5C,YAAY,UAA2B,EAAE,OAAyB,EAAE,WAAyB,KAAK;QAChG,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAES,gBAAgB,CACxB,GAAW,EACX,KAAqB,EACrB,MAAiB,EACjB,SAA4B;QAE5B,mFAAmF;QACnF,+EAA+E;QAC/E,qEAAqE;QACrE,IACE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACpB,KAAK,CAAC,MAAM,GAAG,CAAC;YAChB,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;YAClB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;YACrB,CAAC,CAAC,KAAK,YAAY,kBAAO,CAAC,EAC3B,CAAC;YACD,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC7D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,KAAK,CAAC,gBAAgB,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IAC/D,CAAC;CACF;AA9BD,kDA8BC;AAED;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,UAA2B,EAAE,OAAgB;IACzE,IAAI,OAAO,KAAK,UAAU;QAAE,OAAO,IAAI,2BAAY,CAAC,UAAU,CAAC,CAAC;IAChE,OAAO,IAAI,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * litedbmodel v2 SCP — the single-JSON-param BATCH forms (createMany INSERT,
3
+ * updateMany UPDATE) for MySQL 8 + SQLite (epic #43 / #45).
4
+ *
5
+ * v1 emits a multi-VALUES INSERT / `VALUES ROW(…)`-join (MySQL) / `CASE WHEN` (SQLite)
6
+ * UPDATE, binding one param per cell. These forms instead encode the whole group's rows
7
+ * as ONE JSON array-of-objects param and expand it SERVER-side:
8
+ *
9
+ * MySQL INSERT `INSERT INTO t (cols) SELECT <exprs> FROM JSON_TABLE(?, '$[*]'
10
+ * COLUMNS(c <T> PATH '$.c', …)) jt`
11
+ * MySQL UPDATE `UPDATE t AS u JOIN JSON_TABLE(?, '$[*]' COLUMNS(…)) AS v(…)
12
+ * ON u.k = v.k SET u.c = IF(v._skip_c, u.c, v.c), …`
13
+ * SQLite INSERT `INSERT INTO t (cols) SELECT json_extract(value,'$.c'), … FROM json_each(?)`
14
+ * SQLite UPDATE `UPDATE t SET c = CASE <WHEN key THEN …> END … WHERE k IN
15
+ * (SELECT json_extract(value,'$.k') FROM json_each(?))` — one json param.
16
+ *
17
+ * ONE JSON param per group; the SQL text is STATIC (no per-row placeholder explosion).
18
+ * Correctness bar is RESULT PARITY vs v1, proven on real MySQL 8 + SQLite. PostgreSQL is
19
+ * untouched (UNNEST). A DBToken value (e.g. `NOW()`) cannot be JSON-encoded, so a group
20
+ * carrying one falls back to v1's builder for that group (rare; keeps correctness).
21
+ */
22
+ import type { MakeSQL } from './makesql';
23
+ /** MySQL/SQLite batch dialects. */
24
+ export type JsonArrayDialect = 'mysql' | 'sqlite';
25
+ /** True if any row carries a DBToken value (can't be JSON-encoded → v1 fallback). */
26
+ export declare function rowsHaveDbToken(rows: Record<string, unknown>[], columns: string[]): boolean;
27
+ /**
28
+ * Infer the MySQL `JSON_TABLE` COLUMNS type + SELECT expression for a column, from the
29
+ * first non-null serialized value in the group. Chosen so the extracted value coerces
30
+ * to the target column identically to v1's bound `?`:
31
+ *
32
+ * - boolean / integer / bigint → `BIGINT` (JSON true/false → 1/0; ints exact),
33
+ * - non-integer number → `DECIMAL(65,30)` (lossless),
34
+ * - object / array → `JSON` kept as JSON (json columns),
35
+ * - string / null / other → `JSON`, then `JSON_UNQUOTE(...)` in the SELECT so
36
+ * arbitrary-length text (beyond CHAR's 255 cap) and JSON-null → SQL NULL work.
37
+ *
38
+ * Returns `{ colType, selectExpr }` where `selectExpr` is applied to the JSON_TABLE
39
+ * output column reference (e.g. `jt.name`).
40
+ */
41
+ export declare function mysqlJsonTableColumn(col: string, rows: Record<string, unknown>[]): {
42
+ colType: string;
43
+ selectExpr: (ref: string) => string;
44
+ };
45
+ export interface JsonInsertOptions {
46
+ tableName: string;
47
+ columns: string[];
48
+ records: Record<string, unknown>[];
49
+ onConflict?: string[];
50
+ onConflictIgnore?: boolean;
51
+ onConflictUpdate?: 'all' | string[];
52
+ returning?: string;
53
+ }
54
+ /**
55
+ * Build the MySQL single-JSON-param batch INSERT. `INSERT [IGNORE] INTO t (cols)
56
+ * SELECT <typed exprs> FROM JSON_TABLE(?, '$[*]' COLUMNS(…)) jt [ON DUPLICATE KEY …]`.
57
+ */
58
+ export declare function mysqlInsertJson(opts: JsonInsertOptions): MakeSQL;
59
+ /**
60
+ * Build the SQLite single-JSON-param batch INSERT. `INSERT [OR IGNORE] INTO t (cols)
61
+ * SELECT json_extract(value,'$.c'), … FROM json_each(?) [ON CONFLICT … DO …]`.
62
+ * `json_extract` returns each cell's natural type; SQLite affinity coerces on insert.
63
+ */
64
+ export declare function sqliteInsertJson(opts: JsonInsertOptions): MakeSQL;
65
+ export interface JsonUpdateManyOptions {
66
+ tableName: string;
67
+ keyColumns: string[];
68
+ updateColumns: string[];
69
+ records: Record<string, unknown>[];
70
+ /** record-index → set of SKIP column names (leave DB value unchanged for those). */
71
+ skipMap?: Map<number, Set<string>>;
72
+ returning?: string;
73
+ }
74
+ /**
75
+ * Build the MySQL single-JSON-param batch UPDATE: JSON_TABLE join with the SKIP-column
76
+ * `IF(v._skip_c, u.c, v.c)` logic preserved. Each row's `_skip_c` flag is carried inside
77
+ * the ONE JSON param (as `0`/`1`), so the SQL text stays static.
78
+ *
79
+ * UPDATE t AS u JOIN JSON_TABLE(?, '$[*]' COLUMNS(k <T> PATH '$.k', c <T> PATH '$.c',
80
+ * _skip_c BIGINT PATH '$._skip_c', …)) AS v ON u.k = v.k
81
+ * SET u.c = IF(v._skip_c, u.c, v.c), …
82
+ */
83
+ export declare function mysqlUpdateManyJson(opts: JsonUpdateManyOptions): MakeSQL;
84
+ /**
85
+ * Build the SQLite batch UPDATE over `json_each(?)`, SKIP preserved. A single JSON
86
+ * array-of-objects (keys + update values + `_skip_c` flags) carries every row.
87
+ *
88
+ * NOTE on params: the SAME one JSON string is bound to EACH `?` — one per update column's
89
+ * correlated sub-SELECT plus one for the WHERE — so `params` has `updateColumns.length + 1`
90
+ * entries, ALL equal to that single JSON value. The row COUNT never multiplies the param
91
+ * count (the N-placeholder explosion is what this replaces); the repetition is only across
92
+ * the (fixed) number of SET columns, and the SQL text stays static regardless of row count.
93
+ *
94
+ * Each update column becomes a correlated lookup into the json array by matching key(s):
95
+ * `c = (SELECT CASE WHEN json_extract(je.value,'$._skip_c') THEN <table>.c
96
+ * ELSE json_extract(je.value,'$.c') END
97
+ * FROM json_each(?) je
98
+ * WHERE json_extract(je.value,'$.k') = <table>.k [AND …] LIMIT 1)`
99
+ * and the WHERE clause restricts affected rows to the keys present in the json array.
100
+ */
101
+ export declare function sqliteUpdateManyJson(opts: JsonUpdateManyOptions): MakeSQL;
102
+ //# sourceMappingURL=json-batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-batch.d.ts","sourceRoot":"","sources":["../../../src/scp/makesql/json-batch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,mCAAmC;AACnC,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAO3F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,CAuB1D;AAsBD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gBAAgB,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CA0BhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAkBjE;AAMD,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACnC,oFAAoF;IACpF,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CA6CxE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CA4CzE"}
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — the single-JSON-param BATCH forms (createMany INSERT,
4
+ * updateMany UPDATE) for MySQL 8 + SQLite (epic #43 / #45).
5
+ *
6
+ * v1 emits a multi-VALUES INSERT / `VALUES ROW(…)`-join (MySQL) / `CASE WHEN` (SQLite)
7
+ * UPDATE, binding one param per cell. These forms instead encode the whole group's rows
8
+ * as ONE JSON array-of-objects param and expand it SERVER-side:
9
+ *
10
+ * MySQL INSERT `INSERT INTO t (cols) SELECT <exprs> FROM JSON_TABLE(?, '$[*]'
11
+ * COLUMNS(c <T> PATH '$.c', …)) jt`
12
+ * MySQL UPDATE `UPDATE t AS u JOIN JSON_TABLE(?, '$[*]' COLUMNS(…)) AS v(…)
13
+ * ON u.k = v.k SET u.c = IF(v._skip_c, u.c, v.c), …`
14
+ * SQLite INSERT `INSERT INTO t (cols) SELECT json_extract(value,'$.c'), … FROM json_each(?)`
15
+ * SQLite UPDATE `UPDATE t SET c = CASE <WHEN key THEN …> END … WHERE k IN
16
+ * (SELECT json_extract(value,'$.k') FROM json_each(?))` — one json param.
17
+ *
18
+ * ONE JSON param per group; the SQL text is STATIC (no per-row placeholder explosion).
19
+ * Correctness bar is RESULT PARITY vs v1, proven on real MySQL 8 + SQLite. PostgreSQL is
20
+ * untouched (UNNEST). A DBToken value (e.g. `NOW()`) cannot be JSON-encoded, so a group
21
+ * carrying one falls back to v1's builder for that group (rare; keeps correctness).
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.rowsHaveDbToken = rowsHaveDbToken;
25
+ exports.mysqlJsonTableColumn = mysqlJsonTableColumn;
26
+ exports.mysqlInsertJson = mysqlInsertJson;
27
+ exports.sqliteInsertJson = sqliteInsertJson;
28
+ exports.mysqlUpdateManyJson = mysqlUpdateManyJson;
29
+ exports.sqliteUpdateManyJson = sqliteUpdateManyJson;
30
+ const DBValues_1 = require("../../DBValues");
31
+ /** True if any row carries a DBToken value (can't be JSON-encoded → v1 fallback). */
32
+ function rowsHaveDbToken(rows, columns) {
33
+ for (const r of rows) {
34
+ for (const c of columns) {
35
+ if (r[c] instanceof DBValues_1.DBToken)
36
+ return true;
37
+ }
38
+ }
39
+ return false;
40
+ }
41
+ /**
42
+ * Infer the MySQL `JSON_TABLE` COLUMNS type + SELECT expression for a column, from the
43
+ * first non-null serialized value in the group. Chosen so the extracted value coerces
44
+ * to the target column identically to v1's bound `?`:
45
+ *
46
+ * - boolean / integer / bigint → `BIGINT` (JSON true/false → 1/0; ints exact),
47
+ * - non-integer number → `DECIMAL(65,30)` (lossless),
48
+ * - object / array → `JSON` kept as JSON (json columns),
49
+ * - string / null / other → `JSON`, then `JSON_UNQUOTE(...)` in the SELECT so
50
+ * arbitrary-length text (beyond CHAR's 255 cap) and JSON-null → SQL NULL work.
51
+ *
52
+ * Returns `{ colType, selectExpr }` where `selectExpr` is applied to the JSON_TABLE
53
+ * output column reference (e.g. `jt.name`).
54
+ */
55
+ function mysqlJsonTableColumn(col, rows) {
56
+ let sample;
57
+ for (const r of rows) {
58
+ const v = r[col];
59
+ if (v !== null && v !== undefined) {
60
+ sample = v;
61
+ break;
62
+ }
63
+ }
64
+ if (typeof sample === 'boolean' || typeof sample === 'bigint') {
65
+ return { colType: 'BIGINT', selectExpr: (ref) => ref };
66
+ }
67
+ if (typeof sample === 'number') {
68
+ return Number.isInteger(sample)
69
+ ? { colType: 'BIGINT', selectExpr: (ref) => ref }
70
+ : { colType: 'DECIMAL(65,30)', selectExpr: (ref) => ref };
71
+ }
72
+ if (sample !== null && typeof sample === 'object') {
73
+ // Object/array serialized value (e.g. a json column) — keep as JSON.
74
+ return { colType: 'JSON', selectExpr: (ref) => ref };
75
+ }
76
+ // string, null, date-as-string, json-string, or all-null column → text via JSON_UNQUOTE.
77
+ return { colType: 'JSON', selectExpr: (ref) => `JSON_UNQUOTE(${ref})` };
78
+ }
79
+ /**
80
+ * Serialize a group's rows to a JSON array-of-objects string, keeping only `columns`.
81
+ * A JSON-string value produced by v1's serializer (e.g. a `@column.json()` column whose
82
+ * serializer already `JSON.stringify`'d it) is passed through as-is — it becomes a JSON
83
+ * string element, matching what v1 binds. `undefined` cells are dropped (the group is
84
+ * homogeneous by construction, so every column is present in every row).
85
+ */
86
+ function rowsToJson(rows, columns) {
87
+ const objects = rows.map((r) => {
88
+ const o = {};
89
+ for (const c of columns)
90
+ o[c] = r[c] === undefined ? null : r[c];
91
+ return o;
92
+ });
93
+ return JSON.stringify(objects);
94
+ }
95
+ /**
96
+ * Build the MySQL single-JSON-param batch INSERT. `INSERT [IGNORE] INTO t (cols)
97
+ * SELECT <typed exprs> FROM JSON_TABLE(?, '$[*]' COLUMNS(…)) jt [ON DUPLICATE KEY …]`.
98
+ */
99
+ function mysqlInsertJson(opts) {
100
+ const { tableName, columns, records, onConflict, onConflictIgnore, onConflictUpdate, returning } = opts;
101
+ const jtCols = [];
102
+ const selectExprs = [];
103
+ for (const col of columns) {
104
+ const { colType, selectExpr } = mysqlJsonTableColumn(col, records);
105
+ jtCols.push(`${col} ${colType} PATH '$.${col}'`);
106
+ selectExprs.push(selectExpr(`jt.${col}`));
107
+ }
108
+ const jsonParam = rowsToJson(records, columns);
109
+ const source = `SELECT ${selectExprs.join(', ')} ` +
110
+ `FROM JSON_TABLE(?, '$[*]' COLUMNS(${jtCols.join(', ')})) jt`;
111
+ let sql;
112
+ if (onConflict && onConflictIgnore) {
113
+ sql = `INSERT IGNORE INTO ${tableName} (${columns.join(', ')}) ${source}`;
114
+ }
115
+ else if (onConflict && onConflictUpdate) {
116
+ const updateCols = onConflictUpdate === 'all' ? columns : onConflictUpdate;
117
+ const updateClauses = updateCols.map((c) => `${c} = VALUES(${c})`);
118
+ sql = `INSERT INTO ${tableName} (${columns.join(', ')}) ${source} ON DUPLICATE KEY UPDATE ${updateClauses.join(', ')}`;
119
+ }
120
+ else {
121
+ sql = `INSERT INTO ${tableName} (${columns.join(', ')}) ${source}`;
122
+ }
123
+ if (returning)
124
+ sql += ` RETURNING ${returning}`;
125
+ return { sql, params: [jsonParam] };
126
+ }
127
+ /**
128
+ * Build the SQLite single-JSON-param batch INSERT. `INSERT [OR IGNORE] INTO t (cols)
129
+ * SELECT json_extract(value,'$.c'), … FROM json_each(?) [ON CONFLICT … DO …]`.
130
+ * `json_extract` returns each cell's natural type; SQLite affinity coerces on insert.
131
+ */
132
+ function sqliteInsertJson(opts) {
133
+ const { tableName, columns, records, onConflict, onConflictIgnore, onConflictUpdate, returning } = opts;
134
+ const selectExprs = columns.map((c) => `json_extract(value, '$.${c}')`);
135
+ const jsonParam = rowsToJson(records, columns);
136
+ const source = `SELECT ${selectExprs.join(', ')} FROM json_each(?)`;
137
+ let sql;
138
+ if (onConflict && onConflictIgnore) {
139
+ sql = `INSERT OR IGNORE INTO ${tableName} (${columns.join(', ')}) ${source}`;
140
+ }
141
+ else if (onConflict && onConflictUpdate) {
142
+ const updateCols = onConflictUpdate === 'all' ? columns : onConflictUpdate;
143
+ const updateClauses = updateCols.map((c) => `${c} = excluded.${c}`);
144
+ sql = `INSERT INTO ${tableName} (${columns.join(', ')}) ${source} ON CONFLICT (${onConflict.join(', ')}) DO UPDATE SET ${updateClauses.join(', ')}`;
145
+ }
146
+ else {
147
+ sql = `INSERT INTO ${tableName} (${columns.join(', ')}) ${source}`;
148
+ }
149
+ if (returning)
150
+ sql += ` RETURNING ${returning}`;
151
+ return { sql, params: [jsonParam] };
152
+ }
153
+ /**
154
+ * Build the MySQL single-JSON-param batch UPDATE: JSON_TABLE join with the SKIP-column
155
+ * `IF(v._skip_c, u.c, v.c)` logic preserved. Each row's `_skip_c` flag is carried inside
156
+ * the ONE JSON param (as `0`/`1`), so the SQL text stays static.
157
+ *
158
+ * UPDATE t AS u JOIN JSON_TABLE(?, '$[*]' COLUMNS(k <T> PATH '$.k', c <T> PATH '$.c',
159
+ * _skip_c BIGINT PATH '$._skip_c', …)) AS v ON u.k = v.k
160
+ * SET u.c = IF(v._skip_c, u.c, v.c), …
161
+ */
162
+ function mysqlUpdateManyJson(opts) {
163
+ const { tableName, keyColumns, updateColumns, records, skipMap = new Map(), returning } = opts;
164
+ const allColumns = [...keyColumns, ...updateColumns];
165
+ const skipColumnsNeeded = new Set();
166
+ for (const s of skipMap.values())
167
+ for (const c of s)
168
+ skipColumnsNeeded.add(c);
169
+ // Build the JSON payload: each row's data columns + the _skip_ flags (0/1).
170
+ const skipCols = [...skipColumnsNeeded];
171
+ const payload = records.map((r, i) => {
172
+ const o = {};
173
+ for (const c of allColumns)
174
+ o[c] = r[c] === undefined ? null : r[c];
175
+ for (const c of skipCols)
176
+ o[`_skip_${c}`] = skipMap.get(i)?.has(c) ? 1 : 0;
177
+ return o;
178
+ });
179
+ const jsonParam = JSON.stringify(payload);
180
+ const jtCols = [];
181
+ const selectExprOf = new Map();
182
+ for (const col of allColumns) {
183
+ const { colType, selectExpr } = mysqlJsonTableColumn(col, records);
184
+ jtCols.push(`${col} ${colType} PATH '$.${col}'`);
185
+ selectExprOf.set(col, selectExpr);
186
+ }
187
+ for (const c of skipCols)
188
+ jtCols.push(`_skip_${c} BIGINT PATH '$._skip_${c}'`);
189
+ const setClauses = updateColumns.map((col) => {
190
+ const vExpr = selectExprOf.get(col)(`v.${col}`);
191
+ return skipColumnsNeeded.has(col)
192
+ ? `u.${col} = IF(v._skip_${col}, u.${col}, ${vExpr})`
193
+ : `u.${col} = ${vExpr}`;
194
+ });
195
+ const onConditions = keyColumns.map((k) => `u.${k} = ${selectExprOf.get(k)(`v.${k}`)}`);
196
+ // Target aliased `u`, JSON_TABLE derived rows aliased `v`; the two aliases only need to
197
+ // differ from each other so `u.col` / `v.col` are unambiguous. (v1's MySQL builder used
198
+ // `AS t`; `u` here just keeps the target distinct from the `v` derived table — not a
199
+ // correctness requirement, purely disambiguation.)
200
+ let sql = `UPDATE ${tableName} AS u ` +
201
+ `JOIN JSON_TABLE(?, '$[*]' COLUMNS(${jtCols.join(', ')})) AS v ` +
202
+ `ON ${onConditions.join(' AND ')} ` +
203
+ `SET ${setClauses.join(', ')}`;
204
+ if (returning)
205
+ sql += ` RETURNING ${returning}`;
206
+ return { sql, params: [jsonParam] };
207
+ }
208
+ /**
209
+ * Build the SQLite batch UPDATE over `json_each(?)`, SKIP preserved. A single JSON
210
+ * array-of-objects (keys + update values + `_skip_c` flags) carries every row.
211
+ *
212
+ * NOTE on params: the SAME one JSON string is bound to EACH `?` — one per update column's
213
+ * correlated sub-SELECT plus one for the WHERE — so `params` has `updateColumns.length + 1`
214
+ * entries, ALL equal to that single JSON value. The row COUNT never multiplies the param
215
+ * count (the N-placeholder explosion is what this replaces); the repetition is only across
216
+ * the (fixed) number of SET columns, and the SQL text stays static regardless of row count.
217
+ *
218
+ * Each update column becomes a correlated lookup into the json array by matching key(s):
219
+ * `c = (SELECT CASE WHEN json_extract(je.value,'$._skip_c') THEN <table>.c
220
+ * ELSE json_extract(je.value,'$.c') END
221
+ * FROM json_each(?) je
222
+ * WHERE json_extract(je.value,'$.k') = <table>.k [AND …] LIMIT 1)`
223
+ * and the WHERE clause restricts affected rows to the keys present in the json array.
224
+ */
225
+ function sqliteUpdateManyJson(opts) {
226
+ const { tableName, keyColumns, updateColumns, records, skipMap = new Map(), returning } = opts;
227
+ const allColumns = [...keyColumns, ...updateColumns];
228
+ const skipColumnsNeeded = new Set();
229
+ for (const s of skipMap.values())
230
+ for (const c of s)
231
+ skipColumnsNeeded.add(c);
232
+ const skipCols = [...skipColumnsNeeded];
233
+ const payload = records.map((r, i) => {
234
+ const o = {};
235
+ for (const c of allColumns)
236
+ o[c] = r[c] === undefined ? null : r[c];
237
+ for (const c of skipCols)
238
+ o[`_skip_${c}`] = skipMap.get(i)?.has(c) ? 1 : 0;
239
+ return o;
240
+ });
241
+ const jsonParam = JSON.stringify(payload);
242
+ const params = [];
243
+ const keyMatch = keyColumns.map((k) => `json_extract(je.value, '$.${k}') = ${tableName}.${k}`).join(' AND ');
244
+ const setClauses = updateColumns.map((col) => {
245
+ params.push(jsonParam);
246
+ const valueExpr = skipColumnsNeeded.has(col)
247
+ ? `CASE WHEN json_extract(je.value, '$._skip_${col}') THEN ${tableName}.${col} ELSE json_extract(je.value, '$.${col}') END`
248
+ : `json_extract(je.value, '$.${col}')`;
249
+ return (`${col} = (SELECT ${valueExpr} FROM json_each(?) je ` +
250
+ `WHERE ${keyMatch} LIMIT 1)`);
251
+ });
252
+ // WHERE: restrict to the affected keys. Single key → k IN (SELECT …); composite →
253
+ // EXISTS over json_each matching all key columns.
254
+ let whereClause;
255
+ if (keyColumns.length === 1) {
256
+ params.push(jsonParam);
257
+ whereClause = `${keyColumns[0]} IN (SELECT json_extract(value, '$.${keyColumns[0]}') FROM json_each(?))`;
258
+ }
259
+ else {
260
+ params.push(jsonParam);
261
+ const match = keyColumns.map((k) => `json_extract(je.value, '$.${k}') = ${tableName}.${k}`).join(' AND ');
262
+ whereClause = `EXISTS (SELECT 1 FROM json_each(?) je WHERE ${match})`;
263
+ }
264
+ let sql = `UPDATE ${tableName} SET ${setClauses.join(', ')} WHERE ${whereClause}`;
265
+ if (returning)
266
+ sql += ` RETURNING ${returning}`;
267
+ return { sql, params };
268
+ }
269
+ //# sourceMappingURL=json-batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-batch.js","sourceRoot":"","sources":["../../../src/scp/makesql/json-batch.ts"],"names":[],"mappings":"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"}