@simplysm/orm-common 13.0.69 → 13.0.71
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 +54 -1447
- package/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +9 -9
- package/dist/create-db-context.js.map +1 -1
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +2 -2
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/table-ddl.d.ts +24 -24
- package/dist/ddl/table-ddl.js +4 -4
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +3 -3
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.d.ts +160 -160
- package/dist/exec/queryable.js +119 -119
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.d.ts +37 -37
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr.d.ts +257 -257
- package/dist/expr/expr.js +265 -265
- package/dist/expr/expr.js.map +1 -1
- package/dist/query-builder/base/expr-renderer-base.d.ts +9 -9
- package/dist/query-builder/base/expr-renderer-base.js +2 -2
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -1
- package/dist/query-builder/base/query-builder-base.d.ts +26 -26
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +22 -22
- 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 +18 -18
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.d.ts +2 -2
- package/dist/query-builder/mssql/mssql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-query-builder.js +11 -11
- 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 +17 -17
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +8 -8
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +5 -5
- 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 +17 -17
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +5 -5
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +79 -79
- package/dist/schema/factory/column-builder.js +42 -42
- package/dist/schema/factory/index-builder.d.ts +39 -39
- package/dist/schema/factory/index-builder.js +26 -26
- package/dist/schema/factory/relation-builder.d.ts +99 -99
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +38 -38
- package/dist/schema/procedure-builder.d.ts +49 -49
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +33 -33
- package/dist/schema/table-builder.d.ts +59 -59
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +43 -43
- package/dist/schema/view-builder.d.ts +49 -49
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +32 -32
- package/dist/types/column.d.ts +22 -22
- package/dist/types/column.js +1 -1
- package/dist/types/column.js.map +1 -1
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +59 -59
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +44 -44
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/utils/result-parser.d.ts +11 -11
- package/dist/utils/result-parser.js +3 -3
- package/dist/utils/result-parser.js.map +1 -1
- package/package.json +5 -5
- package/src/create-db-context.ts +20 -20
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +259 -259
- package/src/ddl/relation-ddl.ts +89 -89
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +189 -189
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +2033 -2033
- package/src/exec/search-parser.ts +57 -57
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +2140 -2140
- package/src/query-builder/base/expr-renderer-base.ts +237 -237
- package/src/query-builder/base/query-builder-base.ts +213 -213
- package/src/query-builder/mssql/mssql-expr-renderer.ts +607 -607
- package/src/query-builder/mssql/mssql-query-builder.ts +650 -650
- package/src/query-builder/mysql/mysql-expr-renderer.ts +613 -613
- package/src/query-builder/mysql/mysql-query-builder.ts +759 -759
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +611 -611
- package/src/query-builder/postgresql/postgresql-query-builder.ts +686 -686
- package/src/query-builder/query-builder.ts +19 -19
- package/src/schema/factory/column-builder.ts +423 -423
- package/src/schema/factory/index-builder.ts +164 -164
- package/src/schema/factory/relation-builder.ts +453 -453
- package/src/schema/procedure-builder.ts +232 -232
- package/src/schema/table-builder.ts +319 -319
- package/src/schema/view-builder.ts +221 -221
- package/src/types/column.ts +188 -188
- package/src/types/db.ts +208 -208
- package/src/types/expr.ts +697 -697
- package/src/types/query-def.ts +513 -513
- package/src/utils/result-parser.ts +458 -458
- package/tests/db-context/create-db-context.spec.ts +224 -0
- package/tests/db-context/define-db-context.spec.ts +68 -0
- package/tests/ddl/basic.expected.ts +341 -0
- package/tests/ddl/basic.spec.ts +714 -0
- package/tests/ddl/column-builder.expected.ts +310 -0
- package/tests/ddl/column-builder.spec.ts +637 -0
- package/tests/ddl/index-builder.expected.ts +38 -0
- package/tests/ddl/index-builder.spec.ts +202 -0
- package/tests/ddl/procedure-builder.expected.ts +52 -0
- package/tests/ddl/procedure-builder.spec.ts +234 -0
- package/tests/ddl/relation-builder.expected.ts +36 -0
- package/tests/ddl/relation-builder.spec.ts +372 -0
- package/tests/ddl/table-builder.expected.ts +113 -0
- package/tests/ddl/table-builder.spec.ts +433 -0
- package/tests/ddl/view-builder.expected.ts +38 -0
- package/tests/ddl/view-builder.spec.ts +176 -0
- package/tests/dml/delete.expected.ts +96 -0
- package/tests/dml/delete.spec.ts +160 -0
- package/tests/dml/insert.expected.ts +192 -0
- package/tests/dml/insert.spec.ts +288 -0
- package/tests/dml/update.expected.ts +176 -0
- package/tests/dml/update.spec.ts +318 -0
- package/tests/dml/upsert.expected.ts +215 -0
- package/tests/dml/upsert.spec.ts +242 -0
- package/tests/errors/queryable-errors.spec.ts +177 -0
- package/tests/escape.spec.ts +100 -0
- package/tests/examples/pivot.expected.ts +211 -0
- package/tests/examples/pivot.spec.ts +533 -0
- package/tests/examples/sampling.expected.ts +69 -0
- package/tests/examples/sampling.spec.ts +105 -0
- package/tests/examples/unpivot.expected.ts +120 -0
- package/tests/examples/unpivot.spec.ts +226 -0
- package/tests/exec/search-parser.spec.ts +283 -0
- package/tests/executable/basic.expected.ts +18 -0
- package/tests/executable/basic.spec.ts +54 -0
- package/tests/expr/comparison.expected.ts +282 -0
- package/tests/expr/comparison.spec.ts +400 -0
- package/tests/expr/conditional.expected.ts +134 -0
- package/tests/expr/conditional.spec.ts +276 -0
- package/tests/expr/date.expected.ts +332 -0
- package/tests/expr/date.spec.ts +526 -0
- package/tests/expr/math.expected.ts +62 -0
- package/tests/expr/math.spec.ts +106 -0
- package/tests/expr/string.expected.ts +218 -0
- package/tests/expr/string.spec.ts +356 -0
- package/tests/expr/utility.expected.ts +147 -0
- package/tests/expr/utility.spec.ts +182 -0
- package/tests/select/basic.expected.ts +322 -0
- package/tests/select/basic.spec.ts +502 -0
- package/tests/select/filter.expected.ts +357 -0
- package/tests/select/filter.spec.ts +1068 -0
- package/tests/select/group.expected.ts +169 -0
- package/tests/select/group.spec.ts +244 -0
- package/tests/select/join.expected.ts +582 -0
- package/tests/select/join.spec.ts +805 -0
- package/tests/select/order.expected.ts +150 -0
- package/tests/select/order.spec.ts +189 -0
- package/tests/select/recursive-cte.expected.ts +244 -0
- package/tests/select/recursive-cte.spec.ts +514 -0
- package/tests/select/result-meta.spec.ts +270 -0
- package/tests/select/subquery.expected.ts +363 -0
- package/tests/select/subquery.spec.ts +537 -0
- package/tests/select/view.expected.ts +155 -0
- package/tests/select/view.spec.ts +235 -0
- package/tests/select/window.expected.ts +345 -0
- package/tests/select/window.spec.ts +618 -0
- package/tests/setup/MockExecutor.ts +18 -0
- package/tests/setup/TestDbContext.ts +59 -0
- package/tests/setup/models/Company.ts +13 -0
- package/tests/setup/models/Employee.ts +10 -0
- package/tests/setup/models/MonthlySales.ts +11 -0
- package/tests/setup/models/Post.ts +16 -0
- package/tests/setup/models/Sales.ts +10 -0
- package/tests/setup/models/User.ts +19 -0
- package/tests/setup/procedure/GetAllUsers.ts +9 -0
- package/tests/setup/procedure/GetUserById.ts +12 -0
- package/tests/setup/test-utils.ts +72 -0
- package/tests/setup/views/ActiveUsers.ts +8 -0
- package/tests/setup/views/UserSummary.ts +11 -0
- package/tests/types/nullable-queryable-record.spec.ts +145 -0
- package/tests/utils/result-parser-perf.spec.ts +210 -0
- package/tests/utils/result-parser.spec.ts +701 -0
- package/docs/expressions.md +0 -172
- package/docs/queries.md +0 -444
- package/docs/schema.md +0 -245
package/docs/expressions.md
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Expressions (expr)
|
|
2
|
-
|
|
3
|
-
The `expr` object generates Dialect-independent SQL expressions. It creates JSON AST instead of SQL strings, which the `QueryBuilder` converts for each DBMS.
|
|
4
|
-
|
|
5
|
-
## Comparison Expressions (WHERE)
|
|
6
|
-
|
|
7
|
-
| Method | SQL | Description |
|
|
8
|
-
|--------|-----|------|
|
|
9
|
-
| `expr.eq(a, b)` | `a = b` (NULL-safe) | Equality comparison |
|
|
10
|
-
| `expr.gt(a, b)` | `a > b` | Greater than |
|
|
11
|
-
| `expr.lt(a, b)` | `a < b` | Less than |
|
|
12
|
-
| `expr.gte(a, b)` | `a >= b` | Greater than or equal |
|
|
13
|
-
| `expr.lte(a, b)` | `a <= b` | Less than or equal |
|
|
14
|
-
| `expr.between(a, from, to)` | `a BETWEEN from AND to` | Range (unbounded in direction if undefined) |
|
|
15
|
-
| `expr.null(a)` | `a IS NULL` | NULL check |
|
|
16
|
-
| `expr.like(a, pattern)` | `a LIKE pattern` | Pattern matching |
|
|
17
|
-
| `expr.regexp(a, pattern)` | `a REGEXP pattern` | Regex matching |
|
|
18
|
-
| `expr.in(a, values)` | `a IN (v1, v2, ...)` | Value list comparison |
|
|
19
|
-
| `expr.inQuery(a, query)` | `a IN (SELECT ...)` | Subquery comparison |
|
|
20
|
-
| `expr.exists(query)` | `EXISTS (SELECT ...)` | Subquery existence |
|
|
21
|
-
|
|
22
|
-
## Logical Expressions (WHERE)
|
|
23
|
-
|
|
24
|
-
| Method | SQL | Description |
|
|
25
|
-
|--------|-----|------|
|
|
26
|
-
| `expr.and(conditions)` | `(c1 AND c2 AND ...)` | All conditions met |
|
|
27
|
-
| `expr.or(conditions)` | `(c1 OR c2 OR ...)` | At least one condition met |
|
|
28
|
-
| `expr.not(condition)` | `NOT (condition)` | Negate condition |
|
|
29
|
-
|
|
30
|
-
## String Expressions
|
|
31
|
-
|
|
32
|
-
| Method | SQL | Description |
|
|
33
|
-
|--------|-----|------|
|
|
34
|
-
| `expr.concat(...args)` | `CONCAT(a, b, ...)` | String concatenation |
|
|
35
|
-
| `expr.left(s, n)` | `LEFT(s, n)` | Extract n chars from left |
|
|
36
|
-
| `expr.right(s, n)` | `RIGHT(s, n)` | Extract n chars from right |
|
|
37
|
-
| `expr.trim(s)` | `TRIM(s)` | Trim whitespace from both sides |
|
|
38
|
-
| `expr.padStart(s, n, fill)` | `LPAD(s, n, fill)` | Left padding |
|
|
39
|
-
| `expr.replace(s, from, to)` | `REPLACE(s, from, to)` | String replacement |
|
|
40
|
-
| `expr.upper(s)` | `UPPER(s)` | Convert to uppercase |
|
|
41
|
-
| `expr.lower(s)` | `LOWER(s)` | Convert to lowercase |
|
|
42
|
-
| `expr.length(s)` | `CHAR_LENGTH(s)` | Character count |
|
|
43
|
-
| `expr.byteLength(s)` | `OCTET_LENGTH(s)` | Byte count |
|
|
44
|
-
| `expr.substring(s, start, len)` | `SUBSTRING(s, start, len)` | Substring extraction (1-based) |
|
|
45
|
-
| `expr.indexOf(s, search)` | `LOCATE(search, s)` | Find position (1-based, 0 if not found) |
|
|
46
|
-
|
|
47
|
-
## Numeric Expressions
|
|
48
|
-
|
|
49
|
-
| Method | SQL | Description |
|
|
50
|
-
|--------|-----|------|
|
|
51
|
-
| `expr.abs(n)` | `ABS(n)` | Absolute value |
|
|
52
|
-
| `expr.round(n, digits)` | `ROUND(n, digits)` | Round |
|
|
53
|
-
| `expr.ceil(n)` | `CEILING(n)` | Ceiling |
|
|
54
|
-
| `expr.floor(n)` | `FLOOR(n)` | Floor |
|
|
55
|
-
|
|
56
|
-
## Date Expressions
|
|
57
|
-
|
|
58
|
-
| Method | SQL | Description |
|
|
59
|
-
|--------|-----|------|
|
|
60
|
-
| `expr.year(d)` | `YEAR(d)` | Extract year |
|
|
61
|
-
| `expr.month(d)` | `MONTH(d)` | Extract month (1~12) |
|
|
62
|
-
| `expr.day(d)` | `DAY(d)` | Extract day (1~31) |
|
|
63
|
-
| `expr.hour(d)` | `HOUR(d)` | Extract hour (0~23) |
|
|
64
|
-
| `expr.minute(d)` | `MINUTE(d)` | Extract minute (0~59) |
|
|
65
|
-
| `expr.second(d)` | `SECOND(d)` | Extract second (0~59) |
|
|
66
|
-
| `expr.isoWeek(d)` | `WEEK(d, 3)` | ISO week (1~53) |
|
|
67
|
-
| `expr.isoWeekStartDate(d)` | - | ISO week start date (Monday) |
|
|
68
|
-
| `expr.isoYearMonth(d)` | - | First day of the month |
|
|
69
|
-
| `expr.dateDiff(sep, from, to)` | `DATEDIFF(sep, from, to)` | Date difference |
|
|
70
|
-
| `expr.dateAdd(sep, source, value)` | `DATEADD(sep, value, source)` | Add to date |
|
|
71
|
-
| `expr.formatDate(d, format)` | `DATE_FORMAT(d, format)` | Date formatting |
|
|
72
|
-
|
|
73
|
-
`DateSeparator`: `"year"`, `"month"`, `"day"`, `"hour"`, `"minute"`, `"second"`
|
|
74
|
-
|
|
75
|
-
## Conditional Expressions
|
|
76
|
-
|
|
77
|
-
| Method | SQL | Description |
|
|
78
|
-
|--------|-----|------|
|
|
79
|
-
| `expr.ifNull(a, b, ...)` | `COALESCE(a, b, ...)` | Return first non-null value |
|
|
80
|
-
| `expr.nullIf(a, b)` | `NULLIF(a, b)` | NULL if `a === b` |
|
|
81
|
-
| `expr.is(condition)` | `(condition)` | Convert WHERE to boolean |
|
|
82
|
-
| `expr.if(cond, then, else)` | `IF(cond, then, else)` | Ternary condition |
|
|
83
|
-
| `expr.switch()` | `CASE WHEN ... END` | Multiple condition branching |
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
// CASE WHEN usage example
|
|
87
|
-
db.user().select((u) => ({
|
|
88
|
-
grade: expr.switch<string>()
|
|
89
|
-
.case(expr.gte(u.score, 90), "A")
|
|
90
|
-
.case(expr.gte(u.score, 80), "B")
|
|
91
|
-
.case(expr.gte(u.score, 70), "C")
|
|
92
|
-
.default("F"),
|
|
93
|
-
}))
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Aggregate Expressions
|
|
97
|
-
|
|
98
|
-
| Method | SQL | Description |
|
|
99
|
-
|--------|-----|------|
|
|
100
|
-
| `expr.count(col?, distinct?)` | `COUNT(*)` / `COUNT(DISTINCT col)` | Row count |
|
|
101
|
-
| `expr.sum(col)` | `SUM(col)` | Sum |
|
|
102
|
-
| `expr.avg(col)` | `AVG(col)` | Average |
|
|
103
|
-
| `expr.max(col)` | `MAX(col)` | Maximum |
|
|
104
|
-
| `expr.min(col)` | `MIN(col)` | Minimum |
|
|
105
|
-
| `expr.greatest(...args)` | `GREATEST(a, b, ...)` | Greatest among multiple values |
|
|
106
|
-
| `expr.least(...args)` | `LEAST(a, b, ...)` | Least among multiple values |
|
|
107
|
-
|
|
108
|
-
## Window Functions
|
|
109
|
-
|
|
110
|
-
| Method | SQL | Description |
|
|
111
|
-
|--------|-----|------|
|
|
112
|
-
| `expr.rowNumber(spec)` | `ROW_NUMBER() OVER (...)` | Row number |
|
|
113
|
-
| `expr.rank(spec)` | `RANK() OVER (...)` | Rank (gaps on ties) |
|
|
114
|
-
| `expr.denseRank(spec)` | `DENSE_RANK() OVER (...)` | Dense rank (consecutive) |
|
|
115
|
-
| `expr.ntile(n, spec)` | `NTILE(n) OVER (...)` | Split into n groups |
|
|
116
|
-
| `expr.lag(col, spec, opts?)` | `LAG(col, offset) OVER (...)` | Previous row value |
|
|
117
|
-
| `expr.lead(col, spec, opts?)` | `LEAD(col, offset) OVER (...)` | Next row value |
|
|
118
|
-
| `expr.firstValue(col, spec)` | `FIRST_VALUE(col) OVER (...)` | First value |
|
|
119
|
-
| `expr.lastValue(col, spec)` | `LAST_VALUE(col) OVER (...)` | Last value |
|
|
120
|
-
| `expr.sumOver(col, spec)` | `SUM(col) OVER (...)` | Window sum |
|
|
121
|
-
| `expr.avgOver(col, spec)` | `AVG(col) OVER (...)` | Window average |
|
|
122
|
-
| `expr.countOver(spec, col?)` | `COUNT(*) OVER (...)` | Window count |
|
|
123
|
-
| `expr.minOver(col, spec)` | `MIN(col) OVER (...)` | Window minimum |
|
|
124
|
-
| `expr.maxOver(col, spec)` | `MAX(col) OVER (...)` | Window maximum |
|
|
125
|
-
|
|
126
|
-
`WinSpec`: `{ partitionBy?: [...], orderBy?: [[col, "ASC"|"DESC"], ...] }`
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
// Window function usage example
|
|
130
|
-
db.order().select((o) => ({
|
|
131
|
-
...o,
|
|
132
|
-
rowNum: expr.rowNumber({
|
|
133
|
-
partitionBy: [o.userId],
|
|
134
|
-
orderBy: [[o.createdAt, "DESC"]],
|
|
135
|
-
}),
|
|
136
|
-
runningTotal: expr.sumOver(o.amount, {
|
|
137
|
-
partitionBy: [o.userId],
|
|
138
|
-
orderBy: [[o.createdAt, "ASC"]],
|
|
139
|
-
}),
|
|
140
|
-
}))
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## Other Expressions
|
|
144
|
-
|
|
145
|
-
| Method | SQL | Description |
|
|
146
|
-
|--------|-----|------|
|
|
147
|
-
| `expr.val(dataType, value)` | Literal | Wrap typed value |
|
|
148
|
-
| `expr.col(dataType, ...path)` | Column reference | Create column reference (internal) |
|
|
149
|
-
| `expr.raw(dataType)\`sql\`` | Raw SQL | Escape hatch for DBMS-specific functions |
|
|
150
|
-
| `expr.rowNum()` | - | Total row number |
|
|
151
|
-
| `expr.random()` | `RAND()` / `RANDOM()` | Random number 0~1 |
|
|
152
|
-
| `expr.cast(source, type)` | `CAST(source AS type)` | Type conversion |
|
|
153
|
-
| `expr.subquery(dataType, qr)` | `(SELECT ...)` | Scalar subquery |
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
// Raw SQL (using DBMS-specific functions)
|
|
157
|
-
db.user().select((u) => ({
|
|
158
|
-
name: u.name,
|
|
159
|
-
data: expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`,
|
|
160
|
-
}))
|
|
161
|
-
|
|
162
|
-
// Scalar subquery
|
|
163
|
-
db.user().select((u) => ({
|
|
164
|
-
id: u.id,
|
|
165
|
-
postCount: expr.subquery(
|
|
166
|
-
"number",
|
|
167
|
-
db.post()
|
|
168
|
-
.where((p) => [expr.eq(p.userId, u.id)])
|
|
169
|
-
.select(() => ({ cnt: expr.count() }))
|
|
170
|
-
),
|
|
171
|
-
}))
|
|
172
|
-
```
|
package/docs/queries.md
DELETED
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
# Query Operations
|
|
2
|
-
|
|
3
|
-
## Connection and Transactions
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
import { defineDbContext, createDbContext } from "@simplysm/orm-common";
|
|
7
|
-
|
|
8
|
-
const MyDbDef = defineDbContext({ tables: { user: User } });
|
|
9
|
-
const db = createDbContext(MyDbDef, executor, { database: "mydb" });
|
|
10
|
-
|
|
11
|
-
// Execute within transaction (auto commit/rollback)
|
|
12
|
-
const users = await db.connect(async () => {
|
|
13
|
-
const result = await db.user().result();
|
|
14
|
-
await db.user().insert([{ name: "John Doe", createdAt: DateTime.now() }]);
|
|
15
|
-
return result;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Connect without transaction (for DDL operations or read-only queries)
|
|
19
|
-
await db.connectWithoutTransaction(async () => {
|
|
20
|
-
await db.initialize(); // Code First initialization
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Partial transaction within connectWithoutTransaction
|
|
24
|
-
await db.connectWithoutTransaction(async () => {
|
|
25
|
-
const report = await db.report().result(); // read without transaction
|
|
26
|
-
await db.trans(async () => {
|
|
27
|
-
await db.log().insert([{ message: "accessed" }]); // write with transaction
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Specify isolation level
|
|
32
|
-
await db.connect(async () => {
|
|
33
|
-
// ...
|
|
34
|
-
}, "SERIALIZABLE");
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Connection Methods
|
|
38
|
-
|
|
39
|
-
| Method | Description |
|
|
40
|
-
|--------|-------------|
|
|
41
|
-
| `db.connect(fn, isolationLevel?)` | Open connection, begin transaction, run fn, commit. Auto-rollback on error. |
|
|
42
|
-
| `db.connectWithoutTransaction(fn)` | Open connection without transaction, run fn, close. |
|
|
43
|
-
| `db.trans(fn, isolationLevel?)` | Begin transaction on an already-connected db. Must be called inside `connectWithoutTransaction`. |
|
|
44
|
-
|
|
45
|
-
## SELECT Queries
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// Basic query
|
|
49
|
-
const users = await db.user()
|
|
50
|
-
.where((u) => [expr.eq(u.isActive, true)])
|
|
51
|
-
.orderBy((u) => u.name)
|
|
52
|
-
.result();
|
|
53
|
-
|
|
54
|
-
// Column selection
|
|
55
|
-
const names = await db.user()
|
|
56
|
-
.select((u) => ({
|
|
57
|
-
userName: u.name,
|
|
58
|
-
userEmail: u.email,
|
|
59
|
-
}))
|
|
60
|
-
.result();
|
|
61
|
-
|
|
62
|
-
// Distinct rows
|
|
63
|
-
const uniqueNames = await db.user()
|
|
64
|
-
.select((u) => ({ name: u.name }))
|
|
65
|
-
.distinct()
|
|
66
|
-
.result();
|
|
67
|
-
|
|
68
|
-
// Single result (error if 2 or more)
|
|
69
|
-
const user = await db.user()
|
|
70
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
71
|
-
.single();
|
|
72
|
-
|
|
73
|
-
// First result only
|
|
74
|
-
const latest = await db.user()
|
|
75
|
-
.orderBy((u) => u.createdAt, "DESC")
|
|
76
|
-
.first();
|
|
77
|
-
|
|
78
|
-
// Row count
|
|
79
|
-
const count = await db.user()
|
|
80
|
-
.where((u) => [expr.eq(u.isActive, true)])
|
|
81
|
-
.count();
|
|
82
|
-
|
|
83
|
-
// Existence check
|
|
84
|
-
const hasAdmin = await db.user()
|
|
85
|
-
.where((u) => [expr.eq(u.role, "admin")])
|
|
86
|
-
.exists();
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Queryable Method Reference
|
|
90
|
-
|
|
91
|
-
| Method | Description |
|
|
92
|
-
|--------|-------------|
|
|
93
|
-
| `.select(fn)` | Map columns to a new shape |
|
|
94
|
-
| `.distinct()` | Remove duplicate rows |
|
|
95
|
-
| `.where(fn)` | Add WHERE condition (multiple calls = AND) |
|
|
96
|
-
| `.orderBy(fn, dir?)` | Add ORDER BY (`"ASC"` or `"DESC"`, default `"ASC"`) |
|
|
97
|
-
| `.top(n)` | Return at most n rows |
|
|
98
|
-
| `.limit(skip, take)` | Paginate (requires `orderBy` first) |
|
|
99
|
-
| `.result()` | Execute and return all rows |
|
|
100
|
-
| `.single()` | Execute, return first row, error if > 1 row |
|
|
101
|
-
| `.first()` | Execute, return first row only |
|
|
102
|
-
| `.count(fn?)` | Execute COUNT query |
|
|
103
|
-
| `.exists()` | Return true if any row matches |
|
|
104
|
-
|
|
105
|
-
## JOIN Queries
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
// Manual JOIN (1:N - array)
|
|
109
|
-
const usersWithPosts = await db.user()
|
|
110
|
-
.join("posts", (qr, u) =>
|
|
111
|
-
qr.from(Post).where((p) => [expr.eq(p.authorId, u.id)])
|
|
112
|
-
)
|
|
113
|
-
.result();
|
|
114
|
-
// Result: { id, name, posts: [{ id, title }, ...] }
|
|
115
|
-
|
|
116
|
-
// Manual JOIN (N:1 - single object)
|
|
117
|
-
const postsWithUser = await db.post()
|
|
118
|
-
.joinSingle("author", (qr, p) =>
|
|
119
|
-
qr.from(User).where((u) => [expr.eq(u.id, p.authorId)])
|
|
120
|
-
)
|
|
121
|
-
.result();
|
|
122
|
-
// Result: { id, title, author: { id, name } | undefined }
|
|
123
|
-
|
|
124
|
-
// include (auto JOIN based on relationship definition)
|
|
125
|
-
const postWithAuthor = await db.post()
|
|
126
|
-
.include((p) => p.author)
|
|
127
|
-
.single();
|
|
128
|
-
|
|
129
|
-
// Nested include
|
|
130
|
-
const postWithAuthorCompany = await db.post()
|
|
131
|
-
.include((p) => p.author.company)
|
|
132
|
-
.result();
|
|
133
|
-
|
|
134
|
-
// Multiple includes
|
|
135
|
-
const userWithAll = await db.user()
|
|
136
|
-
.include((u) => u.posts)
|
|
137
|
-
.include((u) => u.profile)
|
|
138
|
-
.result();
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Grouping and Aggregation
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
const stats = await db.order()
|
|
145
|
-
.select((o) => ({
|
|
146
|
-
userId: o.userId,
|
|
147
|
-
totalAmount: expr.sum(o.amount),
|
|
148
|
-
orderCount: expr.count(),
|
|
149
|
-
}))
|
|
150
|
-
.groupBy((o) => [o.userId])
|
|
151
|
-
.having((o) => [expr.gte(o.totalAmount, 10000)])
|
|
152
|
-
.result();
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## Pagination
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
// TOP (no ORDER BY required)
|
|
159
|
-
const topUsers = await db.user().top(10).result();
|
|
160
|
-
|
|
161
|
-
// LIMIT/OFFSET (ORDER BY required)
|
|
162
|
-
const page = await db.user()
|
|
163
|
-
.orderBy((u) => u.createdAt, "DESC")
|
|
164
|
-
.limit(0, 20) // skip 0, take 20
|
|
165
|
-
.result();
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Text Search
|
|
169
|
-
|
|
170
|
-
The `search()` method supports structured search syntax.
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
const users = await db.user()
|
|
174
|
-
.search((u) => [u.name, u.email], "John -deleted")
|
|
175
|
-
.result();
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Search Syntax
|
|
179
|
-
|
|
180
|
-
| Syntax | Description | Example |
|
|
181
|
-
|------|------|------|
|
|
182
|
-
| Space | OR combination | `apple banana` |
|
|
183
|
-
| `""` | Phrase search (required) | `"delicious apple"` |
|
|
184
|
-
| `+` | Required (AND) | `+apple +banana` |
|
|
185
|
-
| `-` | Exclude (NOT) | `apple -banana` |
|
|
186
|
-
| `*` | Wildcard | `app*` |
|
|
187
|
-
| `\*` | Escape | `app\*` (literal `*`) |
|
|
188
|
-
|
|
189
|
-
## UNION
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
const allItems = await Queryable.union(
|
|
193
|
-
db.user()
|
|
194
|
-
.where((u) => [expr.eq(u.type, "admin")])
|
|
195
|
-
.select((u) => ({ name: u.name })),
|
|
196
|
-
db.user()
|
|
197
|
-
.where((u) => [expr.eq(u.type, "manager")])
|
|
198
|
-
.select((u) => ({ name: u.name })),
|
|
199
|
-
).result();
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## Subquery Wrapping (wrap)
|
|
203
|
-
|
|
204
|
-
To use `count()` after `distinct()` or `groupBy()`, wrap the query with `wrap()`.
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
const count = await db.user()
|
|
208
|
-
.select((u) => ({ name: u.name }))
|
|
209
|
-
.distinct()
|
|
210
|
-
.wrap()
|
|
211
|
-
.count();
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## Recursive CTE (recursive)
|
|
215
|
-
|
|
216
|
-
Use for querying hierarchical data (org charts, category trees, etc.).
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
const employees = await db.employee()
|
|
220
|
-
.where((e) => [expr.null(e.managerId)]) // Root node
|
|
221
|
-
.recursive((cte) =>
|
|
222
|
-
cte.from(Employee)
|
|
223
|
-
.where((e) => [expr.eq(e.managerId, e.self[0].id)])
|
|
224
|
-
)
|
|
225
|
-
.result();
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## INSERT
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
// Simple insert
|
|
232
|
-
await db.user().insert([
|
|
233
|
-
{ name: "John Doe", createdAt: DateTime.now() },
|
|
234
|
-
{ name: "Jane Smith", createdAt: DateTime.now() },
|
|
235
|
-
]);
|
|
236
|
-
|
|
237
|
-
// Insert with ID return (outputColumns)
|
|
238
|
-
const [inserted] = await db.user().insert(
|
|
239
|
-
[{ name: "John Doe", createdAt: DateTime.now() }],
|
|
240
|
-
["id"],
|
|
241
|
-
);
|
|
242
|
-
// inserted.id is available
|
|
243
|
-
|
|
244
|
-
// Conditional insert (insert if not exists)
|
|
245
|
-
await db.user()
|
|
246
|
-
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
247
|
-
.insertIfNotExists({ name: "Test", email: "test@test.com", createdAt: DateTime.now() });
|
|
248
|
-
|
|
249
|
-
// INSERT INTO ... SELECT
|
|
250
|
-
await db.user()
|
|
251
|
-
.select((u) => ({ name: u.name, createdAt: u.createdAt }))
|
|
252
|
-
.where((u) => [expr.eq(u.isArchived, false)])
|
|
253
|
-
.insertInto(ArchivedUser);
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## UPDATE
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
// Plain values (recommended)
|
|
260
|
-
await db.user()
|
|
261
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
262
|
-
.update(() => ({
|
|
263
|
-
name: "새이름",
|
|
264
|
-
updatedAt: DateTime.now(),
|
|
265
|
-
}));
|
|
266
|
-
|
|
267
|
-
// Column reference (use ExprUnit from callback parameter)
|
|
268
|
-
await db.product()
|
|
269
|
-
.update((p) => ({
|
|
270
|
-
price: expr.mul(p.price, 1.1),
|
|
271
|
-
}));
|
|
272
|
-
|
|
273
|
-
// Mixed: plain values + expressions
|
|
274
|
-
await db.user()
|
|
275
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
276
|
-
.update((u) => ({
|
|
277
|
-
name: "새이름",
|
|
278
|
-
loginCount: expr.raw("number")`${u.loginCount} + 1`,
|
|
279
|
-
}));
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
## DELETE
|
|
283
|
-
|
|
284
|
-
```typescript
|
|
285
|
-
// Simple delete
|
|
286
|
-
await db.user()
|
|
287
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
288
|
-
.delete();
|
|
289
|
-
|
|
290
|
-
// Return deleted data
|
|
291
|
-
const deleted = await db.user()
|
|
292
|
-
.where((u) => [expr.eq(u.isExpired, true)])
|
|
293
|
-
.delete(["id", "name"]);
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## UPSERT
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
// Same data for UPDATE/INSERT
|
|
300
|
-
await db.user()
|
|
301
|
-
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
302
|
-
.upsert(() => ({
|
|
303
|
-
name: "Test",
|
|
304
|
-
email: "test@test.com",
|
|
305
|
-
}));
|
|
306
|
-
|
|
307
|
-
// Different data for UPDATE/INSERT
|
|
308
|
-
await db.user()
|
|
309
|
-
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
310
|
-
.upsert(
|
|
311
|
-
() => ({ loginCount: 1 }),
|
|
312
|
-
(update) => ({ ...update, email: "test@test.com" }),
|
|
313
|
-
);
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## Row Locking (FOR UPDATE)
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
await db.connect(async () => {
|
|
320
|
-
const user = await db.user()
|
|
321
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
322
|
-
.lock()
|
|
323
|
-
.single();
|
|
324
|
-
});
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
## DDL Operations
|
|
328
|
-
|
|
329
|
-
`DbContextInstance` supports Code First DDL operations.
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
await db.connectWithoutTransaction(async () => {
|
|
333
|
-
// Initialize database (create tables/views/procedures/FKs/indexes)
|
|
334
|
-
await db.initialize();
|
|
335
|
-
|
|
336
|
-
// Force initialize (drop and recreate existing data)
|
|
337
|
-
await db.initialize({ force: true });
|
|
338
|
-
|
|
339
|
-
// Individual DDL operations
|
|
340
|
-
const c = createColumnFactory();
|
|
341
|
-
await db.addColumn({ database: "mydb", name: "User" }, "status", c.varchar(20).nullable());
|
|
342
|
-
await db.modifyColumn({ database: "mydb", name: "User" }, "status", c.varchar(50).nullable());
|
|
343
|
-
await db.renameColumn({ database: "mydb", name: "User" }, "status", "userStatus");
|
|
344
|
-
await db.dropColumn({ database: "mydb", name: "User" }, "userStatus");
|
|
345
|
-
|
|
346
|
-
await db.renameTable({ database: "mydb", name: "User" }, "Member");
|
|
347
|
-
await db.truncate({ database: "mydb", name: "User" });
|
|
348
|
-
|
|
349
|
-
// Table existence / schema check
|
|
350
|
-
const exists = await db.schemaExists("mydb");
|
|
351
|
-
|
|
352
|
-
// FK management
|
|
353
|
-
await db.switchFk({ database: "mydb", name: "User" }, "off");
|
|
354
|
-
await db.switchFk({ database: "mydb", name: "User" }, "on");
|
|
355
|
-
});
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
### DDL Method Reference
|
|
359
|
-
|
|
360
|
-
| Method | Description |
|
|
361
|
-
|--------|-------------|
|
|
362
|
-
| `db.initialize(opts?)` | Create all tables, views, procedures, FKs, indexes per schema. `opts.force` drops before recreating. |
|
|
363
|
-
| `db.createTable(table)` | CREATE TABLE |
|
|
364
|
-
| `db.dropTable(name)` | DROP TABLE |
|
|
365
|
-
| `db.renameTable(name, newName)` | Rename table |
|
|
366
|
-
| `db.createView(view)` | CREATE VIEW |
|
|
367
|
-
| `db.dropView(name)` | DROP VIEW |
|
|
368
|
-
| `db.createProc(proc)` | CREATE PROCEDURE |
|
|
369
|
-
| `db.dropProc(name)` | DROP PROCEDURE |
|
|
370
|
-
| `db.addColumn(table, col, builder)` | ALTER TABLE ADD COLUMN |
|
|
371
|
-
| `db.dropColumn(table, col)` | ALTER TABLE DROP COLUMN |
|
|
372
|
-
| `db.modifyColumn(table, col, builder)` | ALTER TABLE MODIFY COLUMN |
|
|
373
|
-
| `db.renameColumn(table, col, newName)` | Rename column |
|
|
374
|
-
| `db.addPk(table, cols)` | Add primary key constraint |
|
|
375
|
-
| `db.dropPk(table)` | Drop primary key constraint |
|
|
376
|
-
| `db.addFk(table, relName, fkBuilder)` | Add foreign key constraint |
|
|
377
|
-
| `db.dropFk(table, relName)` | Drop foreign key constraint |
|
|
378
|
-
| `db.addIdx(table, idxBuilder)` | Add index |
|
|
379
|
-
| `db.dropIdx(table, cols)` | Drop index |
|
|
380
|
-
| `db.clearSchema(params)` | Drop all objects in schema |
|
|
381
|
-
| `db.schemaExists(database, schema?)` | Check if schema exists |
|
|
382
|
-
| `db.truncate(table)` | TRUNCATE TABLE |
|
|
383
|
-
| `db.switchFk(table, "on"\|"off")` | Enable/disable FK constraints |
|
|
384
|
-
|
|
385
|
-
## Query Builder (SQL Generation)
|
|
386
|
-
|
|
387
|
-
Converts `QueryDef` JSON AST to DBMS-specific SQL strings.
|
|
388
|
-
|
|
389
|
-
```typescript
|
|
390
|
-
import { createQueryBuilder } from "@simplysm/orm-common";
|
|
391
|
-
|
|
392
|
-
const mysqlBuilder = createQueryBuilder("mysql");
|
|
393
|
-
const mssqlBuilder = createQueryBuilder("mssql");
|
|
394
|
-
const postgresqlBuilder = createQueryBuilder("postgresql");
|
|
395
|
-
|
|
396
|
-
// Convert QueryDef to SQL
|
|
397
|
-
const queryDef = db.user()
|
|
398
|
-
.where((u) => [expr.eq(u.isActive, true)])
|
|
399
|
-
.getSelectQueryDef();
|
|
400
|
-
|
|
401
|
-
const { sql } = mysqlBuilder.build(queryDef);
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
`QueryBuilderBase.build(def)` accepts any `QueryDef` and returns `QueryBuildResult`:
|
|
405
|
-
|
|
406
|
-
```typescript
|
|
407
|
-
interface QueryBuildResult {
|
|
408
|
-
sql: string;
|
|
409
|
-
resultSetIndex?: number; // which result set index to use (for multi-result queries)
|
|
410
|
-
resultSetStride?: number; // stride for multi-result queries (MySQL INSERT with OUTPUT)
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## Error Handling
|
|
415
|
-
|
|
416
|
-
```typescript
|
|
417
|
-
import { DbTransactionError, DbErrorCode } from "@simplysm/orm-common";
|
|
418
|
-
|
|
419
|
-
try {
|
|
420
|
-
await db.connect(async () => {
|
|
421
|
-
// ...
|
|
422
|
-
});
|
|
423
|
-
} catch (err) {
|
|
424
|
-
if (err instanceof DbTransactionError) {
|
|
425
|
-
switch (err.code) {
|
|
426
|
-
case DbErrorCode.DEADLOCK:
|
|
427
|
-
// Deadlock retry logic
|
|
428
|
-
break;
|
|
429
|
-
case DbErrorCode.LOCK_TIMEOUT:
|
|
430
|
-
// Timeout handling
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### DbErrorCode
|
|
438
|
-
|
|
439
|
-
| Code | Description |
|
|
440
|
-
|------|------|
|
|
441
|
-
| `NO_ACTIVE_TRANSACTION` | No active transaction (e.g. rollback with no transaction open) |
|
|
442
|
-
| `TRANSACTION_ALREADY_STARTED` | Transaction already started |
|
|
443
|
-
| `DEADLOCK` | Deadlock occurred |
|
|
444
|
-
| `LOCK_TIMEOUT` | Lock timeout |
|