@simplysm/orm-common 13.0.82 → 13.0.84
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/dist/ddl/initialize.d.ts +2 -2
- package/dist/ddl/initialize.js +1 -1
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/table-ddl.d.ts +1 -1
- package/dist/exec/queryable.d.ts +115 -115
- package/dist/exec/queryable.js +68 -68
- package/dist/exec/queryable.js.map +1 -1
- package/dist/expr/expr.d.ts +248 -248
- package/dist/expr/expr.js +250 -250
- package/dist/query-builder/base/expr-renderer-base.d.ts +7 -7
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +3 -3
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +5 -5
- 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 +7 -7
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +2 -2
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +4 -4
- package/dist/query-builder/mysql/mysql-query-builder.d.ts +10 -10
- package/dist/query-builder/mysql/mysql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +2 -2
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +4 -4
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +8 -8
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +7 -7
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/schema/factory/column-builder.d.ts +46 -46
- package/dist/schema/factory/column-builder.js +25 -25
- package/dist/schema/factory/index-builder.d.ts +22 -22
- package/dist/schema/factory/index-builder.js +14 -14
- package/dist/schema/factory/relation-builder.d.ts +93 -93
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +37 -37
- package/dist/schema/procedure-builder.d.ts +38 -38
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +26 -26
- package/dist/schema/table-builder.d.ts +38 -38
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +29 -29
- package/dist/schema/view-builder.d.ts +26 -26
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +18 -18
- package/dist/types/db.d.ts +40 -40
- package/dist/types/expr.d.ts +75 -75
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/query-def.d.ts +32 -32
- package/dist/types/query-def.d.ts.map +1 -1
- 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
- package/src/ddl/initialize.ts +16 -16
- package/src/ddl/table-ddl.ts +1 -1
- package/src/exec/queryable.ts +163 -163
- package/src/expr/expr.ts +257 -257
- package/src/query-builder/base/expr-renderer-base.ts +8 -8
- package/src/query-builder/mssql/mssql-expr-renderer.ts +20 -20
- package/src/query-builder/mssql/mssql-query-builder.ts +28 -28
- package/src/query-builder/mysql/mysql-expr-renderer.ts +22 -22
- package/src/query-builder/mysql/mysql-query-builder.ts +65 -65
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +15 -15
- package/src/query-builder/postgresql/postgresql-query-builder.ts +43 -43
- package/src/query-builder/query-builder.ts +1 -1
- package/src/schema/factory/column-builder.ts +48 -48
- package/src/schema/factory/index-builder.ts +22 -22
- package/src/schema/factory/relation-builder.ts +95 -95
- package/src/schema/procedure-builder.ts +38 -38
- package/src/schema/table-builder.ts +38 -38
- package/src/schema/view-builder.ts +28 -28
- package/src/types/db.ts +41 -41
- package/src/types/expr.ts +79 -79
- package/src/types/query-def.ts +37 -37
- package/tests/ddl/basic.expected.ts +8 -8
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
# Queryable
|
|
2
|
+
|
|
3
|
+
Chainable query builder for constructing type-safe SELECT, INSERT, UPDATE, DELETE, JOIN, GROUP BY, UNION, and recursive CTE queries.
|
|
4
|
+
|
|
5
|
+
## API Reference
|
|
6
|
+
|
|
7
|
+
### `Queryable<TData, TFrom>`
|
|
8
|
+
|
|
9
|
+
The core query builder class. Created automatically from DbContext table/view accessors (e.g., `db.user()`).
|
|
10
|
+
|
|
11
|
+
- `TData` -- Data type of the query result
|
|
12
|
+
- `TFrom` -- Source table builder (required for CUD operations; `never` for views/subqueries)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
### SELECT / Projection
|
|
17
|
+
|
|
18
|
+
#### `.select(fn)`
|
|
19
|
+
|
|
20
|
+
Specify columns to SELECT with optional transformations.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
select<R>(fn: (columns) => R): Queryable<UnwrapQueryableRecord<R>, never>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
db.user().select((u) => ({
|
|
28
|
+
userName: u.name,
|
|
29
|
+
upperEmail: expr.upper(u.email),
|
|
30
|
+
}))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### `.distinct()`
|
|
34
|
+
|
|
35
|
+
Apply DISTINCT to remove duplicate rows.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
db.user().select((u) => ({ name: u.name })).distinct()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### `.lock()`
|
|
42
|
+
|
|
43
|
+
Apply row lock (`FOR UPDATE`) within a transaction.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
db.user().where((u) => [expr.eq(u.id, 1)]).lock()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Filtering
|
|
52
|
+
|
|
53
|
+
#### `.where(predicate)`
|
|
54
|
+
|
|
55
|
+
Add WHERE conditions. Multiple calls are combined with AND.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
where(predicate: (columns) => WhereExprUnit[]): Queryable<TData, TFrom>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
db.user()
|
|
63
|
+
.where((u) => [expr.eq(u.status, "active")])
|
|
64
|
+
.where((u) => [expr.gte(u.age, 18)])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### `.search(fn, searchText)`
|
|
68
|
+
|
|
69
|
+
Full-text search using LIKE patterns. Supports `+required`, `-excluded`, `"exact phrase"`, and `*wildcard` syntax.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
db.user().search((u) => [u.name, u.email], 'John +active -deleted')
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### Sorting and Pagination
|
|
78
|
+
|
|
79
|
+
#### `.orderBy(fn, direction?)`
|
|
80
|
+
|
|
81
|
+
Add ORDER BY. Multiple calls add additional sort columns.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
db.user()
|
|
85
|
+
.orderBy((u) => u.name)
|
|
86
|
+
.orderBy((u) => u.createdAt, "DESC")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `.top(count)`
|
|
90
|
+
|
|
91
|
+
Select only top N rows. Can be used without ORDER BY.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
db.user().top(10)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### `.limit(skip, take)`
|
|
98
|
+
|
|
99
|
+
Set LIMIT/OFFSET for pagination. Requires `orderBy()` first.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
db.user()
|
|
103
|
+
.orderBy((u) => u.createdAt)
|
|
104
|
+
.limit(0, 20) // first 20 rows
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### Grouping
|
|
110
|
+
|
|
111
|
+
#### `.groupBy(fn)`
|
|
112
|
+
|
|
113
|
+
Add GROUP BY clause.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
db.order()
|
|
117
|
+
.select((o) => ({
|
|
118
|
+
userId: o.userId,
|
|
119
|
+
totalAmount: expr.sum(o.amount),
|
|
120
|
+
}))
|
|
121
|
+
.groupBy((o) => [o.userId])
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### `.having(predicate)`
|
|
125
|
+
|
|
126
|
+
Add HAVING clause (filter after GROUP BY).
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
db.order()
|
|
130
|
+
.select((o) => ({
|
|
131
|
+
userId: o.userId,
|
|
132
|
+
totalAmount: expr.sum(o.amount),
|
|
133
|
+
}))
|
|
134
|
+
.groupBy((o) => [o.userId])
|
|
135
|
+
.having((o) => [expr.gte(o.totalAmount, 10000)])
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### JOIN
|
|
141
|
+
|
|
142
|
+
#### `.join(as, fn)` -- 1:N JOIN
|
|
143
|
+
|
|
144
|
+
LEFT OUTER JOIN that adds results as an array property.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
db.user().join("posts", (qr, u) =>
|
|
148
|
+
qr.from(Post).where((p) => [expr.eq(p.authorId, u.id)])
|
|
149
|
+
)
|
|
150
|
+
// Result: { id, name, posts: [{ id, title }, ...] }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### `.joinSingle(as, fn)` -- N:1 / 1:1 JOIN
|
|
154
|
+
|
|
155
|
+
LEFT OUTER JOIN that adds results as a single object property.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
db.post().joinSingle("author", (qr, p) =>
|
|
159
|
+
qr.from(User).where((u) => [expr.eq(u.id, p.authorId)])
|
|
160
|
+
)
|
|
161
|
+
// Result: { id, title, author: { id, name } | undefined }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### `.include(fn)` -- Automatic Relation JOIN
|
|
165
|
+
|
|
166
|
+
Automatically JOIN based on relations defined in `TableBuilder.relations()`.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Single relation
|
|
170
|
+
db.post().include((p) => p.author)
|
|
171
|
+
|
|
172
|
+
// Nested relation
|
|
173
|
+
db.post().include((p) => p.author.company)
|
|
174
|
+
|
|
175
|
+
// Multiple relations (chain include calls)
|
|
176
|
+
db.user()
|
|
177
|
+
.include((u) => u.company)
|
|
178
|
+
.include((u) => u.posts)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### Subquery and UNION
|
|
184
|
+
|
|
185
|
+
#### `.wrap()`
|
|
186
|
+
|
|
187
|
+
Wrap current Queryable as a subquery. Required before `count()` after `distinct()` or `groupBy()`.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
const count = await db.user()
|
|
191
|
+
.select((u) => ({ name: u.name }))
|
|
192
|
+
.distinct()
|
|
193
|
+
.wrap()
|
|
194
|
+
.count();
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### `Queryable.union(...queries)`
|
|
198
|
+
|
|
199
|
+
Combine multiple Queryables with UNION ALL.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const combined = Queryable.union(
|
|
203
|
+
db.user().where((u) => [expr.eq(u.type, "admin")]),
|
|
204
|
+
db.user().where((u) => [expr.eq(u.type, "manager")]),
|
|
205
|
+
);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### Recursive CTE
|
|
211
|
+
|
|
212
|
+
#### `.recursive(fn)`
|
|
213
|
+
|
|
214
|
+
Build recursive Common Table Expressions for hierarchical data.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
db.employee()
|
|
218
|
+
.where((e) => [expr.null(e.managerId)]) // base case: root nodes
|
|
219
|
+
.recursive((cte) =>
|
|
220
|
+
cte.from(Employee)
|
|
221
|
+
.where((e) => [expr.eq(e.managerId, e.self[0].id)])
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### Execution -- SELECT
|
|
228
|
+
|
|
229
|
+
#### `.execute()`
|
|
230
|
+
|
|
231
|
+
Execute SELECT and return result array.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const users: User[] = await db.user()
|
|
235
|
+
.where((u) => [expr.eq(u.isActive, true)])
|
|
236
|
+
.execute();
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `.single()`
|
|
240
|
+
|
|
241
|
+
Return single result. Throws if more than one result.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const user = await db.user()
|
|
245
|
+
.where((u) => [expr.eq(u.id, 1)])
|
|
246
|
+
.single();
|
|
247
|
+
// returns User | undefined
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### `.first()`
|
|
251
|
+
|
|
252
|
+
Return first result (ignores additional results).
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const latest = await db.user()
|
|
256
|
+
.orderBy((u) => u.createdAt, "DESC")
|
|
257
|
+
.first();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### `.count(fn?)`
|
|
261
|
+
|
|
262
|
+
Return row count. Cannot be used directly after `distinct()` or `groupBy()` (use `wrap()` first).
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const count = await db.user()
|
|
266
|
+
.where((u) => [expr.eq(u.isActive, true)])
|
|
267
|
+
.count();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### `.exists()`
|
|
271
|
+
|
|
272
|
+
Check if any matching rows exist.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const hasAdmin = await db.user()
|
|
276
|
+
.where((u) => [expr.eq(u.role, "admin")])
|
|
277
|
+
.exists();
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### Execution -- INSERT
|
|
283
|
+
|
|
284
|
+
#### `.insert(records, outputColumns?)`
|
|
285
|
+
|
|
286
|
+
Insert records. Automatically chunks into batches of 1000 (MSSQL limit).
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// Simple insert
|
|
290
|
+
await db.user().insert([
|
|
291
|
+
{ name: "Alice", email: "alice@test.com" },
|
|
292
|
+
{ name: "Bob" },
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
// Insert with output (get auto-generated IDs)
|
|
296
|
+
const inserted = await db.user().insert(
|
|
297
|
+
[{ name: "Alice" }],
|
|
298
|
+
["id"],
|
|
299
|
+
);
|
|
300
|
+
// inserted[0].id
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### `.insertIfNotExists(record, outputColumns?)`
|
|
304
|
+
|
|
305
|
+
Insert only if WHERE condition matches no rows.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
await db.user()
|
|
309
|
+
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
310
|
+
.insertIfNotExists({ name: "Test", email: "test@test.com" });
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### `.insertInto(targetTable, outputColumns?)`
|
|
314
|
+
|
|
315
|
+
INSERT INTO ... SELECT (copy current query results into another table).
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
await db.user()
|
|
319
|
+
.select((u) => ({ name: u.name, createdAt: u.createdAt }))
|
|
320
|
+
.where((u) => [expr.eq(u.isArchived, false)])
|
|
321
|
+
.insertInto(ArchivedUser);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### Execution -- UPDATE
|
|
327
|
+
|
|
328
|
+
#### `.update(recordFn, outputColumns?)`
|
|
329
|
+
|
|
330
|
+
Update matching rows. The callback receives current column values for reference.
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
await db.user()
|
|
334
|
+
.where((u) => [expr.eq(u.id, 1)])
|
|
335
|
+
.update(() => ({
|
|
336
|
+
name: expr.val("string", "New Name"),
|
|
337
|
+
}));
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
### Execution -- DELETE
|
|
343
|
+
|
|
344
|
+
#### `.delete(outputColumns?)`
|
|
345
|
+
|
|
346
|
+
Delete matching rows.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
await db.user()
|
|
350
|
+
.where((u) => [expr.eq(u.status, "deleted")])
|
|
351
|
+
.delete();
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### Execution -- UPSERT
|
|
357
|
+
|
|
358
|
+
#### `.upsert(record, outputColumns?)`
|
|
359
|
+
|
|
360
|
+
INSERT or UPDATE (MERGE pattern). Requires a WHERE condition to check existence.
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
await db.user()
|
|
364
|
+
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
365
|
+
.upsert({
|
|
366
|
+
insert: { name: "Test", email: "test@test.com" },
|
|
367
|
+
update: { name: expr.val("string", "Updated") },
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### QueryDef Accessors
|
|
374
|
+
|
|
375
|
+
Each execution method has a corresponding `get*QueryDef()` method that returns the raw `QueryDef` without executing it:
|
|
376
|
+
|
|
377
|
+
- `getSelectQueryDef()` -- Returns `SelectQueryDef`
|
|
378
|
+
- `getInsertQueryDef(records, outputColumns?)` -- Returns `InsertQueryDef`
|
|
379
|
+
- `getInsertIfNotExistsQueryDef(record, outputColumns?)` -- Returns `InsertIfNotExistsQueryDef`
|
|
380
|
+
- `getInsertIntoQueryDef(targetTable, outputColumns?)` -- Returns `InsertIntoQueryDef`
|
|
381
|
+
- `getResultMeta(outputColumns?)` -- Returns `ResultMeta` for type parsing
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Usage Examples
|
|
386
|
+
|
|
387
|
+
### Complex Query with Multiple Features
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
const result = await db.order()
|
|
391
|
+
.include((o) => o.customer)
|
|
392
|
+
.where((o) => [
|
|
393
|
+
expr.gte(o.createdAt, expr.val("DateTime", startDate)),
|
|
394
|
+
expr.eq(o.status, "completed"),
|
|
395
|
+
])
|
|
396
|
+
.select((o) => ({
|
|
397
|
+
orderId: o.id,
|
|
398
|
+
customerName: o.customer.name,
|
|
399
|
+
total: o.totalAmount,
|
|
400
|
+
}))
|
|
401
|
+
.orderBy((o) => o.total, "DESC")
|
|
402
|
+
.limit(0, 50)
|
|
403
|
+
.execute();
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Aggregation with GROUP BY
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
const stats = await db.order()
|
|
410
|
+
.select((o) => ({
|
|
411
|
+
month: expr.month(o.createdAt),
|
|
412
|
+
year: expr.year(o.createdAt),
|
|
413
|
+
totalRevenue: expr.sum(o.amount),
|
|
414
|
+
orderCount: expr.count(),
|
|
415
|
+
}))
|
|
416
|
+
.groupBy((o) => [o.month, o.year])
|
|
417
|
+
.orderBy((o) => o.year)
|
|
418
|
+
.orderBy((o) => o.month)
|
|
419
|
+
.execute();
|
|
420
|
+
```
|