@simplysm/orm-common 14.0.46 → 14.0.48

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 (54) hide show
  1. package/README.md +269 -0
  2. package/dist/exec/queryable.d.ts +7 -2
  3. package/dist/exec/queryable.d.ts.map +1 -1
  4. package/dist/exec/queryable.js +10 -2
  5. package/dist/exec/queryable.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/query-builder/base/query-builder-base.d.ts +7 -0
  11. package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
  12. package/dist/query-builder/base/query-builder-base.js +9 -0
  13. package/dist/query-builder/base/query-builder-base.js.map +1 -1
  14. package/dist/query-builder/mssql/mssql-expr-renderer.js +1 -1
  15. package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
  16. package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
  17. package/dist/query-builder/mssql/mssql-query-builder.js +3 -0
  18. package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
  19. package/dist/query-builder/mysql/mysql-expr-renderer.js +1 -1
  20. package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
  21. package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
  22. package/dist/query-builder/mysql/mysql-query-builder.js +23 -7
  23. package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
  24. package/dist/query-builder/postgresql/postgresql-expr-renderer.js +1 -1
  25. package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
  26. package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
  27. package/dist/query-builder/postgresql/postgresql-query-builder.js +3 -0
  28. package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
  29. package/dist/utils/pick-result-sets.d.ts +11 -0
  30. package/dist/utils/pick-result-sets.d.ts.map +1 -0
  31. package/dist/utils/pick-result-sets.js +23 -0
  32. package/dist/utils/pick-result-sets.js.map +1 -0
  33. package/dist/utils/result-parser.d.ts.map +1 -1
  34. package/dist/utils/result-parser.js +36 -7
  35. package/dist/utils/result-parser.js.map +1 -1
  36. package/docs/core.md +188 -0
  37. package/docs/expression.md +190 -0
  38. package/docs/models.md +17 -0
  39. package/docs/query-builder.md +97 -0
  40. package/docs/queryable-executable.md +311 -0
  41. package/docs/schema-builders.md +364 -0
  42. package/docs/types.md +537 -0
  43. package/package.json +4 -3
  44. package/src/exec/queryable.ts +13 -2
  45. package/src/index.ts +1 -0
  46. package/src/query-builder/base/query-builder-base.ts +16 -6
  47. package/src/query-builder/mssql/mssql-expr-renderer.ts +3 -3
  48. package/src/query-builder/mssql/mssql-query-builder.ts +10 -7
  49. package/src/query-builder/mysql/mysql-expr-renderer.ts +3 -3
  50. package/src/query-builder/mysql/mysql-query-builder.ts +40 -22
  51. package/src/query-builder/postgresql/postgresql-expr-renderer.ts +3 -3
  52. package/src/query-builder/postgresql/postgresql-query-builder.ts +10 -7
  53. package/src/utils/pick-result-sets.ts +30 -0
  54. package/src/utils/result-parser.ts +56 -22
package/docs/types.md ADDED
@@ -0,0 +1,537 @@
1
+ # Types
2
+
3
+ ## `Dialect`
4
+
5
+ 지원하는 Database dialect.
6
+
7
+ ```typescript
8
+ export type Dialect = "mysql" | "mssql" | "postgresql";
9
+ ```
10
+
11
+ - `mysql`: MySQL 8.0.14+
12
+ - `mssql`: Microsoft SQL Server 2012+
13
+ - `postgresql`: PostgreSQL 9.0+
14
+
15
+ ## `dialects`
16
+
17
+ 모든 Dialect 목록 배열. 테스트에서 dialect별 검증에 사용.
18
+
19
+ ```typescript
20
+ export const dialects: Dialect[] = ["mysql", "mssql", "postgresql"];
21
+ ```
22
+
23
+ ## `IsolationLevel`
24
+
25
+ 트랜잭션 격리 수준.
26
+
27
+ ```typescript
28
+ export type IsolationLevel = "READ_UNCOMMITTED" | "READ_COMMITTED" | "REPEATABLE_READ" | "SERIALIZABLE";
29
+ ```
30
+
31
+ | Value | Description |
32
+ |-------|-------------|
33
+ | `READ_UNCOMMITTED` | 커밋되지 않은 데이터 읽기 가능 (Dirty Read) |
34
+ | `READ_COMMITTED` | 커밋된 데이터만 읽기 (기본값) |
35
+ | `REPEATABLE_READ` | 동일 query가 동일 결과 반환 보장 |
36
+ | `SERIALIZABLE` | 완전 직렬화 (가장 엄격) |
37
+
38
+ ## `DataRecord`
39
+
40
+ 쿼리 결과 데이터 레코드 타입. 재귀적 구조로 중첩 관계(include) 결과를 표현한다.
41
+
42
+ ```typescript
43
+ export type DataRecord = {
44
+ [key: string]: ColumnPrimitive | DataRecord | DataRecord[];
45
+ };
46
+ ```
47
+
48
+ ## `DbContextExecutor`
49
+
50
+ 실제 DB 연결과 쿼리 실행을 담당하는 인터페이스. `orm-node`의 `NodeDbContextExecutor` 또는 서비스 클라이언트 구현으로 제공한다.
51
+
52
+ ```typescript
53
+ export interface DbContextExecutor {
54
+ connect(): Promise<void>;
55
+ close(): Promise<void>;
56
+ beginTransaction(isolationLevel?: IsolationLevel): Promise<void>;
57
+ commitTransaction(): Promise<void>;
58
+ rollbackTransaction(): Promise<void>;
59
+ executeDefs<T = DataRecord>(defs: QueryDef[], resultMetas?: (ResultMeta | undefined)[]): Promise<T[][]>;
60
+ }
61
+ ```
62
+
63
+ | Method | Description |
64
+ |--------|-------------|
65
+ | `connect()` | DB 연결 수립 |
66
+ | `close()` | DB 연결 종료 |
67
+ | `beginTransaction(isolationLevel?)` | 트랜잭션 시작 |
68
+ | `commitTransaction()` | 트랜잭션 커밋 |
69
+ | `rollbackTransaction()` | 트랜잭션 롤백 |
70
+ | `executeDefs(defs, resultMetas?)` | QueryDef 배열 실행 |
71
+
72
+ ## `QueryBuildResult`
73
+
74
+ `QueryBuilder.build()` 반환 타입.
75
+
76
+ ```typescript
77
+ export interface QueryBuildResult {
78
+ sql: string;
79
+ resultSetIndex?: number;
80
+ resultSetStride?: number;
81
+ }
82
+ ```
83
+
84
+ | Field | Type | Description |
85
+ |-------|------|-------------|
86
+ | `sql` | `string` | 빌드된 SQL 문자열 |
87
+ | `resultSetIndex` | `number \| undefined` | 결과를 가져올 결과 셋 index (기본값: 0) |
88
+ | `resultSetStride` | `number \| undefined` | 다중 결과에서 N번째마다 결과 셋 추출 |
89
+
90
+ ## `ResultMeta`
91
+
92
+ SELECT 결과를 TypeScript 객체로 변환할 때 사용하는 메타데이터.
93
+
94
+ ```typescript
95
+ export interface ResultMeta {
96
+ columns: Record<string, ColumnPrimitiveStr>;
97
+ joins: Record<string, { isSingle: boolean }>;
98
+ }
99
+ ```
100
+
101
+ | Field | Type | Description |
102
+ |-------|------|-------------|
103
+ | `columns` | `Record<string, ColumnPrimitiveStr>` | Column 이름 -> 타입 문자열 매핑 |
104
+ | `joins` | `Record<string, { isSingle: boolean }>` | JOIN alias -> 단일/배열 구분 |
105
+
106
+ ## `Migration`
107
+
108
+ Database migration 정의. Schema 변경을 버전 관리한다.
109
+
110
+ ```typescript
111
+ export interface Migration {
112
+ name: string;
113
+ up: (db: DbContextBase & DbContextDdlMethods) => Promise<void>;
114
+ }
115
+ ```
116
+
117
+ | Field | Type | Description |
118
+ |-------|------|-------------|
119
+ | `name` | `string` | 고유 Migration 이름 (타임스탬프 권장) |
120
+ | `up` | `(db) => Promise<void>` | Migration 실행 함수 |
121
+
122
+ ## `parseQueryResult`
123
+
124
+ DB 쿼리 결과를 TypeScript 객체로 변환한다. 타입 파싱 + JOIN 결과 중첩을 처리한다.
125
+
126
+ ```typescript
127
+ export async function parseQueryResult<TRecord>(
128
+ rawResults: Record<string, unknown>[],
129
+ meta: ResultMeta,
130
+ ): Promise<TRecord[] | undefined>;
131
+ ```
132
+
133
+ - meta 필수: meta 없이는 이 함수를 호출할 필요 없음 (입력 = 출력)
134
+ - 빈 결과 처리: 입력 배열이 비어있거나 파싱 후 모든 레코드가 빈 객체이면 undefined 반환
135
+ - async 전용: 대규모 처리 시 이벤트 루프 양보 (100건마다)
136
+ - JOIN 있음: Map 기반 그룹핑으로 O(n) 복잡도
137
+
138
+ ## `pickResultSets`
139
+
140
+ 다중 결과 셋에서 `QueryBuildResult` 메타데이터에 따라 필요한 결과만 추출한다.
141
+
142
+ ```typescript
143
+ export function pickResultSets<T>(
144
+ rawResults: T[][],
145
+ buildResult: Pick<QueryBuildResult, "resultSetIndex" | "resultSetStride">,
146
+ ): T[];
147
+ ```
148
+
149
+ - `resultSetIndex`가 없으면 첫 번째 셋 반환
150
+ - `resultSetStride`가 없으면 `resultSetIndex`번째 셋 단일 반환
151
+ - `resultSetStride`가 있으면 `resultSetIndex`부터 stride 간격으로 모든 셋을 concat하여 반환 (MySQL 배치 INSERT 결과 수집에 사용)
152
+
153
+ ## `DataType`
154
+
155
+ SQL 데이터 타입 정의. discriminated union으로 `type` 필드로 분기한다.
156
+
157
+ ```typescript
158
+ export type DataType =
159
+ | { type: "int" }
160
+ | { type: "bigint" }
161
+ | { type: "float" }
162
+ | { type: "double" }
163
+ | { type: "decimal"; precision: number; scale?: number }
164
+ | { type: "varchar"; length: number }
165
+ | { type: "char"; length: number }
166
+ | { type: "text" }
167
+ | { type: "binary" }
168
+ | { type: "boolean" }
169
+ | { type: "datetime" }
170
+ | { type: "date" }
171
+ | { type: "time" }
172
+ | { type: "uuid" };
173
+ ```
174
+
175
+ ## `ColumnPrimitiveMap`
176
+
177
+ TypeScript 타입 이름(문자열) -> 실제 TypeScript 타입 매핑.
178
+
179
+ ```typescript
180
+ export type ColumnPrimitiveMap = {
181
+ string: string;
182
+ number: number;
183
+ boolean: boolean;
184
+ DateTime: DateTime;
185
+ DateOnly: DateOnly;
186
+ Time: Time;
187
+ Uuid: Uuid;
188
+ Bytes: Bytes;
189
+ };
190
+ ```
191
+
192
+ ## `ColumnPrimitiveStr`
193
+
194
+ Column 원시 타입 이름 문자열. `keyof ColumnPrimitiveMap`.
195
+
196
+ ```typescript
197
+ export type ColumnPrimitiveStr = "string" | "number" | "boolean" | "DateTime" | "DateOnly" | "Time" | "Uuid" | "Bytes";
198
+ ```
199
+
200
+ ## `ColumnPrimitive`
201
+
202
+ Column에 저장 가능한 모든 원시 타입. undefined는 NULL을 나타냄.
203
+
204
+ ```typescript
205
+ export type ColumnPrimitive = string | number | boolean | DateTime | DateOnly | Time | Uuid | Bytes | undefined;
206
+ ```
207
+
208
+ ## `dataTypeStrToColumnPrimitiveStr`
209
+
210
+ SQL DataType 문자열 -> TypeScript 타입 이름 매핑.
211
+
212
+ ```typescript
213
+ export const dataTypeStrToColumnPrimitiveStr: {
214
+ int: "number"; bigint: "number"; float: "number"; double: "number"; decimal: "number";
215
+ varchar: "string"; char: "string"; text: "string";
216
+ binary: "Bytes"; boolean: "boolean";
217
+ datetime: "DateTime"; date: "DateOnly"; time: "Time"; uuid: "Uuid";
218
+ };
219
+ ```
220
+
221
+ ## `InferColumnPrimitiveFromDataType`
222
+
223
+ DataType에서 TypeScript 타입 추론.
224
+
225
+ ```typescript
226
+ export type InferColumnPrimitiveFromDataType<TDataType extends DataType> =
227
+ ColumnPrimitiveMap[(typeof dataTypeStrToColumnPrimitiveStr)[TDataType["type"]]];
228
+ ```
229
+
230
+ ## `inferColumnPrimitiveStr`
231
+
232
+ 런타임 값에서 ColumnPrimitiveStr을 추론한다. NULL 값으로는 추론 불가 (에러 발생).
233
+
234
+ ```typescript
235
+ export function inferColumnPrimitiveStr(value: ColumnPrimitive): ColumnPrimitiveStr;
236
+ ```
237
+
238
+ ## `ColumnMeta`
239
+
240
+ Column 메타데이터. ColumnBuilder에서 생성되어 TableBuilder에 전달된다.
241
+
242
+ ```typescript
243
+ export interface ColumnMeta {
244
+ type: ColumnPrimitiveStr;
245
+ dataType: DataType;
246
+ autoIncrement?: boolean;
247
+ nullable?: boolean;
248
+ default?: ColumnPrimitive;
249
+ description?: string;
250
+ }
251
+ ```
252
+
253
+ | Field | Type | Description |
254
+ |-------|------|-------------|
255
+ | `type` | `ColumnPrimitiveStr` | TypeScript 타입 이름 |
256
+ | `dataType` | `DataType` | SQL 데이터 타입 |
257
+ | `autoIncrement` | `boolean \| undefined` | 자동 증가 여부 |
258
+ | `nullable` | `boolean \| undefined` | NULL 허용 여부 |
259
+ | `default` | `ColumnPrimitive \| undefined` | 기본값 |
260
+ | `description` | `string \| undefined` | Column 설명 |
261
+
262
+ ## `DateUnit`
263
+
264
+ 날짜 단위.
265
+
266
+ ```typescript
267
+ export type DateUnit = "year" | "month" | "day" | "hour" | "minute" | "second";
268
+ ```
269
+
270
+ ## `Expr`
271
+
272
+ 모든 표현식 AST의 유니온 타입. ExprColumn, ExprValue, ExprRaw와 60+ 개의 개별 표현식 타입의 합집합.
273
+
274
+ ```typescript
275
+ export type Expr = ExprColumn | ExprValue | ExprRaw | ExprConcat | ExprLeft | ... | ExprWindow | ExprSubquery;
276
+ ```
277
+
278
+ 주요 표현식 인터페이스 (각각 `type` discriminant 필드를 가짐):
279
+
280
+ | Interface | type | Description |
281
+ |-----------|------|-------------|
282
+ | `ExprColumn` | `"column"` | Column 참조 (`path: string[]`) |
283
+ | `ExprValue` | `"value"` | 리터럴 값 (`value: unknown`) |
284
+ | `ExprRaw` | `"raw"` | Raw SQL (`sql: string, params: Expr[]`) |
285
+ | `ExprEq` | `"eq"` | 동등 비교 |
286
+ | `ExprGt` | `"gt"` | 초과 비교 |
287
+ | `ExprLt` | `"lt"` | 미만 비교 |
288
+ | `ExprGte` | `"gte"` | 이상 비교 |
289
+ | `ExprLte` | `"lte"` | 이하 비교 |
290
+ | `ExprBetween` | `"between"` | 범위 비교 |
291
+ | `ExprIsNull` | `"null"` | NULL 체크 |
292
+ | `ExprLike` | `"like"` | LIKE 패턴 |
293
+ | `ExprRegexp` | `"regexp"` | 정규식 매칭 |
294
+ | `ExprIn` | `"in"` | IN 목록 |
295
+ | `ExprInQuery` | `"inQuery"` | IN 서브쿼리 |
296
+ | `ExprExists` | `"exists"` | EXISTS 서브쿼리 |
297
+ | `ExprNot` | `"not"` | NOT |
298
+ | `ExprAnd` | `"and"` | AND |
299
+ | `ExprOr` | `"or"` | OR |
300
+ | `ExprConcat` | `"concat"` | 문자열 연결 |
301
+ | `ExprCount` | `"count"` | COUNT 집계 |
302
+ | `ExprSum` | `"sum"` | SUM 집계 |
303
+ | `ExprAvg` | `"avg"` | AVG 집계 |
304
+ | `ExprMax` | `"max"` | MAX 집계 |
305
+ | `ExprMin` | `"min"` | MIN 집계 |
306
+ | `ExprWindow` | `"window"` | Window 함수 |
307
+ | `ExprSubquery` | `"subquery"` | 스칼라 서브쿼리 |
308
+ | `ExprCast` | `"cast"` | 타입 변환 |
309
+ | `ExprSwitch` | `"switch"` | CASE WHEN |
310
+ | `ExprIf` | `"if"` | IF 조건 |
311
+ | `ExprCoalesce` | `"coalesce"` | COALESCE |
312
+
313
+ ## `WhereExpr`
314
+
315
+ WHERE 절 표현식 AST 유니온 타입.
316
+
317
+ ```typescript
318
+ export type WhereExpr = ExprEq | ExprGt | ExprLt | ExprGte | ExprLte | ExprBetween | ExprIsNull | ExprLike | ExprRegexp | ExprIn | ExprInQuery | ExprExists | ExprNot | ExprAnd | ExprOr;
319
+ ```
320
+
321
+ ## `WinSpec`
322
+
323
+ Window 함수 스펙.
324
+
325
+ ```typescript
326
+ export interface WinSpec {
327
+ partitionBy?: Expr[];
328
+ orderBy?: [Expr, ("ASC" | "DESC")?][];
329
+ }
330
+ ```
331
+
332
+ | Field | Type | Description |
333
+ |-------|------|-------------|
334
+ | `partitionBy` | `Expr[] \| undefined` | 파티션 기준 표현식 |
335
+ | `orderBy` | `[Expr, ("ASC" \| "DESC")?][] \| undefined` | 정렬 기준 |
336
+
337
+ ## `WinFn`
338
+
339
+ Window 함수 유니온 타입.
340
+
341
+ ```typescript
342
+ export type WinFn = WinFnRowNumber | WinFnRank | WinFnDenseRank | WinFnNtile | WinFnLag | WinFnLead | WinFnFirstValue | WinFnLastValue | WinFnSum | WinFnAvg | WinFnCount | WinFnMin | WinFnMax;
343
+ ```
344
+
345
+ ## `QueryDef`
346
+
347
+ 모든 쿼리 정의의 유니온 타입.
348
+
349
+ ```typescript
350
+ export type QueryDef = SelectQueryDef | InsertQueryDef | InsertIfNotExistsQueryDef | InsertIntoQueryDef | UpdateQueryDef | DeleteQueryDef | UpsertQueryDef | ExecProcQueryDef | /* DDL types */;
351
+ ```
352
+
353
+ ## `SelectQueryDef`
354
+
355
+ SELECT 쿼리 정의.
356
+
357
+ ```typescript
358
+ export interface SelectQueryDef {
359
+ type: "select";
360
+ from?: QueryDefObjectName | SelectQueryDef | SelectQueryDef[] | string;
361
+ as: string;
362
+ select?: Record<string, Expr>;
363
+ distinct?: boolean;
364
+ top?: number;
365
+ lock?: boolean;
366
+ where?: WhereExpr[];
367
+ joins?: SelectQueryDefJoin[];
368
+ orderBy?: [Expr, ("ASC" | "DESC")?][];
369
+ limit?: [number, number];
370
+ groupBy?: Expr[];
371
+ having?: WhereExpr[];
372
+ with?: { name: string; base: SelectQueryDef; recursive: SelectQueryDef };
373
+ }
374
+ ```
375
+
376
+ ## `InsertQueryDef`
377
+
378
+ INSERT 쿼리 정의.
379
+
380
+ ```typescript
381
+ export interface InsertQueryDef {
382
+ type: "insert";
383
+ table: QueryDefObjectName;
384
+ records: Record<string, unknown>[];
385
+ overrideIdentity?: boolean;
386
+ output?: CudOutputDef;
387
+ }
388
+ ```
389
+
390
+ ## `UpdateQueryDef`
391
+
392
+ UPDATE 쿼리 정의.
393
+
394
+ ```typescript
395
+ export interface UpdateQueryDef {
396
+ type: "update";
397
+ table: QueryDefObjectName;
398
+ as: string;
399
+ record: Record<string, Expr>;
400
+ top?: number;
401
+ where?: WhereExpr[];
402
+ joins?: SelectQueryDefJoin[];
403
+ limit?: [number, number];
404
+ output?: CudOutputDef;
405
+ }
406
+ ```
407
+
408
+ ## `DeleteQueryDef`
409
+
410
+ DELETE 쿼리 정의.
411
+
412
+ ```typescript
413
+ export interface DeleteQueryDef {
414
+ type: "delete";
415
+ table: QueryDefObjectName;
416
+ as: string;
417
+ top?: number;
418
+ where?: WhereExpr[];
419
+ joins?: SelectQueryDefJoin[];
420
+ limit?: [number, number];
421
+ output?: CudOutputDef;
422
+ }
423
+ ```
424
+
425
+ ## `UpsertQueryDef`
426
+
427
+ UPSERT 쿼리 정의.
428
+
429
+ ```typescript
430
+ export interface UpsertQueryDef {
431
+ type: "upsert";
432
+ table: QueryDefObjectName;
433
+ existsSelectQuery: Omit<SelectQueryDef, "select">;
434
+ updateRecord: Record<string, Expr>;
435
+ insertRecord: Record<string, Expr>;
436
+ output?: CudOutputDef;
437
+ }
438
+ ```
439
+
440
+ ## `QueryDefObjectName`
441
+
442
+ 테이블/뷰 이름 정의.
443
+
444
+ ```typescript
445
+ export interface QueryDefObjectName {
446
+ database?: string;
447
+ schema?: string;
448
+ name: string;
449
+ }
450
+ ```
451
+
452
+ | Field | Type | Description |
453
+ |-------|------|-------------|
454
+ | `database` | `string \| undefined` | 데이터베이스 이름 |
455
+ | `schema` | `string \| undefined` | 스키마 이름 |
456
+ | `name` | `string` | 테이블/뷰 이름 |
457
+
458
+ ## `CudOutputDef`
459
+
460
+ CUD 작업 OUTPUT 절 정의.
461
+
462
+ ```typescript
463
+ export interface CudOutputDef {
464
+ columns: string[];
465
+ pkColNames: string[];
466
+ aiColName?: string;
467
+ }
468
+ ```
469
+
470
+ | Field | Type | Description |
471
+ |-------|------|-------------|
472
+ | `columns` | `string[]` | 반환할 column 이름 |
473
+ | `pkColNames` | `string[]` | PK column 이름 |
474
+ | `aiColName` | `string \| undefined` | AutoIncrement column 이름 |
475
+
476
+ ## `SelectQueryDefJoin`
477
+
478
+ JOIN 쿼리 정의.
479
+
480
+ ```typescript
481
+ export interface SelectQueryDefJoin extends SelectQueryDef {
482
+ as: string;
483
+ isSingle: boolean;
484
+ }
485
+ ```
486
+
487
+ ## `DDL_TYPES`
488
+
489
+ DDL 타입 문자열 목록.
490
+
491
+ ```typescript
492
+ export const DDL_TYPES = [
493
+ "clearSchema", "createTable", "dropTable", "renameTable", "truncate",
494
+ "addColumn", "dropColumn", "modifyColumn", "renameColumn",
495
+ "dropPrimaryKey", "addPrimaryKey", "addForeignKey", "dropForeignKey", "addIndex", "dropIndex",
496
+ "createView", "dropView", "createProc", "dropProc",
497
+ ] as const satisfies readonly DdlQueryDef["type"][];
498
+ ```
499
+
500
+ ## `DdlType`
501
+
502
+ DDL 타입 유니온.
503
+
504
+ ```typescript
505
+ export type DdlType = (typeof DDL_TYPES)[number];
506
+ ```
507
+
508
+ ## DDL QueryDef 인터페이스
509
+
510
+ 각 DDL 작업을 위한 QueryDef 인터페이스. 모두 `type` discriminant 필드를 가진다.
511
+
512
+ | Interface | type | Description |
513
+ |-----------|------|-------------|
514
+ | `SwitchFkQueryDef` | `"switchFk"` | FK 제약조건 활성화/비활성화 |
515
+ | `ClearSchemaQueryDef` | `"clearSchema"` | 스키마 전체 삭제 |
516
+ | `CreateTableQueryDef` | `"createTable"` | 테이블 생성 |
517
+ | `DropTableQueryDef` | `"dropTable"` | 테이블 삭제 |
518
+ | `RenameTableQueryDef` | `"renameTable"` | 테이블 이름 변경 |
519
+ | `TruncateQueryDef` | `"truncate"` | 테이블 데이터 전체 삭제 |
520
+ | `AddColumnQueryDef` | `"addColumn"` | Column 추가 |
521
+ | `DropColumnQueryDef` | `"dropColumn"` | Column 삭제 |
522
+ | `ModifyColumnQueryDef` | `"modifyColumn"` | Column 수정 |
523
+ | `RenameColumnQueryDef` | `"renameColumn"` | Column 이름 변경 |
524
+ | `DropPrimaryKeyQueryDef` | `"dropPrimaryKey"` | PK 삭제 |
525
+ | `AddPrimaryKeyQueryDef` | `"addPrimaryKey"` | PK 추가 |
526
+ | `AddForeignKeyQueryDef` | `"addForeignKey"` | FK 추가 |
527
+ | `DropForeignKeyQueryDef` | `"dropForeignKey"` | FK 삭제 |
528
+ | `AddIndexQueryDef` | `"addIndex"` | Index 추가 |
529
+ | `DropIndexQueryDef` | `"dropIndex"` | Index 삭제 |
530
+ | `CreateViewQueryDef` | `"createView"` | View 생성 |
531
+ | `DropViewQueryDef` | `"dropView"` | View 삭제 |
532
+ | `CreateProcQueryDef` | `"createProc"` | Procedure 생성 |
533
+ | `DropProcQueryDef` | `"dropProc"` | Procedure 삭제 |
534
+ | `SchemaExistsQueryDef` | `"schemaExists"` | 스키마 존재 확인 |
535
+ | `ExecProcQueryDef` | `"execProc"` | Procedure 실행 |
536
+ | `InsertIfNotExistsQueryDef` | `"insertIfNotExists"` | 조건부 INSERT |
537
+ | `InsertIntoQueryDef` | `"insertInto"` | INSERT INTO SELECT |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/orm-common",
3
- "version": "14.0.46",
3
+ "version": "14.0.48",
4
4
  "description": "심플리즘 패키지 - ORM (common)",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -14,13 +14,14 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
- "src"
17
+ "src",
18
+ "docs"
18
19
  ],
19
20
  "sideEffects": false,
20
21
  "devDependencies": {
21
22
  "@types/node": "^20.19.39"
22
23
  },
23
24
  "dependencies": {
24
- "@simplysm/core-common": "14.0.46"
25
+ "@simplysm/core-common": "14.0.48"
25
26
  }
26
27
  }
@@ -405,7 +405,10 @@ export class Queryable<
405
405
  /**
406
406
  * 정렬 조건 추가. 여러 번 호출 시 순서대로 적용됨.
407
407
  *
408
- * @param fn - 정렬할 column을 반환하는 함수
408
+ * 문자열 overload는 체인 경로를 받아 `obj.getChainValue`로 컬럼을 찾는다.
409
+ * 동적 정렬(sortingDefs 루프 등)에서 보일러플레이트를 줄이는 용도.
410
+ *
411
+ * @param fnOrKey - 정렬할 column을 반환하는 함수 또는 체인 경로 문자열
409
412
  * @param orderBy - 정렬 방향 (ASC/DESC). 기본값: ASC
410
413
  * @returns 정렬 조건이 추가된 Queryable
411
414
  *
@@ -414,12 +417,20 @@ export class Queryable<
414
417
  * db.user
415
418
  * .orderBy((u) => u.name) // name ASC
416
419
  * .orderBy((u) => u.age, "DESC") // age DESC
420
+ * .orderBy("id", "DESC") // string overload
421
+ * .orderBy("user.name") // chain path
417
422
  * ```
418
423
  */
419
424
  orderBy(
420
- fn: (columns: QueryableRecord<TData>) => ExprUnit<ColumnPrimitive>,
425
+ fnOrKey: string | ((columns: QueryableRecord<TData>) => ExprUnit<ColumnPrimitive>),
421
426
  orderBy?: "ASC" | "DESC",
422
427
  ): Queryable<TData, TFrom> {
428
+ const fn =
429
+ typeof fnOrKey === "string"
430
+ ? (columns: QueryableRecord<TData>) =>
431
+ obj.getChainValue(columns, fnOrKey, true) as ExprUnit<ColumnPrimitive>
432
+ : fnOrKey;
433
+
423
434
  if (Array.isArray(this.meta.from)) {
424
435
  const newFroms = this.meta.from.map((from) => from.orderBy(fn, orderBy));
425
436
  return new Queryable({
package/src/index.ts CHANGED
@@ -63,6 +63,7 @@ export * from "./types/db";
63
63
 
64
64
  // 결과 파싱
65
65
  export * from "./utils/result-parser";
66
+ export * from "./utils/pick-result-sets";
66
67
 
67
68
  // column 타입
68
69
  export * from "./types/column";
@@ -127,18 +127,28 @@ export abstract class QueryBuilderBase {
127
127
  * 사용자가 직접 .select()를 호출하지 않더라도 내부 .joinSingle()이
128
128
  * select/joins를 추가할 수 있으며, 이 경우에도 서브쿼리가 필요함.
129
129
  */
130
- protected needsLateral(join: SelectQueryDefJoin): boolean {
130
+ protected needsLateral(join: SelectQueryDefJoin): boolean {
131
131
  // from이 배열이면 항상 LATERAL (UNION ALL 패턴)
132
132
  if (Array.isArray(join.from)) {
133
133
  return true;
134
134
  }
135
135
 
136
136
  // 기본 JOIN 속성 외 추가 속성이 있으면 LATERAL 필요
137
- return Object.keys(join).some((key) => !BASIC_JOIN_PROPS.has(key));
138
- }
139
-
140
- /** FROM 절 소스 렌더링 */
141
- protected renderFrom(from: SelectQueryDef["from"]): string {
137
+ return Object.keys(join).some((key) => !BASIC_JOIN_PROPS.has(key));
138
+ }
139
+
140
+ /**
141
+ * recursive() 내부에서 생성되는 self 참조 JOIN 감지
142
+ *
143
+ * RecursiveQueryable이 현재 CTE를 `...self` 별칭으로 다시 붙일 때만 사용된다.
144
+ * 이 경우 OUTER JOIN이 아니라 CROSS JOIN이어야 DB별 재귀 CTE 제약을 피할 수 있다.
145
+ */
146
+ protected isRecursiveSelfJoin(join: SelectQueryDefJoin): boolean {
147
+ return typeof join.from === "string" && join.as.endsWith(".self");
148
+ }
149
+
150
+ /** FROM 절 소스 렌더링 */
151
+ protected renderFrom(from: SelectQueryDef["from"]): string {
142
152
  if (from == null) {
143
153
  throw new Error("FROM 절이 필요합니다.");
144
154
  }
@@ -325,9 +325,9 @@ export class MssqlExprRenderer extends ExprRendererBase {
325
325
  return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)}, LEN(${this.render(expr.source)}))`;
326
326
  }
327
327
 
328
- protected indexOf(expr: ExprIndexOf): string {
329
- return `CHARINDEX(${this.render(expr.search)}, ${this.render(expr.source)})`;
330
- }
328
+ protected indexOf(expr: ExprIndexOf): string {
329
+ return `(CHARINDEX(${this.render(expr.search)}, ${this.render(expr.source)}) - 1)`;
330
+ }
331
331
 
332
332
  //#endregion
333
333
 
@@ -74,13 +74,16 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
74
74
  // 그 외(orderBy, top, select 등)이면 renderFrom(join)으로 서브쿼리 생성
75
75
  const from = Array.isArray(join.from) ? this.renderFrom(join.from) : this.renderFrom(join);
76
76
  return ` OUTER APPLY ${from} AS ${alias}`;
77
- }
78
-
79
- // 일반 JOIN
80
- const from = this.renderFrom(join.from);
81
- const where =
82
- join.where != null && join.where.length > 0
83
- ? ` ON ${this.expr.renderWhere(join.where)}`
77
+ }
78
+
79
+ // 일반 JOIN
80
+ const from = this.renderFrom(join.from);
81
+ if (this.isRecursiveSelfJoin(join)) {
82
+ return ` CROSS JOIN ${from} AS ${alias}`;
83
+ }
84
+ const where =
85
+ join.where != null && join.where.length > 0
86
+ ? ` ON ${this.expr.renderWhere(join.where)}`
84
87
  : " ON 1=1";
85
88
  return ` LEFT OUTER JOIN ${from} AS ${alias}${where}`;
86
89
  }
@@ -323,9 +323,9 @@ export class MysqlExprRenderer extends ExprRendererBase {
323
323
  return `SUBSTRING(${this.render(expr.source)}, ${this.render(expr.start)})`;
324
324
  }
325
325
 
326
- protected indexOf(expr: ExprIndexOf): string {
327
- return `LOCATE(${this.render(expr.search)}, ${this.render(expr.source)})`;
328
- }
326
+ protected indexOf(expr: ExprIndexOf): string {
327
+ return `(LOCATE(${this.render(expr.search)}, ${this.render(expr.source)}) - 1)`;
328
+ }
329
329
 
330
330
  //#endregion
331
331