@simplysm/orm-common 13.0.81 → 13.0.83
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 +106 -0
- package/docs/db-context.md +238 -0
- package/docs/expressions.md +413 -0
- package/docs/query-builder.md +198 -0
- package/docs/queryable.md +420 -0
- package/docs/schema-builders.md +216 -0
- package/docs/types-and-utilities.md +353 -0
- package/package.json +4 -3
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# Expression Builder
|
|
2
|
+
|
|
3
|
+
The `expr` object provides a dialect-independent SQL expression AST builder. Expressions are compiled to SQL by the QueryBuilder for each target DBMS (MySQL, MSSQL, PostgreSQL).
|
|
4
|
+
|
|
5
|
+
## API Reference
|
|
6
|
+
|
|
7
|
+
### Value Creation
|
|
8
|
+
|
|
9
|
+
#### `expr.val(dataType, value)`
|
|
10
|
+
|
|
11
|
+
Wrap a literal value as an `ExprUnit`.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
expr.val("string", "active")
|
|
15
|
+
expr.val("number", 100)
|
|
16
|
+
expr.val("DateOnly", DateOnly.today())
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
#### `expr.col(dataType, ...path)`
|
|
20
|
+
|
|
21
|
+
Create a column reference (typically used internally; proxy objects in Queryable callbacks handle this automatically).
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
expr.col("string", "T1", "name")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### `expr.raw(dataType)\`template\``
|
|
28
|
+
|
|
29
|
+
Raw SQL expression (escape hatch). Interpolated values are automatically parameterized.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
db.user().select((u) => ({
|
|
33
|
+
data: expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`,
|
|
34
|
+
}))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### Comparison Operators (WHERE)
|
|
40
|
+
|
|
41
|
+
All comparison operators return `WhereExprUnit` for use in `.where()` callbacks.
|
|
42
|
+
|
|
43
|
+
| Method | SQL | Description |
|
|
44
|
+
|--------|-----|-------------|
|
|
45
|
+
| `expr.eq(source, target)` | `=` (NULL-safe) | Equality comparison |
|
|
46
|
+
| `expr.gt(source, target)` | `>` | Greater than |
|
|
47
|
+
| `expr.lt(source, target)` | `<` | Less than |
|
|
48
|
+
| `expr.gte(source, target)` | `>=` | Greater than or equal |
|
|
49
|
+
| `expr.lte(source, target)` | `<=` | Less than or equal |
|
|
50
|
+
| `expr.between(source, from?, to?)` | `BETWEEN` | Range comparison (undefined = unbounded) |
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
db.user().where((u) => [
|
|
54
|
+
expr.eq(u.status, "active"),
|
|
55
|
+
expr.gte(u.age, 18),
|
|
56
|
+
expr.between(u.score, 60, 100),
|
|
57
|
+
])
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### NULL Check
|
|
63
|
+
|
|
64
|
+
#### `expr.null(source)`
|
|
65
|
+
|
|
66
|
+
Check if value is NULL (`IS NULL`).
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
db.user().where((u) => [expr.null(u.deletedAt)])
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### String Search (WHERE)
|
|
75
|
+
|
|
76
|
+
#### `expr.like(source, pattern)`
|
|
77
|
+
|
|
78
|
+
LIKE pattern matching. `%` matches any characters, `_` matches single character.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
db.user().where((u) => [expr.like(u.name, "John%")])
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `expr.regexp(source, pattern)`
|
|
85
|
+
|
|
86
|
+
Regular expression matching. Syntax varies by DBMS.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
db.user().where((u) => [expr.regexp(u.email, "^[a-z]+@")])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### IN / EXISTS (WHERE)
|
|
95
|
+
|
|
96
|
+
#### `expr.in(source, values)`
|
|
97
|
+
|
|
98
|
+
Check if value is in a list.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
db.user().where((u) => [expr.in(u.status, ["active", "pending"])])
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### `expr.inQuery(source, query)`
|
|
105
|
+
|
|
106
|
+
Check if value is in a subquery result (subquery must SELECT a single column).
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
db.user().where((u) => [
|
|
110
|
+
expr.inQuery(
|
|
111
|
+
u.id,
|
|
112
|
+
db.order().where((o) => [expr.gt(o.amount, 1000)]).select((o) => ({ userId: o.userId })),
|
|
113
|
+
),
|
|
114
|
+
])
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `expr.exists(query)`
|
|
118
|
+
|
|
119
|
+
Check if a subquery returns any rows.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
db.user().where((u) => [
|
|
123
|
+
expr.exists(db.order().where((o) => [expr.eq(o.userId, u.id)])),
|
|
124
|
+
])
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### Logical Operators (WHERE)
|
|
130
|
+
|
|
131
|
+
#### `expr.not(condition)`
|
|
132
|
+
|
|
133
|
+
Negate a condition.
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
expr.not(expr.eq(u.status, "deleted"))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### `expr.and(conditions)`
|
|
140
|
+
|
|
141
|
+
Combine conditions with AND (note: `.where()` arrays are implicitly AND-ed).
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
expr.and([expr.eq(u.status, "active"), expr.gte(u.age, 18)])
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### `expr.or(conditions)`
|
|
148
|
+
|
|
149
|
+
Combine conditions with OR.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
expr.or([expr.eq(u.status, "active"), expr.eq(u.status, "pending")])
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### String Functions (SELECT)
|
|
158
|
+
|
|
159
|
+
| Method | SQL | Description |
|
|
160
|
+
|--------|-----|-------------|
|
|
161
|
+
| `expr.concat(...args)` | `CONCAT` | Concatenate strings (NULL treated as empty) |
|
|
162
|
+
| `expr.left(source, length)` | `LEFT` | Extract left N characters |
|
|
163
|
+
| `expr.right(source, length)` | `RIGHT` | Extract right N characters |
|
|
164
|
+
| `expr.trim(source)` | `TRIM` | Remove leading/trailing whitespace |
|
|
165
|
+
| `expr.padStart(source, length, fill)` | `LPAD` | Left-pad string |
|
|
166
|
+
| `expr.replace(source, from, to)` | `REPLACE` | Replace occurrences |
|
|
167
|
+
| `expr.upper(source)` | `UPPER` | Convert to uppercase |
|
|
168
|
+
| `expr.lower(source)` | `LOWER` | Convert to lowercase |
|
|
169
|
+
| `expr.length(source)` | `CHAR_LENGTH` | Character count |
|
|
170
|
+
| `expr.byteLength(source)` | `OCTET_LENGTH` | Byte count |
|
|
171
|
+
| `expr.substring(source, start, length?)` | `SUBSTRING` | Extract substring (1-based index) |
|
|
172
|
+
| `expr.indexOf(source, search)` | `LOCATE/CHARINDEX` | Find position (1-based, 0 if not found) |
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
db.user().select((u) => ({
|
|
176
|
+
fullName: expr.concat(u.firstName, " ", u.lastName),
|
|
177
|
+
initial: expr.left(u.name, 1),
|
|
178
|
+
phone: expr.replace(u.phone, "-", ""),
|
|
179
|
+
}))
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Numeric Functions (SELECT)
|
|
185
|
+
|
|
186
|
+
| Method | SQL | Description |
|
|
187
|
+
|--------|-----|-------------|
|
|
188
|
+
| `expr.abs(source)` | `ABS` | Absolute value |
|
|
189
|
+
| `expr.round(source, digits)` | `ROUND` | Round to N decimal places |
|
|
190
|
+
| `expr.ceil(source)` | `CEILING` | Round up |
|
|
191
|
+
| `expr.floor(source)` | `FLOOR` | Round down |
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
db.product().select((p) => ({
|
|
195
|
+
price: expr.round(p.price, 2),
|
|
196
|
+
absDiscount: expr.abs(p.discount),
|
|
197
|
+
}))
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### Date Functions (SELECT)
|
|
203
|
+
|
|
204
|
+
#### Extraction
|
|
205
|
+
|
|
206
|
+
| Method | SQL | Returns |
|
|
207
|
+
|--------|-----|---------|
|
|
208
|
+
| `expr.year(source)` | `YEAR` | Year (4-digit) |
|
|
209
|
+
| `expr.month(source)` | `MONTH` | Month (1-12) |
|
|
210
|
+
| `expr.day(source)` | `DAY` | Day (1-31) |
|
|
211
|
+
| `expr.hour(source)` | `HOUR` | Hour (0-23) |
|
|
212
|
+
| `expr.minute(source)` | `MINUTE` | Minute (0-59) |
|
|
213
|
+
| `expr.second(source)` | `SECOND` | Second (0-59) |
|
|
214
|
+
| `expr.isoWeek(source)` | `WEEK` | ISO week number (1-53) |
|
|
215
|
+
| `expr.isoWeekStartDate(source)` | -- | Monday of the week |
|
|
216
|
+
| `expr.isoYearMonth(source)` | -- | First day of the month |
|
|
217
|
+
|
|
218
|
+
#### Arithmetic
|
|
219
|
+
|
|
220
|
+
| Method | SQL | Description |
|
|
221
|
+
|--------|-----|-------------|
|
|
222
|
+
| `expr.dateDiff(unit, from, to)` | `DATEDIFF` | Difference between dates |
|
|
223
|
+
| `expr.dateAdd(unit, source, value)` | `DATEADD` | Add interval to date |
|
|
224
|
+
| `expr.formatDate(source, format)` | `DATE_FORMAT` | Format date as string |
|
|
225
|
+
|
|
226
|
+
**DateUnit values:** `"year"` | `"month"` | `"day"` | `"hour"` | `"minute"` | `"second"`
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
db.user().select((u) => ({
|
|
230
|
+
age: expr.dateDiff("year", u.birthDate, expr.val("DateOnly", DateOnly.today())),
|
|
231
|
+
expiresAt: expr.dateAdd("month", u.startDate, 12),
|
|
232
|
+
birthYear: expr.year(u.birthDate),
|
|
233
|
+
}))
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### Conditional Functions (SELECT)
|
|
239
|
+
|
|
240
|
+
#### `expr.coalesce(...args)`
|
|
241
|
+
|
|
242
|
+
Return first non-null value (`COALESCE`).
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
expr.coalesce(u.nickname, u.name, "Guest")
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### `expr.nullIf(source, value)`
|
|
249
|
+
|
|
250
|
+
Return NULL if source equals value (`NULLIF`).
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
expr.nullIf(u.bio, "") // empty string -> NULL
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `expr.is(condition)`
|
|
257
|
+
|
|
258
|
+
Convert WHERE expression to boolean value for SELECT.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
db.user().select((u) => ({
|
|
262
|
+
isActive: expr.is(expr.eq(u.status, "active")),
|
|
263
|
+
}))
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
#### `expr.switch<T>()`
|
|
267
|
+
|
|
268
|
+
CASE WHEN expression builder (chaining API).
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
db.user().select((u) => ({
|
|
272
|
+
grade: expr.switch<string>()
|
|
273
|
+
.case(expr.gte(u.score, 90), "A")
|
|
274
|
+
.case(expr.gte(u.score, 80), "B")
|
|
275
|
+
.default("C"),
|
|
276
|
+
}))
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### `expr.if(condition, then, else_)`
|
|
280
|
+
|
|
281
|
+
Simple IF/IIF expression.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
expr.if(expr.gte(u.age, 18), "adult", "minor")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### Aggregate Functions (SELECT)
|
|
290
|
+
|
|
291
|
+
All aggregate functions ignore NULL values. Return NULL only when all values are NULL or no rows.
|
|
292
|
+
|
|
293
|
+
| Method | SQL | Description |
|
|
294
|
+
|--------|-----|-------------|
|
|
295
|
+
| `expr.count(arg?, distinct?)` | `COUNT` | Row count (no arg = count all) |
|
|
296
|
+
| `expr.sum(arg)` | `SUM` | Sum of numeric column |
|
|
297
|
+
| `expr.avg(arg)` | `AVG` | Average of numeric column |
|
|
298
|
+
| `expr.max(arg)` | `MAX` | Maximum value |
|
|
299
|
+
| `expr.min(arg)` | `MIN` | Minimum value |
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
db.order().select((o) => ({
|
|
303
|
+
userId: o.userId,
|
|
304
|
+
totalAmount: expr.sum(o.amount),
|
|
305
|
+
orderCount: expr.count(),
|
|
306
|
+
uniqueProducts: expr.count(o.productId, true),
|
|
307
|
+
lastOrder: expr.max(o.createdAt),
|
|
308
|
+
})).groupBy((o) => [o.userId])
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### Other Functions
|
|
314
|
+
|
|
315
|
+
| Method | SQL | Description |
|
|
316
|
+
|--------|-----|-------------|
|
|
317
|
+
| `expr.greatest(...args)` | `GREATEST` | Maximum of multiple values |
|
|
318
|
+
| `expr.least(...args)` | `LEAST` | Minimum of multiple values |
|
|
319
|
+
| `expr.rowNum()` | `ROW_NUMBER` | Simple row number |
|
|
320
|
+
| `expr.random()` | `RAND/RANDOM` | Random number |
|
|
321
|
+
| `expr.cast(source, targetType)` | `CAST` | Type conversion |
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
expr.cast(u.id, { type: "varchar", length: 10 })
|
|
325
|
+
expr.greatest(u.score1, u.score2, u.score3)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### Window Functions
|
|
331
|
+
|
|
332
|
+
#### `expr.window(fn, spec?)`
|
|
333
|
+
|
|
334
|
+
Apply a window function with OVER clause.
|
|
335
|
+
|
|
336
|
+
**Window function types:**
|
|
337
|
+
|
|
338
|
+
| Function | Description |
|
|
339
|
+
|----------|-------------|
|
|
340
|
+
| `{ type: "rowNumber" }` | ROW_NUMBER() |
|
|
341
|
+
| `{ type: "rank" }` | RANK() |
|
|
342
|
+
| `{ type: "denseRank" }` | DENSE_RANK() |
|
|
343
|
+
| `{ type: "ntile", n }` | NTILE(n) |
|
|
344
|
+
| `{ type: "lag", column, offset?, default? }` | LAG() |
|
|
345
|
+
| `{ type: "lead", column, offset?, default? }` | LEAD() |
|
|
346
|
+
| `{ type: "firstValue", column }` | FIRST_VALUE() |
|
|
347
|
+
| `{ type: "lastValue", column }` | LAST_VALUE() |
|
|
348
|
+
| `{ type: "sum", column }` | Window SUM |
|
|
349
|
+
| `{ type: "avg", column }` | Window AVG |
|
|
350
|
+
| `{ type: "count", column? }` | Window COUNT |
|
|
351
|
+
| `{ type: "min", column }` | Window MIN |
|
|
352
|
+
| `{ type: "max", column }` | Window MAX |
|
|
353
|
+
|
|
354
|
+
**WinSpec:**
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
{
|
|
358
|
+
partitionBy?: ExprInput[],
|
|
359
|
+
orderBy?: [ExprInput, ("ASC" | "DESC")?][],
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
db.order().select((o) => ({
|
|
365
|
+
id: o.id,
|
|
366
|
+
rowNum: expr.window(
|
|
367
|
+
{ type: "rowNumber" },
|
|
368
|
+
{ partitionBy: [o.userId], orderBy: [[o.createdAt, "DESC"]] },
|
|
369
|
+
),
|
|
370
|
+
runningTotal: expr.window(
|
|
371
|
+
{ type: "sum", column: o.amount },
|
|
372
|
+
{ partitionBy: [o.userId], orderBy: [[o.createdAt]] },
|
|
373
|
+
),
|
|
374
|
+
}))
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Subquery
|
|
380
|
+
|
|
381
|
+
#### `expr.subquery(query, fn)`
|
|
382
|
+
|
|
383
|
+
Scalar subquery for use in SELECT expressions.
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
db.user().select((u) => ({
|
|
387
|
+
name: u.name,
|
|
388
|
+
postCount: expr.subquery(
|
|
389
|
+
db.post().where((p) => [expr.eq(p.authorId, u.id)]),
|
|
390
|
+
(q) => expr.count(q.id),
|
|
391
|
+
),
|
|
392
|
+
}))
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
### Helper Types
|
|
398
|
+
|
|
399
|
+
#### `ExprUnit<TPrimitive>`
|
|
400
|
+
|
|
401
|
+
Type-safe expression wrapper that tracks the return type via TypeScript generics. Access `.n` to assert non-nullable.
|
|
402
|
+
|
|
403
|
+
#### `WhereExprUnit`
|
|
404
|
+
|
|
405
|
+
Expression wrapper for WHERE clause conditions.
|
|
406
|
+
|
|
407
|
+
#### `ExprInput<TPrimitive>`
|
|
408
|
+
|
|
409
|
+
Union type accepting either `ExprUnit<T>` or a literal value of type `T`.
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
type ExprInput<T> = ExprUnit<T> | T;
|
|
413
|
+
```
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Query Builder
|
|
2
|
+
|
|
3
|
+
Converts `QueryDef` AST objects into dialect-specific SQL strings for MySQL, MSSQL, and PostgreSQL.
|
|
4
|
+
|
|
5
|
+
## API Reference
|
|
6
|
+
|
|
7
|
+
### `createQueryBuilder(dialect)`
|
|
8
|
+
|
|
9
|
+
Factory function that returns a dialect-specific `QueryBuilderBase` instance.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
function createQueryBuilder(dialect: Dialect): QueryBuilderBase
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Parameters:**
|
|
16
|
+
- `dialect` -- `"mysql"` | `"mssql"` | `"postgresql"`
|
|
17
|
+
|
|
18
|
+
**Returns:** `MysqlQueryBuilder` | `MssqlQueryBuilder` | `PostgresqlQueryBuilder`
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
### `QueryBuilderBase`
|
|
23
|
+
|
|
24
|
+
Abstract base class for SQL rendering. Contains shared logic and dispatches to dialect-specific implementations.
|
|
25
|
+
|
|
26
|
+
#### `build(def)`
|
|
27
|
+
|
|
28
|
+
Convert any `QueryDef` to SQL.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
build(def: QueryDef): QueryBuildResult
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**QueryBuildResult:**
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
interface QueryBuildResult {
|
|
38
|
+
sql: string;
|
|
39
|
+
resultSetIndex?: number; // Which result set to read (for multi-statement queries)
|
|
40
|
+
resultSetStride?: number; // Read every Nth result set
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### `ExprRendererBase`
|
|
47
|
+
|
|
48
|
+
Abstract base class for rendering `Expr` AST nodes to SQL strings.
|
|
49
|
+
|
|
50
|
+
#### Key Methods
|
|
51
|
+
|
|
52
|
+
| Method | Description |
|
|
53
|
+
|--------|-------------|
|
|
54
|
+
| `render(expr)` | Render any Expr or WhereExpr to SQL string |
|
|
55
|
+
| `renderWhere(exprs)` | Render array of WhereExpr joined with AND |
|
|
56
|
+
| `wrap(name)` | Wrap identifier (MySQL: `` `name` ``, MSSQL: `[name]`, PostgreSQL: `"name"`) |
|
|
57
|
+
| `escapeString(value)` | Escape string for SQL literal |
|
|
58
|
+
| `escapeValue(value)` | Escape any value to appropriate SQL literal |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### Dialect Implementations
|
|
63
|
+
|
|
64
|
+
#### `MysqlQueryBuilder` / `MysqlExprRenderer`
|
|
65
|
+
|
|
66
|
+
MySQL 8.0.14+ specific SQL generation.
|
|
67
|
+
|
|
68
|
+
- Identifier quoting: `` `name` ``
|
|
69
|
+
- NULL-safe equality: `<=>`
|
|
70
|
+
- LATERAL JOIN support
|
|
71
|
+
- `LIMIT offset, count` syntax
|
|
72
|
+
|
|
73
|
+
#### `MssqlQueryBuilder` / `MssqlExprRenderer`
|
|
74
|
+
|
|
75
|
+
Microsoft SQL Server 2012+ specific SQL generation.
|
|
76
|
+
|
|
77
|
+
- Identifier quoting: `[name]`
|
|
78
|
+
- Three-part naming: `[database].[schema].[name]`
|
|
79
|
+
- `CROSS APPLY` / `OUTER APPLY` instead of LATERAL
|
|
80
|
+
- `OFFSET ... FETCH NEXT` for pagination
|
|
81
|
+
- `IDENTITY_INSERT` handling
|
|
82
|
+
|
|
83
|
+
#### `PostgresqlQueryBuilder` / `PostgresqlExprRenderer`
|
|
84
|
+
|
|
85
|
+
PostgreSQL 9.0+ specific SQL generation.
|
|
86
|
+
|
|
87
|
+
- Identifier quoting: `"name"`
|
|
88
|
+
- `LATERAL` JOIN support
|
|
89
|
+
- `LIMIT ... OFFSET` syntax
|
|
90
|
+
- `RETURN QUERY` for procedures
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Supported QueryDef Types
|
|
95
|
+
|
|
96
|
+
The query builder handles all `QueryDef` union types:
|
|
97
|
+
|
|
98
|
+
#### DML
|
|
99
|
+
|
|
100
|
+
| Type | Description |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| `select` | SELECT query |
|
|
103
|
+
| `insert` | INSERT query |
|
|
104
|
+
| `insertIfNotExists` | Conditional INSERT |
|
|
105
|
+
| `insertInto` | INSERT INTO ... SELECT |
|
|
106
|
+
| `update` | UPDATE query |
|
|
107
|
+
| `delete` | DELETE query |
|
|
108
|
+
| `upsert` | INSERT or UPDATE (MERGE) |
|
|
109
|
+
|
|
110
|
+
#### DDL -- Table
|
|
111
|
+
|
|
112
|
+
| Type | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `createTable` | CREATE TABLE |
|
|
115
|
+
| `dropTable` | DROP TABLE |
|
|
116
|
+
| `renameTable` | RENAME TABLE |
|
|
117
|
+
| `truncate` | TRUNCATE TABLE |
|
|
118
|
+
|
|
119
|
+
#### DDL -- Column
|
|
120
|
+
|
|
121
|
+
| Type | Description |
|
|
122
|
+
|------|-------------|
|
|
123
|
+
| `addColumn` | ALTER TABLE ADD COLUMN |
|
|
124
|
+
| `dropColumn` | ALTER TABLE DROP COLUMN |
|
|
125
|
+
| `modifyColumn` | ALTER TABLE MODIFY COLUMN |
|
|
126
|
+
| `renameColumn` | ALTER TABLE RENAME COLUMN |
|
|
127
|
+
|
|
128
|
+
#### DDL -- Constraints
|
|
129
|
+
|
|
130
|
+
| Type | Description |
|
|
131
|
+
|------|-------------|
|
|
132
|
+
| `addPrimaryKey` | ADD PRIMARY KEY |
|
|
133
|
+
| `dropPrimaryKey` | DROP PRIMARY KEY |
|
|
134
|
+
| `addForeignKey` | ADD FOREIGN KEY |
|
|
135
|
+
| `dropForeignKey` | DROP FOREIGN KEY |
|
|
136
|
+
| `addIndex` | CREATE INDEX |
|
|
137
|
+
| `dropIndex` | DROP INDEX |
|
|
138
|
+
|
|
139
|
+
#### DDL -- View/Procedure
|
|
140
|
+
|
|
141
|
+
| Type | Description |
|
|
142
|
+
|------|-------------|
|
|
143
|
+
| `createView` | CREATE VIEW |
|
|
144
|
+
| `dropView` | DROP VIEW |
|
|
145
|
+
| `createProc` | CREATE PROCEDURE |
|
|
146
|
+
| `dropProc` | DROP PROCEDURE |
|
|
147
|
+
| `execProc` | EXECUTE PROCEDURE |
|
|
148
|
+
|
|
149
|
+
#### Utility
|
|
150
|
+
|
|
151
|
+
| Type | Description |
|
|
152
|
+
|------|-------------|
|
|
153
|
+
| `clearSchema` | Drop all objects in schema |
|
|
154
|
+
| `schemaExists` | Check schema existence |
|
|
155
|
+
| `switchFk` | Enable/disable FK constraints |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Usage Examples
|
|
160
|
+
|
|
161
|
+
### Build SQL from QueryDef
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { createQueryBuilder } from "@simplysm/orm-common";
|
|
165
|
+
|
|
166
|
+
const builder = createQueryBuilder("mysql");
|
|
167
|
+
const result = builder.build({
|
|
168
|
+
type: "select",
|
|
169
|
+
from: { database: "mydb", name: "User" },
|
|
170
|
+
as: "T1",
|
|
171
|
+
select: {
|
|
172
|
+
id: { type: "column", path: ["T1", "id"] },
|
|
173
|
+
name: { type: "column", path: ["T1", "name"] },
|
|
174
|
+
},
|
|
175
|
+
where: [{
|
|
176
|
+
type: "eq",
|
|
177
|
+
source: { type: "column", path: ["T1", "status"] },
|
|
178
|
+
target: { type: "value", value: "active" },
|
|
179
|
+
}],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
console.log(result.sql);
|
|
183
|
+
// SELECT `T1`.`id` AS `id`, `T1`.`name` AS `name`
|
|
184
|
+
// FROM `mydb`.`User` AS `T1`
|
|
185
|
+
// WHERE `T1`.`status` <=> 'active'
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Multi-dialect Testing
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { createQueryBuilder, dialects } from "@simplysm/orm-common";
|
|
192
|
+
|
|
193
|
+
for (const dialect of dialects) {
|
|
194
|
+
const builder = createQueryBuilder(dialect);
|
|
195
|
+
const result = builder.build(queryDef);
|
|
196
|
+
console.log(`[${dialect}] ${result.sql}`);
|
|
197
|
+
}
|
|
198
|
+
```
|