@simplysm/orm-common 13.0.0-beta.28 → 13.0.0-beta.30
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 +73 -828
- package/docs/expressions.md +172 -0
- package/docs/queries.md +351 -0
- package/docs/schema.md +212 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -27,825 +27,54 @@ pnpm add @simplysm/orm-common
|
|
|
27
27
|
|
|
28
28
|
## Core Modules
|
|
29
29
|
|
|
30
|
-
### Schema
|
|
30
|
+
### Schema Definition
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|--------|------|
|
|
34
|
-
| `Table(name)` | Table builder factory function |
|
|
35
|
-
| `TableBuilder` | Table schema definition (Fluent API) |
|
|
36
|
-
| `View(name)` | View builder factory function |
|
|
37
|
-
| `ViewBuilder` | View schema definition (Fluent API) |
|
|
38
|
-
| `Procedure(name)` | Procedure builder factory function |
|
|
39
|
-
| `ProcedureBuilder` | Procedure schema definition (Fluent API) |
|
|
40
|
-
| `ColumnBuilder` | Column definition builder |
|
|
41
|
-
| `createColumnFactory()` | Create column type factory |
|
|
42
|
-
| `IndexBuilder` | Index definition builder |
|
|
43
|
-
| `createIndexFactory()` | Create index factory |
|
|
44
|
-
| `ForeignKeyBuilder` | FK relationship builder (N:1, creates DB FK) |
|
|
45
|
-
| `ForeignKeyTargetBuilder` | FK reverse reference builder (1:N) |
|
|
46
|
-
| `RelationKeyBuilder` | Logical relationship builder (N:1, no DB FK) |
|
|
47
|
-
| `RelationKeyTargetBuilder` | Logical reverse reference builder (1:N) |
|
|
48
|
-
| `createRelationFactory()` | Create relation factory |
|
|
49
|
-
|
|
50
|
-
### Query Execution
|
|
51
|
-
|
|
52
|
-
| export | Description |
|
|
53
|
-
|--------|------|
|
|
54
|
-
| `Queryable` | Query builder class (SELECT/INSERT/UPDATE/DELETE) |
|
|
55
|
-
| `queryable(db, table)` | Create table Queryable from `DbContext` |
|
|
56
|
-
| `Executable` | Procedure execution wrapper class |
|
|
57
|
-
| `executable(db, proc)` | Create procedure Executable from `DbContext` |
|
|
58
|
-
| `DbContext` | Database context abstract class (connection, transaction, DDL) |
|
|
59
|
-
|
|
60
|
-
### Expressions
|
|
61
|
-
|
|
62
|
-
| export | Description |
|
|
63
|
-
|--------|------|
|
|
64
|
-
| `expr` | SQL expression builder object |
|
|
65
|
-
| `toExpr(value)` | Convert `ExprInput` to `Expr` AST |
|
|
66
|
-
| `ExprUnit` | Value expression wrapper class |
|
|
67
|
-
| `WhereExprUnit` | WHERE condition expression wrapper class |
|
|
68
|
-
|
|
69
|
-
### Query Builder (SQL Generation)
|
|
70
|
-
|
|
71
|
-
| export | Description |
|
|
72
|
-
|--------|------|
|
|
73
|
-
| `createQueryBuilder(dialect)` | Create dialect-specific query builder instance |
|
|
74
|
-
| `QueryBuilderBase` | Query builder abstract base class |
|
|
75
|
-
| `MysqlQueryBuilder` | MySQL SQL generator |
|
|
76
|
-
| `MssqlQueryBuilder` | MSSQL SQL generator |
|
|
77
|
-
| `PostgresqlQueryBuilder` | PostgreSQL SQL generator |
|
|
78
|
-
| `ExprRendererBase` | Expression renderer abstract base class |
|
|
79
|
-
| `MysqlExprRenderer` | MySQL expression renderer |
|
|
80
|
-
| `MssqlExprRenderer` | MSSQL expression renderer |
|
|
81
|
-
| `PostgresqlExprRenderer` | PostgreSQL expression renderer |
|
|
82
|
-
|
|
83
|
-
### Utilities
|
|
84
|
-
|
|
85
|
-
| export | Description |
|
|
86
|
-
|--------|------|
|
|
87
|
-
| `parseSearchQuery(text)` | Parse search string into SQL LIKE patterns |
|
|
88
|
-
| `parseQueryResult(rows, meta)` | Convert flat query results to nested objects |
|
|
89
|
-
| `getMatchedPrimaryKeys(fk, table)` | Match FK columns with target table PK |
|
|
90
|
-
| `SystemMigration` | Internal table for migration history management |
|
|
91
|
-
|
|
92
|
-
### Errors
|
|
93
|
-
|
|
94
|
-
| export | Description |
|
|
95
|
-
|--------|------|
|
|
96
|
-
| `DbTransactionError` | Transaction error (DBMS independent) |
|
|
97
|
-
| `DbErrorCode` | Error code enum (`NO_ACTIVE_TRANSACTION`, `DEADLOCK`, `LOCK_TIMEOUT`, etc.) |
|
|
98
|
-
|
|
99
|
-
### Types
|
|
100
|
-
|
|
101
|
-
| export | Description |
|
|
102
|
-
|--------|------|
|
|
103
|
-
| `Dialect` | `"mysql" \| "mssql" \| "postgresql"` |
|
|
104
|
-
| `dialects` | All Dialect array (for testing) |
|
|
105
|
-
| `IsolationLevel` | Transaction isolation level |
|
|
106
|
-
| `DbContextStatus` | `"ready" \| "connect" \| "transact"` |
|
|
107
|
-
| `DbContextExecutor` | Query executor interface |
|
|
108
|
-
| `Migration` | Migration definition interface |
|
|
109
|
-
| `ResultMeta` | Query result conversion metadata |
|
|
110
|
-
| `QueryBuildResult` | Built SQL + result set meta |
|
|
111
|
-
| `DataRecord` | Query result record (supports recursive nesting) |
|
|
112
|
-
| `DataType` | Column data type definition |
|
|
113
|
-
| `ColumnPrimitive` | Column primitive type union |
|
|
114
|
-
| `ColumnPrimitiveMap` | TypeScript type name to actual type mapping |
|
|
115
|
-
| `ColumnPrimitiveStr` | Column primitive type name union (`"string" \| "number" \| ...`) |
|
|
116
|
-
| `ColumnMeta` | Column metadata |
|
|
117
|
-
| `ColumnBuilderRecord` | Record of column name to `ColumnBuilder` |
|
|
118
|
-
| `InferColumns<T>` | Infer value types from column builder |
|
|
119
|
-
| `InferInsertColumns<T>` | Infer types for INSERT (autoIncrement/nullable/default are optional) |
|
|
120
|
-
| `InferUpdateColumns<T>` | Infer types for UPDATE (all fields optional) |
|
|
121
|
-
| `InferColumnExprs<T>` | Infer expression input types from column builder |
|
|
122
|
-
| `InferColumnPrimitiveFromDataType<T>` | Infer TypeScript type from `DataType` |
|
|
123
|
-
| `DataToColumnBuilderRecord<T>` | Convert `DataRecord` to `ColumnBuilderRecord` |
|
|
124
|
-
| `InferDeepRelations<T>` | Deep type inference from relation definitions |
|
|
125
|
-
| `RelationBuilderRecord` | Record of relation name to relation builder |
|
|
126
|
-
| `QueryableRecord<T>` | Queryable internal column record type |
|
|
127
|
-
| `PathProxy<T>` | Type-safe path proxy for `include()` |
|
|
128
|
-
| `ExprInput<T>` | Expression input type (`ExprUnit<T> \| T`) |
|
|
129
|
-
| `SwitchExprBuilder<T>` | CASE WHEN builder (returned by `expr.switch()`) |
|
|
130
|
-
| `ParsedSearchQuery` | Result of `parseSearchQuery()` (`{ or, must, not }`) |
|
|
131
|
-
| `QueryDef` | Query definition union type (DML + DDL) |
|
|
132
|
-
| `SelectQueryDef` | SELECT query definition |
|
|
133
|
-
| `DDL_TYPES` | Array of all DDL query type strings |
|
|
134
|
-
| `DdlType` | Union type of DDL query types |
|
|
135
|
-
| `Expr`, `WhereExpr` | Expression AST types |
|
|
136
|
-
| `DateSeparator` | Date part separator (`"year" \| "month" \| "day" \| ...`) |
|
|
137
|
-
| `WinSpec` | Window function specification (`partitionBy`, `orderBy`) |
|
|
138
|
-
| `dataTypeStrToColumnPrimitiveStr` | SQL type name to TypeScript type name mapping |
|
|
139
|
-
| `inferColumnPrimitiveStr(value)` | Infer `ColumnPrimitiveStr` from runtime value |
|
|
140
|
-
|
|
141
|
-
## Usage
|
|
142
|
-
|
|
143
|
-
### Table Definition
|
|
144
|
-
|
|
145
|
-
Define table schema using the `Table()` factory function and Fluent API.
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
import { Table } from "@simplysm/orm-common";
|
|
149
|
-
|
|
150
|
-
const User = Table("User")
|
|
151
|
-
.database("mydb")
|
|
152
|
-
.columns((c) => ({
|
|
153
|
-
id: c.bigint().autoIncrement(),
|
|
154
|
-
name: c.varchar(100),
|
|
155
|
-
email: c.varchar(200).nullable(),
|
|
156
|
-
isActive: c.boolean().default(true),
|
|
157
|
-
createdAt: c.datetime(),
|
|
158
|
-
}))
|
|
159
|
-
.primaryKey("id")
|
|
160
|
-
.indexes((i) => [
|
|
161
|
-
i.index("email").unique(),
|
|
162
|
-
i.index("name", "createdAt").orderBy("ASC", "DESC"),
|
|
163
|
-
]);
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
#### Column Types
|
|
167
|
-
|
|
168
|
-
| Factory Method | SQL Type | TypeScript Type |
|
|
169
|
-
|--------------|----------|----------------|
|
|
170
|
-
| `c.int()` | INT | `number` |
|
|
171
|
-
| `c.bigint()` | BIGINT | `number` |
|
|
172
|
-
| `c.float()` | FLOAT | `number` |
|
|
173
|
-
| `c.double()` | DOUBLE | `number` |
|
|
174
|
-
| `c.decimal(p, s)` | DECIMAL(p, s) | `number` |
|
|
175
|
-
| `c.varchar(n)` | VARCHAR(n) | `string` |
|
|
176
|
-
| `c.char(n)` | CHAR(n) | `string` |
|
|
177
|
-
| `c.text()` | TEXT | `string` |
|
|
178
|
-
| `c.boolean()` | BOOLEAN / BIT / TINYINT(1) | `boolean` |
|
|
179
|
-
| `c.datetime()` | DATETIME | `DateTime` |
|
|
180
|
-
| `c.date()` | DATE | `DateOnly` |
|
|
181
|
-
| `c.time()` | TIME | `Time` |
|
|
182
|
-
| `c.uuid()` | UUID / UNIQUEIDENTIFIER / BINARY(16) | `Uuid` |
|
|
183
|
-
| `c.binary()` | BLOB / VARBINARY(MAX) / BYTEA | `Bytes` |
|
|
184
|
-
|
|
185
|
-
#### Column Options
|
|
186
|
-
|
|
187
|
-
| Method | Description |
|
|
188
|
-
|--------|------|
|
|
189
|
-
| `.autoIncrement()` | Auto increment (optional on INSERT) |
|
|
190
|
-
| `.nullable()` | Allow NULL (adds `undefined` to type) |
|
|
191
|
-
| `.default(value)` | Set default value (optional on INSERT) |
|
|
192
|
-
| `.description(text)` | Column description (DDL comment) |
|
|
193
|
-
|
|
194
|
-
### Relationship Definition
|
|
195
|
-
|
|
196
|
-
Define relationships between tables to enable automatic JOINs via `include()`.
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
const Post = Table("Post")
|
|
200
|
-
.database("mydb")
|
|
201
|
-
.columns((c) => ({
|
|
202
|
-
id: c.bigint().autoIncrement(),
|
|
203
|
-
authorId: c.bigint(),
|
|
204
|
-
title: c.varchar(200),
|
|
205
|
-
content: c.text(),
|
|
206
|
-
}))
|
|
207
|
-
.primaryKey("id")
|
|
208
|
-
.relations((r) => ({
|
|
209
|
-
// N:1 relationship - Post.authorId → User.id (creates DB FK)
|
|
210
|
-
author: r.foreignKey(["authorId"], () => User),
|
|
211
|
-
}));
|
|
212
|
-
|
|
213
|
-
const User = Table("User")
|
|
214
|
-
.database("mydb")
|
|
215
|
-
.columns((c) => ({
|
|
216
|
-
id: c.bigint().autoIncrement(),
|
|
217
|
-
name: c.varchar(100),
|
|
218
|
-
}))
|
|
219
|
-
.primaryKey("id")
|
|
220
|
-
.relations((r) => ({
|
|
221
|
-
// 1:N reverse reference - User ← Post.author
|
|
222
|
-
posts: r.foreignKeyTarget(() => Post, "author"),
|
|
223
|
-
|
|
224
|
-
// 1:1 relationship (single object)
|
|
225
|
-
profile: r.foreignKeyTarget(() => Profile, "user").single(),
|
|
226
|
-
}));
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
#### Relationship Builder Types
|
|
230
|
-
|
|
231
|
-
| Method | Cardinality | Creates DB FK | Available For |
|
|
232
|
-
|--------|-----------|-----------|--------------|
|
|
233
|
-
| `r.foreignKey(cols, targetFn)` | N:1 | Yes | Table |
|
|
234
|
-
| `r.foreignKeyTarget(targetFn, relName)` | 1:N | - | Table |
|
|
235
|
-
| `r.relationKey(cols, targetFn)` | N:1 | No | Table, View |
|
|
236
|
-
| `r.relationKeyTarget(targetFn, relName)` | 1:N | - | Table, View |
|
|
237
|
-
|
|
238
|
-
Calling `.single()` on `foreignKeyTarget` / `relationKeyTarget` establishes a 1:1 relationship (returns single object instead of array).
|
|
239
|
-
|
|
240
|
-
### DbContext Configuration
|
|
241
|
-
|
|
242
|
-
Register tables and procedures by extending `DbContext`.
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
import { DbContext, queryable, executable, expr } from "@simplysm/orm-common";
|
|
246
|
-
|
|
247
|
-
class MyDb extends DbContext {
|
|
248
|
-
readonly user = queryable(this, User);
|
|
249
|
-
readonly post = queryable(this, Post);
|
|
250
|
-
readonly getUserById = executable(this, GetUserById);
|
|
251
|
-
|
|
252
|
-
// Migration definitions
|
|
253
|
-
readonly migrations = [
|
|
254
|
-
{
|
|
255
|
-
name: "20260101_add_status",
|
|
256
|
-
up: async (db: MyDb) => {
|
|
257
|
-
const c = createColumnFactory();
|
|
258
|
-
await db.addColumn(
|
|
259
|
-
{ database: "mydb", name: "User" },
|
|
260
|
-
"status",
|
|
261
|
-
c.varchar(20).nullable(),
|
|
262
|
-
);
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
];
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### Connection and Transactions
|
|
270
|
-
|
|
271
|
-
```typescript
|
|
272
|
-
// executor is NodeDbContextExecutor from orm-node package, etc.
|
|
273
|
-
const db = new MyDb(executor, { database: "mydb" });
|
|
274
|
-
|
|
275
|
-
// Execute within transaction (auto commit/rollback)
|
|
276
|
-
const users = await db.connect(async () => {
|
|
277
|
-
const result = await db.user().result();
|
|
278
|
-
await db.user().insert([{ name: "John Doe", createdAt: DateTime.now() }]);
|
|
279
|
-
return result;
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Connect without transaction (for DDL operations)
|
|
283
|
-
await db.connectWithoutTransaction(async () => {
|
|
284
|
-
await db.initialize(); // Code First initialization
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Specify isolation level
|
|
288
|
-
await db.connect(async () => {
|
|
289
|
-
// ...
|
|
290
|
-
}, "SERIALIZABLE");
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### SELECT Queries
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
// Basic query
|
|
297
|
-
const users = await db.user()
|
|
298
|
-
.where((u) => [expr.eq(u.isActive, true)])
|
|
299
|
-
.orderBy((u) => u.name)
|
|
300
|
-
.result();
|
|
301
|
-
|
|
302
|
-
// Column selection
|
|
303
|
-
const names = await db.user()
|
|
304
|
-
.select((u) => ({
|
|
305
|
-
userName: u.name,
|
|
306
|
-
userEmail: u.email,
|
|
307
|
-
}))
|
|
308
|
-
.result();
|
|
309
|
-
|
|
310
|
-
// Single result (error if 2 or more)
|
|
311
|
-
const user = await db.user()
|
|
312
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
313
|
-
.single();
|
|
314
|
-
|
|
315
|
-
// First result only
|
|
316
|
-
const latest = await db.user()
|
|
317
|
-
.orderBy((u) => u.createdAt, "DESC")
|
|
318
|
-
.first();
|
|
319
|
-
|
|
320
|
-
// Row count
|
|
321
|
-
const count = await db.user()
|
|
322
|
-
.where((u) => [expr.eq(u.isActive, true)])
|
|
323
|
-
.count();
|
|
324
|
-
|
|
325
|
-
// Existence check
|
|
326
|
-
const hasAdmin = await db.user()
|
|
327
|
-
.where((u) => [expr.eq(u.role, "admin")])
|
|
328
|
-
.exists();
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### JOIN Queries
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
// Manual JOIN (1:N - array)
|
|
335
|
-
const usersWithPosts = await db.user()
|
|
336
|
-
.join("posts", (qr, u) =>
|
|
337
|
-
qr.from(Post).where((p) => [expr.eq(p.authorId, u.id)])
|
|
338
|
-
)
|
|
339
|
-
.result();
|
|
340
|
-
// Result: { id, name, posts: [{ id, title }, ...] }
|
|
341
|
-
|
|
342
|
-
// Manual JOIN (N:1 - single object)
|
|
343
|
-
const postsWithUser = await db.post()
|
|
344
|
-
.joinSingle("author", (qr, p) =>
|
|
345
|
-
qr.from(User).where((u) => [expr.eq(u.id, p.authorId)])
|
|
346
|
-
)
|
|
347
|
-
.result();
|
|
348
|
-
// Result: { id, title, author: { id, name } | undefined }
|
|
349
|
-
|
|
350
|
-
// include (auto JOIN based on relationship definition)
|
|
351
|
-
const postWithAuthor = await db.post()
|
|
352
|
-
.include((p) => p.author)
|
|
353
|
-
.single();
|
|
354
|
-
|
|
355
|
-
// Nested include
|
|
356
|
-
const postWithAuthorCompany = await db.post()
|
|
357
|
-
.include((p) => p.author.company)
|
|
358
|
-
.result();
|
|
359
|
-
|
|
360
|
-
// Multiple includes
|
|
361
|
-
const userWithAll = await db.user()
|
|
362
|
-
.include((u) => u.posts)
|
|
363
|
-
.include((u) => u.profile)
|
|
364
|
-
.result();
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### Grouping and Aggregation
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
const stats = await db.order()
|
|
371
|
-
.select((o) => ({
|
|
372
|
-
userId: o.userId,
|
|
373
|
-
totalAmount: expr.sum(o.amount),
|
|
374
|
-
orderCount: expr.count(),
|
|
375
|
-
}))
|
|
376
|
-
.groupBy((o) => [o.userId])
|
|
377
|
-
.having((o) => [expr.gte(o.totalAmount, 10000)])
|
|
378
|
-
.result();
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### Pagination
|
|
382
|
-
|
|
383
|
-
```typescript
|
|
384
|
-
// TOP (no ORDER BY required)
|
|
385
|
-
const topUsers = await db.user().top(10).result();
|
|
386
|
-
|
|
387
|
-
// LIMIT/OFFSET (ORDER BY required)
|
|
388
|
-
const page = await db.user()
|
|
389
|
-
.orderBy((u) => u.createdAt, "DESC")
|
|
390
|
-
.limit(0, 20) // skip 0, take 20
|
|
391
|
-
.result();
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### Text Search
|
|
395
|
-
|
|
396
|
-
The `search()` method supports structured search syntax.
|
|
397
|
-
|
|
398
|
-
```typescript
|
|
399
|
-
const users = await db.user()
|
|
400
|
-
.search((u) => [u.name, u.email], "John -deleted")
|
|
401
|
-
.result();
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
#### Search Syntax
|
|
405
|
-
|
|
406
|
-
| Syntax | Description | Example |
|
|
407
|
-
|------|------|------|
|
|
408
|
-
| Space | OR combination | `apple banana` |
|
|
409
|
-
| `""` | Phrase search (required) | `"delicious apple"` |
|
|
410
|
-
| `+` | Required (AND) | `+apple +banana` |
|
|
411
|
-
| `-` | Exclude (NOT) | `apple -banana` |
|
|
412
|
-
| `*` | Wildcard | `app*` |
|
|
413
|
-
| `\*` | Escape | `app\*` (literal `*`) |
|
|
414
|
-
|
|
415
|
-
### UNION
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
const allItems = await Queryable.union(
|
|
419
|
-
db.user()
|
|
420
|
-
.where((u) => [expr.eq(u.type, "admin")])
|
|
421
|
-
.select((u) => ({ name: u.name })),
|
|
422
|
-
db.user()
|
|
423
|
-
.where((u) => [expr.eq(u.type, "manager")])
|
|
424
|
-
.select((u) => ({ name: u.name })),
|
|
425
|
-
).result();
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### Subquery Wrapping (wrap)
|
|
429
|
-
|
|
430
|
-
To use `count()` after `distinct()` or `groupBy()`, wrap the query with `wrap()`.
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
const count = await db.user()
|
|
434
|
-
.select((u) => ({ name: u.name }))
|
|
435
|
-
.distinct()
|
|
436
|
-
.wrap()
|
|
437
|
-
.count();
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
### Recursive CTE (recursive)
|
|
441
|
-
|
|
442
|
-
Use for querying hierarchical data (org charts, category trees, etc.).
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
const employees = await db.employee()
|
|
446
|
-
.where((e) => [expr.null(e.managerId)]) // Root node
|
|
447
|
-
.recursive((cte) =>
|
|
448
|
-
cte.from(Employee)
|
|
449
|
-
.where((e) => [expr.eq(e.managerId, e.self[0].id)])
|
|
450
|
-
)
|
|
451
|
-
.result();
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
### INSERT
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
// Simple insert
|
|
458
|
-
await db.user().insert([
|
|
459
|
-
{ name: "John Doe", createdAt: DateTime.now() },
|
|
460
|
-
{ name: "Jane Smith", createdAt: DateTime.now() },
|
|
461
|
-
]);
|
|
462
|
-
|
|
463
|
-
// Insert with ID return (outputColumns)
|
|
464
|
-
const [inserted] = await db.user().insert(
|
|
465
|
-
[{ name: "John Doe", createdAt: DateTime.now() }],
|
|
466
|
-
["id"],
|
|
467
|
-
);
|
|
468
|
-
// inserted.id is available
|
|
469
|
-
|
|
470
|
-
// Conditional insert (insert if not exists)
|
|
471
|
-
await db.user()
|
|
472
|
-
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
473
|
-
.insertIfNotExists({ name: "Test", email: "test@test.com", createdAt: DateTime.now() });
|
|
474
|
-
|
|
475
|
-
// INSERT INTO ... SELECT
|
|
476
|
-
await db.user()
|
|
477
|
-
.select((u) => ({ name: u.name, createdAt: u.createdAt }))
|
|
478
|
-
.where((u) => [expr.eq(u.isArchived, false)])
|
|
479
|
-
.insertInto(ArchivedUser);
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### UPDATE
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
// Simple update
|
|
486
|
-
await db.user()
|
|
487
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
488
|
-
.update((u) => ({
|
|
489
|
-
name: expr.val("string", "New Name"),
|
|
490
|
-
}));
|
|
491
|
-
|
|
492
|
-
// Reference existing value
|
|
493
|
-
await db.product()
|
|
494
|
-
.update((p) => ({
|
|
495
|
-
viewCount: expr.val("number", p.viewCount + 1),
|
|
496
|
-
}));
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### DELETE
|
|
500
|
-
|
|
501
|
-
```typescript
|
|
502
|
-
// Simple delete
|
|
503
|
-
await db.user()
|
|
504
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
505
|
-
.delete();
|
|
506
|
-
|
|
507
|
-
// Return deleted data
|
|
508
|
-
const deleted = await db.user()
|
|
509
|
-
.where((u) => [expr.eq(u.isExpired, true)])
|
|
510
|
-
.delete(["id", "name"]);
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### UPSERT
|
|
514
|
-
|
|
515
|
-
```typescript
|
|
516
|
-
// Same data for UPDATE/INSERT
|
|
517
|
-
await db.user()
|
|
518
|
-
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
519
|
-
.upsert(() => ({
|
|
520
|
-
name: expr.val("string", "Test"),
|
|
521
|
-
email: expr.val("string", "test@test.com"),
|
|
522
|
-
}));
|
|
523
|
-
|
|
524
|
-
// Different data for UPDATE/INSERT
|
|
525
|
-
await db.user()
|
|
526
|
-
.where((u) => [expr.eq(u.email, "test@test.com")])
|
|
527
|
-
.upsert(
|
|
528
|
-
() => ({ loginCount: expr.val("number", 1) }),
|
|
529
|
-
(update) => ({ ...update, email: expr.val("string", "test@test.com") }),
|
|
530
|
-
);
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
### Row Locking (FOR UPDATE)
|
|
534
|
-
|
|
535
|
-
```typescript
|
|
536
|
-
await db.connect(async () => {
|
|
537
|
-
const user = await db.user()
|
|
538
|
-
.where((u) => [expr.eq(u.id, 1)])
|
|
539
|
-
.lock()
|
|
540
|
-
.single();
|
|
541
|
-
});
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
## Expressions (expr)
|
|
545
|
-
|
|
546
|
-
The `expr` object generates Dialect-independent SQL expressions. It creates JSON AST instead of SQL strings, which the `QueryBuilder` converts for each DBMS.
|
|
547
|
-
|
|
548
|
-
### Comparison Expressions (WHERE)
|
|
549
|
-
|
|
550
|
-
| Method | SQL | Description |
|
|
551
|
-
|--------|-----|------|
|
|
552
|
-
| `expr.eq(a, b)` | `a = b` (NULL-safe) | Equality comparison |
|
|
553
|
-
| `expr.gt(a, b)` | `a > b` | Greater than |
|
|
554
|
-
| `expr.lt(a, b)` | `a < b` | Less than |
|
|
555
|
-
| `expr.gte(a, b)` | `a >= b` | Greater than or equal |
|
|
556
|
-
| `expr.lte(a, b)` | `a <= b` | Less than or equal |
|
|
557
|
-
| `expr.between(a, from, to)` | `a BETWEEN from AND to` | Range (unbounded in direction if undefined) |
|
|
558
|
-
| `expr.null(a)` | `a IS NULL` | NULL check |
|
|
559
|
-
| `expr.like(a, pattern)` | `a LIKE pattern` | Pattern matching |
|
|
560
|
-
| `expr.regexp(a, pattern)` | `a REGEXP pattern` | Regex matching |
|
|
561
|
-
| `expr.in(a, values)` | `a IN (v1, v2, ...)` | Value list comparison |
|
|
562
|
-
| `expr.inQuery(a, query)` | `a IN (SELECT ...)` | Subquery comparison |
|
|
563
|
-
| `expr.exists(query)` | `EXISTS (SELECT ...)` | Subquery existence |
|
|
564
|
-
|
|
565
|
-
### Logical Expressions (WHERE)
|
|
566
|
-
|
|
567
|
-
| Method | SQL | Description |
|
|
568
|
-
|--------|-----|------|
|
|
569
|
-
| `expr.and(conditions)` | `(c1 AND c2 AND ...)` | All conditions met |
|
|
570
|
-
| `expr.or(conditions)` | `(c1 OR c2 OR ...)` | At least one condition met |
|
|
571
|
-
| `expr.not(condition)` | `NOT (condition)` | Negate condition |
|
|
572
|
-
|
|
573
|
-
### String Expressions
|
|
574
|
-
|
|
575
|
-
| Method | SQL | Description |
|
|
576
|
-
|--------|-----|------|
|
|
577
|
-
| `expr.concat(...args)` | `CONCAT(a, b, ...)` | String concatenation |
|
|
578
|
-
| `expr.left(s, n)` | `LEFT(s, n)` | Extract n chars from left |
|
|
579
|
-
| `expr.right(s, n)` | `RIGHT(s, n)` | Extract n chars from right |
|
|
580
|
-
| `expr.trim(s)` | `TRIM(s)` | Trim whitespace from both sides |
|
|
581
|
-
| `expr.padStart(s, n, fill)` | `LPAD(s, n, fill)` | Left padding |
|
|
582
|
-
| `expr.replace(s, from, to)` | `REPLACE(s, from, to)` | String replacement |
|
|
583
|
-
| `expr.upper(s)` | `UPPER(s)` | Convert to uppercase |
|
|
584
|
-
| `expr.lower(s)` | `LOWER(s)` | Convert to lowercase |
|
|
585
|
-
| `expr.length(s)` | `CHAR_LENGTH(s)` | Character count |
|
|
586
|
-
| `expr.byteLength(s)` | `OCTET_LENGTH(s)` | Byte count |
|
|
587
|
-
| `expr.substring(s, start, len)` | `SUBSTRING(s, start, len)` | Substring extraction (1-based) |
|
|
588
|
-
| `expr.indexOf(s, search)` | `LOCATE(search, s)` | Find position (1-based, 0 if not found) |
|
|
589
|
-
|
|
590
|
-
### Numeric Expressions
|
|
591
|
-
|
|
592
|
-
| Method | SQL | Description |
|
|
593
|
-
|--------|-----|------|
|
|
594
|
-
| `expr.abs(n)` | `ABS(n)` | Absolute value |
|
|
595
|
-
| `expr.round(n, digits)` | `ROUND(n, digits)` | Round |
|
|
596
|
-
| `expr.ceil(n)` | `CEILING(n)` | Ceiling |
|
|
597
|
-
| `expr.floor(n)` | `FLOOR(n)` | Floor |
|
|
598
|
-
|
|
599
|
-
### Date Expressions
|
|
600
|
-
|
|
601
|
-
| Method | SQL | Description |
|
|
602
|
-
|--------|-----|------|
|
|
603
|
-
| `expr.year(d)` | `YEAR(d)` | Extract year |
|
|
604
|
-
| `expr.month(d)` | `MONTH(d)` | Extract month (1~12) |
|
|
605
|
-
| `expr.day(d)` | `DAY(d)` | Extract day (1~31) |
|
|
606
|
-
| `expr.hour(d)` | `HOUR(d)` | Extract hour (0~23) |
|
|
607
|
-
| `expr.minute(d)` | `MINUTE(d)` | Extract minute (0~59) |
|
|
608
|
-
| `expr.second(d)` | `SECOND(d)` | Extract second (0~59) |
|
|
609
|
-
| `expr.isoWeek(d)` | `WEEK(d, 3)` | ISO week (1~53) |
|
|
610
|
-
| `expr.isoWeekStartDate(d)` | - | ISO week start date (Monday) |
|
|
611
|
-
| `expr.isoYearMonth(d)` | - | First day of the month |
|
|
612
|
-
| `expr.dateDiff(sep, from, to)` | `DATEDIFF(sep, from, to)` | Date difference |
|
|
613
|
-
| `expr.dateAdd(sep, source, value)` | `DATEADD(sep, value, source)` | Add to date |
|
|
614
|
-
| `expr.formatDate(d, format)` | `DATE_FORMAT(d, format)` | Date formatting |
|
|
615
|
-
|
|
616
|
-
`DateSeparator`: `"year"`, `"month"`, `"day"`, `"hour"`, `"minute"`, `"second"`
|
|
617
|
-
|
|
618
|
-
### Conditional Expressions
|
|
619
|
-
|
|
620
|
-
| Method | SQL | Description |
|
|
621
|
-
|--------|-----|------|
|
|
622
|
-
| `expr.ifNull(a, b, ...)` | `COALESCE(a, b, ...)` | Return first non-null value |
|
|
623
|
-
| `expr.nullIf(a, b)` | `NULLIF(a, b)` | NULL if `a === b` |
|
|
624
|
-
| `expr.is(condition)` | `(condition)` | Convert WHERE to boolean |
|
|
625
|
-
| `expr.if(cond, then, else)` | `IF(cond, then, else)` | Ternary condition |
|
|
626
|
-
| `expr.switch()` | `CASE WHEN ... END` | Multiple condition branching |
|
|
627
|
-
|
|
628
|
-
```typescript
|
|
629
|
-
// CASE WHEN usage example
|
|
630
|
-
db.user().select((u) => ({
|
|
631
|
-
grade: expr.switch<string>()
|
|
632
|
-
.case(expr.gte(u.score, 90), "A")
|
|
633
|
-
.case(expr.gte(u.score, 80), "B")
|
|
634
|
-
.case(expr.gte(u.score, 70), "C")
|
|
635
|
-
.default("F"),
|
|
636
|
-
}))
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
### Aggregate Expressions
|
|
640
|
-
|
|
641
|
-
| Method | SQL | Description |
|
|
642
|
-
|--------|-----|------|
|
|
643
|
-
| `expr.count(col?, distinct?)` | `COUNT(*)` / `COUNT(DISTINCT col)` | Row count |
|
|
644
|
-
| `expr.sum(col)` | `SUM(col)` | Sum |
|
|
645
|
-
| `expr.avg(col)` | `AVG(col)` | Average |
|
|
646
|
-
| `expr.max(col)` | `MAX(col)` | Maximum |
|
|
647
|
-
| `expr.min(col)` | `MIN(col)` | Minimum |
|
|
648
|
-
| `expr.greatest(...args)` | `GREATEST(a, b, ...)` | Greatest among multiple values |
|
|
649
|
-
| `expr.least(...args)` | `LEAST(a, b, ...)` | Least among multiple values |
|
|
650
|
-
|
|
651
|
-
### Window Functions
|
|
652
|
-
|
|
653
|
-
| Method | SQL | Description |
|
|
654
|
-
|--------|-----|------|
|
|
655
|
-
| `expr.rowNumber(spec)` | `ROW_NUMBER() OVER (...)` | Row number |
|
|
656
|
-
| `expr.rank(spec)` | `RANK() OVER (...)` | Rank (gaps on ties) |
|
|
657
|
-
| `expr.denseRank(spec)` | `DENSE_RANK() OVER (...)` | Dense rank (consecutive) |
|
|
658
|
-
| `expr.ntile(n, spec)` | `NTILE(n) OVER (...)` | Split into n groups |
|
|
659
|
-
| `expr.lag(col, spec, opts?)` | `LAG(col, offset) OVER (...)` | Previous row value |
|
|
660
|
-
| `expr.lead(col, spec, opts?)` | `LEAD(col, offset) OVER (...)` | Next row value |
|
|
661
|
-
| `expr.firstValue(col, spec)` | `FIRST_VALUE(col) OVER (...)` | First value |
|
|
662
|
-
| `expr.lastValue(col, spec)` | `LAST_VALUE(col) OVER (...)` | Last value |
|
|
663
|
-
| `expr.sumOver(col, spec)` | `SUM(col) OVER (...)` | Window sum |
|
|
664
|
-
| `expr.avgOver(col, spec)` | `AVG(col) OVER (...)` | Window average |
|
|
665
|
-
| `expr.countOver(spec, col?)` | `COUNT(*) OVER (...)` | Window count |
|
|
666
|
-
| `expr.minOver(col, spec)` | `MIN(col) OVER (...)` | Window minimum |
|
|
667
|
-
| `expr.maxOver(col, spec)` | `MAX(col) OVER (...)` | Window maximum |
|
|
668
|
-
|
|
669
|
-
`WinSpec`: `{ partitionBy?: [...], orderBy?: [[col, "ASC"|"DESC"], ...] }`
|
|
670
|
-
|
|
671
|
-
```typescript
|
|
672
|
-
// Window function usage example
|
|
673
|
-
db.order().select((o) => ({
|
|
674
|
-
...o,
|
|
675
|
-
rowNum: expr.rowNumber({
|
|
676
|
-
partitionBy: [o.userId],
|
|
677
|
-
orderBy: [[o.createdAt, "DESC"]],
|
|
678
|
-
}),
|
|
679
|
-
runningTotal: expr.sumOver(o.amount, {
|
|
680
|
-
partitionBy: [o.userId],
|
|
681
|
-
orderBy: [[o.createdAt, "ASC"]],
|
|
682
|
-
}),
|
|
683
|
-
}))
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
### Other Expressions
|
|
687
|
-
|
|
688
|
-
| Method | SQL | Description |
|
|
689
|
-
|--------|-----|------|
|
|
690
|
-
| `expr.val(dataType, value)` | Literal | Wrap typed value |
|
|
691
|
-
| `expr.col(dataType, ...path)` | Column reference | Create column reference (internal) |
|
|
692
|
-
| `expr.raw(dataType)\`sql\`` | Raw SQL | Escape hatch for DBMS-specific functions |
|
|
693
|
-
| `expr.rowNum()` | - | Total row number |
|
|
694
|
-
| `expr.random()` | `RAND()` / `RANDOM()` | Random number 0~1 |
|
|
695
|
-
| `expr.cast(source, type)` | `CAST(source AS type)` | Type conversion |
|
|
696
|
-
| `expr.subquery(dataType, qr)` | `(SELECT ...)` | Scalar subquery |
|
|
697
|
-
|
|
698
|
-
```typescript
|
|
699
|
-
// Raw SQL (using DBMS-specific functions)
|
|
700
|
-
db.user().select((u) => ({
|
|
701
|
-
name: u.name,
|
|
702
|
-
data: expr.raw("string")`JSON_EXTRACT(${u.metadata}, '$.email')`,
|
|
703
|
-
}))
|
|
704
|
-
|
|
705
|
-
// Scalar subquery
|
|
706
|
-
db.user().select((u) => ({
|
|
707
|
-
id: u.id,
|
|
708
|
-
postCount: expr.subquery(
|
|
709
|
-
"number",
|
|
710
|
-
db.post()
|
|
711
|
-
.where((p) => [expr.eq(p.userId, u.id)])
|
|
712
|
-
.select(() => ({ cnt: expr.count() }))
|
|
713
|
-
),
|
|
714
|
-
}))
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
## View Definition
|
|
718
|
-
|
|
719
|
-
```typescript
|
|
720
|
-
import { View, expr } from "@simplysm/orm-common";
|
|
32
|
+
See [docs/schema.md](docs/schema.md) for full documentation.
|
|
721
33
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
email: u.email,
|
|
731
|
-
}))
|
|
732
|
-
);
|
|
733
|
-
|
|
734
|
-
// Define logical relationships on views (no DB FK)
|
|
735
|
-
const UserSummary = View("UserSummary")
|
|
736
|
-
.database("mydb")
|
|
737
|
-
.query((db: MyDb) =>
|
|
738
|
-
db.user().select((u) => ({
|
|
739
|
-
id: u.id,
|
|
740
|
-
name: u.name,
|
|
741
|
-
companyId: u.companyId,
|
|
742
|
-
}))
|
|
743
|
-
)
|
|
744
|
-
.relations((r) => ({
|
|
745
|
-
company: r.relationKey(["companyId"], () => Company),
|
|
746
|
-
}));
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
## Procedure Definition
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
import { Procedure, executable } from "@simplysm/orm-common";
|
|
753
|
-
|
|
754
|
-
const GetUserById = Procedure("GetUserById")
|
|
755
|
-
.database("mydb")
|
|
756
|
-
.params((c) => ({
|
|
757
|
-
userId: c.bigint(),
|
|
758
|
-
}))
|
|
759
|
-
.returns((c) => ({
|
|
760
|
-
id: c.bigint(),
|
|
761
|
-
name: c.varchar(100),
|
|
762
|
-
email: c.varchar(200),
|
|
763
|
-
}))
|
|
764
|
-
.body("SELECT id, name, email FROM User WHERE id = userId");
|
|
765
|
-
|
|
766
|
-
// Register in DbContext
|
|
767
|
-
class MyDb extends DbContext {
|
|
768
|
-
readonly getUserById = executable(this, GetUserById);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Invoke
|
|
772
|
-
const result = await db.getUserById().execute({ userId: 1 });
|
|
773
|
-
```
|
|
774
|
-
|
|
775
|
-
## Query Builder (SQL Generation)
|
|
776
|
-
|
|
777
|
-
Converts `QueryDef` JSON AST to DBMS-specific SQL strings.
|
|
778
|
-
|
|
779
|
-
```typescript
|
|
780
|
-
import { createQueryBuilder } from "@simplysm/orm-common";
|
|
781
|
-
|
|
782
|
-
const mysqlBuilder = createQueryBuilder("mysql");
|
|
783
|
-
const mssqlBuilder = createQueryBuilder("mssql");
|
|
784
|
-
const postgresqlBuilder = createQueryBuilder("postgresql");
|
|
785
|
-
|
|
786
|
-
// Convert QueryDef to SQL
|
|
787
|
-
const queryDef = db.user()
|
|
788
|
-
.where((u) => [expr.eq(u.isActive, true)])
|
|
789
|
-
.getSelectQueryDef();
|
|
790
|
-
|
|
791
|
-
const { sql } = mysqlBuilder.build(queryDef);
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
## DDL Operations
|
|
34
|
+
- **[Table(name)](docs/schema.md#table-definition)** - Table builder factory function
|
|
35
|
+
- **[View(name)](docs/schema.md#view-definition)** - View builder factory function
|
|
36
|
+
- **[Procedure(name)](docs/schema.md#procedure-definition)** - Procedure builder factory function
|
|
37
|
+
- **[Column Types](docs/schema.md#column-types)** - `c.int()`, `c.varchar()`, `c.datetime()`, etc.
|
|
38
|
+
- **[Column Options](docs/schema.md#column-options)** - `.nullable()`, `.autoIncrement()`, `.default()`, `.description()`
|
|
39
|
+
- **[Relationships](docs/schema.md#relationship-definition)** - `r.foreignKey()`, `r.foreignKeyTarget()`, `r.relationKey()`, `r.relationKeyTarget()`
|
|
40
|
+
- **[DbContext](docs/schema.md#dbcontext-configuration)** - Database context class for connection, transactions, and migrations
|
|
41
|
+
- **[Type Inference](docs/schema.md#type-inference)** - `$infer`, `$inferInsert`, `$inferUpdate`
|
|
795
42
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
```typescript
|
|
799
|
-
await db.connectWithoutTransaction(async () => {
|
|
800
|
-
// Initialize database (create tables/views/procedures/FKs/indexes)
|
|
801
|
-
await db.initialize();
|
|
802
|
-
|
|
803
|
-
// Force initialize (drop and recreate existing data)
|
|
804
|
-
await db.initialize({ force: true });
|
|
805
|
-
|
|
806
|
-
// Individual DDL operations
|
|
807
|
-
const c = createColumnFactory();
|
|
808
|
-
await db.addColumn({ database: "mydb", name: "User" }, "status", c.varchar(20).nullable());
|
|
809
|
-
await db.modifyColumn({ database: "mydb", name: "User" }, "status", c.varchar(50).nullable());
|
|
810
|
-
await db.renameColumn({ database: "mydb", name: "User" }, "status", "userStatus");
|
|
811
|
-
await db.dropColumn({ database: "mydb", name: "User" }, "userStatus");
|
|
812
|
-
|
|
813
|
-
await db.renameTable({ database: "mydb", name: "User" }, "Member");
|
|
814
|
-
await db.truncate({ database: "mydb", name: "User" });
|
|
815
|
-
});
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
## Error Handling
|
|
819
|
-
|
|
820
|
-
```typescript
|
|
821
|
-
import { DbTransactionError, DbErrorCode } from "@simplysm/orm-common";
|
|
822
|
-
|
|
823
|
-
try {
|
|
824
|
-
await db.connect(async () => {
|
|
825
|
-
// ...
|
|
826
|
-
});
|
|
827
|
-
} catch (err) {
|
|
828
|
-
if (err instanceof DbTransactionError) {
|
|
829
|
-
switch (err.code) {
|
|
830
|
-
case DbErrorCode.DEADLOCK:
|
|
831
|
-
// Deadlock retry logic
|
|
832
|
-
break;
|
|
833
|
-
case DbErrorCode.LOCK_TIMEOUT:
|
|
834
|
-
// Timeout handling
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
```
|
|
840
|
-
|
|
841
|
-
### DbErrorCode
|
|
43
|
+
### Query Execution
|
|
842
44
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
45
|
+
See [docs/queries.md](docs/queries.md) for full documentation.
|
|
46
|
+
|
|
47
|
+
- **[Connection & Transactions](docs/queries.md#connection-and-transactions)** - `db.connect()`, `db.connectWithoutTransaction()`
|
|
48
|
+
- **[SELECT Queries](docs/queries.md#select-queries)** - `.where()`, `.select()`, `.single()`, `.first()`, `.result()`, `.count()`, `.exists()`
|
|
49
|
+
- **[JOIN Queries](docs/queries.md#join-queries)** - `.join()`, `.joinSingle()`, `.include()`
|
|
50
|
+
- **[Grouping & Aggregation](docs/queries.md#grouping-and-aggregation)** - `.groupBy()`, `.having()`
|
|
51
|
+
- **[Pagination](docs/queries.md#pagination)** - `.top()`, `.limit()`
|
|
52
|
+
- **[Text Search](docs/queries.md#text-search)** - `.search()` with structured search syntax
|
|
53
|
+
- **[UNION](docs/queries.md#union)** - `Queryable.union()`
|
|
54
|
+
- **[Subquery Wrapping](docs/queries.md#subquery-wrapping-wrap)** - `.wrap()`
|
|
55
|
+
- **[Recursive CTE](docs/queries.md#recursive-cte-recursive)** - `.recursive()`
|
|
56
|
+
- **[INSERT](docs/queries.md#insert)** - `.insert()`, `.insertIfNotExists()`, `.insertInto()`
|
|
57
|
+
- **[UPDATE](docs/queries.md#update)** - `.update()`
|
|
58
|
+
- **[DELETE](docs/queries.md#delete)** - `.delete()`
|
|
59
|
+
- **[UPSERT](docs/queries.md#upsert)** - `.upsert()`
|
|
60
|
+
- **[Row Locking](docs/queries.md#row-locking-for-update)** - `.lock()` (FOR UPDATE)
|
|
61
|
+
- **[DDL Operations](docs/queries.md#ddl-operations)** - `db.initialize()`, `db.addColumn()`, `db.modifyColumn()`, etc.
|
|
62
|
+
- **[Query Builder](docs/queries.md#query-builder-sql-generation)** - `createQueryBuilder()` for converting QueryDef to SQL
|
|
63
|
+
- **[Error Handling](docs/queries.md#error-handling)** - `DbTransactionError`, `DbErrorCode`
|
|
64
|
+
|
|
65
|
+
### SQL Expressions
|
|
66
|
+
|
|
67
|
+
See [docs/expressions.md](docs/expressions.md) for full documentation.
|
|
68
|
+
|
|
69
|
+
- **[Comparison Expressions](docs/expressions.md#comparison-expressions-where)** - `expr.eq()`, `expr.gt()`, `expr.lt()`, `expr.between()`, `expr.in()`, `expr.like()`, `expr.regexp()`, `expr.exists()`
|
|
70
|
+
- **[Logical Expressions](docs/expressions.md#logical-expressions-where)** - `expr.and()`, `expr.or()`, `expr.not()`
|
|
71
|
+
- **[String Expressions](docs/expressions.md#string-expressions)** - `expr.concat()`, `expr.trim()`, `expr.substring()`, `expr.upper()`, `expr.lower()`, `expr.length()`
|
|
72
|
+
- **[Numeric Expressions](docs/expressions.md#numeric-expressions)** - `expr.abs()`, `expr.round()`, `expr.ceil()`, `expr.floor()`
|
|
73
|
+
- **[Date Expressions](docs/expressions.md#date-expressions)** - `expr.year()`, `expr.month()`, `expr.day()`, `expr.dateDiff()`, `expr.dateAdd()`, `expr.formatDate()`
|
|
74
|
+
- **[Conditional Expressions](docs/expressions.md#conditional-expressions)** - `expr.ifNull()`, `expr.nullIf()`, `expr.if()`, `expr.switch()`
|
|
75
|
+
- **[Aggregate Expressions](docs/expressions.md#aggregate-expressions)** - `expr.count()`, `expr.sum()`, `expr.avg()`, `expr.max()`, `expr.min()`, `expr.greatest()`, `expr.least()`
|
|
76
|
+
- **[Window Functions](docs/expressions.md#window-functions)** - `expr.rowNumber()`, `expr.rank()`, `expr.denseRank()`, `expr.lag()`, `expr.lead()`, `expr.sumOver()`, `expr.avgOver()`
|
|
77
|
+
- **[Other Expressions](docs/expressions.md#other-expressions)** - `expr.val()`, `expr.raw()`, `expr.cast()`, `expr.subquery()`, `expr.random()`
|
|
849
78
|
|
|
850
79
|
## Security Notes
|
|
851
80
|
|
|
@@ -867,31 +96,47 @@ if (Number.isNaN(userId)) throw new Error("Invalid ID");
|
|
|
867
96
|
await db.user().where((u) => [expr.eq(u.id, userId)]).result();
|
|
868
97
|
```
|
|
869
98
|
|
|
870
|
-
##
|
|
871
|
-
|
|
872
|
-
`TableBuilder` automatically infers types from column definitions.
|
|
99
|
+
## Quick Start
|
|
873
100
|
|
|
874
101
|
```typescript
|
|
102
|
+
import { Table, DbContext, queryable, expr } from "@simplysm/orm-common";
|
|
103
|
+
|
|
104
|
+
// Define table schema
|
|
875
105
|
const User = Table("User")
|
|
106
|
+
.database("mydb")
|
|
876
107
|
.columns((c) => ({
|
|
877
108
|
id: c.bigint().autoIncrement(),
|
|
878
109
|
name: c.varchar(100),
|
|
879
110
|
email: c.varchar(200).nullable(),
|
|
880
|
-
|
|
111
|
+
createdAt: c.datetime(),
|
|
881
112
|
}))
|
|
882
113
|
.primaryKey("id");
|
|
883
114
|
|
|
884
|
-
//
|
|
885
|
-
|
|
886
|
-
|
|
115
|
+
// Create DbContext
|
|
116
|
+
class MyDb extends DbContext {
|
|
117
|
+
readonly user = queryable(this, User);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Use with executor (from orm-node package)
|
|
121
|
+
const db = new MyDb(executor, { database: "mydb" });
|
|
122
|
+
|
|
123
|
+
// Execute queries
|
|
124
|
+
await db.connect(async () => {
|
|
125
|
+
// INSERT
|
|
126
|
+
await db.user().insert([
|
|
127
|
+
{ name: "John", createdAt: DateTime.now() }
|
|
128
|
+
]);
|
|
887
129
|
|
|
888
|
-
//
|
|
889
|
-
|
|
890
|
-
|
|
130
|
+
// SELECT
|
|
131
|
+
const users = await db.user()
|
|
132
|
+
.where((u) => [expr.eq(u.email, "john@example.com")])
|
|
133
|
+
.result();
|
|
891
134
|
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
135
|
+
// UPDATE
|
|
136
|
+
await db.user()
|
|
137
|
+
.where((u) => [expr.eq(u.id, 1)])
|
|
138
|
+
.update(() => ({ name: expr.val("string", "Jane") }));
|
|
139
|
+
});
|
|
895
140
|
```
|
|
896
141
|
|
|
897
142
|
## License
|