@simplysm/orm-common 13.0.28 → 13.0.30
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 +210 -3
- package/dist/create-db-context.d.ts.map +1 -1
- package/dist/create-db-context.js +7 -2
- package/dist/create-db-context.js.map +1 -1
- package/dist/ddl/column-ddl.d.ts.map +1 -1
- package/dist/ddl/column-ddl.js.map +1 -1
- package/dist/ddl/initialize.d.ts.map +1 -1
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/relation-ddl.d.ts.map +1 -1
- package/dist/ddl/relation-ddl.js.map +1 -1
- package/dist/ddl/schema-ddl.d.ts.map +1 -1
- package/dist/ddl/schema-ddl.js.map +1 -1
- package/dist/ddl/table-ddl.d.ts.map +1 -1
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/define-db-context.d.ts.map +1 -1
- package/dist/define-db-context.js.map +1 -1
- package/dist/exec/executable.d.ts.map +1 -1
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.d.ts.map +1 -1
- package/dist/exec/queryable.js +19 -6
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.js.map +1 -1
- package/dist/expr/expr.d.ts.map +1 -1
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +3 -1
- package/dist/query-builder/base/query-builder-base.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +3 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +9 -3
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +3 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +12 -4
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +3 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +21 -7
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/schema/factory/column-builder.d.ts.map +1 -1
- package/dist/schema/factory/column-builder.js.map +1 -1
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js.map +1 -1
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js.map +1 -1
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +3 -1
- package/dist/schema/table-builder.js.map +1 -1
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +3 -1
- package/dist/schema/view-builder.js.map +1 -1
- package/dist/types/db-context-def.d.ts.map +1 -1
- package/dist/types/db.d.ts.map +1 -1
- package/dist/utils/result-parser.js +3 -1
- package/dist/utils/result-parser.js.map +1 -1
- package/docs/queries.md +89 -5
- package/docs/schema.md +12 -20
- package/package.json +2 -2
- package/src/create-db-context.ts +53 -15
- package/src/ddl/column-ddl.ts +4 -1
- package/src/ddl/initialize.ts +13 -3
- package/src/ddl/relation-ddl.ts +4 -1
- package/src/ddl/schema-ddl.ts +8 -2
- package/src/ddl/table-ddl.ts +12 -3
- package/src/define-db-context.ts +3 -1
- package/src/exec/executable.ts +4 -1
- package/src/exec/queryable.ts +91 -26
- package/src/exec/search-parser.ts +5 -1
- package/src/expr/expr.ts +56 -15
- package/src/query-builder/base/query-builder-base.ts +3 -1
- package/src/query-builder/mssql/mssql-expr-renderer.ts +6 -2
- package/src/query-builder/mssql/mssql-query-builder.ts +15 -5
- package/src/query-builder/mysql/mysql-expr-renderer.ts +6 -2
- package/src/query-builder/mysql/mysql-query-builder.ts +19 -6
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +6 -2
- package/src/query-builder/postgresql/postgresql-query-builder.ts +35 -13
- package/src/schema/factory/column-builder.ts +16 -5
- package/src/schema/factory/relation-builder.ts +14 -4
- package/src/schema/procedure-builder.ts +4 -1
- package/src/schema/table-builder.ts +14 -3
- package/src/schema/view-builder.ts +5 -1
- package/src/types/db-context-def.ts +37 -8
- package/src/types/db.ts +9 -2
- package/src/utils/result-parser.ts +10 -3
package/src/exec/executable.ts
CHANGED
|
@@ -103,7 +103,10 @@ export class Executable<TParams extends ColumnBuilderRecord, TReturns extends Co
|
|
|
103
103
|
* @see {@link Executable} 실행 클래스
|
|
104
104
|
* @see {@link ProcedureBuilder} 프로시저 정의
|
|
105
105
|
*/
|
|
106
|
-
export function executable<
|
|
106
|
+
export function executable<
|
|
107
|
+
TParams extends ColumnBuilderRecord,
|
|
108
|
+
TReturns extends ColumnBuilderRecord,
|
|
109
|
+
>(
|
|
107
110
|
db: DbContextBase,
|
|
108
111
|
builder: ProcedureBuilder<TParams, TReturns>,
|
|
109
112
|
): () => Executable<TParams, TReturns> {
|
package/src/exec/queryable.ts
CHANGED
|
@@ -14,7 +14,10 @@ import type {
|
|
|
14
14
|
UpsertQueryDef,
|
|
15
15
|
} from "../types/query-def";
|
|
16
16
|
import type { DbContextBase } from "../types/db-context-def";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type ColumnBuilderRecord,
|
|
19
|
+
type DataToColumnBuilderRecord,
|
|
20
|
+
} from "../schema/factory/column-builder";
|
|
18
21
|
import type { ColumnPrimitive, ColumnPrimitiveStr } from "../types/column";
|
|
19
22
|
import type { WhereExprUnit } from "../expr/expr-unit";
|
|
20
23
|
import { ExprUnit } from "../expr/expr-unit";
|
|
@@ -110,7 +113,9 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
110
113
|
* @param table - 재귀할 대상 테이블
|
|
111
114
|
* @returns self 속성이 추가된 Queryable (자기 참조용)
|
|
112
115
|
*/
|
|
113
|
-
from<T extends TableBuilder<any, any>>(
|
|
116
|
+
from<T extends TableBuilder<any, any>>(
|
|
117
|
+
table: T,
|
|
118
|
+
): Queryable<T["$infer"] & { self?: TBaseData[] }, T> {
|
|
114
119
|
const selfAlias = `${this._cteName}.self`;
|
|
115
120
|
|
|
116
121
|
return queryable(this._baseQr.meta.db, table, this._cteName)().join(
|
|
@@ -132,7 +137,9 @@ class RecursiveQueryable<TBaseData extends DataRecord> {
|
|
|
132
137
|
* @param columns - 커스텀 컬럼 정의
|
|
133
138
|
* @returns self 속성이 추가된 Queryable
|
|
134
139
|
*/
|
|
135
|
-
select<R extends DataRecord>(
|
|
140
|
+
select<R extends DataRecord>(
|
|
141
|
+
columns: QueryableRecord<R>,
|
|
142
|
+
): Queryable<R & { self?: TBaseData[] }, never> {
|
|
136
143
|
const selfAlias = `${this._cteName}.self`;
|
|
137
144
|
|
|
138
145
|
return new Queryable<R, never>({
|
|
@@ -240,7 +247,9 @@ export class Queryable<
|
|
|
240
247
|
* }))
|
|
241
248
|
* ```
|
|
242
249
|
*/
|
|
243
|
-
select<R extends DataRecord>(
|
|
250
|
+
select<R extends DataRecord>(
|
|
251
|
+
fn: (columns: QueryableRecord<TData>) => QueryableRecord<R>,
|
|
252
|
+
): Queryable<R, never> {
|
|
244
253
|
if (Array.isArray(this.meta.from)) {
|
|
245
254
|
const newFroms = this.meta.from.map((from) => from.select(fn));
|
|
246
255
|
return new Queryable({
|
|
@@ -507,7 +516,9 @@ export class Queryable<
|
|
|
507
516
|
conditions.push(expr.or(columnMatches));
|
|
508
517
|
} else if (parsed.or.length > 1) {
|
|
509
518
|
const orConditions = parsed.or.map((pattern) => {
|
|
510
|
-
const columnMatches = columns.map((col) =>
|
|
519
|
+
const columnMatches = columns.map((col) =>
|
|
520
|
+
expr.like(expr.lower(col), pattern.toLowerCase()),
|
|
521
|
+
);
|
|
511
522
|
return expr.or(columnMatches);
|
|
512
523
|
});
|
|
513
524
|
conditions.push(expr.or(orConditions));
|
|
@@ -552,7 +563,9 @@ export class Queryable<
|
|
|
552
563
|
* .groupBy((o) => [o.userId])
|
|
553
564
|
* ```
|
|
554
565
|
*/
|
|
555
|
-
groupBy(
|
|
566
|
+
groupBy(
|
|
567
|
+
fn: (columns: QueryableRecord<TData>) => ExprUnit<ColumnPrimitive>[],
|
|
568
|
+
): Queryable<TData, never> {
|
|
556
569
|
if (Array.isArray(this.meta.from)) {
|
|
557
570
|
const newFroms = this.meta.from.map((from) => from.groupBy(fn));
|
|
558
571
|
return new Queryable({
|
|
@@ -677,7 +690,10 @@ export class Queryable<
|
|
|
677
690
|
joinSingle<A extends string, R extends DataRecord>(
|
|
678
691
|
as: A,
|
|
679
692
|
fwd: (qr: JoinQueryable, cols: QueryableRecord<TData>) => Queryable<R, any>,
|
|
680
|
-
): Queryable<
|
|
693
|
+
): Queryable<
|
|
694
|
+
{ [K in keyof TData as K extends A ? never : K]: TData[K] } & { [K in A]?: R },
|
|
695
|
+
TFrom
|
|
696
|
+
> {
|
|
681
697
|
if (Array.isArray(this.meta.from)) {
|
|
682
698
|
const newFroms = this.meta.from.map((from) => from.joinSingle(as, fwd));
|
|
683
699
|
return new Queryable({
|
|
@@ -797,7 +813,9 @@ export class Queryable<
|
|
|
797
813
|
|
|
798
814
|
// FKT join은 배열로 저장되므로 배열인 경우 첫 번째 요소 사용
|
|
799
815
|
const srcColsRaw = parentChain ? parentCols[parentChain] : parentCols;
|
|
800
|
-
const srcCols = (
|
|
816
|
+
const srcCols = (
|
|
817
|
+
Array.isArray(srcColsRaw) ? srcColsRaw[0] : srcColsRaw
|
|
818
|
+
) as QueryableRecord<any>;
|
|
801
819
|
const conditions: WhereExprUnit[] = [];
|
|
802
820
|
|
|
803
821
|
for (let i = 0; i < fkColKeys.length; i++) {
|
|
@@ -811,7 +829,10 @@ export class Queryable<
|
|
|
811
829
|
});
|
|
812
830
|
|
|
813
831
|
currentTable = targetTable;
|
|
814
|
-
} else if (
|
|
832
|
+
} else if (
|
|
833
|
+
relationDef instanceof ForeignKeyTargetBuilder ||
|
|
834
|
+
relationDef instanceof RelationKeyTargetBuilder
|
|
835
|
+
) {
|
|
815
836
|
// FKT/RelationKeyTarget (1:N 또는 1:1): User.posts → Post[]
|
|
816
837
|
// 조건: Post.userId = User.id
|
|
817
838
|
const targetTable = relationDef.meta.targetTableFn();
|
|
@@ -834,7 +855,9 @@ export class Queryable<
|
|
|
834
855
|
|
|
835
856
|
// FKT join은 배열로 저장되므로 배열인 경우 첫 번째 요소 사용
|
|
836
857
|
const srcColsRaw = parentChain ? parentCols[parentChain] : parentCols;
|
|
837
|
-
const srcCols = (
|
|
858
|
+
const srcCols = (
|
|
859
|
+
Array.isArray(srcColsRaw) ? srcColsRaw[0] : srcColsRaw
|
|
860
|
+
) as QueryableRecord<any>;
|
|
838
861
|
const conditions: WhereExprUnit[] = [];
|
|
839
862
|
|
|
840
863
|
for (let i = 0; i < fkColKeys.length; i++) {
|
|
@@ -905,7 +928,9 @@ export class Queryable<
|
|
|
905
928
|
* );
|
|
906
929
|
* ```
|
|
907
930
|
*/
|
|
908
|
-
static union<TData extends DataRecord>(
|
|
931
|
+
static union<TData extends DataRecord>(
|
|
932
|
+
...queries: Queryable<TData, any>[]
|
|
933
|
+
): Queryable<TData, never> {
|
|
909
934
|
if (queries.length < 2) {
|
|
910
935
|
throw new ArgumentError("union은 최소 2개의 queryable이 필요합니다.", {
|
|
911
936
|
provided: queries.length,
|
|
@@ -946,7 +971,9 @@ export class Queryable<
|
|
|
946
971
|
* )
|
|
947
972
|
* ```
|
|
948
973
|
*/
|
|
949
|
-
recursive(
|
|
974
|
+
recursive(
|
|
975
|
+
fwd: (qr: RecursiveQueryable<TData>) => Queryable<TData, any>,
|
|
976
|
+
): Queryable<TData, never> {
|
|
950
977
|
if (Array.isArray(this.meta.from)) {
|
|
951
978
|
const newFroms = this.meta.from.map((from) => from.recursive(fwd));
|
|
952
979
|
return new Queryable({
|
|
@@ -994,7 +1021,10 @@ export class Queryable<
|
|
|
994
1021
|
* ```
|
|
995
1022
|
*/
|
|
996
1023
|
async result(): Promise<TData[]> {
|
|
997
|
-
const results = await this.meta.db.executeDefs<TData>(
|
|
1024
|
+
const results = await this.meta.db.executeDefs<TData>(
|
|
1025
|
+
[this.getSelectQueryDef()],
|
|
1026
|
+
[this.getResultMeta()],
|
|
1027
|
+
);
|
|
998
1028
|
return results[0];
|
|
999
1029
|
}
|
|
1000
1030
|
|
|
@@ -1126,7 +1156,12 @@ export class Queryable<
|
|
|
1126
1156
|
});
|
|
1127
1157
|
}
|
|
1128
1158
|
|
|
1129
|
-
private _buildFromDef():
|
|
1159
|
+
private _buildFromDef():
|
|
1160
|
+
| QueryDefObjectName
|
|
1161
|
+
| SelectQueryDef
|
|
1162
|
+
| SelectQueryDef[]
|
|
1163
|
+
| string
|
|
1164
|
+
| undefined {
|
|
1130
1165
|
const from = this.meta.from;
|
|
1131
1166
|
|
|
1132
1167
|
if (from instanceof TableBuilder || from instanceof ViewBuilder) {
|
|
@@ -1440,7 +1475,9 @@ export class Queryable<
|
|
|
1440
1475
|
* }));
|
|
1441
1476
|
* ```
|
|
1442
1477
|
*/
|
|
1443
|
-
async update(
|
|
1478
|
+
async update(
|
|
1479
|
+
recordFwd: (cols: QueryableRecord<TData>) => QueryableRecord<TFrom["$inferUpdate"]>,
|
|
1480
|
+
): Promise<void>;
|
|
1444
1481
|
async update<K extends keyof TFrom["$columns"] & string>(
|
|
1445
1482
|
recordFwd: (cols: QueryableRecord<TData>) => QueryableRecord<TFrom["$inferUpdate"]>,
|
|
1446
1483
|
outputColumns: K[],
|
|
@@ -1479,7 +1516,9 @@ export class Queryable<
|
|
|
1479
1516
|
* ```
|
|
1480
1517
|
*/
|
|
1481
1518
|
async delete(): Promise<void>;
|
|
1482
|
-
async delete<K extends keyof TFrom["$columns"] & string>(
|
|
1519
|
+
async delete<K extends keyof TFrom["$columns"] & string>(
|
|
1520
|
+
outputColumns: K[],
|
|
1521
|
+
): Promise<Pick<TFrom["$columns"], K>[]>;
|
|
1483
1522
|
async delete<K extends keyof TFrom["$columns"] & string>(
|
|
1484
1523
|
outputColumns?: K[],
|
|
1485
1524
|
): Promise<Pick<TFrom["$columns"], K>[] | void> {
|
|
@@ -1574,7 +1613,9 @@ export class Queryable<
|
|
|
1574
1613
|
* );
|
|
1575
1614
|
* ```
|
|
1576
1615
|
*/
|
|
1577
|
-
async upsert(
|
|
1616
|
+
async upsert(
|
|
1617
|
+
updateFwd: (cols: QueryableRecord<TData>) => QueryableRecord<TFrom["$inferUpdate"]>,
|
|
1618
|
+
): Promise<void>;
|
|
1578
1619
|
async upsert<K extends keyof TFrom["$inferColumns"] & string>(
|
|
1579
1620
|
insertFwd: (cols: QueryableRecord<TData>) => QueryableRecord<TFrom["$inferInsert"]>,
|
|
1580
1621
|
outputColumns?: K[],
|
|
@@ -1583,12 +1624,18 @@ export class Queryable<
|
|
|
1583
1624
|
updateFwd: (cols: QueryableRecord<TData>) => U,
|
|
1584
1625
|
insertFwd: (updateRecord: U) => QueryableRecord<TFrom["$inferInsert"]>,
|
|
1585
1626
|
): Promise<void>;
|
|
1586
|
-
async upsert<
|
|
1627
|
+
async upsert<
|
|
1628
|
+
U extends QueryableRecord<TFrom["$inferUpdate"]>,
|
|
1629
|
+
K extends keyof TFrom["$inferColumns"] & string,
|
|
1630
|
+
>(
|
|
1587
1631
|
updateFwd: (cols: QueryableRecord<TData>) => U,
|
|
1588
1632
|
insertFwd: (updateRecord: U) => QueryableRecord<TFrom["$inferInsert"]>,
|
|
1589
1633
|
outputColumns?: K[],
|
|
1590
1634
|
): Promise<Pick<TFrom["$inferColumns"], K>[]>;
|
|
1591
|
-
async upsert<
|
|
1635
|
+
async upsert<
|
|
1636
|
+
U extends QueryableRecord<TFrom["$inferUpdate"]>,
|
|
1637
|
+
K extends keyof TFrom["$inferColumns"] & string,
|
|
1638
|
+
>(
|
|
1592
1639
|
updateFwdOrInsertFwd:
|
|
1593
1640
|
| ((cols: QueryableRecord<TData>) => U)
|
|
1594
1641
|
| ((cols: QueryableRecord<TData>) => QueryableRecord<TFrom["$inferInsert"]>),
|
|
@@ -1601,7 +1648,8 @@ export class Queryable<
|
|
|
1601
1648
|
insertFwdOrOutputColumns instanceof Function ? insertFwdOrOutputColumns : updateFwdOrInsertFwd
|
|
1602
1649
|
) as (updateRecord: U) => QueryableRecord<TFrom["$inferInsert"]>;
|
|
1603
1650
|
|
|
1604
|
-
const realOutputColumns =
|
|
1651
|
+
const realOutputColumns =
|
|
1652
|
+
insertFwdOrOutputColumns instanceof Function ? outputColumns : insertFwdOrOutputColumns;
|
|
1605
1653
|
|
|
1606
1654
|
const results = await this.meta.db.executeDefs<Pick<TFrom["$inferColumns"], K>>(
|
|
1607
1655
|
[this.getUpsertQueryDef(updateRecordFwd, insertRecordFwd, realOutputColumns)],
|
|
@@ -1662,7 +1710,9 @@ export class Queryable<
|
|
|
1662
1710
|
async switchFk(switch_: "on" | "off"): Promise<void> {
|
|
1663
1711
|
const from = this.meta.from;
|
|
1664
1712
|
if (!(from instanceof TableBuilder) && !(from instanceof ViewBuilder)) {
|
|
1665
|
-
throw new Error(
|
|
1713
|
+
throw new Error(
|
|
1714
|
+
"switchFk는 TableBuilder 또는 ViewBuilder 기반 queryable에서만 사용할 수 있습니다.",
|
|
1715
|
+
);
|
|
1666
1716
|
}
|
|
1667
1717
|
await this.meta.db.switchFk(this.meta.db.getQueryDefObjectName(from), switch_);
|
|
1668
1718
|
}
|
|
@@ -1711,7 +1761,10 @@ export class Queryable<
|
|
|
1711
1761
|
* @returns 매칭된 PK 컬럼명 배열
|
|
1712
1762
|
* @throws FK/PK 컬럼 수 불일치 시
|
|
1713
1763
|
*/
|
|
1714
|
-
export function getMatchedPrimaryKeys(
|
|
1764
|
+
export function getMatchedPrimaryKeys(
|
|
1765
|
+
fkCols: string[],
|
|
1766
|
+
targetTable: TableBuilder<any, any>,
|
|
1767
|
+
): string[] {
|
|
1715
1768
|
const pk = targetTable.meta.primaryKey;
|
|
1716
1769
|
if (pk == null || fkCols.length !== pk.length) {
|
|
1717
1770
|
throw new Error(
|
|
@@ -1749,7 +1802,9 @@ function transformColumnsAlias<TRecord extends DataRecord>(
|
|
|
1749
1802
|
result[key] = expr.col(value.dataType, alias, fullKey);
|
|
1750
1803
|
} else if (Array.isArray(value)) {
|
|
1751
1804
|
if (value.length > 0) {
|
|
1752
|
-
result[key] = [
|
|
1805
|
+
result[key] = [
|
|
1806
|
+
transformColumnsAlias(value[0] as QueryableRecord<DataRecord>, alias, fullKey),
|
|
1807
|
+
];
|
|
1753
1808
|
}
|
|
1754
1809
|
} else if (typeof value === "object" && value != null) {
|
|
1755
1810
|
result[key] = transformColumnsAlias(value as QueryableRecord<DataRecord>, alias, fullKey);
|
|
@@ -1767,7 +1822,12 @@ function transformColumnsAlias<TRecord extends DataRecord>(
|
|
|
1767
1822
|
|
|
1768
1823
|
interface QueryableMeta<TData extends DataRecord> {
|
|
1769
1824
|
db: DbContextBase;
|
|
1770
|
-
from?:
|
|
1825
|
+
from?:
|
|
1826
|
+
| TableBuilder<any, any>
|
|
1827
|
+
| ViewBuilder<any, any, any>
|
|
1828
|
+
| Queryable<any, any>
|
|
1829
|
+
| Queryable<TData, any>[]
|
|
1830
|
+
| string;
|
|
1771
1831
|
as: string;
|
|
1772
1832
|
columns: QueryableRecord<TData>;
|
|
1773
1833
|
isCustomColumns?: boolean;
|
|
@@ -1835,7 +1895,9 @@ const PATH_SYMBOL = Symbol("path");
|
|
|
1835
1895
|
* include()용 타입 안전 경로 프록시
|
|
1836
1896
|
*/
|
|
1837
1897
|
export type PathProxy<TObject> = {
|
|
1838
|
-
[K in keyof TObject as TObject[K] extends ColumnPrimitive ? never : K]-?: PathProxy<
|
|
1898
|
+
[K in keyof TObject as TObject[K] extends ColumnPrimitive ? never : K]-?: PathProxy<
|
|
1899
|
+
UnwrapArray<TObject[K]>
|
|
1900
|
+
>;
|
|
1839
1901
|
} & { readonly [PATH_SYMBOL]: string[] };
|
|
1840
1902
|
|
|
1841
1903
|
/**
|
|
@@ -1898,7 +1960,10 @@ export function queryable<TBuilder extends TableBuilder<any, any> | ViewBuilder<
|
|
|
1898
1960
|
from: tableOrView,
|
|
1899
1961
|
as: finalAs,
|
|
1900
1962
|
columns: Object.fromEntries(
|
|
1901
|
-
Object.entries(columnDefs).map(([key, colDef]) => [
|
|
1963
|
+
Object.entries(columnDefs).map(([key, colDef]) => [
|
|
1964
|
+
key,
|
|
1965
|
+
expr.col(colDef.meta.type, finalAs, key),
|
|
1966
|
+
]),
|
|
1902
1967
|
),
|
|
1903
1968
|
}) as any;
|
|
1904
1969
|
}
|
|
@@ -164,7 +164,11 @@ function termToLikePattern(term: string): string {
|
|
|
164
164
|
let pattern = term.replace(/\*/g, WILDCARD);
|
|
165
165
|
|
|
166
166
|
// SQL LIKE 특수문자 이스케이프 (\ → \\, % → \%, _ → \_, [ → \[)
|
|
167
|
-
pattern = pattern
|
|
167
|
+
pattern = pattern
|
|
168
|
+
.replace(/\\/g, "\\\\")
|
|
169
|
+
.replace(/%/g, "\\%")
|
|
170
|
+
.replace(/_/g, "\\_")
|
|
171
|
+
.replace(/\[/g, "\\[");
|
|
168
172
|
|
|
169
173
|
// 와일드카드 마커 → % (SQL 와일드카드)
|
|
170
174
|
pattern = pattern.replaceAll(WILDCARD, "%");
|
package/src/expr/expr.ts
CHANGED
|
@@ -88,7 +88,9 @@ export const expr = {
|
|
|
88
88
|
val<TStr extends ColumnPrimitiveStr, T extends ColumnPrimitiveMap[TStr] | undefined>(
|
|
89
89
|
dataType: TStr,
|
|
90
90
|
value: T,
|
|
91
|
-
): ExprUnit<
|
|
91
|
+
): ExprUnit<
|
|
92
|
+
T extends undefined ? ColumnPrimitiveMap[TStr] | undefined : ColumnPrimitiveMap[TStr]
|
|
93
|
+
> {
|
|
92
94
|
return new ExprUnit(dataType, { type: "value", value });
|
|
93
95
|
},
|
|
94
96
|
|
|
@@ -287,7 +289,11 @@ export const expr = {
|
|
|
287
289
|
* // WHERE age >= 18
|
|
288
290
|
* ```
|
|
289
291
|
*/
|
|
290
|
-
between<T extends ColumnPrimitive>(
|
|
292
|
+
between<T extends ColumnPrimitive>(
|
|
293
|
+
source: ExprUnit<T>,
|
|
294
|
+
from?: ExprInput<T>,
|
|
295
|
+
to?: ExprInput<T>,
|
|
296
|
+
): WhereExprUnit {
|
|
291
297
|
return new WhereExprUnit({
|
|
292
298
|
type: "between",
|
|
293
299
|
source: toExpr(source),
|
|
@@ -343,7 +349,10 @@ export const expr = {
|
|
|
343
349
|
* db.user().where((u) => [expr.like(u.email, "%@gmail.com")])
|
|
344
350
|
* ```
|
|
345
351
|
*/
|
|
346
|
-
like(
|
|
352
|
+
like(
|
|
353
|
+
source: ExprUnit<string | undefined>,
|
|
354
|
+
pattern: ExprInput<string | undefined>,
|
|
355
|
+
): WhereExprUnit {
|
|
347
356
|
return new WhereExprUnit({
|
|
348
357
|
type: "like",
|
|
349
358
|
source: toExpr(source),
|
|
@@ -366,7 +375,10 @@ export const expr = {
|
|
|
366
375
|
* // MySQL: WHERE email REGEXP '^[a-z]+@'
|
|
367
376
|
* ```
|
|
368
377
|
*/
|
|
369
|
-
regexp(
|
|
378
|
+
regexp(
|
|
379
|
+
source: ExprUnit<string | undefined>,
|
|
380
|
+
pattern: ExprInput<string | undefined>,
|
|
381
|
+
): WhereExprUnit {
|
|
370
382
|
return new WhereExprUnit({
|
|
371
383
|
type: "regexp",
|
|
372
384
|
source: toExpr(source),
|
|
@@ -1022,7 +1034,9 @@ export const expr = {
|
|
|
1022
1034
|
* // SELECT HOUR(createdAt) AS logHour
|
|
1023
1035
|
* ```
|
|
1024
1036
|
*/
|
|
1025
|
-
hour<T extends DateTime | Time | undefined>(
|
|
1037
|
+
hour<T extends DateTime | Time | undefined>(
|
|
1038
|
+
source: ExprUnit<T>,
|
|
1039
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1026
1040
|
return new ExprUnit("number", {
|
|
1027
1041
|
type: "hour",
|
|
1028
1042
|
arg: toExpr(source),
|
|
@@ -1091,7 +1105,9 @@ export const expr = {
|
|
|
1091
1105
|
* // SELECT WEEK(orderDate, 3) AS weekNum (MySQL)
|
|
1092
1106
|
* ```
|
|
1093
1107
|
*/
|
|
1094
|
-
isoWeek<T extends DateOnly | undefined>(
|
|
1108
|
+
isoWeek<T extends DateOnly | undefined>(
|
|
1109
|
+
source: ExprUnit<T>,
|
|
1110
|
+
): ExprUnit<T extends undefined ? undefined : number> {
|
|
1095
1111
|
return new ExprUnit("number", {
|
|
1096
1112
|
type: "isoWeek",
|
|
1097
1113
|
arg: toExpr(source),
|
|
@@ -1271,7 +1287,10 @@ export const expr = {
|
|
|
1271
1287
|
* // SELECT NULLIF(bio, '') AS bio
|
|
1272
1288
|
* ```
|
|
1273
1289
|
*/
|
|
1274
|
-
nullIf<T extends ColumnPrimitive>(
|
|
1290
|
+
nullIf<T extends ColumnPrimitive>(
|
|
1291
|
+
source: ExprUnit<T>,
|
|
1292
|
+
value: ExprInput<T>,
|
|
1293
|
+
): ExprUnit<T | undefined> {
|
|
1275
1294
|
return new ExprUnit(source.dataType, {
|
|
1276
1295
|
type: "nullIf",
|
|
1277
1296
|
source: toExpr(source),
|
|
@@ -1341,7 +1360,11 @@ export const expr = {
|
|
|
1341
1360
|
* // SELECT IF(age >= 18, 'adult', 'minor') AS type
|
|
1342
1361
|
* ```
|
|
1343
1362
|
*/
|
|
1344
|
-
if<T extends ColumnPrimitive>(
|
|
1363
|
+
if<T extends ColumnPrimitive>(
|
|
1364
|
+
condition: WhereExprUnit,
|
|
1365
|
+
then: ExprInput<T>,
|
|
1366
|
+
else_: ExprInput<T>,
|
|
1367
|
+
): ExprUnit<T> {
|
|
1345
1368
|
const allValues = [then, else_];
|
|
1346
1369
|
// 1. ExprUnit에서 dataType 찾기
|
|
1347
1370
|
const exprUnit = allValues.find((v): v is ExprUnit<T> => v instanceof ExprUnit);
|
|
@@ -1832,7 +1855,10 @@ export const expr = {
|
|
|
1832
1855
|
* }))
|
|
1833
1856
|
* ```
|
|
1834
1857
|
*/
|
|
1835
|
-
firstValue<T extends ColumnPrimitive>(
|
|
1858
|
+
firstValue<T extends ColumnPrimitive>(
|
|
1859
|
+
column: ExprUnit<T>,
|
|
1860
|
+
spec: WinSpecInput,
|
|
1861
|
+
): ExprUnit<T | undefined> {
|
|
1836
1862
|
return new ExprUnit(column.dataType, {
|
|
1837
1863
|
type: "window",
|
|
1838
1864
|
fn: { type: "firstValue", column: toExpr(column) },
|
|
@@ -1858,7 +1884,10 @@ export const expr = {
|
|
|
1858
1884
|
* }))
|
|
1859
1885
|
* ```
|
|
1860
1886
|
*/
|
|
1861
|
-
lastValue<T extends ColumnPrimitive>(
|
|
1887
|
+
lastValue<T extends ColumnPrimitive>(
|
|
1888
|
+
column: ExprUnit<T>,
|
|
1889
|
+
spec: WinSpecInput,
|
|
1890
|
+
): ExprUnit<T | undefined> {
|
|
1862
1891
|
return new ExprUnit(column.dataType, {
|
|
1863
1892
|
type: "window",
|
|
1864
1893
|
fn: { type: "lastValue", column: toExpr(column) },
|
|
@@ -1962,7 +1991,10 @@ export const expr = {
|
|
|
1962
1991
|
* }))
|
|
1963
1992
|
* ```
|
|
1964
1993
|
*/
|
|
1965
|
-
minOver<T extends ColumnPrimitive>(
|
|
1994
|
+
minOver<T extends ColumnPrimitive>(
|
|
1995
|
+
column: ExprUnit<T>,
|
|
1996
|
+
spec: WinSpecInput,
|
|
1997
|
+
): ExprUnit<T | undefined> {
|
|
1966
1998
|
return new ExprUnit(column.dataType, {
|
|
1967
1999
|
type: "window",
|
|
1968
2000
|
fn: { type: "min", column: toExpr(column) },
|
|
@@ -1987,7 +2019,10 @@ export const expr = {
|
|
|
1987
2019
|
* }))
|
|
1988
2020
|
* ```
|
|
1989
2021
|
*/
|
|
1990
|
-
maxOver<T extends ColumnPrimitive>(
|
|
2022
|
+
maxOver<T extends ColumnPrimitive>(
|
|
2023
|
+
column: ExprUnit<T>,
|
|
2024
|
+
spec: WinSpecInput,
|
|
2025
|
+
): ExprUnit<T | undefined> {
|
|
1991
2026
|
return new ExprUnit(column.dataType, {
|
|
1992
2027
|
type: "window",
|
|
1993
2028
|
fn: { type: "max", column: toExpr(column) },
|
|
@@ -2022,8 +2057,12 @@ function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
|
2022
2057
|
ExprInput<NonNullable<TPrimitive>>,
|
|
2023
2058
|
]
|
|
2024
2059
|
): ExprUnit<NonNullable<TPrimitive>>;
|
|
2025
|
-
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2026
|
-
|
|
2060
|
+
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2061
|
+
...args: ExprInput<TPrimitive>[]
|
|
2062
|
+
): ExprUnit<TPrimitive>;
|
|
2063
|
+
function ifNull<TPrimitive extends ColumnPrimitive>(
|
|
2064
|
+
...args: ExprInput<TPrimitive>[]
|
|
2065
|
+
): ExprUnit<TPrimitive> {
|
|
2027
2066
|
return new ExprUnit(findDataType(args), {
|
|
2028
2067
|
type: "ifNull",
|
|
2029
2068
|
args: args.map((a) => toExpr(a)),
|
|
@@ -2077,7 +2116,9 @@ export function toExpr(value: ExprInput<ColumnPrimitive>): Expr {
|
|
|
2077
2116
|
return { type: "value", value };
|
|
2078
2117
|
}
|
|
2079
2118
|
|
|
2080
|
-
function findDataType<TPrimitive extends ColumnPrimitive>(
|
|
2119
|
+
function findDataType<TPrimitive extends ColumnPrimitive>(
|
|
2120
|
+
args: ExprInput<TPrimitive>[],
|
|
2121
|
+
): ColumnPrimitiveStr {
|
|
2081
2122
|
const exprUnit = args.find((a): a is ExprUnit<TPrimitive> => a instanceof ExprUnit);
|
|
2082
2123
|
if (!exprUnit) {
|
|
2083
2124
|
throw new Error("args중 적어도 하나는 ExprUnit이어야 합니다.");
|
|
@@ -78,7 +78,9 @@ export abstract class QueryBuilderBase {
|
|
|
78
78
|
/** ORDER BY 절 렌더링 */
|
|
79
79
|
protected renderOrderBy(orderBy: [Expr, ("ASC" | "DESC")?][] | undefined): string {
|
|
80
80
|
if (orderBy == null || orderBy.length === 0) return "";
|
|
81
|
-
const parts = orderBy.map(
|
|
81
|
+
const parts = orderBy.map(
|
|
82
|
+
([e, dir]) => `${this.expr.render(e)}${dir != null ? ` ${dir}` : ""}`,
|
|
83
|
+
);
|
|
82
84
|
return ` ORDER BY ${parts.join(", ")}`;
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -457,7 +457,9 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
protected switch(expr: ExprSwitch): string {
|
|
460
|
-
const cases = expr.cases
|
|
460
|
+
const cases = expr.cases
|
|
461
|
+
.map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`)
|
|
462
|
+
.join(" ");
|
|
461
463
|
return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
|
|
462
464
|
}
|
|
463
465
|
|
|
@@ -585,7 +587,9 @@ export class MssqlExprRenderer extends ExprRendererBase {
|
|
|
585
587
|
parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
|
|
586
588
|
}
|
|
587
589
|
if (spec.orderBy != null && spec.orderBy.length > 0) {
|
|
588
|
-
const orderParts = spec.orderBy.map(
|
|
590
|
+
const orderParts = spec.orderBy.map(
|
|
591
|
+
([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`,
|
|
592
|
+
);
|
|
589
593
|
parts.push(`ORDER BY ${orderParts.join(", ")}`);
|
|
590
594
|
}
|
|
591
595
|
return parts.join(" ");
|
|
@@ -75,7 +75,10 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// 일반 JOIN
|
|
78
|
-
const where =
|
|
78
|
+
const where =
|
|
79
|
+
join.where != null && join.where.length > 0
|
|
80
|
+
? ` ON ${this.expr.renderWhere(join.where)}`
|
|
81
|
+
: " ON 1=1";
|
|
79
82
|
return ` LEFT OUTER JOIN ${from} AS ${alias}${where}`;
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -321,7 +324,8 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
321
324
|
|
|
322
325
|
let sql = `MERGE ${table} AS ${alias}\n`;
|
|
323
326
|
sql += `USING (SELECT 1 AS [_]) AS [_src] ON `;
|
|
324
|
-
sql +=
|
|
327
|
+
sql +=
|
|
328
|
+
existsWhere != null && existsWhere.length > 0 ? this.expr.renderWhere(existsWhere) : "1=0";
|
|
325
329
|
|
|
326
330
|
if (updateSetParts.length > 0) {
|
|
327
331
|
sql += `\nWHEN MATCHED THEN UPDATE SET ${updateSetParts.join(", ")}`;
|
|
@@ -422,7 +426,9 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
422
426
|
}
|
|
423
427
|
|
|
424
428
|
protected dropColumn(def: DropColumnQueryDef): QueryBuildResult {
|
|
425
|
-
return {
|
|
429
|
+
return {
|
|
430
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP COLUMN ${this.expr.wrap(def.column)}`,
|
|
431
|
+
};
|
|
426
432
|
}
|
|
427
433
|
|
|
428
434
|
protected modifyColumn(def: ModifyColumnQueryDef): QueryBuildResult {
|
|
@@ -458,7 +464,9 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
458
464
|
const table = this.tableName(def.table);
|
|
459
465
|
const cols = def.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
460
466
|
const pkName = `PK_${def.table.name}`;
|
|
461
|
-
return {
|
|
467
|
+
return {
|
|
468
|
+
sql: `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(pkName)} PRIMARY KEY (${cols})`,
|
|
469
|
+
};
|
|
462
470
|
}
|
|
463
471
|
|
|
464
472
|
protected dropPk(def: DropPkQueryDef): QueryBuildResult {
|
|
@@ -484,7 +492,9 @@ export class MssqlQueryBuilder extends QueryBuilderBase {
|
|
|
484
492
|
}
|
|
485
493
|
|
|
486
494
|
protected dropFk(def: DropFkQueryDef): QueryBuildResult {
|
|
487
|
-
return {
|
|
495
|
+
return {
|
|
496
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP CONSTRAINT ${this.expr.wrap(def.foreignKey)}`,
|
|
497
|
+
};
|
|
488
498
|
}
|
|
489
499
|
|
|
490
500
|
protected addIdx(def: AddIdxQueryDef): QueryBuildResult {
|
|
@@ -467,7 +467,9 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
protected switch(expr: ExprSwitch): string {
|
|
470
|
-
const cases = expr.cases
|
|
470
|
+
const cases = expr.cases
|
|
471
|
+
.map((c) => `WHEN ${this.render(c.when)} THEN ${this.render(c.then)}`)
|
|
472
|
+
.join(" ");
|
|
471
473
|
return `CASE ${cases} ELSE ${this.render(expr.else)} END`;
|
|
472
474
|
}
|
|
473
475
|
|
|
@@ -591,7 +593,9 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
591
593
|
parts.push(`PARTITION BY ${spec.partitionBy.map((p) => this.render(p)).join(", ")}`);
|
|
592
594
|
}
|
|
593
595
|
if (spec.orderBy != null && spec.orderBy.length > 0) {
|
|
594
|
-
const orderParts = spec.orderBy.map(
|
|
596
|
+
const orderParts = spec.orderBy.map(
|
|
597
|
+
([expr, dir]) => `${this.render(expr)}${dir != null ? ` ${dir}` : ""}`,
|
|
598
|
+
);
|
|
595
599
|
parts.push(`ORDER BY ${orderParts.join(", ")}`);
|
|
596
600
|
}
|
|
597
601
|
return parts.join(" ");
|
|
@@ -75,7 +75,10 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
|
|
|
75
75
|
protected renderJoin(join: SelectQueryDefJoin): string {
|
|
76
76
|
const alias = this.expr.wrap(join.as);
|
|
77
77
|
const from = this.renderFrom(join.from);
|
|
78
|
-
const where =
|
|
78
|
+
const where =
|
|
79
|
+
join.where != null && join.where.length > 0
|
|
80
|
+
? ` ON ${this.expr.renderWhere(join.where)}`
|
|
81
|
+
: " ON TRUE";
|
|
79
82
|
|
|
80
83
|
// LATERAL JOIN 필요 여부 감지
|
|
81
84
|
if (this.needsLateral(join)) {
|
|
@@ -287,7 +290,9 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
|
|
|
287
290
|
// recordsSelectQuery에서 PK 컬럼만 추출한 SELECT 생성
|
|
288
291
|
const pkSelectDef: SelectQueryDef = {
|
|
289
292
|
...def.recordsSelectQuery,
|
|
290
|
-
select: Object.fromEntries(
|
|
293
|
+
select: Object.fromEntries(
|
|
294
|
+
def.output.pkColNames.map((pk) => [pk, def.recordsSelectQuery.select![pk]]),
|
|
295
|
+
),
|
|
291
296
|
};
|
|
292
297
|
const pkSelectSql = this.select(pkSelectDef).sql;
|
|
293
298
|
|
|
@@ -569,7 +574,9 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
|
|
|
569
574
|
}
|
|
570
575
|
|
|
571
576
|
protected dropColumn(def: DropColumnQueryDef): QueryBuildResult {
|
|
572
|
-
return {
|
|
577
|
+
return {
|
|
578
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP COLUMN ${this.expr.wrap(def.column)}`,
|
|
579
|
+
};
|
|
573
580
|
}
|
|
574
581
|
|
|
575
582
|
protected modifyColumn(def: ModifyColumnQueryDef): QueryBuildResult {
|
|
@@ -632,7 +639,9 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
|
|
|
632
639
|
}
|
|
633
640
|
|
|
634
641
|
protected dropFk(def: DropFkQueryDef): QueryBuildResult {
|
|
635
|
-
return {
|
|
642
|
+
return {
|
|
643
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP FOREIGN KEY ${this.expr.wrap(def.foreignKey)}`,
|
|
644
|
+
};
|
|
636
645
|
}
|
|
637
646
|
|
|
638
647
|
protected addIdx(def: AddIdxQueryDef): QueryBuildResult {
|
|
@@ -731,12 +740,16 @@ SET FOREIGN_KEY_CHECKS = 1`,
|
|
|
731
740
|
protected schemaExists(def: SchemaExistsQueryDef): QueryBuildResult {
|
|
732
741
|
// MySQL: database와 schema는 동의어
|
|
733
742
|
const dbName = this.expr.escapeString(def.database);
|
|
734
|
-
return {
|
|
743
|
+
return {
|
|
744
|
+
sql: `SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${dbName}'`,
|
|
745
|
+
};
|
|
735
746
|
}
|
|
736
747
|
|
|
737
748
|
/** MySQL은 전역 설정만 지원 (테이블 파라미터 무시됨) */
|
|
738
749
|
protected switchFk(def: SwitchFkQueryDef): QueryBuildResult {
|
|
739
|
-
return def.switch === "on"
|
|
750
|
+
return def.switch === "on"
|
|
751
|
+
? { sql: "SET FOREIGN_KEY_CHECKS = 1" }
|
|
752
|
+
: { sql: "SET FOREIGN_KEY_CHECKS = 0" };
|
|
740
753
|
}
|
|
741
754
|
|
|
742
755
|
//#endregion
|