@simplysm/orm-common 13.0.100 → 14.0.4

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 (246) hide show
  1. package/README.md +90 -147
  2. package/dist/create-db-context.d.ts +10 -10
  3. package/dist/create-db-context.js +312 -276
  4. package/dist/create-db-context.js.map +1 -6
  5. package/dist/ddl/column-ddl.d.ts +4 -4
  6. package/dist/ddl/column-ddl.js +41 -35
  7. package/dist/ddl/column-ddl.js.map +1 -6
  8. package/dist/ddl/initialize.d.ts +17 -17
  9. package/dist/ddl/initialize.js +200 -142
  10. package/dist/ddl/initialize.js.map +1 -6
  11. package/dist/ddl/relation-ddl.d.ts +6 -6
  12. package/dist/ddl/relation-ddl.js +55 -48
  13. package/dist/ddl/relation-ddl.js.map +1 -6
  14. package/dist/ddl/schema-ddl.d.ts +4 -4
  15. package/dist/ddl/schema-ddl.js +21 -15
  16. package/dist/ddl/schema-ddl.js.map +1 -6
  17. package/dist/ddl/table-ddl.d.ts +20 -20
  18. package/dist/ddl/table-ddl.js +139 -93
  19. package/dist/ddl/table-ddl.js.map +1 -6
  20. package/dist/define-db-context.js +10 -13
  21. package/dist/define-db-context.js.map +1 -6
  22. package/dist/errors/db-transaction-error.d.ts +15 -15
  23. package/dist/errors/db-transaction-error.d.ts.map +1 -1
  24. package/dist/errors/db-transaction-error.js +53 -19
  25. package/dist/errors/db-transaction-error.js.map +1 -6
  26. package/dist/exec/executable.d.ts +23 -23
  27. package/dist/exec/executable.js +94 -40
  28. package/dist/exec/executable.js.map +1 -6
  29. package/dist/exec/queryable.d.ts +97 -97
  30. package/dist/exec/queryable.js +1310 -1204
  31. package/dist/exec/queryable.js.map +1 -6
  32. package/dist/exec/search-parser.d.ts +31 -31
  33. package/dist/exec/search-parser.d.ts.map +1 -1
  34. package/dist/exec/search-parser.js +158 -59
  35. package/dist/exec/search-parser.js.map +1 -6
  36. package/dist/expr/expr-unit.d.ts +4 -4
  37. package/dist/expr/expr-unit.js +24 -18
  38. package/dist/expr/expr-unit.js.map +1 -6
  39. package/dist/expr/expr.d.ts +108 -108
  40. package/dist/expr/expr.js +1872 -1844
  41. package/dist/expr/expr.js.map +1 -6
  42. package/dist/index.js +23 -1
  43. package/dist/index.js.map +1 -6
  44. package/dist/models/system-migration.js +7 -7
  45. package/dist/models/system-migration.js.map +1 -6
  46. package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
  47. package/dist/query-builder/base/expr-renderer-base.js +27 -21
  48. package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
  49. package/dist/query-builder/base/query-builder-base.d.ts +21 -21
  50. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  51. package/dist/query-builder/base/query-builder-base.js +90 -80
  52. package/dist/query-builder/base/query-builder-base.js.map +1 -6
  53. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +5 -5
  54. package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
  55. package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
  56. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
  57. package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
  58. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  59. package/dist/query-builder/mssql/mssql-query-builder.js +483 -443
  60. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
  61. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +5 -5
  62. package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
  63. package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
  64. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
  65. package/dist/query-builder/mysql/mysql-query-builder.d.ts +10 -10
  66. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  67. package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
  68. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
  69. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +5 -5
  70. package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
  71. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
  72. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
  73. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +8 -8
  74. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  75. package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
  76. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
  77. package/dist/query-builder/query-builder.d.ts +1 -1
  78. package/dist/query-builder/query-builder.js +13 -13
  79. package/dist/query-builder/query-builder.js.map +1 -6
  80. package/dist/schema/factory/column-builder.d.ts +84 -84
  81. package/dist/schema/factory/column-builder.js +248 -185
  82. package/dist/schema/factory/column-builder.js.map +1 -6
  83. package/dist/schema/factory/index-builder.d.ts +38 -38
  84. package/dist/schema/factory/index-builder.js +144 -85
  85. package/dist/schema/factory/index-builder.js.map +1 -6
  86. package/dist/schema/factory/relation-builder.d.ts +99 -99
  87. package/dist/schema/factory/relation-builder.d.ts.map +1 -1
  88. package/dist/schema/factory/relation-builder.js +274 -136
  89. package/dist/schema/factory/relation-builder.js.map +1 -6
  90. package/dist/schema/procedure-builder.d.ts +51 -51
  91. package/dist/schema/procedure-builder.d.ts.map +1 -1
  92. package/dist/schema/procedure-builder.js +205 -131
  93. package/dist/schema/procedure-builder.js.map +1 -6
  94. package/dist/schema/table-builder.d.ts +55 -55
  95. package/dist/schema/table-builder.d.ts.map +1 -1
  96. package/dist/schema/table-builder.js +274 -205
  97. package/dist/schema/table-builder.js.map +1 -6
  98. package/dist/schema/view-builder.d.ts +44 -44
  99. package/dist/schema/view-builder.d.ts.map +1 -1
  100. package/dist/schema/view-builder.js +189 -116
  101. package/dist/schema/view-builder.js.map +1 -6
  102. package/dist/types/column.d.ts +21 -21
  103. package/dist/types/column.js +60 -30
  104. package/dist/types/column.js.map +1 -6
  105. package/dist/types/db-context-def.d.ts +9 -9
  106. package/dist/types/db-context-def.js +2 -1
  107. package/dist/types/db-context-def.js.map +1 -6
  108. package/dist/types/db.d.ts +47 -47
  109. package/dist/types/db.js +15 -5
  110. package/dist/types/db.js.map +1 -6
  111. package/dist/types/expr.d.ts +81 -81
  112. package/dist/types/expr.d.ts.map +1 -1
  113. package/dist/types/expr.js +3 -1
  114. package/dist/types/expr.js.map +1 -6
  115. package/dist/types/query-def.d.ts +46 -46
  116. package/dist/types/query-def.d.ts.map +1 -1
  117. package/dist/types/query-def.js +31 -24
  118. package/dist/types/query-def.js.map +1 -6
  119. package/dist/utils/result-parser.d.ts +11 -11
  120. package/dist/utils/result-parser.js +362 -221
  121. package/dist/utils/result-parser.js.map +1 -6
  122. package/docs/core.md +117 -145
  123. package/docs/expression.md +186 -203
  124. package/docs/query-builder.md +75 -42
  125. package/docs/queryable.md +189 -151
  126. package/docs/schema-builders.md +172 -283
  127. package/docs/types.md +229 -173
  128. package/package.json +7 -5
  129. package/src/create-db-context.ts +31 -31
  130. package/src/ddl/column-ddl.ts +4 -4
  131. package/src/ddl/initialize.ts +38 -38
  132. package/src/ddl/relation-ddl.ts +6 -6
  133. package/src/ddl/schema-ddl.ts +4 -4
  134. package/src/ddl/table-ddl.ts +24 -24
  135. package/src/errors/db-transaction-error.ts +13 -13
  136. package/src/exec/executable.ts +25 -25
  137. package/src/exec/queryable.ts +152 -152
  138. package/src/exec/search-parser.ts +50 -50
  139. package/src/expr/expr-unit.ts +4 -4
  140. package/src/expr/expr.ts +118 -118
  141. package/src/index.ts +8 -8
  142. package/src/models/system-migration.ts +1 -1
  143. package/src/query-builder/base/expr-renderer-base.ts +21 -21
  144. package/src/query-builder/base/query-builder-base.ts +33 -33
  145. package/src/query-builder/mssql/mssql-expr-renderer.ts +28 -28
  146. package/src/query-builder/mssql/mssql-query-builder.ts +37 -37
  147. package/src/query-builder/mysql/mysql-expr-renderer.ts +29 -29
  148. package/src/query-builder/mysql/mysql-query-builder.ts +70 -70
  149. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +22 -22
  150. package/src/query-builder/postgresql/postgresql-query-builder.ts +54 -54
  151. package/src/query-builder/query-builder.ts +1 -1
  152. package/src/schema/factory/column-builder.ts +86 -86
  153. package/src/schema/factory/index-builder.ts +38 -38
  154. package/src/schema/factory/relation-builder.ts +102 -102
  155. package/src/schema/procedure-builder.ts +52 -52
  156. package/src/schema/table-builder.ts +56 -56
  157. package/src/schema/view-builder.ts +47 -47
  158. package/src/types/column.ts +24 -24
  159. package/src/types/db-context-def.ts +15 -15
  160. package/src/types/db.ts +50 -50
  161. package/src/types/expr.ts +103 -103
  162. package/src/types/query-def.ts +50 -50
  163. package/src/utils/result-parser.ts +88 -88
  164. package/docs/utilities.md +0 -27
  165. package/tests/db-context/create-db-context.spec.ts +0 -193
  166. package/tests/db-context/define-db-context.spec.ts +0 -17
  167. package/tests/ddl/basic.expected.ts +0 -341
  168. package/tests/ddl/basic.spec.ts +0 -557
  169. package/tests/ddl/column-builder.expected.ts +0 -310
  170. package/tests/ddl/column-builder.spec.ts +0 -525
  171. package/tests/ddl/index-builder.expected.ts +0 -38
  172. package/tests/ddl/index-builder.spec.ts +0 -148
  173. package/tests/ddl/procedure-builder.expected.ts +0 -52
  174. package/tests/ddl/procedure-builder.spec.ts +0 -128
  175. package/tests/ddl/relation-builder.expected.ts +0 -36
  176. package/tests/ddl/relation-builder.spec.ts +0 -171
  177. package/tests/ddl/table-builder.expected.ts +0 -113
  178. package/tests/ddl/table-builder.spec.ts +0 -399
  179. package/tests/ddl/view-builder.expected.ts +0 -38
  180. package/tests/ddl/view-builder.spec.ts +0 -116
  181. package/tests/dml/delete.expected.ts +0 -96
  182. package/tests/dml/delete.spec.ts +0 -127
  183. package/tests/dml/insert.expected.ts +0 -192
  184. package/tests/dml/insert.spec.ts +0 -210
  185. package/tests/dml/update.expected.ts +0 -176
  186. package/tests/dml/update.spec.ts +0 -222
  187. package/tests/dml/upsert.expected.ts +0 -215
  188. package/tests/dml/upsert.spec.ts +0 -190
  189. package/tests/errors/queryable-errors.spec.ts +0 -126
  190. package/tests/escape.spec.ts +0 -59
  191. package/tests/examples/pivot.expected.ts +0 -211
  192. package/tests/examples/pivot.spec.ts +0 -200
  193. package/tests/examples/sampling.expected.ts +0 -69
  194. package/tests/examples/sampling.spec.ts +0 -42
  195. package/tests/examples/unpivot.expected.ts +0 -120
  196. package/tests/examples/unpivot.spec.ts +0 -161
  197. package/tests/exec/search-parser.spec.ts +0 -267
  198. package/tests/executable/basic.expected.ts +0 -18
  199. package/tests/executable/basic.spec.ts +0 -54
  200. package/tests/expr/comparison.expected.ts +0 -282
  201. package/tests/expr/comparison.spec.ts +0 -334
  202. package/tests/expr/conditional.expected.ts +0 -134
  203. package/tests/expr/conditional.spec.ts +0 -249
  204. package/tests/expr/date.expected.ts +0 -332
  205. package/tests/expr/date.spec.ts +0 -459
  206. package/tests/expr/math.expected.ts +0 -62
  207. package/tests/expr/math.spec.ts +0 -59
  208. package/tests/expr/string.expected.ts +0 -218
  209. package/tests/expr/string.spec.ts +0 -300
  210. package/tests/expr/utility.expected.ts +0 -147
  211. package/tests/expr/utility.spec.ts +0 -155
  212. package/tests/select/basic.expected.ts +0 -322
  213. package/tests/select/basic.spec.ts +0 -433
  214. package/tests/select/filter.expected.ts +0 -357
  215. package/tests/select/filter.spec.ts +0 -954
  216. package/tests/select/group.expected.ts +0 -169
  217. package/tests/select/group.spec.ts +0 -159
  218. package/tests/select/join.expected.ts +0 -582
  219. package/tests/select/join.spec.ts +0 -692
  220. package/tests/select/order.expected.ts +0 -150
  221. package/tests/select/order.spec.ts +0 -140
  222. package/tests/select/recursive-cte.expected.ts +0 -244
  223. package/tests/select/recursive-cte.spec.ts +0 -514
  224. package/tests/select/result-meta.spec.ts +0 -270
  225. package/tests/select/subquery.expected.ts +0 -363
  226. package/tests/select/subquery.spec.ts +0 -441
  227. package/tests/select/view.expected.ts +0 -155
  228. package/tests/select/view.spec.ts +0 -235
  229. package/tests/select/window.expected.ts +0 -345
  230. package/tests/select/window.spec.ts +0 -433
  231. package/tests/setup/MockExecutor.ts +0 -18
  232. package/tests/setup/TestDbContext.ts +0 -59
  233. package/tests/setup/models/Company.ts +0 -13
  234. package/tests/setup/models/Employee.ts +0 -10
  235. package/tests/setup/models/MonthlySales.ts +0 -11
  236. package/tests/setup/models/Post.ts +0 -16
  237. package/tests/setup/models/Sales.ts +0 -10
  238. package/tests/setup/models/User.ts +0 -19
  239. package/tests/setup/procedure/GetAllUsers.ts +0 -9
  240. package/tests/setup/procedure/GetUserById.ts +0 -12
  241. package/tests/setup/test-utils.ts +0 -72
  242. package/tests/setup/views/ActiveUsers.ts +0 -8
  243. package/tests/setup/views/UserSummary.ts +0 -11
  244. package/tests/types/nullable-queryable-record.spec.ts +0 -97
  245. package/tests/utils/result-parser-perf.spec.ts +0 -143
  246. package/tests/utils/result-parser.spec.ts +0 -667
@@ -1,265 +1,406 @@
1
1
  import { bytes, obj, DateOnly, DateTime, Time, Uuid } from "@simplysm/core-common";
2
+ // ============================================
3
+ // 타입 파서
4
+ // ============================================
5
+ /**
6
+ * 값을 지정된 타입으로 파싱
7
+ *
8
+ * @param value - 파싱할 값
9
+ * @param type - 대상 타입 (ColumnPrimitiveStr)
10
+ * @returns 파싱된 값
11
+ * @throws 파싱 실패 시 Error
12
+ */
2
13
  function parseValue(value, type) {
3
- if (value == null) {
4
- return void 0;
5
- }
6
- switch (type) {
7
- case "number": {
8
- const num = Number(value);
9
- if (Number.isNaN(num)) {
10
- throw new Error(`Failed to parse number: ${String(value)}`);
11
- }
12
- return num;
14
+ // null/undefined는 그대로 반환 (key 제거는 호출자가 처리)
15
+ if (value == null) {
16
+ return undefined;
17
+ }
18
+ switch (type) {
19
+ case "number": {
20
+ const num = Number(value);
21
+ if (Number.isNaN(num)) {
22
+ throw new Error(`숫자 파싱 실패: ${String(value)}`);
23
+ }
24
+ return num;
25
+ }
26
+ case "string":
27
+ return String(value);
28
+ case "boolean":
29
+ // 0, 1, "0", "1", true, false 등 처리
30
+ if (value === 0 || value === "0" || value === false)
31
+ return false;
32
+ if (value === 1 || value === "1" || value === true)
33
+ return true;
34
+ return Boolean(value);
35
+ case "DateTime":
36
+ return DateTime.parse(value);
37
+ case "DateOnly":
38
+ return DateOnly.parse(value);
39
+ case "Time":
40
+ return Time.parse(value);
41
+ case "Uuid":
42
+ if (value instanceof Uint8Array)
43
+ return Uuid.fromBytes(value);
44
+ return new Uuid(value);
45
+ case "Bytes":
46
+ if (value instanceof Uint8Array)
47
+ return value;
48
+ if (typeof value === "string")
49
+ return bytes.fromHex(value);
50
+ throw new Error(`Bytes 파싱 실패: ${typeof value}`);
13
51
  }
14
- case "string":
15
- return String(value);
16
- case "boolean":
17
- if (value === 0 || value === "0" || value === false) return false;
18
- if (value === 1 || value === "1" || value === true) return true;
19
- return Boolean(value);
20
- case "DateTime":
21
- return DateTime.parse(value);
22
- case "DateOnly":
23
- return DateOnly.parse(value);
24
- case "Time":
25
- return Time.parse(value);
26
- case "Uuid":
27
- if (value instanceof Uint8Array) return Uuid.fromBytes(value);
28
- return new Uuid(value);
29
- case "Bytes":
30
- if (value instanceof Uint8Array) return value;
31
- if (typeof value === "string") return bytes.fromHex(value);
32
- throw new Error(`Failed to parse Bytes: ${typeof value}`);
33
- }
34
52
  }
53
+ /** 고유한 columns 객체마다 column 정보를 한 번만 사전 계산 */
35
54
  function buildColumnInfos(columns) {
36
- return Object.entries(columns).map(([key, type]) => ({
37
- key,
38
- type,
39
- parts: key.includes(".") ? key.split(".") : void 0
40
- }));
55
+ return Object.entries(columns).map(([key, type]) => ({
56
+ key,
57
+ type,
58
+ parts: key.includes(".") ? key.split(".") : undefined,
59
+ }));
41
60
  }
61
+ /**
62
+ * 플랫 레코드를 중첩 객체로 변환
63
+ *
64
+ * @example
65
+ * { "posts.id": 1, "posts.title": "Hi" } → { posts: { id: 1, title: "Hi" } }
66
+ */
42
67
  function flatToNested(record, columnInfos) {
43
- const result = {};
44
- for (const { key, type, parts } of columnInfos) {
45
- const rawValue = record[key];
46
- const parsedValue = parseValue(rawValue, type);
47
- if (parsedValue === void 0) continue;
48
- if (parts != null) {
49
- let current = result;
50
- for (let i = 0; i < parts.length - 1; i++) {
51
- const part = parts[i];
52
- if (current[part] == null) {
53
- current[part] = {};
68
+ const result = {};
69
+ for (const { key, type, parts } of columnInfos) {
70
+ const rawValue = record[key];
71
+ const parsedValue = parseValue(rawValue, type);
72
+ // undefined 값은 key로 추가하지 않음
73
+ if (parsedValue === undefined)
74
+ continue;
75
+ if (parts != null) {
76
+ // 중첩 key: "posts.id" → { posts: { id: ... } }
77
+ let current = result;
78
+ for (let i = 0; i < parts.length - 1; i++) {
79
+ const part = parts[i];
80
+ if (current[part] == null) {
81
+ current[part] = {};
82
+ }
83
+ current = current[part];
84
+ }
85
+ current[parts[parts.length - 1]] = parsedValue;
86
+ }
87
+ else {
88
+ // 단순 key
89
+ result[key] = parsedValue;
54
90
  }
55
- current = current[part];
56
- }
57
- current[parts[parts.length - 1]] = parsedValue;
58
- } else {
59
- result[key] = parsedValue;
60
91
  }
61
- }
62
- return result;
92
+ return result;
63
93
  }
94
+ /**
95
+ * 객체가 비어있는지 확인 (모든 값이 undefined)
96
+ */
64
97
  function isEmptyObject(record) {
65
- return Object.keys(record).length === 0;
98
+ return Object.keys(record).length === 0;
66
99
  }
100
+ // ============================================
101
+ // 메인 함수
102
+ // ============================================
103
+ /** 양보 간격: N개 레코드마다 이벤트 루프에 양보 */
67
104
  const YIELD_INTERVAL = 100;
68
- const yieldToEventLoop = typeof setImmediate !== "undefined" ? () => new Promise((resolve) => setImmediate(resolve)) : () => new Promise((resolve) => setTimeout(resolve, 0));
69
- async function parseQueryResult(rawResults, meta) {
70
- if (rawResults.length === 0) {
71
- return void 0;
72
- }
73
- const joinKeys = Object.keys(meta.joins);
74
- if (joinKeys.length === 0) {
75
- return parseSimpleRecords(rawResults, meta.columns);
76
- }
77
- return parseJoinedRecords(rawResults, meta);
105
+ /** 이벤트 루프 양보: Node.js는 setImmediate, 브라우저는 setTimeout 폴백 */
106
+ const yieldToEventLoop = typeof setImmediate !== "undefined"
107
+ ? () => new Promise((resolve) => setImmediate(resolve))
108
+ : () => new Promise((resolve) => setTimeout(resolve, 0));
109
+ /**
110
+ * ResultMeta를 통해 DB 쿼리 결과를 TypeScript 객체로 변환
111
+ *
112
+ * @param rawResults - 데이터베이스에서 반환된 원시 결과 배열
113
+ * @param meta - 타입 변환 및 JOIN 구조 정보 (필수)
114
+ * @returns 타입 변환 및 중첩된 결과 배열. 입력이 비어있거나 유효한 결과가 없으면 undefined 반환
115
+ * @throws 타입 파싱 실패 시 Error
116
+ *
117
+ * @remarks
118
+ * - meta 필수: meta 없이는 이 함수를 호출할 필요 없음 (입력 = 출력)
119
+ * - async 전용: 대규모 처리 시 외부 인터럽트 허용을 위해 동기 버전 미제공
120
+ * - 브라우저/Node 호환: setTimeout(resolve, 0)으로 양보
121
+ * - 빈 결과 처리: 입력 배열이 비어있거나 파싱 후 모든 레코드가 빈 객체이면 undefined 반환
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * // 1. 단순 타입 파싱
126
+ * const raw = [{ id: "1", createdAt: "2026-01-07T10:00:00.000Z" }];
127
+ * const meta = { columns: { id: "number", createdAt: "DateTime" }, joins: {} };
128
+ * const result = await parseQueryResult(raw, meta);
129
+ * // [{ id: 1, createdAt: DateTime(...) }]
130
+ *
131
+ * // 2. JOIN 결과 중첩
132
+ * const raw = [
133
+ * { id: 1, name: "User1", "posts.id": 10, "posts.title": "Post1" },
134
+ * { id: 1, name: "User1", "posts.id": 11, "posts.title": "Post2" },
135
+ * ];
136
+ * const meta = {
137
+ * columns: { id: "number", name: "string", "posts.id": "number", "posts.title": "string" },
138
+ * joins: { posts: { isSingle: false } }
139
+ * };
140
+ * const result = await parseQueryResult(raw, meta);
141
+ * // [{ id: 1, name: "User1", posts: [{ id: 10, title: "Post1" }, { id: 11, title: "Post2" }] }]
142
+ * ```
143
+ */
144
+ export async function parseQueryResult(rawResults, meta) {
145
+ // 빈 입력 처리
146
+ if (rawResults.length === 0) {
147
+ return undefined;
148
+ }
149
+ const joinKeys = Object.keys(meta.joins);
150
+ // JOIN 없음: 단순 타입 파싱만 수행
151
+ if (joinKeys.length === 0) {
152
+ return parseSimpleRecords(rawResults, meta.columns);
153
+ }
154
+ // JOIN 있음: 그룹핑 + 중첩
155
+ return parseJoinedRecords(rawResults, meta);
78
156
  }
157
+ /**
158
+ * JOIN이 없는 단순 레코드 파싱
159
+ */
79
160
  async function parseSimpleRecords(rawResults, columns) {
80
- const columnInfos = buildColumnInfos(columns);
81
- const results = [];
82
- for (let i = 0; i < rawResults.length; i++) {
83
- if (i > 0 && i % YIELD_INTERVAL === 0) {
84
- await yieldToEventLoop();
85
- }
86
- const parsed = flatToNested(rawResults[i], columnInfos);
87
- if (!isEmptyObject(parsed)) {
88
- results.push(parsed);
161
+ const columnInfos = buildColumnInfos(columns);
162
+ const results = [];
163
+ for (let i = 0; i < rawResults.length; i++) {
164
+ // 이벤트 루프에 양보
165
+ if (i > 0 && i % YIELD_INTERVAL === 0) {
166
+ await yieldToEventLoop();
167
+ }
168
+ const parsed = flatToNested(rawResults[i], columnInfos);
169
+ // 빈 객체 제외
170
+ if (!isEmptyObject(parsed)) {
171
+ results.push(parsed);
172
+ }
89
173
  }
90
- }
91
- return results.length > 0 ? results : void 0;
174
+ // 빈 배열은 undefined 반환
175
+ return results.length > 0 ? results : undefined;
92
176
  }
177
+ /**
178
+ * JOIN key를 깊이순으로 정렬 (얕은 것 우선)
179
+ * "posts" (1) < "posts.comments" (2)
180
+ */
93
181
  function sortJoinKeysByDepth(joinKeys) {
94
- return [...joinKeys].sort((a, b) => {
95
- const depthA = a.split(".").length;
96
- const depthB = b.split(".").length;
97
- return depthA - depthB;
98
- });
182
+ return [...joinKeys].sort((a, b) => {
183
+ const depthA = a.split(".").length;
184
+ const depthB = b.split(".").length;
185
+ return depthA - depthB; // 얕은 것 우선
186
+ });
99
187
  }
188
+ /**
189
+ * JOIN이 있는 레코드 파싱 (재귀 그룹핑)
190
+ */
100
191
  async function parseJoinedRecords(rawResults, meta) {
101
- const columnInfos = buildColumnInfos(meta.columns);
102
- const nestedRecords = [];
103
- for (let i = 0; i < rawResults.length; i++) {
104
- if (i > 0 && i % YIELD_INTERVAL === 0) {
105
- await yieldToEventLoop();
192
+ // 1. 모든 레코드를 중첩 구조로 변환
193
+ const columnInfos = buildColumnInfos(meta.columns);
194
+ const nestedRecords = [];
195
+ for (let i = 0; i < rawResults.length; i++) {
196
+ if (i > 0 && i % YIELD_INTERVAL === 0) {
197
+ await yieldToEventLoop();
198
+ }
199
+ nestedRecords.push(flatToNested(rawResults[i], columnInfos));
106
200
  }
107
- nestedRecords.push(flatToNested(rawResults[i], columnInfos));
108
- }
109
- const sortedJoinKeys = sortJoinKeysByDepth(Object.keys(meta.joins));
110
- const results = groupRecordsRecursively(nestedRecords, sortedJoinKeys, meta.joins, "");
111
- const filteredResults = results.filter((r) => !isEmptyObject(r));
112
- return filteredResults.length > 0 ? filteredResults : void 0;
201
+ // 2. JOIN key를 깊이순으로 정렬 (얕은 것 우선)
202
+ const sortedJoinKeys = sortJoinKeysByDepth(Object.keys(meta.joins));
203
+ // 3. 루트 레벨부터 재귀적으로 그룹핑
204
+ const results = groupRecordsRecursively(nestedRecords, sortedJoinKeys, meta.joins, "");
205
+ // 4. 결과 필터링
206
+ const filteredResults = results.filter((r) => !isEmptyObject(r));
207
+ return filteredResults.length > 0 ? filteredResults : undefined;
113
208
  }
209
+ /**
210
+ * 그룹 key를 문자열로 직렬화 (Map key로 사용)
211
+ *
212
+ * JSON.stringify보다 빠른 커스텀 직렬화
213
+ */
114
214
  function serializeGroupKey(groupKey, cachedKeyOrder) {
115
- const keys = cachedKeyOrder ?? Object.keys(groupKey).sort((a, b) => a.localeCompare(b));
116
- let result = "";
117
- for (let i = 0; i < keys.length; i++) {
118
- if (i > 0) result += "|";
119
- const v = groupKey[keys[i]];
120
- result += keys[i];
121
- result += ":";
122
- result += v === null ? "null" : String(v);
123
- }
124
- return result;
215
+ const keys = cachedKeyOrder ?? Object.keys(groupKey).sort((a, b) => a.localeCompare(b));
216
+ let result = "";
217
+ for (let i = 0; i < keys.length; i++) {
218
+ if (i > 0)
219
+ result += "|";
220
+ const v = groupKey[keys[i]];
221
+ result += keys[i];
222
+ result += ":";
223
+ result += v === null ? "null" : String(v);
224
+ }
225
+ return result;
125
226
  }
227
+ /**
228
+ * 현재 경로에 대해 레코드를 재귀적으로 그룹핑
229
+ *
230
+ * Map 기반 그룹핑으로 O(n) 복잡도 달성
231
+ *
232
+ * @param records - 그룹핑할 레코드 배열
233
+ * @param allJoinKeys - 모든 JOIN key (깊이순 정렬)
234
+ * @param joinsConfig - JOIN 설정
235
+ * @param currentPath - 현재 경로 (예: "", "posts", "posts.comments")
236
+ */
126
237
  function groupRecordsRecursively(records, allJoinKeys, joinsConfig, currentPath) {
127
- const childJoinKeys = allJoinKeys.filter((key) => {
128
- if (currentPath === "") {
129
- return !key.includes(".");
130
- } else {
131
- return key.startsWith(currentPath + ".") && key.slice(currentPath.length + 1).indexOf(".") === -1;
132
- }
133
- });
134
- if (childJoinKeys.length === 0) {
135
- return records;
136
- }
137
- const groupMap = /* @__PURE__ */ new Map();
138
- const joinKeyExclusions = buildJoinKeyExclusionSet(childJoinKeys);
139
- let groupKeyOrder;
140
- for (const record of records) {
141
- const groupKey = extractGroupKey(record, joinKeyExclusions);
142
- if (groupKeyOrder == null) {
143
- groupKeyOrder = Object.keys(groupKey).sort((a, b) => a.localeCompare(b));
238
+ // 현재 경로에 직접 대응하는 JOIN key 찾기
239
+ // 예: currentPath="" ["posts", "company"]
240
+ // 예: currentPath="posts" → ["posts.comments"]
241
+ const childJoinKeys = allJoinKeys.filter((key) => {
242
+ if (currentPath === "") {
243
+ // 루트 레벨: 점이 없는 key
244
+ return !key.includes(".");
245
+ }
246
+ else {
247
+ // 하위 레벨: 현재 경로 + "." + key
248
+ return (key.startsWith(currentPath + ".") && key.slice(currentPath.length + 1).indexOf(".") === -1);
249
+ }
250
+ });
251
+ if (childJoinKeys.length === 0) {
252
+ // 이상 그룹핑할 JOIN 없음
253
+ return records;
144
254
  }
145
- const keyStr = serializeGroupKey(groupKey, groupKeyOrder);
146
- const existingGroup = groupMap.get(keyStr);
147
- if (existingGroup != null) {
148
- for (const joinKey of childJoinKeys) {
149
- const localKey = currentPath === "" ? joinKey : joinKey.slice(currentPath.length + 1);
150
- mergeJoinData(existingGroup, record, localKey, joinsConfig[joinKey].isSingle);
151
- }
152
- } else {
153
- const newGroup = { ...record };
154
- for (const joinKey of childJoinKeys) {
155
- const localKey = currentPath === "" ? joinKey : joinKey.slice(currentPath.length + 1);
156
- const joinData = newGroup[localKey];
157
- if (joinData != null && !isEmptyObject(joinData)) {
158
- if (!joinsConfig[joinKey].isSingle) {
159
- newGroup[localKey] = [joinData];
160
- }
161
- } else {
162
- delete newGroup[localKey];
255
+ // Map 기반 그룹핑 (O(n) 복잡도)
256
+ const groupMap = new Map();
257
+ // O(1) 조회를 위한 JOIN key 제외 집합 사전 계산
258
+ const joinKeyExclusions = buildJoinKeyExclusionSet(childJoinKeys);
259
+ // Key 순서 캐싱 (첫 번째 레코드에서 결정 재사용)
260
+ let groupKeyOrder;
261
+ for (const record of records) {
262
+ // 그룹 key 추출 및 직렬화 (JOIN key 제외)
263
+ const groupKey = extractGroupKey(record, joinKeyExclusions);
264
+ if (groupKeyOrder == null) {
265
+ groupKeyOrder = Object.keys(groupKey).sort((a, b) => a.localeCompare(b));
266
+ }
267
+ const keyStr = serializeGroupKey(groupKey, groupKeyOrder);
268
+ const existingGroup = groupMap.get(keyStr);
269
+ if (existingGroup != null) {
270
+ // 기존 그룹에 JOIN 데이터 병합
271
+ for (const joinKey of childJoinKeys) {
272
+ const localKey = currentPath === "" ? joinKey : joinKey.slice(currentPath.length + 1);
273
+ mergeJoinData(existingGroup, record, localKey, joinsConfig[joinKey].isSingle);
274
+ }
275
+ }
276
+ else {
277
+ // 새 그룹 생성
278
+ const newGroup = { ...record };
279
+ // 각 JOIN key를 배열 또는 단일 객체로 초기화
280
+ for (const joinKey of childJoinKeys) {
281
+ const localKey = currentPath === "" ? joinKey : joinKey.slice(currentPath.length + 1);
282
+ const joinData = newGroup[localKey];
283
+ if (joinData != null && !isEmptyObject(joinData)) {
284
+ if (!joinsConfig[joinKey].isSingle) {
285
+ // 배열로 변환
286
+ newGroup[localKey] = [joinData];
287
+ }
288
+ }
289
+ else {
290
+ // 데이터가 비어있으면 key 삭제
291
+ delete newGroup[localKey];
292
+ }
293
+ }
294
+ groupMap.set(keyStr, newGroup);
163
295
  }
164
- }
165
- groupMap.set(keyStr, newGroup);
166
296
  }
167
- }
168
- const grouped = Array.from(groupMap.values());
169
- for (const group of grouped) {
170
- for (const joinKey of childJoinKeys) {
171
- const localKey = currentPath === "" ? joinKey : joinKey.slice(currentPath.length + 1);
172
- const joinData = group[localKey];
173
- if (Array.isArray(joinData) && joinData.length > 0) {
174
- group[localKey] = groupRecordsRecursively(
175
- joinData,
176
- allJoinKeys,
177
- joinsConfig,
178
- joinKey
179
- );
180
- } else if (joinData != null && typeof joinData === "object" && !Array.isArray(joinData)) {
181
- const processed = groupRecordsRecursively(
182
- [joinData],
183
- allJoinKeys,
184
- joinsConfig,
185
- joinKey
186
- );
187
- if (processed.length > 0) {
188
- group[localKey] = processed[0];
297
+ // Map을 배열로 변환
298
+ const grouped = Array.from(groupMap.values());
299
+ // JOIN의 하위 레벨을 재귀적으로 처리
300
+ for (const group of grouped) {
301
+ for (const joinKey of childJoinKeys) {
302
+ const localKey = currentPath === "" ? joinKey : joinKey.slice(currentPath.length + 1);
303
+ const joinData = group[localKey];
304
+ if (Array.isArray(joinData) && joinData.length > 0) {
305
+ // 배열인 경우: 하위 레벨을 재귀적으로 처리
306
+ group[localKey] = groupRecordsRecursively(joinData, allJoinKeys, joinsConfig, joinKey);
307
+ }
308
+ else if (joinData != null && typeof joinData === "object" && !Array.isArray(joinData)) {
309
+ // 단일 객체인 경우 (isSingle: true)
310
+ const processed = groupRecordsRecursively([joinData], allJoinKeys, joinsConfig, joinKey);
311
+ if (processed.length > 0) {
312
+ group[localKey] = processed[0];
313
+ }
314
+ }
189
315
  }
190
- }
191
316
  }
192
- }
193
- for (const group of grouped) {
194
- for (const key of Object.keys(group)) {
195
- if (key.startsWith("__hashSet__")) {
196
- delete group[key];
197
- }
317
+ // __hashSet__ 내부 속성 제거 (중복 검사용 임시 속성)
318
+ for (const group of grouped) {
319
+ for (const key of Object.keys(group)) {
320
+ if (key.startsWith("__hashSet__")) {
321
+ delete group[key];
322
+ }
323
+ }
198
324
  }
199
- }
200
- return grouped;
325
+ return grouped;
201
326
  }
327
+ /**
328
+ * 그룹 key에서 제외할 key의 Set 구성 (join key와 그 접두사)
329
+ */
202
330
  function buildJoinKeyExclusionSet(joinKeys) {
203
- const exclusions = /* @__PURE__ */ new Set();
204
- for (const jk of joinKeys) {
205
- exclusions.add(jk);
206
- const parts = jk.split(".");
207
- for (let i = 1; i < parts.length; i++) {
208
- exclusions.add(parts.slice(0, i).join("."));
331
+ const exclusions = new Set();
332
+ for (const jk of joinKeys) {
333
+ exclusions.add(jk);
334
+ // 상위 경로도 제외 (예: join key "posts.comments"에 대해 "posts")
335
+ const parts = jk.split(".");
336
+ for (let i = 1; i < parts.length; i++) {
337
+ exclusions.add(parts.slice(0, i).join("."));
338
+ }
209
339
  }
210
- }
211
- return exclusions;
340
+ return exclusions;
212
341
  }
342
+ /**
343
+ * JOIN key를 제외하고 레코드에서 그룹 key 추출
344
+ */
213
345
  function extractGroupKey(record, joinKeyExclusions) {
214
- const result = {};
215
- for (const [key, value] of Object.entries(record)) {
216
- if (!joinKeyExclusions.has(key)) {
217
- if (value == null || typeof value !== "object") {
218
- result[key] = value;
219
- }
346
+ const result = {};
347
+ for (const [key, value] of Object.entries(record)) {
348
+ // JOIN이 아닌 key 포함
349
+ if (!joinKeyExclusions.has(key)) {
350
+ // 프리미티브 값만 그룹 key 사용 (객체/배열 제외)
351
+ if (value == null || typeof value !== "object") {
352
+ result[key] = value;
353
+ }
354
+ }
220
355
  }
221
- }
222
- return result;
356
+ return result;
223
357
  }
358
+ /**
359
+ * 기존 그룹에 JOIN 데이터 병합
360
+ */
224
361
  function mergeJoinData(existingGroup, newRecord, localKey, isSingle) {
225
- const newJoinData = newRecord[localKey];
226
- if (newJoinData == null || isEmptyObject(newJoinData)) {
227
- return;
228
- }
229
- const existingJoinData = existingGroup[localKey];
230
- if (isSingle) {
231
- if (existingJoinData != null) {
232
- if (!obj.equal(existingJoinData, newJoinData)) {
233
- throw new Error(`isSingle relationship '${localKey}' has multiple different results.`);
234
- }
235
- } else {
236
- existingGroup[localKey] = newJoinData;
362
+ const newJoinData = newRecord[localKey];
363
+ if (newJoinData == null || isEmptyObject(newJoinData)) {
364
+ return; // 병합할 데이터 없음
365
+ }
366
+ const existingJoinData = existingGroup[localKey];
367
+ if (isSingle) {
368
+ // isSingle: true - 데이터가 존재하고 값이 다르면 에러
369
+ if (existingJoinData != null) {
370
+ if (!obj.equal(existingJoinData, newJoinData)) {
371
+ throw new Error(`isSingle 관계 '${localKey}'에 여러 개의 다른 결과가 있습니다.`);
372
+ }
373
+ }
374
+ else {
375
+ existingGroup[localKey] = newJoinData;
376
+ }
237
377
  }
238
- } else {
239
- const hashSetKey = `__hashSet__${localKey}`;
240
- if (!Array.isArray(existingJoinData)) {
241
- existingGroup[localKey] = [newJoinData];
242
- existingGroup[hashSetKey] = /* @__PURE__ */ new Set([serializeGroupKey(newJoinData)]);
243
- } else {
244
- const hashSet = existingGroup[hashSetKey];
245
- const newHash = serializeGroupKey(newJoinData);
246
- if (hashSet != null) {
247
- if (!hashSet.has(newHash)) {
248
- hashSet.add(newHash);
249
- existingJoinData.push(newJoinData);
378
+ else {
379
+ // isSingle: false → 배열에 추가
380
+ const hashSetKey = `__hashSet__${localKey}`;
381
+ if (!Array.isArray(existingJoinData)) {
382
+ existingGroup[localKey] = [newJoinData];
383
+ // Set 기반 중복 검사용 내부 속성 초기화
384
+ existingGroup[hashSetKey] = new Set([serializeGroupKey(newJoinData)]);
250
385
  }
251
- } else {
252
- const isDuplicate = existingJoinData.some(
253
- (item) => obj.equal(item, newJoinData)
254
- );
255
- if (!isDuplicate) {
256
- existingJoinData.push(newJoinData);
386
+ else {
387
+ // Set 기반 중복 검사 (O(1))
388
+ const hashSet = existingGroup[hashSetKey];
389
+ const newHash = serializeGroupKey(newJoinData);
390
+ if (hashSet != null) {
391
+ if (!hashSet.has(newHash)) {
392
+ hashSet.add(newHash);
393
+ existingJoinData.push(newJoinData);
394
+ }
395
+ }
396
+ else {
397
+ // hashSet 없는 폴백 (레거시 방식)
398
+ const isDuplicate = existingJoinData.some((item) => obj.equal(item, newJoinData));
399
+ if (!isDuplicate) {
400
+ existingJoinData.push(newJoinData);
401
+ }
402
+ }
257
403
  }
258
- }
259
404
  }
260
- }
261
405
  }
262
- export {
263
- parseQueryResult
264
- };
265
- //# sourceMappingURL=result-parser.js.map
406
+ //# sourceMappingURL=result-parser.js.map