@simplysm/orm-common 13.0.76 → 13.0.77
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.
- package/README.md +575 -50
- package/dist/create-db-context.d.ts +1 -1
- package/dist/create-db-context.d.ts.map +1 -1
- package/dist/create-db-context.js +34 -27
- package/dist/create-db-context.js.map +1 -1
- package/dist/ddl/initialize.js +4 -4
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/relation-ddl.d.ts +7 -7
- package/dist/ddl/relation-ddl.d.ts.map +1 -1
- package/dist/ddl/relation-ddl.js +18 -18
- package/dist/ddl/relation-ddl.js.map +1 -1
- package/dist/ddl/schema-ddl.d.ts +1 -1
- package/dist/ddl/schema-ddl.d.ts.map +1 -1
- package/dist/ddl/schema-ddl.js +2 -2
- package/dist/ddl/schema-ddl.js.map +1 -1
- package/dist/ddl/table-ddl.js +2 -2
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/exec/queryable.d.ts +24 -24
- package/dist/exec/queryable.d.ts.map +1 -1
- package/dist/exec/queryable.js +37 -37
- package/dist/exec/queryable.js.map +1 -1
- package/dist/expr/expr-unit.js +1 -1
- package/dist/expr/expr-unit.js.map +1 -1
- package/dist/expr/expr.d.ts +9 -9
- package/dist/expr/expr.d.ts.map +1 -1
- package/dist/expr/expr.js +10 -10
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/base/expr-renderer-base.d.ts +2 -2
- package/dist/query-builder/base/expr-renderer-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.d.ts +7 -15
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +2 -2
- package/dist/query-builder/base/query-builder-base.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +8 -8
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +7 -7
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +7 -7
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +9 -9
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +7 -7
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +11 -11
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +8 -8
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +7 -7
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +7 -7
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/schema/procedure-builder.d.ts +1 -1
- package/dist/schema/table-builder.d.ts +1 -1
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +1 -1
- package/dist/schema/view-builder.d.ts +1 -1
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +1 -1
- package/dist/types/db-context-def.d.ts +18 -18
- package/dist/types/db-context-def.d.ts.map +1 -1
- package/dist/types/expr.d.ts +6 -6
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +15 -15
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/types/query-def.js +6 -6
- package/dist/utils/result-parser.d.ts.map +1 -1
- package/dist/utils/result-parser.js +44 -16
- package/dist/utils/result-parser.js.map +1 -1
- package/package.json +2 -2
- package/src/create-db-context.ts +36 -29
- package/src/ddl/initialize.ts +4 -4
- package/src/ddl/relation-ddl.ts +16 -16
- package/src/ddl/schema-ddl.ts +2 -2
- package/src/ddl/table-ddl.ts +2 -2
- package/src/exec/queryable.ts +58 -58
- package/src/expr/expr-unit.ts +1 -1
- package/src/expr/expr.ts +13 -13
- package/src/query-builder/base/expr-renderer-base.ts +2 -2
- package/src/query-builder/base/query-builder-base.ts +18 -14
- package/src/query-builder/mssql/mssql-expr-renderer.ts +11 -10
- package/src/query-builder/mssql/mssql-query-builder.ts +13 -13
- package/src/query-builder/mysql/mysql-expr-renderer.ts +12 -11
- package/src/query-builder/mysql/mysql-query-builder.ts +17 -17
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +11 -10
- package/src/query-builder/postgresql/postgresql-query-builder.ts +13 -13
- package/src/schema/procedure-builder.ts +1 -1
- package/src/schema/table-builder.ts +1 -1
- package/src/schema/view-builder.ts +1 -1
- package/src/types/db-context-def.ts +18 -18
- package/src/types/expr.ts +6 -6
- package/src/types/query-def.ts +31 -31
- package/src/utils/result-parser.ts +60 -16
- package/tests/db-context/create-db-context.spec.ts +6 -6
- package/tests/ddl/basic.expected.ts +8 -8
- package/tests/ddl/basic.spec.ts +24 -24
- package/tests/ddl/index-builder.spec.ts +10 -10
- package/tests/ddl/relation-builder.spec.ts +4 -4
- package/tests/dml/update.spec.ts +2 -2
- package/tests/expr/conditional.expected.ts +2 -2
- package/tests/expr/conditional.spec.ts +8 -8
- package/tests/expr/date.spec.ts +5 -5
- package/tests/select/basic.spec.ts +5 -5
- package/tests/utils/result-parser.spec.ts +4 -4
package/src/exec/queryable.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type { ColumnPrimitive, ColumnPrimitiveStr } from "../types/column";
|
|
|
22
22
|
import type { WhereExprUnit, ExprInput } from "../expr/expr-unit";
|
|
23
23
|
import { ExprUnit } from "../expr/expr-unit";
|
|
24
24
|
import type { Expr } from "../types/expr";
|
|
25
|
-
import { ArgumentError,
|
|
25
|
+
import { ArgumentError, obj } from "@simplysm/core-common";
|
|
26
26
|
import {
|
|
27
27
|
ForeignKeyBuilder,
|
|
28
28
|
ForeignKeyTargetBuilder,
|
|
@@ -49,7 +49,7 @@ class JoinQueryable {
|
|
|
49
49
|
* @param table - Table to join
|
|
50
50
|
* @returns Joined Queryable
|
|
51
51
|
*/
|
|
52
|
-
from<T extends TableBuilder<any, any>>(table: T): Queryable<T["$
|
|
52
|
+
from<T extends TableBuilder<any, any>>(table: T): Queryable<T["$inferSelect"], T> {
|
|
53
53
|
return queryable(this._db, table, this._joinAlias)();
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -115,7 +115,7 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
115
115
|
*/
|
|
116
116
|
from<T extends TableBuilder<any, any>>(
|
|
117
117
|
table: T,
|
|
118
|
-
): Queryable<T["$
|
|
118
|
+
): Queryable<T["$inferSelect"] & { self?: TBaseData[] }, T> {
|
|
119
119
|
const selfAlias = `${this._cteName}.self`;
|
|
120
120
|
|
|
121
121
|
return queryable(this._baseQr.meta.db, table, this._cteName)().join(
|
|
@@ -214,12 +214,12 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
214
214
|
* const users = await db.user()
|
|
215
215
|
* .where((u) => [expr.eq(u.isActive, true)])
|
|
216
216
|
* .orderBy((u) => u.name)
|
|
217
|
-
* .
|
|
217
|
+
* .execute();
|
|
218
218
|
*
|
|
219
219
|
* // JOIN query
|
|
220
220
|
* const posts = await db.post()
|
|
221
221
|
* .include((p) => p.user)
|
|
222
|
-
* .
|
|
222
|
+
* .execute();
|
|
223
223
|
*
|
|
224
224
|
* // INSERT
|
|
225
225
|
* await db.user().insert([{ name: "Gildong Hong", email: "test@test.com" }]);
|
|
@@ -621,7 +621,7 @@ export class Queryable<
|
|
|
621
621
|
* 1:N 관계의 LEFT OUTER JOIN을 수행 (배열로 result Add)
|
|
622
622
|
*
|
|
623
623
|
* @param as - Result에 추가할 property 이름
|
|
624
|
-
* @param
|
|
624
|
+
* @param fn - Join 조건을 정의하는 콜백 function
|
|
625
625
|
* @returns join 결과가 배열로 추가된 Queryable
|
|
626
626
|
*
|
|
627
627
|
* @example
|
|
@@ -636,10 +636,10 @@ export class Queryable<
|
|
|
636
636
|
*/
|
|
637
637
|
join<A extends string, R extends DataRecord>(
|
|
638
638
|
as: A,
|
|
639
|
-
|
|
639
|
+
fn: (qr: JoinQueryable, cols: QueryableRecord<TData>) => Queryable<R, any>,
|
|
640
640
|
): Queryable<TData & { [K in A]?: R[] }, TFrom> {
|
|
641
641
|
if (Array.isArray(this.meta.from)) {
|
|
642
|
-
const newFroms = this.meta.from.map((from) => from.join(as,
|
|
642
|
+
const newFroms = this.meta.from.map((from) => from.join(as, fn));
|
|
643
643
|
return new Queryable({
|
|
644
644
|
...this.meta,
|
|
645
645
|
from: newFroms,
|
|
@@ -653,8 +653,8 @@ export class Queryable<
|
|
|
653
653
|
// 2. target → Queryable Transform (alias 전달)
|
|
654
654
|
const joinQr = new JoinQueryable(this.meta.db, joinAlias);
|
|
655
655
|
|
|
656
|
-
// 3.
|
|
657
|
-
const resultQr =
|
|
656
|
+
// 3. fn 실행 (where 등 condition 추가된 Queryable return)
|
|
657
|
+
const resultQr = fn(joinQr, this.meta.columns);
|
|
658
658
|
|
|
659
659
|
// 4. 새 columns에 join result Add
|
|
660
660
|
const joinColumns = transformColumnsAlias(resultQr.meta.columns, joinAlias);
|
|
@@ -674,7 +674,7 @@ export class Queryable<
|
|
|
674
674
|
* N:1 또는 1:1 관계의 LEFT OUTER JOIN을 수행 (단일 객체로 result Add)
|
|
675
675
|
*
|
|
676
676
|
* @param as - Result에 추가할 property 이름
|
|
677
|
-
* @param
|
|
677
|
+
* @param fn - Join 조건을 정의하는 콜백 function
|
|
678
678
|
* @returns join 결과가 단일 객체로 추가된 Queryable
|
|
679
679
|
*
|
|
680
680
|
* @example
|
|
@@ -689,13 +689,13 @@ export class Queryable<
|
|
|
689
689
|
*/
|
|
690
690
|
joinSingle<A extends string, R extends DataRecord>(
|
|
691
691
|
as: A,
|
|
692
|
-
|
|
692
|
+
fn: (qr: JoinQueryable, cols: QueryableRecord<TData>) => Queryable<R, any>,
|
|
693
693
|
): Queryable<
|
|
694
694
|
{ [K in keyof TData as K extends A ? never : K]: TData[K] } & { [K in A]?: R },
|
|
695
695
|
TFrom
|
|
696
696
|
> {
|
|
697
697
|
if (Array.isArray(this.meta.from)) {
|
|
698
|
-
const newFroms = this.meta.from.map((from) => from.joinSingle(as,
|
|
698
|
+
const newFroms = this.meta.from.map((from) => from.joinSingle(as, fn));
|
|
699
699
|
return new Queryable({
|
|
700
700
|
...this.meta,
|
|
701
701
|
from: newFroms,
|
|
@@ -709,8 +709,8 @@ export class Queryable<
|
|
|
709
709
|
// 2. target → Queryable Transform (alias 전달)
|
|
710
710
|
const joinQr = new JoinQueryable(this.meta.db, joinAlias);
|
|
711
711
|
|
|
712
|
-
// 3.
|
|
713
|
-
const resultQr =
|
|
712
|
+
// 3. fn 실행 (where 등 condition 추가된 Queryable return)
|
|
713
|
+
const resultQr = fn(joinQr, this.meta.columns);
|
|
714
714
|
|
|
715
715
|
// 4. 새 columns에 join result Add
|
|
716
716
|
const joinColumns = transformColumnsAlias(resultQr.meta.columns, joinAlias);
|
|
@@ -957,7 +957,7 @@ export class Queryable<
|
|
|
957
957
|
*
|
|
958
958
|
* 계층 structure data(조직도, 카테고리 트리 등)를 조회할 때 사용
|
|
959
959
|
*
|
|
960
|
-
* @param
|
|
960
|
+
* @param fn - recursive part을 정의하는 콜백 function
|
|
961
961
|
* @returns recursive CTE가 apply된 Queryable
|
|
962
962
|
*
|
|
963
963
|
* @example
|
|
@@ -972,10 +972,10 @@ export class Queryable<
|
|
|
972
972
|
* ```
|
|
973
973
|
*/
|
|
974
974
|
recursive(
|
|
975
|
-
|
|
975
|
+
fn: (qr: RecursiveQueryable<TData>) => Queryable<TData, any>,
|
|
976
976
|
): Queryable<TData, never> {
|
|
977
977
|
if (Array.isArray(this.meta.from)) {
|
|
978
|
-
const newFroms = this.meta.from.map((from) => from.recursive(
|
|
978
|
+
const newFroms = this.meta.from.map((from) => from.recursive(fn));
|
|
979
979
|
return new Queryable({
|
|
980
980
|
...this.meta,
|
|
981
981
|
from: newFroms,
|
|
@@ -988,8 +988,8 @@ export class Queryable<
|
|
|
988
988
|
// 2. target → Queryable Transform (CTE 이름 전달)
|
|
989
989
|
const cteQr = new RecursiveQueryable(this, cteName);
|
|
990
990
|
|
|
991
|
-
// 3.
|
|
992
|
-
const resultQr =
|
|
991
|
+
// 3. fn 실행 (where 등 condition 추가된 Queryable return)
|
|
992
|
+
const resultQr = fn(cteQr);
|
|
993
993
|
|
|
994
994
|
return new Queryable({
|
|
995
995
|
db: this.meta.db,
|
|
@@ -1017,10 +1017,10 @@ export class Queryable<
|
|
|
1017
1017
|
* ```typescript
|
|
1018
1018
|
* const users = await db.user()
|
|
1019
1019
|
* .where((u) => [expr.eq(u.isActive, true)])
|
|
1020
|
-
* .
|
|
1020
|
+
* .execute();
|
|
1021
1021
|
* ```
|
|
1022
1022
|
*/
|
|
1023
|
-
async
|
|
1023
|
+
async execute(): Promise<TData[]> {
|
|
1024
1024
|
const results = await this.meta.db.executeDefs<TData>(
|
|
1025
1025
|
[this.getSelectQueryDef()],
|
|
1026
1026
|
[this.getResultMeta()],
|
|
@@ -1042,7 +1042,7 @@ export class Queryable<
|
|
|
1042
1042
|
* ```
|
|
1043
1043
|
*/
|
|
1044
1044
|
async single(): Promise<TData | undefined> {
|
|
1045
|
-
const result = await this.top(2).
|
|
1045
|
+
const result = await this.top(2).execute();
|
|
1046
1046
|
if (result.length > 1) {
|
|
1047
1047
|
throw new ArgumentError("Expected single result but multiple results returned.", {
|
|
1048
1048
|
table: this._getSourceName(),
|
|
@@ -1079,14 +1079,14 @@ export class Queryable<
|
|
|
1079
1079
|
* ```
|
|
1080
1080
|
*/
|
|
1081
1081
|
async first(): Promise<TData | undefined> {
|
|
1082
|
-
const results = await this.top(1).
|
|
1082
|
+
const results = await this.top(1).execute();
|
|
1083
1083
|
return results[0];
|
|
1084
1084
|
}
|
|
1085
1085
|
|
|
1086
1086
|
/**
|
|
1087
1087
|
* result row 수를 return
|
|
1088
1088
|
*
|
|
1089
|
-
* @param
|
|
1089
|
+
* @param fn - 카운트할 column을 지정하는 function (Select)
|
|
1090
1090
|
* @returns row 수
|
|
1091
1091
|
* @throws distinct() 또는 groupBy() 후 직접 호출 시 에러 (wrap() 필요)
|
|
1092
1092
|
*
|
|
@@ -1097,7 +1097,7 @@ export class Queryable<
|
|
|
1097
1097
|
* .count();
|
|
1098
1098
|
* ```
|
|
1099
1099
|
*/
|
|
1100
|
-
async count(
|
|
1100
|
+
async count(fn?: (cols: QueryableRecord<TData>) => ExprUnit<ColumnPrimitive>): Promise<number> {
|
|
1101
1101
|
if (this.meta.distinct) {
|
|
1102
1102
|
throw new Error("Cannot use count() after distinct(). Use wrap() first.");
|
|
1103
1103
|
}
|
|
@@ -1105,8 +1105,8 @@ export class Queryable<
|
|
|
1105
1105
|
throw new Error("Cannot use count() after groupBy(). Use wrap() first.");
|
|
1106
1106
|
}
|
|
1107
1107
|
|
|
1108
|
-
const countQr =
|
|
1109
|
-
? this.select((c) => ({ cnt: expr.count(
|
|
1108
|
+
const countQr = fn
|
|
1109
|
+
? this.select((c) => ({ cnt: expr.count(fn(c)) }))
|
|
1110
1110
|
: this.select(() => ({ cnt: expr.count() }));
|
|
1111
1111
|
|
|
1112
1112
|
const result = await countQr.single();
|
|
@@ -1132,7 +1132,7 @@ export class Queryable<
|
|
|
1132
1132
|
}
|
|
1133
1133
|
|
|
1134
1134
|
getSelectQueryDef(): SelectQueryDef {
|
|
1135
|
-
return
|
|
1135
|
+
return obj.clearUndefined({
|
|
1136
1136
|
type: "select",
|
|
1137
1137
|
from: this._buildFromDef(),
|
|
1138
1138
|
as: this.meta.as,
|
|
@@ -1394,7 +1394,7 @@ export class Queryable<
|
|
|
1394
1394
|
outputDef.aiColName != null &&
|
|
1395
1395
|
records.some((r) => (r as Record<string, unknown>)[outputDef.aiColName!] !== undefined);
|
|
1396
1396
|
|
|
1397
|
-
return
|
|
1397
|
+
return obj.clearUndefined({
|
|
1398
1398
|
type: "insert",
|
|
1399
1399
|
table: this.meta.db.getQueryDefObjectName(from),
|
|
1400
1400
|
records,
|
|
@@ -1418,7 +1418,7 @@ export class Queryable<
|
|
|
1418
1418
|
|
|
1419
1419
|
const { select: _, ...existsSelectQuery } = this.getSelectQueryDef();
|
|
1420
1420
|
|
|
1421
|
-
return
|
|
1421
|
+
return obj.clearUndefined({
|
|
1422
1422
|
type: "insertIfNotExists",
|
|
1423
1423
|
table: this.meta.db.getQueryDefObjectName(from),
|
|
1424
1424
|
record,
|
|
@@ -1439,7 +1439,7 @@ export class Queryable<
|
|
|
1439
1439
|
): InsertIntoQueryDef {
|
|
1440
1440
|
const outputDef = this._getCudOutputDef();
|
|
1441
1441
|
|
|
1442
|
-
return
|
|
1442
|
+
return obj.clearUndefined({
|
|
1443
1443
|
type: "insertInto",
|
|
1444
1444
|
table: this.meta.db.getQueryDefObjectName(targetTable),
|
|
1445
1445
|
recordsSelectQuery: this.getSelectQueryDef(),
|
|
@@ -1545,7 +1545,7 @@ export class Queryable<
|
|
|
1545
1545
|
const from = this.meta.from as TableBuilder<any, any> | ViewBuilder<any, any, any>;
|
|
1546
1546
|
const outputDef = this._getCudOutputDef();
|
|
1547
1547
|
|
|
1548
|
-
return
|
|
1548
|
+
return obj.clearUndefined({
|
|
1549
1549
|
type: "update",
|
|
1550
1550
|
table: this.meta.db.getQueryDefObjectName(from),
|
|
1551
1551
|
as: this.meta.as,
|
|
@@ -1568,7 +1568,7 @@ export class Queryable<
|
|
|
1568
1568
|
const from = this.meta.from as TableBuilder<any, any> | ViewBuilder<any, any, any>;
|
|
1569
1569
|
const outputDef = this._getCudOutputDef();
|
|
1570
1570
|
|
|
1571
|
-
return
|
|
1571
|
+
return obj.clearUndefined({
|
|
1572
1572
|
type: "delete",
|
|
1573
1573
|
table: this.meta.db.getQueryDefObjectName(from),
|
|
1574
1574
|
as: this.meta.as,
|
|
@@ -1595,8 +1595,8 @@ export class Queryable<
|
|
|
1595
1595
|
*
|
|
1596
1596
|
* WHERE condition에 맞는 data가 있으면 UPDATE, 없으면 INSERT
|
|
1597
1597
|
*
|
|
1598
|
-
* @param
|
|
1599
|
-
* @param
|
|
1598
|
+
* @param updateFn - Update할 column과 값을 반환하는 function
|
|
1599
|
+
* @param insertFn - Insert할 레코드를 반환하는 function (selection, 미지정 시 updateFn와 동일)
|
|
1600
1600
|
* @param outputColumns - column name array to receive (Select)
|
|
1601
1601
|
* @returns outputColumns 지정 시 영향받은 레코드 array return
|
|
1602
1602
|
*
|
|
@@ -1620,47 +1620,47 @@ export class Queryable<
|
|
|
1620
1620
|
* ```
|
|
1621
1621
|
*/
|
|
1622
1622
|
async upsert(
|
|
1623
|
-
|
|
1623
|
+
updateFn: (cols: QueryableRecord<TData>) => QueryableWriteRecord<TFrom["$inferUpdate"]>,
|
|
1624
1624
|
): Promise<void>;
|
|
1625
1625
|
async upsert<K extends keyof TFrom["$inferColumns"] & string>(
|
|
1626
|
-
|
|
1626
|
+
insertFn: (cols: QueryableRecord<TData>) => QueryableWriteRecord<TFrom["$inferInsert"]>,
|
|
1627
1627
|
outputColumns?: K[],
|
|
1628
1628
|
): Promise<Pick<TFrom["$inferColumns"], K>[]>;
|
|
1629
1629
|
async upsert<U extends QueryableWriteRecord<TFrom["$inferUpdate"]>>(
|
|
1630
|
-
|
|
1631
|
-
|
|
1630
|
+
updateFn: (cols: QueryableRecord<TData>) => U,
|
|
1631
|
+
insertFn: (updateRecord: U) => QueryableWriteRecord<TFrom["$inferInsert"]>,
|
|
1632
1632
|
): Promise<void>;
|
|
1633
1633
|
async upsert<
|
|
1634
1634
|
U extends QueryableWriteRecord<TFrom["$inferUpdate"]>,
|
|
1635
1635
|
K extends keyof TFrom["$inferColumns"] & string,
|
|
1636
1636
|
>(
|
|
1637
|
-
|
|
1638
|
-
|
|
1637
|
+
updateFn: (cols: QueryableRecord<TData>) => U,
|
|
1638
|
+
insertFn: (updateRecord: U) => QueryableWriteRecord<TFrom["$inferInsert"]>,
|
|
1639
1639
|
outputColumns?: K[],
|
|
1640
1640
|
): Promise<Pick<TFrom["$inferColumns"], K>[]>;
|
|
1641
1641
|
async upsert<
|
|
1642
1642
|
U extends QueryableWriteRecord<TFrom["$inferUpdate"]>,
|
|
1643
1643
|
K extends keyof TFrom["$inferColumns"] & string,
|
|
1644
1644
|
>(
|
|
1645
|
-
|
|
1645
|
+
updateFnOrInsertFn:
|
|
1646
1646
|
| ((cols: QueryableRecord<TData>) => U)
|
|
1647
1647
|
| ((cols: QueryableRecord<TData>) => QueryableWriteRecord<TFrom["$inferInsert"]>),
|
|
1648
|
-
|
|
1648
|
+
insertFnOrOutputColumns?:
|
|
1649
1649
|
| ((updateRecord: U) => QueryableWriteRecord<TFrom["$inferInsert"]>)
|
|
1650
1650
|
| K[],
|
|
1651
1651
|
outputColumns?: K[],
|
|
1652
1652
|
): Promise<Pick<TFrom["$inferColumns"], K>[] | void> {
|
|
1653
|
-
const
|
|
1653
|
+
const updateRecordFn = updateFnOrInsertFn as (cols: QueryableRecord<TData>) => U;
|
|
1654
1654
|
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1655
|
+
const insertRecordFn = (
|
|
1656
|
+
insertFnOrOutputColumns instanceof Function ? insertFnOrOutputColumns : updateFnOrInsertFn
|
|
1657
1657
|
) as (updateRecord: U) => QueryableWriteRecord<TFrom["$inferInsert"]>;
|
|
1658
1658
|
|
|
1659
1659
|
const realOutputColumns =
|
|
1660
|
-
|
|
1660
|
+
insertFnOrOutputColumns instanceof Function ? outputColumns : insertFnOrOutputColumns;
|
|
1661
1661
|
|
|
1662
1662
|
const results = await this.meta.db.executeDefs<Pick<TFrom["$inferColumns"], K>>(
|
|
1663
|
-
[this.getUpsertQueryDef(
|
|
1663
|
+
[this.getUpsertQueryDef(updateRecordFn, insertRecordFn, realOutputColumns)],
|
|
1664
1664
|
[realOutputColumns ? this.getResultMeta(realOutputColumns) : undefined],
|
|
1665
1665
|
);
|
|
1666
1666
|
|
|
@@ -1670,8 +1670,8 @@ export class Queryable<
|
|
|
1670
1670
|
}
|
|
1671
1671
|
|
|
1672
1672
|
getUpsertQueryDef<U extends QueryableWriteRecord<TFrom["$inferUpdate"]>>(
|
|
1673
|
-
|
|
1674
|
-
|
|
1673
|
+
updateRecordFn: (cols: QueryableRecord<TData>) => U,
|
|
1674
|
+
insertRecordFn: (updateRecord: U) => QueryableWriteRecord<TFrom["$inferInsert"]>,
|
|
1675
1675
|
outputColumns?: (keyof TFrom["$inferColumns"] & string)[],
|
|
1676
1676
|
): UpsertQueryDef {
|
|
1677
1677
|
const from = this.meta.from as TableBuilder<any, any> | ViewBuilder<any, any, any>;
|
|
@@ -1680,19 +1680,19 @@ export class Queryable<
|
|
|
1680
1680
|
const { select: _sel, ...existsSelectQuery } = this.getSelectQueryDef();
|
|
1681
1681
|
|
|
1682
1682
|
// updateRecord Generate
|
|
1683
|
-
const updateQrRecord =
|
|
1683
|
+
const updateQrRecord = updateRecordFn(this.meta.columns);
|
|
1684
1684
|
const updateRecord: Record<string, Expr> = {};
|
|
1685
1685
|
for (const [key, value] of Object.entries(updateQrRecord)) {
|
|
1686
1686
|
updateRecord[key] = expr.toExpr(value);
|
|
1687
1687
|
}
|
|
1688
1688
|
|
|
1689
1689
|
// insertRecord Generate (updateRecordRaw를 두 번째 인자로)
|
|
1690
|
-
const insertRecordRaw =
|
|
1690
|
+
const insertRecordRaw = insertRecordFn(updateQrRecord);
|
|
1691
1691
|
const insertRecord = Object.fromEntries(
|
|
1692
1692
|
Object.entries(insertRecordRaw).map(([key, value]) => [key, expr.toExpr(value)]),
|
|
1693
1693
|
);
|
|
1694
1694
|
|
|
1695
|
-
return
|
|
1695
|
+
return obj.clearUndefined({
|
|
1696
1696
|
type: "upsert",
|
|
1697
1697
|
table: this.meta.db.getQueryDefObjectName(from),
|
|
1698
1698
|
existsSelectQuery,
|
|
@@ -1715,14 +1715,14 @@ export class Queryable<
|
|
|
1715
1715
|
/**
|
|
1716
1716
|
* FK constraint on/off (transaction 내 사용 가능)
|
|
1717
1717
|
*/
|
|
1718
|
-
async switchFk(
|
|
1718
|
+
async switchFk(enabled: boolean): Promise<void> {
|
|
1719
1719
|
const from = this.meta.from;
|
|
1720
1720
|
if (!(from instanceof TableBuilder) && !(from instanceof ViewBuilder)) {
|
|
1721
1721
|
throw new Error(
|
|
1722
1722
|
"switchFk는 TableBuilder 또는 ViewBuilder 기반 queryable에서만 사용할 수 있습니다.",
|
|
1723
1723
|
);
|
|
1724
1724
|
}
|
|
1725
|
-
await this.meta.db.switchFk(this.meta.db.getQueryDefObjectName(from),
|
|
1725
|
+
await this.meta.db.switchFk(this.meta.db.getQueryDefObjectName(from), enabled);
|
|
1726
1726
|
}
|
|
1727
1727
|
|
|
1728
1728
|
//#endregion
|
|
@@ -1981,7 +1981,7 @@ function createPathProxy<TObject>(path: string[] = []): PathProxy<TObject> {
|
|
|
1981
1981
|
* async getActiveUsers() {
|
|
1982
1982
|
* return this.user()
|
|
1983
1983
|
* .where((u) => [expr.eq(u.isActive, true)])
|
|
1984
|
-
* .
|
|
1984
|
+
* .execute();
|
|
1985
1985
|
* }
|
|
1986
1986
|
* }
|
|
1987
1987
|
* ```
|
|
@@ -1990,7 +1990,7 @@ export function queryable<TBuilder extends TableBuilder<any, any> | ViewBuilder<
|
|
|
1990
1990
|
db: DbContextBase,
|
|
1991
1991
|
tableOrView: TBuilder,
|
|
1992
1992
|
as?: string,
|
|
1993
|
-
): () => Queryable<TBuilder["$
|
|
1993
|
+
): () => Queryable<TBuilder["$inferSelect"], TBuilder extends TableBuilder<any, any> ? TBuilder : never> {
|
|
1994
1994
|
return () => {
|
|
1995
1995
|
// as가 명시되지 않으면 db.getNextAlias() 사용 (카운터 증가)
|
|
1996
1996
|
// as가 명시되면 그대로 사용 (카운터 증가 안함)
|
package/src/expr/expr-unit.ts
CHANGED
|
@@ -9,7 +9,7 @@ export class ExprUnit<TPrimitive extends ColumnPrimitive> {
|
|
|
9
9
|
readonly $infer!: TPrimitive;
|
|
10
10
|
|
|
11
11
|
get n(): ExprUnit<NonNullable<TPrimitive>> {
|
|
12
|
-
return
|
|
12
|
+
return new ExprUnit<NonNullable<TPrimitive>>(this.dataType, this.expr);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
constructor(
|
package/src/expr/expr.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "../types/column";
|
|
11
11
|
import type { ExprInput } from "./expr-unit";
|
|
12
12
|
import { ExprUnit, WhereExprUnit } from "./expr-unit";
|
|
13
|
-
import type { Expr,
|
|
13
|
+
import type { Expr, DateUnit, WhereExpr, WinSpec } from "../types/expr";
|
|
14
14
|
import type { SelectQueryDef } from "../types/query-def";
|
|
15
15
|
import type { Queryable } from "../exec/queryable";
|
|
16
16
|
|
|
@@ -1163,7 +1163,7 @@ export const expr = {
|
|
|
1163
1163
|
/**
|
|
1164
1164
|
* Date 차이 계산 (DATEDIFF)
|
|
1165
1165
|
*
|
|
1166
|
-
* @param
|
|
1166
|
+
* @param unit - 단위 ("year", "month", "day", "hour", "minute", "second")
|
|
1167
1167
|
* @param from - start Date
|
|
1168
1168
|
* @param to - 끝 Date
|
|
1169
1169
|
* @returns 차이 value (to - from)
|
|
@@ -1177,13 +1177,13 @@ export const expr = {
|
|
|
1177
1177
|
* ```
|
|
1178
1178
|
*/
|
|
1179
1179
|
dateDiff<T extends DateTime | DateOnly | Time | undefined>(
|
|
1180
|
-
|
|
1180
|
+
unit: DateUnit,
|
|
1181
1181
|
from: ExprInput<T>,
|
|
1182
1182
|
to: ExprInput<T>,
|
|
1183
1183
|
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1184
1184
|
return new ExprUnit("number", {
|
|
1185
1185
|
type: "dateDiff",
|
|
1186
|
-
|
|
1186
|
+
unit,
|
|
1187
1187
|
from: toExpr(from),
|
|
1188
1188
|
to: toExpr(to),
|
|
1189
1189
|
});
|
|
@@ -1192,7 +1192,7 @@ export const expr = {
|
|
|
1192
1192
|
/**
|
|
1193
1193
|
* Date 더하기 (DATEADD)
|
|
1194
1194
|
*
|
|
1195
|
-
* @param
|
|
1195
|
+
* @param unit - 단위 ("year", "month", "day", "hour", "minute", "second")
|
|
1196
1196
|
* @param source - 원본 Date
|
|
1197
1197
|
* @param value - 더할 value (음수 가능)
|
|
1198
1198
|
* @returns 계산된 Date
|
|
@@ -1206,13 +1206,13 @@ export const expr = {
|
|
|
1206
1206
|
* ```
|
|
1207
1207
|
*/
|
|
1208
1208
|
dateAdd<T extends DateTime | DateOnly | Time | undefined>(
|
|
1209
|
-
|
|
1209
|
+
unit: DateUnit,
|
|
1210
1210
|
source: ExprUnit<T>,
|
|
1211
1211
|
value: ExprInput<number>,
|
|
1212
1212
|
): ExprUnit<T> {
|
|
1213
1213
|
return new ExprUnit(source.dataType, {
|
|
1214
1214
|
type: "dateAdd",
|
|
1215
|
-
|
|
1215
|
+
unit,
|
|
1216
1216
|
source: toExpr(source),
|
|
1217
1217
|
value: toExpr(value),
|
|
1218
1218
|
});
|
|
@@ -1262,12 +1262,12 @@ export const expr = {
|
|
|
1262
1262
|
* @example
|
|
1263
1263
|
* ```typescript
|
|
1264
1264
|
* db.user().select((u) => ({
|
|
1265
|
-
* displayName: expr.
|
|
1265
|
+
* displayName: expr.coalesce(u.nickname, u.name, "Guest"),
|
|
1266
1266
|
* }))
|
|
1267
1267
|
* // SELECT COALESCE(nickname, name, 'Guest') AS displayName
|
|
1268
1268
|
* ```
|
|
1269
1269
|
*/
|
|
1270
|
-
|
|
1270
|
+
coalesce,
|
|
1271
1271
|
|
|
1272
1272
|
/**
|
|
1273
1273
|
* 특정 값이면 NULL return (NULLIF)
|
|
@@ -2050,21 +2050,21 @@ export const expr = {
|
|
|
2050
2050
|
//#region ========== Internal Helpers ==========
|
|
2051
2051
|
|
|
2052
2052
|
// 여러 value 중 첫 번째 non-null return (COALESCE)
|
|
2053
|
-
function
|
|
2053
|
+
function coalesce<TPrimitive extends ColumnPrimitive>(
|
|
2054
2054
|
...args: [
|
|
2055
2055
|
ExprInput<TPrimitive | undefined>,
|
|
2056
2056
|
...ExprInput<TPrimitive | undefined>[],
|
|
2057
2057
|
ExprInput<NonNullable<TPrimitive>>,
|
|
2058
2058
|
]
|
|
2059
2059
|
): ExprUnit<NonNullable<TPrimitive>>;
|
|
2060
|
-
function
|
|
2060
|
+
function coalesce<TPrimitive extends ColumnPrimitive>(
|
|
2061
2061
|
...args: ExprInput<TPrimitive>[]
|
|
2062
2062
|
): ExprUnit<TPrimitive>;
|
|
2063
|
-
function
|
|
2063
|
+
function coalesce<TPrimitive extends ColumnPrimitive>(
|
|
2064
2064
|
...args: ExprInput<TPrimitive>[]
|
|
2065
2065
|
): ExprUnit<TPrimitive> {
|
|
2066
2066
|
return new ExprUnit(findDataType(args), {
|
|
2067
|
-
type: "
|
|
2067
|
+
type: "coalesce",
|
|
2068
2068
|
args: args.map((a) => toExpr(a)),
|
|
2069
2069
|
});
|
|
2070
2070
|
}
|
|
@@ -47,7 +47,7 @@ import type {
|
|
|
47
47
|
ExprDateDiff,
|
|
48
48
|
ExprDateAdd,
|
|
49
49
|
ExprFormatDate,
|
|
50
|
-
|
|
50
|
+
ExprCoalesce,
|
|
51
51
|
ExprNullIf,
|
|
52
52
|
ExprIs,
|
|
53
53
|
ExprSwitch,
|
|
@@ -195,7 +195,7 @@ export abstract class ExprRendererBase {
|
|
|
195
195
|
|
|
196
196
|
//#region ========== Abstract - Condition ==========
|
|
197
197
|
|
|
198
|
-
protected abstract
|
|
198
|
+
protected abstract coalesce(expr: ExprCoalesce): string;
|
|
199
199
|
protected abstract nullIf(expr: ExprNullIf): string;
|
|
200
200
|
protected abstract is(expr: ExprIs): string;
|
|
201
201
|
protected abstract switch(expr: ExprSwitch): string;
|
|
@@ -15,12 +15,12 @@ import type {
|
|
|
15
15
|
DropColumnQueryDef,
|
|
16
16
|
ModifyColumnQueryDef,
|
|
17
17
|
RenameColumnQueryDef,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
AddPrimaryKeyQueryDef,
|
|
19
|
+
DropPrimaryKeyQueryDef,
|
|
20
|
+
AddForeignKeyQueryDef,
|
|
21
|
+
DropForeignKeyQueryDef,
|
|
22
|
+
AddIndexQueryDef,
|
|
23
|
+
DropIndexQueryDef,
|
|
24
24
|
CreateViewQueryDef,
|
|
25
25
|
DropViewQueryDef,
|
|
26
26
|
CreateProcQueryDef,
|
|
@@ -44,6 +44,11 @@ import type { ExprRendererBase } from "./expr-renderer-base";
|
|
|
44
44
|
* - If different at all, make it abstract
|
|
45
45
|
* - Method name identical to def.type (enables dynamic dispatch)
|
|
46
46
|
*/
|
|
47
|
+
/** Properties that are part of a basic (non-LATERAL) JOIN */
|
|
48
|
+
const BASIC_JOIN_PROPS: ReadonlySet<string> = new Set<
|
|
49
|
+
keyof Pick<SelectQueryDefJoin, "type" | "from" | "as" | "where" | "isSingle">
|
|
50
|
+
>(["type", "from", "as", "where", "isSingle"]);
|
|
51
|
+
|
|
47
52
|
export abstract class QueryBuilderBase {
|
|
48
53
|
protected abstract expr: ExprRendererBase;
|
|
49
54
|
|
|
@@ -129,8 +134,7 @@ export abstract class QueryBuilderBase {
|
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
// LATERAL needed if join has additional properties beyond basic JOIN properties
|
|
132
|
-
|
|
133
|
-
return Object.keys(join).some((key) => !basicJoinProps.includes(key));
|
|
137
|
+
return Object.keys(join).some((key) => !BASIC_JOIN_PROPS.has(key));
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
/** FROM clause source render */
|
|
@@ -184,12 +188,12 @@ export abstract class QueryBuilderBase {
|
|
|
184
188
|
|
|
185
189
|
//#region ========== Abstract - DDL Constraint ==========
|
|
186
190
|
|
|
187
|
-
protected abstract
|
|
188
|
-
protected abstract
|
|
189
|
-
protected abstract
|
|
190
|
-
protected abstract
|
|
191
|
-
protected abstract
|
|
192
|
-
protected abstract
|
|
191
|
+
protected abstract addPrimaryKey(def: AddPrimaryKeyQueryDef): QueryBuildResult;
|
|
192
|
+
protected abstract dropPrimaryKey(def: DropPrimaryKeyQueryDef): QueryBuildResult;
|
|
193
|
+
protected abstract addForeignKey(def: AddForeignKeyQueryDef): QueryBuildResult;
|
|
194
|
+
protected abstract dropForeignKey(def: DropForeignKeyQueryDef): QueryBuildResult;
|
|
195
|
+
protected abstract addIndex(def: AddIndexQueryDef): QueryBuildResult;
|
|
196
|
+
protected abstract dropIndex(def: DropIndexQueryDef): QueryBuildResult;
|
|
193
197
|
|
|
194
198
|
//#endregion
|
|
195
199
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DateOnly, DateTime, Time, Uuid
|
|
1
|
+
import { bytes, DateOnly, DateTime, Time, Uuid } from "@simplysm/core-common";
|
|
2
2
|
import type {
|
|
3
3
|
ExprColumn,
|
|
4
4
|
ExprValue,
|
|
@@ -46,7 +46,7 @@ import type {
|
|
|
46
46
|
ExprDateDiff,
|
|
47
47
|
ExprDateAdd,
|
|
48
48
|
ExprFormatDate,
|
|
49
|
-
|
|
49
|
+
ExprCoalesce,
|
|
50
50
|
ExprNullIf,
|
|
51
51
|
ExprIs,
|
|
52
52
|
ExprSwitch,
|
|
@@ -60,9 +60,10 @@ import type {
|
|
|
60
60
|
ExprLeast,
|
|
61
61
|
ExprRowNum,
|
|
62
62
|
ExprCast,
|
|
63
|
+
ExprRandom,
|
|
63
64
|
ExprWindow,
|
|
64
65
|
ExprSubquery,
|
|
65
|
-
|
|
66
|
+
DateUnit,
|
|
66
67
|
} from "../../types/expr";
|
|
67
68
|
import type { DataType } from "../../types/column";
|
|
68
69
|
import { ExprRendererBase } from "../base/expr-renderer-base";
|
|
@@ -110,7 +111,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
110
111
|
return `'${value.toString()}'`;
|
|
111
112
|
}
|
|
112
113
|
if (value instanceof Uint8Array) {
|
|
113
|
-
return `0x${
|
|
114
|
+
return `0x${bytes.toHex(value)}`;
|
|
114
115
|
}
|
|
115
116
|
throw new Error(`Unknown value type: ${typeof value}`);
|
|
116
117
|
}
|
|
@@ -398,14 +399,14 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
398
399
|
protected dateDiff(expr: ExprDateDiff): string {
|
|
399
400
|
const from = this.render(expr.from);
|
|
400
401
|
const to = this.render(expr.to);
|
|
401
|
-
const unit = this.
|
|
402
|
+
const unit = this.dateUnitToSql(expr.unit);
|
|
402
403
|
return `DATEDIFF(${unit}, ${from}, ${to})`;
|
|
403
404
|
}
|
|
404
405
|
|
|
405
406
|
protected dateAdd(expr: ExprDateAdd): string {
|
|
406
407
|
const source = this.render(expr.source);
|
|
407
408
|
const value = this.render(expr.value);
|
|
408
|
-
const unit = this.
|
|
409
|
+
const unit = this.dateUnitToSql(expr.unit);
|
|
409
410
|
return `DATEADD(${unit}, ${value}, ${source})`;
|
|
410
411
|
}
|
|
411
412
|
|
|
@@ -415,8 +416,8 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
415
416
|
return `FORMAT(${this.render(expr.source)}, '${mssqlFormat}')`;
|
|
416
417
|
}
|
|
417
418
|
|
|
418
|
-
private
|
|
419
|
-
switch (
|
|
419
|
+
private dateUnitToSql(unit: DateUnit): string {
|
|
420
|
+
switch (unit) {
|
|
420
421
|
case "year":
|
|
421
422
|
return "YEAR";
|
|
422
423
|
case "month":
|
|
@@ -441,7 +442,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
441
442
|
|
|
442
443
|
//#region ========== condition ==========
|
|
443
444
|
|
|
444
|
-
protected
|
|
445
|
+
protected coalesce(expr: ExprCoalesce): string {
|
|
445
446
|
if (expr.args.length === 0) return "NULL";
|
|
446
447
|
if (expr.args.length === 1) return this.render(expr.args[0]);
|
|
447
448
|
// MSSQL: COALESCE
|
|
@@ -520,7 +521,7 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
520
521
|
return "ROW_NUMBER() OVER (ORDER BY (SELECT NULL))";
|
|
521
522
|
}
|
|
522
523
|
|
|
523
|
-
protected random(): string {
|
|
524
|
+
protected random(_expr: ExprRandom): string {
|
|
524
525
|
return "NEWID()";
|
|
525
526
|
}
|
|
526
527
|
|