@simplysm/orm-common 13.0.85 → 13.0.87
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 +65 -59
- package/docs/ddl.md +195 -0
- package/docs/query.md +469 -0
- package/docs/schema.md +238 -0
- package/package.json +2 -2
- package/docs/db-context.md +0 -238
- package/docs/expressions.md +0 -413
- package/docs/query-builder.md +0 -198
- package/docs/queryable.md +0 -420
- package/docs/schema-builders.md +0 -216
- package/docs/types-and-utilities.md +0 -353
package/docs/db-context.md
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
# DbContext
|
|
2
|
-
|
|
3
|
-
Central entry point for database operations. Combines schema definitions with an executor for connection management, transaction handling, DDL operations, and schema initialization.
|
|
4
|
-
|
|
5
|
-
## API Reference
|
|
6
|
-
|
|
7
|
-
### `defineDbContext(config)`
|
|
8
|
-
|
|
9
|
-
Creates a `DbContextDef` (blueprint) containing schema metadata but no runtime state.
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
function defineDbContext<TTables, TViews, TProcedures>(config: {
|
|
13
|
-
tables?: TTables;
|
|
14
|
-
views?: TViews;
|
|
15
|
-
procedures?: TProcedures;
|
|
16
|
-
migrations?: Migration[];
|
|
17
|
-
}): DbContextDef<TTables & { _migration: typeof _Migration }, TViews, TProcedures>
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
A `_migration` system table is automatically added to track applied migrations.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### `createDbContext(def, executor, opt)`
|
|
25
|
-
|
|
26
|
-
Creates a complete `DbContextInstance` from a definition and executor.
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
function createDbContext<TDef extends DbContextDef<any, any, any>>(
|
|
30
|
-
def: TDef,
|
|
31
|
-
executor: DbContextExecutor,
|
|
32
|
-
opt: { database: string; schema?: string },
|
|
33
|
-
): DbContextInstance<TDef>
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
**Parameters:**
|
|
37
|
-
- `def` -- Definition created by `defineDbContext()`
|
|
38
|
-
- `executor` -- Query executor implementation (e.g., `NodeDbContextExecutor`, `ServiceDbContextExecutor`)
|
|
39
|
-
- `opt.database` -- Database name
|
|
40
|
-
- `opt.schema` -- Schema name (MSSQL: `dbo`, PostgreSQL: `public`)
|
|
41
|
-
|
|
42
|
-
The returned instance automatically maps:
|
|
43
|
-
- Each table key to a `() => Queryable` accessor
|
|
44
|
-
- Each view key to a `() => Queryable` accessor
|
|
45
|
-
- Each procedure key to a `() => Executable` accessor
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
### Connection Management
|
|
50
|
-
|
|
51
|
-
#### `db.connect(fn, isolationLevel?)`
|
|
52
|
-
|
|
53
|
-
Execute callback within a managed connection and transaction. Automatically commits on success or rolls back on error.
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
connect<TResult>(
|
|
57
|
-
fn: () => Promise<TResult>,
|
|
58
|
-
isolationLevel?: IsolationLevel
|
|
59
|
-
): Promise<TResult>
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
#### `db.connectWithoutTransaction(fn)`
|
|
63
|
-
|
|
64
|
-
Connect without a transaction. Used for DDL operations or read-only operations.
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
connectWithoutTransaction<TResult>(
|
|
68
|
-
fn: () => Promise<TResult>
|
|
69
|
-
): Promise<TResult>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
#### `db.transaction(fn, isolationLevel?)`
|
|
73
|
-
|
|
74
|
-
Start a nested transaction within an already connected state (`connectWithoutTransaction`).
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
transaction<TResult>(
|
|
78
|
-
fn: () => Promise<TResult>,
|
|
79
|
-
isolationLevel?: IsolationLevel
|
|
80
|
-
): Promise<TResult>
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**IsolationLevel values:** `"READ_UNCOMMITTED"` | `"READ_COMMITTED"` | `"REPEATABLE_READ"` | `"SERIALIZABLE"`
|
|
84
|
-
|
|
85
|
-
**DbContextStatus lifecycle:** `"ready"` -> `"connect"` -> `"transact"` -> `"connect"` -> `"ready"`
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
### DDL Methods
|
|
90
|
-
|
|
91
|
-
All DDL methods are available on the `DbContextInstance`. They cannot be executed inside a transaction.
|
|
92
|
-
|
|
93
|
-
#### Table Operations
|
|
94
|
-
|
|
95
|
-
| Method | Description |
|
|
96
|
-
|--------|-------------|
|
|
97
|
-
| `createTable(table)` | CREATE TABLE from a TableBuilder |
|
|
98
|
-
| `dropTable(table)` | DROP TABLE |
|
|
99
|
-
| `renameTable(table, newName)` | RENAME TABLE |
|
|
100
|
-
| `createView(view)` | CREATE VIEW from a ViewBuilder |
|
|
101
|
-
| `dropView(view)` | DROP VIEW |
|
|
102
|
-
| `createProc(procedure)` | CREATE PROCEDURE from a ProcedureBuilder |
|
|
103
|
-
| `dropProc(procedure)` | DROP PROCEDURE |
|
|
104
|
-
|
|
105
|
-
#### Column Operations
|
|
106
|
-
|
|
107
|
-
| Method | Description |
|
|
108
|
-
|--------|-------------|
|
|
109
|
-
| `addColumn(table, name, column)` | ADD COLUMN |
|
|
110
|
-
| `dropColumn(table, column)` | DROP COLUMN |
|
|
111
|
-
| `modifyColumn(table, name, column)` | MODIFY COLUMN (type/properties) |
|
|
112
|
-
| `renameColumn(table, column, newName)` | RENAME COLUMN |
|
|
113
|
-
|
|
114
|
-
#### Constraint Operations
|
|
115
|
-
|
|
116
|
-
| Method | Description |
|
|
117
|
-
|--------|-------------|
|
|
118
|
-
| `addPrimaryKey(table, columns)` | ADD PRIMARY KEY |
|
|
119
|
-
| `dropPrimaryKey(table)` | DROP PRIMARY KEY |
|
|
120
|
-
| `addForeignKey(table, name, fkBuilder)` | ADD FOREIGN KEY |
|
|
121
|
-
| `dropForeignKey(table, name)` | DROP FOREIGN KEY |
|
|
122
|
-
| `addIndex(table, indexBuilder)` | CREATE INDEX |
|
|
123
|
-
| `dropIndex(table, columns)` | DROP INDEX |
|
|
124
|
-
|
|
125
|
-
#### Schema Operations
|
|
126
|
-
|
|
127
|
-
| Method | Description |
|
|
128
|
-
|--------|-------------|
|
|
129
|
-
| `clearSchema(params)` | Drop all objects in a schema |
|
|
130
|
-
| `schemaExists(database, schema?)` | Check if schema/database exists |
|
|
131
|
-
| `truncate(table)` | TRUNCATE TABLE |
|
|
132
|
-
| `switchFk(table, enabled)` | Enable/disable FK constraints |
|
|
133
|
-
|
|
134
|
-
Each DDL method also has a corresponding `get*QueryDef()` method that returns the raw `QueryDef` without executing it.
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
### `db.initialize(options?)`
|
|
139
|
-
|
|
140
|
-
Run all pending migrations and create missing tables/views/procedures.
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
initialize(options?: {
|
|
144
|
-
dbs?: string[]; // Limit to specific databases
|
|
145
|
-
force?: boolean; // Force re-create (drop and recreate)
|
|
146
|
-
}): Promise<void>
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
### DbContextExecutor Interface
|
|
152
|
-
|
|
153
|
-
Implement this interface to connect the ORM to a specific database driver.
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
interface DbContextExecutor {
|
|
157
|
-
connect(): Promise<void>;
|
|
158
|
-
close(): Promise<void>;
|
|
159
|
-
beginTransaction(isolationLevel?: IsolationLevel): Promise<void>;
|
|
160
|
-
commitTransaction(): Promise<void>;
|
|
161
|
-
rollbackTransaction(): Promise<void>;
|
|
162
|
-
executeDefs<T = DataRecord>(
|
|
163
|
-
defs: QueryDef[],
|
|
164
|
-
resultMetas?: (ResultMeta | undefined)[],
|
|
165
|
-
): Promise<T[][]>;
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
### Migration
|
|
172
|
-
|
|
173
|
-
Define schema migrations for version control.
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
interface Migration {
|
|
177
|
-
name: string;
|
|
178
|
-
up: (db: DbContextBase & DbContextDdlMethods) => Promise<void>;
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
## Usage Examples
|
|
185
|
-
|
|
186
|
-
### Basic Setup and Query
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
const MyDb = defineDbContext({
|
|
190
|
-
tables: { user: User, post: Post },
|
|
191
|
-
views: { activeUsers: ActiveUsers },
|
|
192
|
-
procedures: { getUserById: GetUserById },
|
|
193
|
-
migrations: [
|
|
194
|
-
{
|
|
195
|
-
name: "20260101_001_add_status_column",
|
|
196
|
-
up: async (db) => {
|
|
197
|
-
await db.addColumn(
|
|
198
|
-
{ database: "mydb", name: "User" },
|
|
199
|
-
"status",
|
|
200
|
-
createColumnFactory().varchar(20).default("active"),
|
|
201
|
-
);
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
],
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
const db = createDbContext(MyDb, executor, { database: "mydb" });
|
|
208
|
-
|
|
209
|
-
// Transactional query
|
|
210
|
-
await db.connect(async () => {
|
|
211
|
-
const users = await db.user().execute();
|
|
212
|
-
const activeUsers = await db.activeUsers().execute();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// DDL operations (no transaction)
|
|
216
|
-
await db.connectWithoutTransaction(async () => {
|
|
217
|
-
await db.initialize();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Execute procedure
|
|
221
|
-
await db.connect(async () => {
|
|
222
|
-
const result = await db.getUserById().execute({ userId: 1n });
|
|
223
|
-
});
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### Nested Transaction
|
|
227
|
-
|
|
228
|
-
```typescript
|
|
229
|
-
await db.connectWithoutTransaction(async () => {
|
|
230
|
-
// DDL first (no transaction needed)
|
|
231
|
-
await db.createTable(NewTable);
|
|
232
|
-
|
|
233
|
-
// Then transactional DML
|
|
234
|
-
await db.transaction(async () => {
|
|
235
|
-
await db.user().insert([{ name: "Alice" }]);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
```
|
package/docs/expressions.md
DELETED
|
@@ -1,413 +0,0 @@
|
|
|
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
|
-
```
|