@simplysm/orm-common 13.0.97 → 13.0.98
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 +267 -0
- package/docs/core.md +225 -0
- package/docs/expression.md +296 -0
- package/docs/query-builder.md +196 -0
- package/docs/queryable.md +578 -0
- package/docs/schema-builders.md +415 -0
- package/docs/types.md +445 -0
- package/docs/utilities.md +122 -0
- package/package.json +2 -2
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Expression
|
|
2
|
+
|
|
3
|
+
Dialect-independent SQL expression builder. Generates JSON AST (`Expr`) instead of SQL strings, which `QueryBuilder` converts to each DBMS dialect.
|
|
4
|
+
|
|
5
|
+
Source: `src/expr/expr-unit.ts`, `src/expr/expr.ts`
|
|
6
|
+
|
|
7
|
+
## ExprUnit
|
|
8
|
+
|
|
9
|
+
Type-safe expression wrapper. Tracks expression return type using TypeScript generics.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
class ExprUnit<TPrimitive extends ColumnPrimitive> {
|
|
13
|
+
readonly $infer!: TPrimitive;
|
|
14
|
+
readonly dataType: ColumnPrimitiveStr;
|
|
15
|
+
readonly expr: Expr;
|
|
16
|
+
|
|
17
|
+
/** Strip undefined from the type (non-null assertion) */
|
|
18
|
+
get n(): ExprUnit<NonNullable<TPrimitive>>;
|
|
19
|
+
|
|
20
|
+
constructor(dataType: ColumnPrimitiveStr, expr: Expr);
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## WhereExprUnit
|
|
25
|
+
|
|
26
|
+
Expression wrapper for WHERE clause conditions.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
class WhereExprUnit {
|
|
30
|
+
readonly expr: WhereExpr;
|
|
31
|
+
constructor(expr: WhereExpr);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## ExprInput
|
|
36
|
+
|
|
37
|
+
Input type that accepts either an `ExprUnit` or a literal value.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
type ExprInput<TPrimitive extends ColumnPrimitive> = ExprUnit<TPrimitive> | TPrimitive;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## SwitchExprBuilder
|
|
44
|
+
|
|
45
|
+
Builder interface returned by `expr.switch()` for CASE WHEN expressions.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
interface SwitchExprBuilder<TPrimitive extends ColumnPrimitive> {
|
|
49
|
+
case(condition: WhereExprUnit, then: ExprInput<TPrimitive>): SwitchExprBuilder<TPrimitive>;
|
|
50
|
+
default(value: ExprInput<TPrimitive>): ExprUnit<TPrimitive>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## expr
|
|
57
|
+
|
|
58
|
+
The main expression builder object. All methods return `ExprUnit` or `WhereExprUnit`.
|
|
59
|
+
|
|
60
|
+
### Value Creation
|
|
61
|
+
|
|
62
|
+
| Method | Signature | Description |
|
|
63
|
+
|--------|-----------|-------------|
|
|
64
|
+
| `val` | `val<TStr>(dataType: TStr, value: T): ExprUnit` | Wrap literal value as expression |
|
|
65
|
+
| `col` | `col<TStr>(dataType: ColumnPrimitiveStr, ...path: string[]): ExprUnit` | Column reference (internal use) |
|
|
66
|
+
| `raw` | `raw<T>(dataType: T): (strings, ...values) => ExprUnit` | Raw SQL tagged template (escape hatch) |
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
expr.val("string", "active")
|
|
70
|
+
expr.val("number", 100)
|
|
71
|
+
expr.val("DateTime", DateTime.now())
|
|
72
|
+
|
|
73
|
+
// Raw SQL
|
|
74
|
+
expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Comparison Operators (WHERE)
|
|
78
|
+
|
|
79
|
+
| Method | SQL | Description |
|
|
80
|
+
|--------|-----|-------------|
|
|
81
|
+
| `eq(source, target)` | `<=>` / `IS NULL OR =` | Equality (NULL-safe) |
|
|
82
|
+
| `gt(source, target)` | `>` | Greater than |
|
|
83
|
+
| `lt(source, target)` | `<` | Less than |
|
|
84
|
+
| `gte(source, target)` | `>=` | Greater than or equal |
|
|
85
|
+
| `lte(source, target)` | `<=` | Less than or equal |
|
|
86
|
+
| `between(source, from?, to?)` | `BETWEEN` | Range (undefined = unbounded) |
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
db.user().where((u) => [
|
|
90
|
+
expr.eq(u.status, "active"),
|
|
91
|
+
expr.gte(u.age, 18),
|
|
92
|
+
expr.between(u.score, 60, 100),
|
|
93
|
+
])
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### NULL Check
|
|
97
|
+
|
|
98
|
+
| Method | SQL | Description |
|
|
99
|
+
|--------|-----|-------------|
|
|
100
|
+
| `null(source)` | `IS NULL` | Check if value is NULL |
|
|
101
|
+
|
|
102
|
+
### String Search (WHERE)
|
|
103
|
+
|
|
104
|
+
| Method | SQL | Description |
|
|
105
|
+
|--------|-----|-------------|
|
|
106
|
+
| `like(source, pattern)` | `LIKE ... ESCAPE '\'` | Pattern matching (`%`, `_` wildcards) |
|
|
107
|
+
| `regexp(source, pattern)` | `REGEXP` | Regular expression matching |
|
|
108
|
+
|
|
109
|
+
### IN / EXISTS (WHERE)
|
|
110
|
+
|
|
111
|
+
| Method | SQL | Description |
|
|
112
|
+
|--------|-----|-------------|
|
|
113
|
+
| `in(source, values)` | `IN (...)` | Value list inclusion |
|
|
114
|
+
| `inQuery(source, query)` | `IN (SELECT ...)` | Subquery inclusion (single column) |
|
|
115
|
+
| `exists(query)` | `EXISTS (SELECT ...)` | Subquery existence check |
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
db.user().where((u) => [
|
|
119
|
+
expr.in(u.status, ["active", "pending"]),
|
|
120
|
+
expr.exists(
|
|
121
|
+
db.order().where((o) => [expr.eq(o.userId, u.id)])
|
|
122
|
+
),
|
|
123
|
+
])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Logical Operators (WHERE)
|
|
127
|
+
|
|
128
|
+
| Method | SQL | Description |
|
|
129
|
+
|--------|-----|-------------|
|
|
130
|
+
| `not(arg)` | `NOT (...)` | Negate a condition |
|
|
131
|
+
| `and(conditions)` | `... AND ...` | All conditions must be true |
|
|
132
|
+
| `or(conditions)` | `... OR ...` | At least one must be true |
|
|
133
|
+
|
|
134
|
+
### String Functions (SELECT)
|
|
135
|
+
|
|
136
|
+
| Method | SQL | Description |
|
|
137
|
+
|--------|-----|-------------|
|
|
138
|
+
| `concat(...args)` | `CONCAT(...)` | String concatenation (NULL-safe) |
|
|
139
|
+
| `left(source, length)` | `LEFT(...)` | Extract from left |
|
|
140
|
+
| `right(source, length)` | `RIGHT(...)` | Extract from right |
|
|
141
|
+
| `trim(source)` | `TRIM(...)` | Remove whitespace |
|
|
142
|
+
| `padStart(source, length, fill)` | `LPAD(...)` | Left padding |
|
|
143
|
+
| `replace(source, from, to)` | `REPLACE(...)` | String replacement |
|
|
144
|
+
| `upper(source)` | `UPPER(...)` | Uppercase |
|
|
145
|
+
| `lower(source)` | `LOWER(...)` | Lowercase |
|
|
146
|
+
| `length(source)` | `CHAR_LENGTH(...)` | Character count |
|
|
147
|
+
| `byteLength(source)` | `OCTET_LENGTH(...)` | Byte count |
|
|
148
|
+
| `substring(source, start, length?)` | `SUBSTRING(...)` | Extract substring (1-based) |
|
|
149
|
+
| `indexOf(source, search)` | `LOCATE(...)`/`CHARINDEX(...)` | Find position (1-based, 0 if not found) |
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
db.user().select((u) => ({
|
|
153
|
+
fullName: expr.concat(u.firstName, " ", u.lastName),
|
|
154
|
+
initial: expr.left(u.name, 1),
|
|
155
|
+
email: expr.lower(u.email),
|
|
156
|
+
}))
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Numeric Functions (SELECT)
|
|
160
|
+
|
|
161
|
+
| Method | SQL | Description |
|
|
162
|
+
|--------|-----|-------------|
|
|
163
|
+
| `abs(source)` | `ABS(...)` | Absolute value |
|
|
164
|
+
| `round(source, digits)` | `ROUND(...)` | Round to N digits |
|
|
165
|
+
| `ceil(source)` | `CEILING(...)` | Ceiling |
|
|
166
|
+
| `floor(source)` | `FLOOR(...)` | Floor |
|
|
167
|
+
|
|
168
|
+
### Date Functions (SELECT)
|
|
169
|
+
|
|
170
|
+
| Method | SQL | Description |
|
|
171
|
+
|--------|-----|-------------|
|
|
172
|
+
| `year(source)` | `YEAR(...)` | Extract year |
|
|
173
|
+
| `month(source)` | `MONTH(...)` | Extract month (1-12) |
|
|
174
|
+
| `day(source)` | `DAY(...)` | Extract day (1-31) |
|
|
175
|
+
| `hour(source)` | `HOUR(...)` | Extract hour (0-23) |
|
|
176
|
+
| `minute(source)` | `MINUTE(...)` | Extract minute (0-59) |
|
|
177
|
+
| `second(source)` | `SECOND(...)` | Extract second (0-59) |
|
|
178
|
+
| `isoWeek(source)` | `WEEK(..., 3)` | ISO week number (1-53) |
|
|
179
|
+
| `isoWeekStartDate(source)` | (computed) | Monday of the week |
|
|
180
|
+
| `isoYearMonth(source)` | (computed) | First day of the month |
|
|
181
|
+
| `dateDiff(unit, from, to)` | `DATEDIFF(...)` | Date difference |
|
|
182
|
+
| `dateAdd(unit, source, value)` | `DATEADD(...)` | Add to date |
|
|
183
|
+
| `formatDate(source, format)` | `DATE_FORMAT(...)` | Format date as string |
|
|
184
|
+
|
|
185
|
+
`DateUnit` values: `"year"`, `"month"`, `"day"`, `"hour"`, `"minute"`, `"second"`
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
db.user().select((u) => ({
|
|
189
|
+
age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
|
|
190
|
+
expiresAt: expr.dateAdd("month", u.startDate, 12),
|
|
191
|
+
}))
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Conditional Functions (SELECT)
|
|
195
|
+
|
|
196
|
+
| Method | SQL | Description |
|
|
197
|
+
|--------|-----|-------------|
|
|
198
|
+
| `coalesce(...args)` | `COALESCE(...)` | First non-null value |
|
|
199
|
+
| `nullIf(source, value)` | `NULLIF(...)` | Return NULL if equal |
|
|
200
|
+
| `is(condition)` | (computed) | Transform WHERE to boolean column |
|
|
201
|
+
| `switch<T>()` | `CASE WHEN ... END` | CASE WHEN builder |
|
|
202
|
+
| `if(condition, then, else_)` | `IF(...)`/`IIF(...)` | Ternary conditional |
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
db.user().select((u) => ({
|
|
206
|
+
displayName: expr.coalesce(u.nickname, u.name, "Guest"),
|
|
207
|
+
isActive: expr.is(expr.eq(u.status, "active")),
|
|
208
|
+
grade: expr.switch<string>()
|
|
209
|
+
.case(expr.gte(u.score, 90), "A")
|
|
210
|
+
.case(expr.gte(u.score, 80), "B")
|
|
211
|
+
.default("C"),
|
|
212
|
+
}))
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Aggregate Functions (SELECT)
|
|
216
|
+
|
|
217
|
+
| Method | SQL | Description |
|
|
218
|
+
|--------|-----|-------------|
|
|
219
|
+
| `count(arg?, distinct?)` | `COUNT(...)` | Row count |
|
|
220
|
+
| `sum(arg)` | `SUM(...)` | Sum (NULL ignored) |
|
|
221
|
+
| `avg(arg)` | `AVG(...)` | Average (NULL ignored) |
|
|
222
|
+
| `max(arg)` | `MAX(...)` | Maximum value |
|
|
223
|
+
| `min(arg)` | `MIN(...)` | Minimum value |
|
|
224
|
+
|
|
225
|
+
### Other Functions (SELECT)
|
|
226
|
+
|
|
227
|
+
| Method | SQL | Description |
|
|
228
|
+
|--------|-----|-------------|
|
|
229
|
+
| `greatest(...args)` | `GREATEST(...)` | Greatest among values |
|
|
230
|
+
| `least(...args)` | `LEAST(...)` | Least among values |
|
|
231
|
+
| `rowNum()` | (computed) | Row number (no OVER) |
|
|
232
|
+
| `random()` | `RAND()`/`RANDOM()` | Random number (0-1) |
|
|
233
|
+
| `cast(source, targetType)` | `CAST(... AS ...)` | Type conversion |
|
|
234
|
+
| `subquery(dataType, queryable)` | `(SELECT ...)` | Scalar subquery |
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
db.user().select((u) => ({
|
|
238
|
+
id: u.id,
|
|
239
|
+
postCount: expr.subquery("number",
|
|
240
|
+
db.post()
|
|
241
|
+
.where((p) => [expr.eq(p.userId, u.id)])
|
|
242
|
+
.select(() => ({ cnt: expr.count() }))
|
|
243
|
+
),
|
|
244
|
+
}))
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Window Functions (SELECT)
|
|
248
|
+
|
|
249
|
+
All window functions accept a `WinSpecInput`:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
interface WinSpecInput {
|
|
253
|
+
partitionBy?: ExprInput<ColumnPrimitive>[];
|
|
254
|
+
orderBy?: [ExprInput<ColumnPrimitive>, ("ASC" | "DESC")?][];
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
| Method | SQL | Description |
|
|
259
|
+
|--------|-----|-------------|
|
|
260
|
+
| `rowNumber(spec)` | `ROW_NUMBER() OVER(...)` | Row number within partition |
|
|
261
|
+
| `rank(spec)` | `RANK() OVER(...)` | Rank (ties skip: 1,1,3) |
|
|
262
|
+
| `denseRank(spec)` | `DENSE_RANK() OVER(...)` | Dense rank (ties consecutive: 1,1,2) |
|
|
263
|
+
| `ntile(n, spec)` | `NTILE(n) OVER(...)` | Split into n groups |
|
|
264
|
+
| `lag(column, spec, options?)` | `LAG() OVER(...)` | Previous row value |
|
|
265
|
+
| `lead(column, spec, options?)` | `LEAD() OVER(...)` | Next row value |
|
|
266
|
+
| `firstValue(column, spec)` | `FIRST_VALUE() OVER(...)` | First value in frame |
|
|
267
|
+
| `lastValue(column, spec)` | `LAST_VALUE() OVER(...)` | Last value in frame |
|
|
268
|
+
| `sumOver(column, spec)` | `SUM() OVER(...)` | Window sum |
|
|
269
|
+
| `avgOver(column, spec)` | `AVG() OVER(...)` | Window average |
|
|
270
|
+
| `countOver(spec, column?)` | `COUNT() OVER(...)` | Window count |
|
|
271
|
+
| `minOver(column, spec)` | `MIN() OVER(...)` | Window minimum |
|
|
272
|
+
| `maxOver(column, spec)` | `MAX() OVER(...)` | Window maximum |
|
|
273
|
+
|
|
274
|
+
`lag` and `lead` options:
|
|
275
|
+
- `offset?: number` -- default 1
|
|
276
|
+
- `default?: ExprInput<T>` -- default value when no row exists
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
db.order().select((o) => ({
|
|
280
|
+
...o,
|
|
281
|
+
rowNum: expr.rowNumber({
|
|
282
|
+
partitionBy: [o.userId],
|
|
283
|
+
orderBy: [[o.createdAt, "DESC"]],
|
|
284
|
+
}),
|
|
285
|
+
runningTotal: expr.sumOver(o.amount, {
|
|
286
|
+
partitionBy: [o.userId],
|
|
287
|
+
orderBy: [[o.createdAt, "ASC"]],
|
|
288
|
+
}),
|
|
289
|
+
}))
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Helper
|
|
293
|
+
|
|
294
|
+
| Method | Description |
|
|
295
|
+
|--------|-------------|
|
|
296
|
+
| `toExpr(value: ExprInput<ColumnPrimitive>): Expr` | Convert ExprInput to Expr JSON AST (internal use) |
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Query Builder
|
|
2
|
+
|
|
3
|
+
Render `QueryDef` JSON AST to dialect-specific SQL strings.
|
|
4
|
+
|
|
5
|
+
Source: `src/query-builder/query-builder.ts`, `src/query-builder/base/query-builder-base.ts`, `src/query-builder/base/expr-renderer-base.ts`, `src/query-builder/mysql/`, `src/query-builder/mssql/`, `src/query-builder/postgresql/`
|
|
6
|
+
|
|
7
|
+
## createQueryBuilder
|
|
8
|
+
|
|
9
|
+
Factory function that returns a dialect-specific `QueryBuilderBase` implementation.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
function createQueryBuilder(dialect: Dialect): QueryBuilderBase;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Dialect | QueryBuilder | ExprRenderer |
|
|
16
|
+
|---------|-------------|--------------|
|
|
17
|
+
| `"mysql"` | `MysqlQueryBuilder` | `MysqlExprRenderer` |
|
|
18
|
+
| `"mssql"` | `MssqlQueryBuilder` | `MssqlExprRenderer` |
|
|
19
|
+
| `"postgresql"` | `PostgresqlQueryBuilder` | `PostgresqlExprRenderer` |
|
|
20
|
+
|
|
21
|
+
**Example:**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { createQueryBuilder } from "@simplysm/orm-common";
|
|
25
|
+
|
|
26
|
+
const builder = createQueryBuilder("mysql");
|
|
27
|
+
const result = builder.build(queryDef);
|
|
28
|
+
console.log(result.sql);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## QueryBuilderBase
|
|
34
|
+
|
|
35
|
+
Abstract base class for `QueryDef` to SQL rendering. Implements common dispatch logic and rendering helpers.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
abstract class QueryBuilderBase {
|
|
39
|
+
/**
|
|
40
|
+
* Main entry point: dispatch to the appropriate method based on def.type
|
|
41
|
+
*/
|
|
42
|
+
build(def: QueryDef): QueryBuildResult;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Design Principles
|
|
47
|
+
|
|
48
|
+
- Method names match `def.type` for dynamic dispatch
|
|
49
|
+
- Only 100% dialect-identical logic is implemented in the base class
|
|
50
|
+
- Any dialect-specific behavior is declared as `abstract`
|
|
51
|
+
|
|
52
|
+
### Common Render Methods (implemented)
|
|
53
|
+
|
|
54
|
+
| Method | Description |
|
|
55
|
+
|--------|-------------|
|
|
56
|
+
| `renderWhere(wheres)` | Render WHERE clause |
|
|
57
|
+
| `renderOrderBy(orderBy)` | Render ORDER BY clause |
|
|
58
|
+
| `renderGroupBy(groupBy)` | Render GROUP BY clause |
|
|
59
|
+
| `renderHaving(having)` | Render HAVING clause |
|
|
60
|
+
| `renderJoins(joins)` | Render all JOIN clauses |
|
|
61
|
+
| `renderFrom(from)` | Render FROM clause source (table, subquery, union, CTE reference) |
|
|
62
|
+
| `needsLateral(join)` | Detect if JOIN needs LATERAL/CROSS APPLY |
|
|
63
|
+
|
|
64
|
+
### Abstract Methods (implemented per dialect)
|
|
65
|
+
|
|
66
|
+
**DML:**
|
|
67
|
+
- `select(def: SelectQueryDef): QueryBuildResult`
|
|
68
|
+
- `insert(def: InsertQueryDef): QueryBuildResult`
|
|
69
|
+
- `insertIfNotExists(def: InsertIfNotExistsQueryDef): QueryBuildResult`
|
|
70
|
+
- `insertInto(def: InsertIntoQueryDef): QueryBuildResult`
|
|
71
|
+
- `update(def: UpdateQueryDef): QueryBuildResult`
|
|
72
|
+
- `delete(def: DeleteQueryDef): QueryBuildResult`
|
|
73
|
+
- `upsert(def: UpsertQueryDef): QueryBuildResult`
|
|
74
|
+
|
|
75
|
+
**DDL - Table:**
|
|
76
|
+
- `createTable(def)`, `dropTable(def)`, `renameTable(def)`, `truncate(def)`
|
|
77
|
+
|
|
78
|
+
**DDL - Column:**
|
|
79
|
+
- `addColumn(def)`, `dropColumn(def)`, `modifyColumn(def)`, `renameColumn(def)`
|
|
80
|
+
|
|
81
|
+
**DDL - Constraint:**
|
|
82
|
+
- `addPrimaryKey(def)`, `dropPrimaryKey(def)`, `addForeignKey(def)`, `dropForeignKey(def)`, `addIndex(def)`, `dropIndex(def)`
|
|
83
|
+
|
|
84
|
+
**DDL - View/Procedure:**
|
|
85
|
+
- `createView(def)`, `dropView(def)`, `createProc(def)`, `dropProc(def)`, `execProc(def)`
|
|
86
|
+
|
|
87
|
+
**Utils:**
|
|
88
|
+
- `clearSchema(def)`, `schemaExists(def)`, `switchFk(def)`
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## ExprRendererBase
|
|
93
|
+
|
|
94
|
+
Abstract base class for `Expr`/`WhereExpr` to SQL rendering. Implements dispatch logic.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
abstract class ExprRendererBase {
|
|
98
|
+
constructor(protected buildSelect: (def: SelectQueryDef) => string);
|
|
99
|
+
|
|
100
|
+
/** Render a single expression to SQL */
|
|
101
|
+
render(expr: Expr | WhereExpr): string;
|
|
102
|
+
|
|
103
|
+
/** Render multiple WHERE expressions joined with AND */
|
|
104
|
+
renderWhere(exprs: WhereExpr[]): string;
|
|
105
|
+
|
|
106
|
+
/** Wrap identifier (table/column name) */
|
|
107
|
+
abstract wrap(name: string): string;
|
|
108
|
+
|
|
109
|
+
/** Escape string value for SQL literals */
|
|
110
|
+
abstract escapeString(value: string): string;
|
|
111
|
+
|
|
112
|
+
/** Escape any value to SQL literal */
|
|
113
|
+
abstract escapeValue(value: unknown): string;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Abstract Methods (per category)
|
|
118
|
+
|
|
119
|
+
**Value:** `column`, `value`, `raw`
|
|
120
|
+
|
|
121
|
+
**Comparison:** `eq`, `gt`, `lt`, `gte`, `lte`, `between`, `null`, `like`, `regexp`, `in`, `inQuery`, `exists`
|
|
122
|
+
|
|
123
|
+
**Logic:** `not`, `and`, `or`
|
|
124
|
+
|
|
125
|
+
**String:** `concat`, `left`, `right`, `trim`, `padStart`, `replace`, `upper`, `lower`, `length`, `byteLength`, `substring`, `indexOf`
|
|
126
|
+
|
|
127
|
+
**Number:** `abs`, `round`, `ceil`, `floor`
|
|
128
|
+
|
|
129
|
+
**Date:** `year`, `month`, `day`, `hour`, `minute`, `second`, `isoWeek`, `isoWeekStartDate`, `isoYearMonth`, `dateDiff`, `dateAdd`, `formatDate`
|
|
130
|
+
|
|
131
|
+
**Condition:** `coalesce`, `nullIf`, `is`, `switch`, `if`
|
|
132
|
+
|
|
133
|
+
**Aggregate:** `count`, `sum`, `avg`, `max`, `min`
|
|
134
|
+
|
|
135
|
+
**Other:** `greatest`, `least`, `rowNum`, `random`, `cast`
|
|
136
|
+
|
|
137
|
+
**Window:** `window`
|
|
138
|
+
|
|
139
|
+
**System:** `subquery`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Dialect Implementations
|
|
144
|
+
|
|
145
|
+
### MysqlQueryBuilder / MysqlExprRenderer
|
|
146
|
+
|
|
147
|
+
MySQL-specific implementation.
|
|
148
|
+
|
|
149
|
+
- Identifier wrapping: `` `name` ``
|
|
150
|
+
- NULL-safe equality: `<=>`
|
|
151
|
+
- LATERAL JOIN support
|
|
152
|
+
- UUID stored as `BINARY(16)`
|
|
153
|
+
- BOOLEAN mapped to `TINYINT(1)`
|
|
154
|
+
|
|
155
|
+
### MssqlQueryBuilder / MssqlExprRenderer
|
|
156
|
+
|
|
157
|
+
Microsoft SQL Server-specific implementation.
|
|
158
|
+
|
|
159
|
+
- Identifier wrapping: `[name]`
|
|
160
|
+
- NULL-safe equality: `(source IS NULL AND target IS NULL) OR source = target`
|
|
161
|
+
- CROSS APPLY for lateral joins
|
|
162
|
+
- `TOP` and `OFFSET...FETCH` for pagination
|
|
163
|
+
- `IDENTITY_INSERT` for explicit AI column values
|
|
164
|
+
- UUID mapped to `UNIQUEIDENTIFIER`
|
|
165
|
+
|
|
166
|
+
### PostgresqlQueryBuilder / PostgresqlExprRenderer
|
|
167
|
+
|
|
168
|
+
PostgreSQL-specific implementation.
|
|
169
|
+
|
|
170
|
+
- Identifier wrapping: `"name"`
|
|
171
|
+
- NULL-safe equality: `IS NOT DISTINCT FROM`
|
|
172
|
+
- LATERAL JOIN support
|
|
173
|
+
- `LIMIT...OFFSET` for pagination
|
|
174
|
+
- UUID as native `UUID` type
|
|
175
|
+
- `RETURN QUERY` in stored procedures
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Usage
|
|
180
|
+
|
|
181
|
+
Typically used internally by `DbContextExecutor` implementations. Direct usage:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { createQueryBuilder } from "@simplysm/orm-common";
|
|
185
|
+
|
|
186
|
+
const builder = createQueryBuilder("mysql");
|
|
187
|
+
|
|
188
|
+
// Build from a Queryable
|
|
189
|
+
const qr = db.user()
|
|
190
|
+
.where((u) => [expr.eq(u.status, "active")])
|
|
191
|
+
.orderBy((u) => u.name);
|
|
192
|
+
|
|
193
|
+
const queryDef = qr.getSelectQueryDef();
|
|
194
|
+
const result = builder.build(queryDef);
|
|
195
|
+
// result.sql: "SELECT ... FROM `mydb`.`User` AS `T1` WHERE ..."
|
|
196
|
+
```
|