@grest-ts/sql 0.0.6 → 0.0.8
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/LICENSE +21 -21
- package/README.md +498 -498
- package/dist/src/select/parts/S0Union.d.ts +2 -2
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/MysqlTable.ts +101 -101
- package/src/MysqlTableStructureParser.ts +283 -283
- package/src/SQL.ts +244 -244
- package/src/SqlExpression.ts +172 -172
- package/src/SqlFunctions.ts +471 -471
- package/src/Symbols.ts +10 -10
- package/src/Types.ts +244 -244
- package/src/delete/DeleteBuilder.ts +64 -64
- package/src/delete/DeleteInterfaces.ts +70 -70
- package/src/escape.ts +51 -51
- package/src/index.ts +5 -5
- package/src/insert/InsertBuilder.ts +75 -75
- package/src/insert/InsertInterfaces.ts +16 -16
- package/src/select/SelectBuilder.ts +371 -371
- package/src/select/parts/GatewayWhere.ts +30 -30
- package/src/select/parts/S0Union.ts +43 -43
- package/src/select/parts/S0Uses.ts +21 -21
- package/src/select/parts/S0With.ts +21 -21
- package/src/select/parts/S1Join.ts +38 -38
- package/src/select/parts/S2Columns.ts +68 -68
- package/src/select/parts/S2Where.ts +51 -51
- package/src/select/parts/S3GroupBy.ts +23 -23
- package/src/select/parts/S4Having.ts +24 -24
- package/src/select/parts/S5OrderBy.ts +21 -21
- package/src/select/parts/S6Limit.ts +9 -9
- package/src/select/parts/S7Exec.ts +41 -41
- package/src/update/UpdateBuilder.ts +95 -95
- package/src/update/UpdateInterfaces.ts +87 -87
package/README.md
CHANGED
|
@@ -3,501 +3,501 @@
|
|
|
3
3
|
> [Documentation](https://github.com/grest-ts/grest-ts#readme) | [All packages](https://github.com/grest-ts/grest-ts#package-reference)
|
|
4
4
|
<!-- GREST-TS-BANNER-END -->
|
|
5
5
|
|
|
6
|
-
# Typescript Mysql query builder
|
|
7
|
-
|
|
8
|
-
Powerful SQL query builder with lots of compile time errors to hint what is wrong. Some examples include:
|
|
9
|
-
|
|
10
|
-
* Fully type checked, it is very hard to use wrong property names or types.
|
|
11
|
-
* Fully checks that all used tables are actually part of the query.
|
|
12
|
-
* Protects against duplicate alias usage.
|
|
13
|
-
|
|
14
|
-
In short - no more SQL syntax errors even when testing, only logic errors are left for the developer to figure out :)
|
|
15
|
-
|
|
16
|
-
In addition
|
|
17
|
-
|
|
18
|
-
* Good support for refactoring. If you happen to rename a field, you can rename it in the whole codebase with a single go.
|
|
19
|
-
* Very similar to actual SQL including MySQL functions like DATE(c.created), NOW(), and so on.
|
|
20
|
-
* Heavily optimized for readability
|
|
21
|
-
* Free to use any mysql library to connect to database. This library is only for SQL query building.
|
|
22
|
-
|
|
23
|
-
# Basic example
|
|
24
|
-
|
|
25
|
-
This library does not execute queries, it only prepares SQL statements.
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
// Your custom query execution function. (libraries: mysql or mysql2 usually)
|
|
29
|
-
function execute<Result>(query: SqlQuery<Result>): Promise<Result[]> {
|
|
30
|
-
const sqlString = query.toSqlString();
|
|
31
|
-
// Execute your query...
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const rows = await execute(SQL
|
|
35
|
-
.selectFrom(tUser)
|
|
36
|
-
.columns(
|
|
37
|
-
tUser.id,
|
|
38
|
-
tUser.firstName,
|
|
39
|
-
COUNT(tUser.firstName).as("count"),
|
|
40
|
-
DATE(MAX(tUser.created)).as("lastCreatedDate")
|
|
41
|
-
)
|
|
42
|
-
.where(tUser.isActive.eq(1))
|
|
43
|
-
.groupBy(tUser.firstName)
|
|
44
|
-
.having(COUNT(tUser.firstName).eq(3))
|
|
45
|
-
.orderBy(tUser.firstName)
|
|
46
|
-
.limit(20))
|
|
47
|
-
|
|
48
|
-
// Resulting type is
|
|
49
|
-
const res: {
|
|
50
|
-
id: number,
|
|
51
|
-
firstName: string,
|
|
52
|
-
count: number,
|
|
53
|
-
lastCreatedDate: string
|
|
54
|
-
} = undefined
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
# Error checking
|
|
59
|
-
|
|
60
|
-
As typescript errors can be sometimes cryptic for complex cases, there are lots of simplified errors used by this library to make understanding where the issue is easier.
|
|
61
|
-
|
|
62
|
-
----
|
|
63
|
-
Can't add columns with same name multiple times to the query
|
|
64
|
-
|
|
65
|
-
1. 
|
|
66
|
-
|
|
67
|
-
2. 
|
|
68
|
-
|
|
69
|
-
----
|
|
70
|
-
If table is not added to the query via from, join etc...
|
|
71
|
-
|
|
72
|
-
1. 
|
|
73
|
-
|
|
74
|
-
2. 
|
|
75
|
-
|
|
76
|
-
----
|
|
77
|
-
Alias is already used, can't have duplicate aliases.
|
|
78
|
-
|
|
79
|
-
1. 
|
|
80
|
-
|
|
81
|
-
----
|
|
82
|
-
Table joined is not defined in the with part of the query (WITH ... SELECT ...)
|
|
83
|
-
|
|
84
|
-
1. 
|
|
85
|
-
|
|
86
|
-
----
|
|
87
|
-
|
|
88
|
-
Union erros, when added table has more or less fields than expected.
|
|
89
|
-
|
|
90
|
-
1. 
|
|
91
|
-
|
|
92
|
-
2. 
|
|
93
|
-
|
|
94
|
-
----
|
|
95
|
-
|
|
96
|
-
# SELECT
|
|
97
|
-
|
|
98
|
-
## Using as a gateway
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
MyDb.user.select("username")
|
|
102
|
-
.where({id: 10})
|
|
103
|
-
.toSqlString();
|
|
104
|
-
|
|
105
|
-
MyDb.user.select("id", "username")
|
|
106
|
-
.where({birthYear: 1986})
|
|
107
|
-
.orderBy("username", "asc", "id")
|
|
108
|
-
.limit([10, 10])
|
|
109
|
-
.toSqlString();
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Uses "result columns" in HAVING and ORDER BY clauses.
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
SQL
|
|
116
|
-
.selectFrom(tUser)
|
|
117
|
-
.columns(
|
|
118
|
-
tUser.firstName.as("username2"),
|
|
119
|
-
COUNT(tUser.firstName).as("count")
|
|
120
|
-
)
|
|
121
|
-
.where(tUser.isMan.eq(1))
|
|
122
|
-
.groupBy(tUser.firstName)
|
|
123
|
-
.havingF((r) => [r.count.compare(">", 1)]) // Via argument 'r' you can access properties defined in columns.
|
|
124
|
-
.orderByF((r) => [r.count, "desc"]) // Via argument 'r' you can access properties defined in columns.
|
|
125
|
-
.toSqlString()
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Special methods to select columns.
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
SQL
|
|
132
|
-
.selectFrom(tUser)
|
|
133
|
-
.one() // 1 as one
|
|
134
|
-
.where(tUser.isMan.eq(1))
|
|
135
|
-
.toSqlString()
|
|
136
|
-
|
|
137
|
-
SQL
|
|
138
|
-
.selectFrom(tUser)
|
|
139
|
-
.allColumnsFrom(tUser)
|
|
140
|
-
.where(tUser.isMan.eq(1))
|
|
141
|
-
.toSqlString()
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Using subqueries
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// Scalar subquery is used to return single value as a column.
|
|
148
|
-
const scalarSub = SQL
|
|
149
|
-
.uses(tUser)
|
|
150
|
-
.selectFrom(tArticle)
|
|
151
|
-
.columns(MAX(tArticle.created))
|
|
152
|
-
.where(tUser.createdBy.eq(10))
|
|
153
|
-
.asScalar("subColumn");
|
|
154
|
-
|
|
155
|
-
const joinSub = SQL
|
|
156
|
-
.uses(tUser)
|
|
157
|
-
.selectFrom(tArticle)
|
|
158
|
-
.columns(tArticle.title, tArticle.createdBy)
|
|
159
|
-
.noLimit()
|
|
160
|
-
.as("joinSub")
|
|
161
|
-
|
|
162
|
-
const query = SQL
|
|
163
|
-
.selectFrom(tUser)
|
|
164
|
-
.join(joinSub, joinSub.createdBy.eq(tUser.id))
|
|
165
|
-
.columns(
|
|
166
|
-
tUser.id.as("userId"),
|
|
167
|
-
joinSub.title.as("articleTitle"),
|
|
168
|
-
joinSub.createdBy.as("articleCreatedBy"),
|
|
169
|
-
scalarSub.as("lastArticleCreated"),
|
|
170
|
-
)
|
|
171
|
-
.where(tUser.firstName.startsWith("Oliver"))
|
|
172
|
-
.toSqlString()
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Using with
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
const sub = SQL.selectFrom(c)
|
|
179
|
-
.columns(
|
|
180
|
-
tUser.id,
|
|
181
|
-
tUser.created,
|
|
182
|
-
RANK().over(f => f.partitionBy(tUser.age).orderBy(tUser.created, "desc")).as("latest"),
|
|
183
|
-
)
|
|
184
|
-
.where(tUser.keyCheck.in([1, 2, 3, 4] as tUserId[]))
|
|
185
|
-
.as("sorted")
|
|
186
|
-
|
|
187
|
-
// Queries always use alias, in this case we need to give an alias to a query defined in the WITH part.
|
|
188
|
-
const ref = SQL.createRef(sub, "tbl")
|
|
189
|
-
|
|
190
|
-
const query = SQL
|
|
191
|
-
.with(sub)
|
|
192
|
-
.selectFrom(ref)
|
|
193
|
-
.window("win", w => w.partitionBy(sub.created))
|
|
194
|
-
.columns(
|
|
195
|
-
ref.id,
|
|
196
|
-
BIN_TO_UUID(ref.id).as("uuid"),
|
|
197
|
-
UNIX_TIMESTAMP(ref.created),
|
|
198
|
-
RANK().over("win", w => w.orderBy(sub.created))
|
|
199
|
-
)
|
|
200
|
-
.where(ref.latest.eq(1))
|
|
201
|
-
.orderBy(ref.created_at, "desc")
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## Prepared select queries
|
|
205
|
-
|
|
206
|
-
If performance matters so much that you really want to skip constructing the SQL query.
|
|
207
|
-
This creates an execution function that has query cached and it only needs to set variable values.
|
|
208
|
-
It does not support any dynamic construction though, only replacing variables!
|
|
209
|
-
(It is not Mysql prepared query, it is just a string that is prepared and cached!)
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
interface Args {
|
|
213
|
-
userId: number
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// This is your implementation of sending the query to database.
|
|
217
|
-
// This library does not connect to database directly.
|
|
218
|
-
const exec = (query: string): any[] => {
|
|
219
|
-
// Send query to databse.
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const preparedQuery = SQL.prepare((args: Args) => {
|
|
223
|
-
return SQL
|
|
224
|
-
.selectFrom(tUser)
|
|
225
|
-
.columns(tUser.id, c.username)
|
|
226
|
-
.where(tUser.id.eq(args.id))
|
|
227
|
-
.noLimit()
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
const oftenCalled = async () => {
|
|
231
|
-
const query = preparedQuery({id: 10}).toSqlString();
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
# INSERT
|
|
236
|
-
|
|
237
|
-
## Using as gateway
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
MyDb.user.insert({username: "Oliver", birthYear: "1986"});
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## Using as query
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
const row = {username: "Oliver", birthYear: "1986"};
|
|
247
|
-
|
|
248
|
-
SQL.insertInto(tUser).set(row).toSqlString()
|
|
249
|
-
|
|
250
|
-
SQL.insertInto(tUser).values([row, row]).toSqlString()
|
|
251
|
-
|
|
252
|
-
SQL.insertIgnoreInto(tUser).select(SQL
|
|
253
|
-
.selectFrom(tUser)
|
|
254
|
-
.columns(tUser.username, tUser.birthYear)
|
|
255
|
-
.where(tUser.birthYear.eq(1986))
|
|
256
|
-
.as("sub")
|
|
257
|
-
).toSqlString()
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
# UPDATE
|
|
261
|
-
|
|
262
|
-
## Using as gateway
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
MyDb.user
|
|
266
|
-
.update({username: "Oliver", birthYear: 1986})
|
|
267
|
-
.where({birthYear: 1980})
|
|
268
|
-
.orderBy("id")
|
|
269
|
-
.limit(10)
|
|
270
|
-
.toSqlString()
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
## Using as query
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
SQL.update(tUser)
|
|
277
|
-
.set({
|
|
278
|
-
firstName: input.firstName
|
|
279
|
-
})
|
|
280
|
-
.where(tUser.id.eq(input.userId))
|
|
281
|
-
.toSqlString()
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
## Using as query and subquery
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
const sub = SQL
|
|
288
|
-
.selectFrom(tArticle)
|
|
289
|
-
.columns(
|
|
290
|
-
tArticle.userId,
|
|
291
|
-
MAX(tArticle.created).as("lastArticle")
|
|
292
|
-
)
|
|
293
|
-
.groupBy(tArticle.userId)
|
|
294
|
-
.as("sub")
|
|
295
|
-
|
|
296
|
-
const q3 = SQL
|
|
297
|
-
.update(tUser)
|
|
298
|
-
.join(sub, sub.userId.eq(tUser.id))
|
|
299
|
-
.set({
|
|
300
|
-
lastArticle: sub.lastArticle
|
|
301
|
-
})
|
|
302
|
-
.toSqlString()
|
|
303
|
-
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
# DELETE
|
|
307
|
-
|
|
308
|
-
## Using as gateway
|
|
309
|
-
|
|
310
|
-
```typescript
|
|
311
|
-
MyDb.user.deleteWhere({id: input.userId}).toSqlString();
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## Using as query
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
SQL
|
|
318
|
-
.deleteFrom(tUser)
|
|
319
|
-
.where(tUser.id.eq(input.userId))
|
|
320
|
-
.orderBy(tUser.id)
|
|
321
|
-
.limit(10)
|
|
322
|
-
.toSqlString()
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
# Functions
|
|
326
|
-
|
|
327
|
-
Mostly MYSQL functions, but there are some special ones.
|
|
328
|
-
|
|
329
|
-
### Comparison
|
|
330
|
-
|
|
331
|
-
* (special) VALUE(ARG) - any value you want to exist in the query.
|
|
332
|
-
* (special) NULL<type>() - If you need a dummy placeholder value in the query.
|
|
333
|
-
* (special) ONE() - Just "1 as one" to simplify certain queries.
|
|
334
|
-
* OR( bool_expr, ...)
|
|
335
|
-
* AND( bool_expr, ... )
|
|
336
|
-
* NOT( bool_expr )
|
|
337
|
-
* IF( bool_expr, expr | value, expr | value )
|
|
338
|
-
* IFNULL( expr | value, expr | value )
|
|
339
|
-
* IS_NULL( expr )
|
|
340
|
-
* NOT_NULL( expr )
|
|
341
|
-
* IN( expr | array )
|
|
342
|
-
* EQ( expr | value, expr | value )
|
|
343
|
-
* COMPARE( expr | value, operator, expr | value)
|
|
344
|
-
* LIKE( expr )
|
|
345
|
-
* EXISTS( expr )
|
|
346
|
-
* NOT_EXISTS( expr )
|
|
347
|
-
* (special) CONTAINS( expr ) - LIKE %value%
|
|
348
|
-
* (special) STARTS_WITH( expr ) - LIKE value%
|
|
349
|
-
* (special) ENDS_WITH( expr ) - LIKE %value
|
|
350
|
-
|
|
351
|
-
### String
|
|
352
|
-
|
|
353
|
-
* TRIM( expr )
|
|
354
|
-
* CONCAT( expr | value, ...)
|
|
355
|
-
* CONCAT_WS( separator, expr | value, ...)
|
|
356
|
-
* GROUP_CONCAT( f => f.all(c.id).orderBy(expr).separator(",") )
|
|
357
|
-
|
|
358
|
-
### Date
|
|
359
|
-
|
|
360
|
-
* NOW()
|
|
361
|
-
* CUR_DATE()
|
|
362
|
-
* YEAR( expr )
|
|
363
|
-
* UNIX_TIMESTAMP( expr )
|
|
364
|
-
* DATE( expr )
|
|
365
|
-
* DATE_TIME( expr )
|
|
366
|
-
* DATE_FORMAT( expr )
|
|
367
|
-
* DATEDIFF( expr1 | date, expr2 | date )
|
|
368
|
-
* DATE_ADD( expr, amount, unit )
|
|
369
|
-
* DATE_SUB( expr, amount, unit )
|
|
370
|
-
|
|
371
|
-
### Numbers
|
|
372
|
-
|
|
373
|
-
* MATH("? + ?", [expr1, expr2, ...]) // Can only contain numbers and math operators. No letters of any kind.
|
|
374
|
-
* ABS( expr )
|
|
375
|
-
* CEIL( expr )
|
|
376
|
-
* FLOOR( expr )
|
|
377
|
-
* ROUND( expr )
|
|
378
|
-
* SIGN( expr )
|
|
379
|
-
* SQRT( expr )
|
|
380
|
-
|
|
381
|
-
### Aggregate
|
|
382
|
-
|
|
383
|
-
Aggregate functions have an additional method .over( f => f.partitionBy( expr, ... ).orderBy( expr, "asc" | "desc", ... ))
|
|
384
|
-
|
|
385
|
-
* MIN( expr )
|
|
386
|
-
* MAX( expr )
|
|
387
|
-
* SUM( expr )
|
|
388
|
-
* COUNT( expr )
|
|
389
|
-
* RANK()
|
|
390
|
-
* ROW_NUMBER()
|
|
391
|
-
* LAG( expr )
|
|
392
|
-
* LEAD( expr )
|
|
393
|
-
* FIRST_VALUE( expr )
|
|
394
|
-
* LAST_VALUE( expr )
|
|
395
|
-
* NTH_VALUE( expr )
|
|
396
|
-
|
|
397
|
-
### Misc
|
|
398
|
-
|
|
399
|
-
* BIN_TO_UUID( expr )
|
|
400
|
-
|
|
401
|
-
## Creating your own functions
|
|
402
|
-
|
|
403
|
-
1. Make a pull request to get new MySQL compatible functions to get merged
|
|
404
|
-
2. If you don't really want to....
|
|
405
|
-
|
|
406
|
-
Most functions are single liners. Complexity is in handling the types, not really the execution part.
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
// Functions track 3 types:
|
|
410
|
-
// TableRef - what tables have been used "user as a" | "article as a"
|
|
411
|
-
// Name - name of the field used. (will be used in 'field as $Name')
|
|
412
|
-
// Type - type of the field.
|
|
413
|
-
export function DATE<Name, TableRef>(field: Expr<TableRef, Name, vDate | vDateTime>): Expr<TableRef, Name, vDate> {
|
|
414
|
-
return SqlExpression.create("DATE(" + field.expression + ")")
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Comparison function should have SQL_BOOL (1 | 0) as type.
|
|
418
|
-
export function IS_NULL<TableRef, Name, Type extends string | number>(col: Expr<TableRef, Name, Type>): Expr<TableRef, Name, SQL_BOOL> {
|
|
419
|
-
return SqlExpression.create(col.expression + " IS NULL")
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
# Getting started
|
|
424
|
-
|
|
425
|
-
To use the library these kinds of structure need to be created (read: generated).
|
|
426
|
-
Mysql schema parser and generator for these particular cases are added to this library.
|
|
427
|
-
|
|
428
|
-
```typescript
|
|
429
|
-
const data = runQuery(MysqlTableStructureParser.getSchemaRowsQuery("my_database_name"))
|
|
430
|
-
const parsed = MysqlTableStructureParser.parse(data);
|
|
431
|
-
CodeGenerator.generateAllToSingleFile(parsed, "./out/dir/");
|
|
432
|
-
// or
|
|
433
|
-
CodeGenerator.generateCustomTypesSupportedFiles(parsed, "./out/dir/");
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
## Simple approach
|
|
437
|
-
|
|
438
|
-
Just generate and keep regenerating everything. Positive is that this approach is very simple
|
|
439
|
-
Negative is that you can't use custom types, only library supported ones: number | string | vDate | vDateTime
|
|
440
|
-
|
|
441
|
-
```typescript
|
|
442
|
-
export class MyDb {
|
|
443
|
-
|
|
444
|
-
public static user = new MysqlTable<"user", UserRow, UserRowForInsert>("user", {
|
|
445
|
-
id: {type: ColumnDataType.INT},
|
|
446
|
-
username: {type: ColumnDataType.VARCHAR},
|
|
447
|
-
})
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// This is precise structure of the row, ideal for SELECT queries.
|
|
451
|
-
export interface UserRow {
|
|
452
|
-
id: number;
|
|
453
|
-
created_at: vDateTime,
|
|
454
|
-
username: string;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// This interface is only used internally to understand database structure better. (for example id column is not something you usually set for insert calls).
|
|
458
|
-
export interface UserRowForInsert {
|
|
459
|
-
username: unknown;
|
|
460
|
-
}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
Convenience access constants
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
const tUser = MyDb.user.as("user");
|
|
467
|
-
const tArticle = MyDb.article.as("article");
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
## 'I want custom types' approach (recommended):
|
|
471
|
-
|
|
472
|
-
1) Generate Row interfaces. We generate these files only once!
|
|
473
|
-
Later errors are discovered during compile time when other files are regenerated
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
|
|
477
|
-
export type tUserId = number & { tUserId: true };
|
|
478
|
-
|
|
479
|
-
export interface UserRow {
|
|
480
|
-
id: tUserId;
|
|
481
|
-
created_at: vDateTime,
|
|
482
|
-
username: string;
|
|
483
|
-
}
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
2) Generate Database structure file and "xxxRowForEdit" interfaces into a single file.
|
|
487
|
-
We keep regenerating these files in case database structure changes.
|
|
488
|
-
|
|
489
|
-
```typescript
|
|
490
|
-
export class MyDb {
|
|
491
|
-
|
|
492
|
-
public static user = new MysqlTable<"user", UserRow, UserRowForInsert>("user", {
|
|
493
|
-
id: {type: ColumnDataType.INT},
|
|
494
|
-
username: {type: ColumnDataType.VARCHAR},
|
|
495
|
-
})
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
export interface UserRowForInsert {
|
|
499
|
-
username: unknown;
|
|
500
|
-
}
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
|
|
6
|
+
# Typescript Mysql query builder
|
|
7
|
+
|
|
8
|
+
Powerful SQL query builder with lots of compile time errors to hint what is wrong. Some examples include:
|
|
9
|
+
|
|
10
|
+
* Fully type checked, it is very hard to use wrong property names or types.
|
|
11
|
+
* Fully checks that all used tables are actually part of the query.
|
|
12
|
+
* Protects against duplicate alias usage.
|
|
13
|
+
|
|
14
|
+
In short - no more SQL syntax errors even when testing, only logic errors are left for the developer to figure out :)
|
|
15
|
+
|
|
16
|
+
In addition
|
|
17
|
+
|
|
18
|
+
* Good support for refactoring. If you happen to rename a field, you can rename it in the whole codebase with a single go.
|
|
19
|
+
* Very similar to actual SQL including MySQL functions like DATE(c.created), NOW(), and so on.
|
|
20
|
+
* Heavily optimized for readability
|
|
21
|
+
* Free to use any mysql library to connect to database. This library is only for SQL query building.
|
|
22
|
+
|
|
23
|
+
# Basic example
|
|
24
|
+
|
|
25
|
+
This library does not execute queries, it only prepares SQL statements.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Your custom query execution function. (libraries: mysql or mysql2 usually)
|
|
29
|
+
function execute<Result>(query: SqlQuery<Result>): Promise<Result[]> {
|
|
30
|
+
const sqlString = query.toSqlString();
|
|
31
|
+
// Execute your query...
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const rows = await execute(SQL
|
|
35
|
+
.selectFrom(tUser)
|
|
36
|
+
.columns(
|
|
37
|
+
tUser.id,
|
|
38
|
+
tUser.firstName,
|
|
39
|
+
COUNT(tUser.firstName).as("count"),
|
|
40
|
+
DATE(MAX(tUser.created)).as("lastCreatedDate")
|
|
41
|
+
)
|
|
42
|
+
.where(tUser.isActive.eq(1))
|
|
43
|
+
.groupBy(tUser.firstName)
|
|
44
|
+
.having(COUNT(tUser.firstName).eq(3))
|
|
45
|
+
.orderBy(tUser.firstName)
|
|
46
|
+
.limit(20))
|
|
47
|
+
|
|
48
|
+
// Resulting type is
|
|
49
|
+
const res: {
|
|
50
|
+
id: number,
|
|
51
|
+
firstName: string,
|
|
52
|
+
count: number,
|
|
53
|
+
lastCreatedDate: string
|
|
54
|
+
} = undefined
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
# Error checking
|
|
59
|
+
|
|
60
|
+
As typescript errors can be sometimes cryptic for complex cases, there are lots of simplified errors used by this library to make understanding where the issue is easier.
|
|
61
|
+
|
|
62
|
+
----
|
|
63
|
+
Can't add columns with same name multiple times to the query
|
|
64
|
+
|
|
65
|
+
1. 
|
|
66
|
+
|
|
67
|
+
2. 
|
|
68
|
+
|
|
69
|
+
----
|
|
70
|
+
If table is not added to the query via from, join etc...
|
|
71
|
+
|
|
72
|
+
1. 
|
|
73
|
+
|
|
74
|
+
2. 
|
|
75
|
+
|
|
76
|
+
----
|
|
77
|
+
Alias is already used, can't have duplicate aliases.
|
|
78
|
+
|
|
79
|
+
1. 
|
|
80
|
+
|
|
81
|
+
----
|
|
82
|
+
Table joined is not defined in the with part of the query (WITH ... SELECT ...)
|
|
83
|
+
|
|
84
|
+
1. 
|
|
85
|
+
|
|
86
|
+
----
|
|
87
|
+
|
|
88
|
+
Union erros, when added table has more or less fields than expected.
|
|
89
|
+
|
|
90
|
+
1. 
|
|
91
|
+
|
|
92
|
+
2. 
|
|
93
|
+
|
|
94
|
+
----
|
|
95
|
+
|
|
96
|
+
# SELECT
|
|
97
|
+
|
|
98
|
+
## Using as a gateway
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
MyDb.user.select("username")
|
|
102
|
+
.where({id: 10})
|
|
103
|
+
.toSqlString();
|
|
104
|
+
|
|
105
|
+
MyDb.user.select("id", "username")
|
|
106
|
+
.where({birthYear: 1986})
|
|
107
|
+
.orderBy("username", "asc", "id")
|
|
108
|
+
.limit([10, 10])
|
|
109
|
+
.toSqlString();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Uses "result columns" in HAVING and ORDER BY clauses.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
SQL
|
|
116
|
+
.selectFrom(tUser)
|
|
117
|
+
.columns(
|
|
118
|
+
tUser.firstName.as("username2"),
|
|
119
|
+
COUNT(tUser.firstName).as("count")
|
|
120
|
+
)
|
|
121
|
+
.where(tUser.isMan.eq(1))
|
|
122
|
+
.groupBy(tUser.firstName)
|
|
123
|
+
.havingF((r) => [r.count.compare(">", 1)]) // Via argument 'r' you can access properties defined in columns.
|
|
124
|
+
.orderByF((r) => [r.count, "desc"]) // Via argument 'r' you can access properties defined in columns.
|
|
125
|
+
.toSqlString()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Special methods to select columns.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
SQL
|
|
132
|
+
.selectFrom(tUser)
|
|
133
|
+
.one() // 1 as one
|
|
134
|
+
.where(tUser.isMan.eq(1))
|
|
135
|
+
.toSqlString()
|
|
136
|
+
|
|
137
|
+
SQL
|
|
138
|
+
.selectFrom(tUser)
|
|
139
|
+
.allColumnsFrom(tUser)
|
|
140
|
+
.where(tUser.isMan.eq(1))
|
|
141
|
+
.toSqlString()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Using subqueries
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Scalar subquery is used to return single value as a column.
|
|
148
|
+
const scalarSub = SQL
|
|
149
|
+
.uses(tUser)
|
|
150
|
+
.selectFrom(tArticle)
|
|
151
|
+
.columns(MAX(tArticle.created))
|
|
152
|
+
.where(tUser.createdBy.eq(10))
|
|
153
|
+
.asScalar("subColumn");
|
|
154
|
+
|
|
155
|
+
const joinSub = SQL
|
|
156
|
+
.uses(tUser)
|
|
157
|
+
.selectFrom(tArticle)
|
|
158
|
+
.columns(tArticle.title, tArticle.createdBy)
|
|
159
|
+
.noLimit()
|
|
160
|
+
.as("joinSub")
|
|
161
|
+
|
|
162
|
+
const query = SQL
|
|
163
|
+
.selectFrom(tUser)
|
|
164
|
+
.join(joinSub, joinSub.createdBy.eq(tUser.id))
|
|
165
|
+
.columns(
|
|
166
|
+
tUser.id.as("userId"),
|
|
167
|
+
joinSub.title.as("articleTitle"),
|
|
168
|
+
joinSub.createdBy.as("articleCreatedBy"),
|
|
169
|
+
scalarSub.as("lastArticleCreated"),
|
|
170
|
+
)
|
|
171
|
+
.where(tUser.firstName.startsWith("Oliver"))
|
|
172
|
+
.toSqlString()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Using with
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const sub = SQL.selectFrom(c)
|
|
179
|
+
.columns(
|
|
180
|
+
tUser.id,
|
|
181
|
+
tUser.created,
|
|
182
|
+
RANK().over(f => f.partitionBy(tUser.age).orderBy(tUser.created, "desc")).as("latest"),
|
|
183
|
+
)
|
|
184
|
+
.where(tUser.keyCheck.in([1, 2, 3, 4] as tUserId[]))
|
|
185
|
+
.as("sorted")
|
|
186
|
+
|
|
187
|
+
// Queries always use alias, in this case we need to give an alias to a query defined in the WITH part.
|
|
188
|
+
const ref = SQL.createRef(sub, "tbl")
|
|
189
|
+
|
|
190
|
+
const query = SQL
|
|
191
|
+
.with(sub)
|
|
192
|
+
.selectFrom(ref)
|
|
193
|
+
.window("win", w => w.partitionBy(sub.created))
|
|
194
|
+
.columns(
|
|
195
|
+
ref.id,
|
|
196
|
+
BIN_TO_UUID(ref.id).as("uuid"),
|
|
197
|
+
UNIX_TIMESTAMP(ref.created),
|
|
198
|
+
RANK().over("win", w => w.orderBy(sub.created))
|
|
199
|
+
)
|
|
200
|
+
.where(ref.latest.eq(1))
|
|
201
|
+
.orderBy(ref.created_at, "desc")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Prepared select queries
|
|
205
|
+
|
|
206
|
+
If performance matters so much that you really want to skip constructing the SQL query.
|
|
207
|
+
This creates an execution function that has query cached and it only needs to set variable values.
|
|
208
|
+
It does not support any dynamic construction though, only replacing variables!
|
|
209
|
+
(It is not Mysql prepared query, it is just a string that is prepared and cached!)
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
interface Args {
|
|
213
|
+
userId: number
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// This is your implementation of sending the query to database.
|
|
217
|
+
// This library does not connect to database directly.
|
|
218
|
+
const exec = (query: string): any[] => {
|
|
219
|
+
// Send query to databse.
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const preparedQuery = SQL.prepare((args: Args) => {
|
|
223
|
+
return SQL
|
|
224
|
+
.selectFrom(tUser)
|
|
225
|
+
.columns(tUser.id, c.username)
|
|
226
|
+
.where(tUser.id.eq(args.id))
|
|
227
|
+
.noLimit()
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const oftenCalled = async () => {
|
|
231
|
+
const query = preparedQuery({id: 10}).toSqlString();
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
# INSERT
|
|
236
|
+
|
|
237
|
+
## Using as gateway
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
MyDb.user.insert({username: "Oliver", birthYear: "1986"});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Using as query
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
const row = {username: "Oliver", birthYear: "1986"};
|
|
247
|
+
|
|
248
|
+
SQL.insertInto(tUser).set(row).toSqlString()
|
|
249
|
+
|
|
250
|
+
SQL.insertInto(tUser).values([row, row]).toSqlString()
|
|
251
|
+
|
|
252
|
+
SQL.insertIgnoreInto(tUser).select(SQL
|
|
253
|
+
.selectFrom(tUser)
|
|
254
|
+
.columns(tUser.username, tUser.birthYear)
|
|
255
|
+
.where(tUser.birthYear.eq(1986))
|
|
256
|
+
.as("sub")
|
|
257
|
+
).toSqlString()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
# UPDATE
|
|
261
|
+
|
|
262
|
+
## Using as gateway
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
MyDb.user
|
|
266
|
+
.update({username: "Oliver", birthYear: 1986})
|
|
267
|
+
.where({birthYear: 1980})
|
|
268
|
+
.orderBy("id")
|
|
269
|
+
.limit(10)
|
|
270
|
+
.toSqlString()
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Using as query
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
SQL.update(tUser)
|
|
277
|
+
.set({
|
|
278
|
+
firstName: input.firstName
|
|
279
|
+
})
|
|
280
|
+
.where(tUser.id.eq(input.userId))
|
|
281
|
+
.toSqlString()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Using as query and subquery
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const sub = SQL
|
|
288
|
+
.selectFrom(tArticle)
|
|
289
|
+
.columns(
|
|
290
|
+
tArticle.userId,
|
|
291
|
+
MAX(tArticle.created).as("lastArticle")
|
|
292
|
+
)
|
|
293
|
+
.groupBy(tArticle.userId)
|
|
294
|
+
.as("sub")
|
|
295
|
+
|
|
296
|
+
const q3 = SQL
|
|
297
|
+
.update(tUser)
|
|
298
|
+
.join(sub, sub.userId.eq(tUser.id))
|
|
299
|
+
.set({
|
|
300
|
+
lastArticle: sub.lastArticle
|
|
301
|
+
})
|
|
302
|
+
.toSqlString()
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
# DELETE
|
|
307
|
+
|
|
308
|
+
## Using as gateway
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
MyDb.user.deleteWhere({id: input.userId}).toSqlString();
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Using as query
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
SQL
|
|
318
|
+
.deleteFrom(tUser)
|
|
319
|
+
.where(tUser.id.eq(input.userId))
|
|
320
|
+
.orderBy(tUser.id)
|
|
321
|
+
.limit(10)
|
|
322
|
+
.toSqlString()
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
# Functions
|
|
326
|
+
|
|
327
|
+
Mostly MYSQL functions, but there are some special ones.
|
|
328
|
+
|
|
329
|
+
### Comparison
|
|
330
|
+
|
|
331
|
+
* (special) VALUE(ARG) - any value you want to exist in the query.
|
|
332
|
+
* (special) NULL<type>() - If you need a dummy placeholder value in the query.
|
|
333
|
+
* (special) ONE() - Just "1 as one" to simplify certain queries.
|
|
334
|
+
* OR( bool_expr, ...)
|
|
335
|
+
* AND( bool_expr, ... )
|
|
336
|
+
* NOT( bool_expr )
|
|
337
|
+
* IF( bool_expr, expr | value, expr | value )
|
|
338
|
+
* IFNULL( expr | value, expr | value )
|
|
339
|
+
* IS_NULL( expr )
|
|
340
|
+
* NOT_NULL( expr )
|
|
341
|
+
* IN( expr | array )
|
|
342
|
+
* EQ( expr | value, expr | value )
|
|
343
|
+
* COMPARE( expr | value, operator, expr | value)
|
|
344
|
+
* LIKE( expr )
|
|
345
|
+
* EXISTS( expr )
|
|
346
|
+
* NOT_EXISTS( expr )
|
|
347
|
+
* (special) CONTAINS( expr ) - LIKE %value%
|
|
348
|
+
* (special) STARTS_WITH( expr ) - LIKE value%
|
|
349
|
+
* (special) ENDS_WITH( expr ) - LIKE %value
|
|
350
|
+
|
|
351
|
+
### String
|
|
352
|
+
|
|
353
|
+
* TRIM( expr )
|
|
354
|
+
* CONCAT( expr | value, ...)
|
|
355
|
+
* CONCAT_WS( separator, expr | value, ...)
|
|
356
|
+
* GROUP_CONCAT( f => f.all(c.id).orderBy(expr).separator(",") )
|
|
357
|
+
|
|
358
|
+
### Date
|
|
359
|
+
|
|
360
|
+
* NOW()
|
|
361
|
+
* CUR_DATE()
|
|
362
|
+
* YEAR( expr )
|
|
363
|
+
* UNIX_TIMESTAMP( expr )
|
|
364
|
+
* DATE( expr )
|
|
365
|
+
* DATE_TIME( expr )
|
|
366
|
+
* DATE_FORMAT( expr )
|
|
367
|
+
* DATEDIFF( expr1 | date, expr2 | date )
|
|
368
|
+
* DATE_ADD( expr, amount, unit )
|
|
369
|
+
* DATE_SUB( expr, amount, unit )
|
|
370
|
+
|
|
371
|
+
### Numbers
|
|
372
|
+
|
|
373
|
+
* MATH("? + ?", [expr1, expr2, ...]) // Can only contain numbers and math operators. No letters of any kind.
|
|
374
|
+
* ABS( expr )
|
|
375
|
+
* CEIL( expr )
|
|
376
|
+
* FLOOR( expr )
|
|
377
|
+
* ROUND( expr )
|
|
378
|
+
* SIGN( expr )
|
|
379
|
+
* SQRT( expr )
|
|
380
|
+
|
|
381
|
+
### Aggregate
|
|
382
|
+
|
|
383
|
+
Aggregate functions have an additional method .over( f => f.partitionBy( expr, ... ).orderBy( expr, "asc" | "desc", ... ))
|
|
384
|
+
|
|
385
|
+
* MIN( expr )
|
|
386
|
+
* MAX( expr )
|
|
387
|
+
* SUM( expr )
|
|
388
|
+
* COUNT( expr )
|
|
389
|
+
* RANK()
|
|
390
|
+
* ROW_NUMBER()
|
|
391
|
+
* LAG( expr )
|
|
392
|
+
* LEAD( expr )
|
|
393
|
+
* FIRST_VALUE( expr )
|
|
394
|
+
* LAST_VALUE( expr )
|
|
395
|
+
* NTH_VALUE( expr )
|
|
396
|
+
|
|
397
|
+
### Misc
|
|
398
|
+
|
|
399
|
+
* BIN_TO_UUID( expr )
|
|
400
|
+
|
|
401
|
+
## Creating your own functions
|
|
402
|
+
|
|
403
|
+
1. Make a pull request to get new MySQL compatible functions to get merged
|
|
404
|
+
2. If you don't really want to....
|
|
405
|
+
|
|
406
|
+
Most functions are single liners. Complexity is in handling the types, not really the execution part.
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// Functions track 3 types:
|
|
410
|
+
// TableRef - what tables have been used "user as a" | "article as a"
|
|
411
|
+
// Name - name of the field used. (will be used in 'field as $Name')
|
|
412
|
+
// Type - type of the field.
|
|
413
|
+
export function DATE<Name, TableRef>(field: Expr<TableRef, Name, vDate | vDateTime>): Expr<TableRef, Name, vDate> {
|
|
414
|
+
return SqlExpression.create("DATE(" + field.expression + ")")
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Comparison function should have SQL_BOOL (1 | 0) as type.
|
|
418
|
+
export function IS_NULL<TableRef, Name, Type extends string | number>(col: Expr<TableRef, Name, Type>): Expr<TableRef, Name, SQL_BOOL> {
|
|
419
|
+
return SqlExpression.create(col.expression + " IS NULL")
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
# Getting started
|
|
424
|
+
|
|
425
|
+
To use the library these kinds of structure need to be created (read: generated).
|
|
426
|
+
Mysql schema parser and generator for these particular cases are added to this library.
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
const data = runQuery(MysqlTableStructureParser.getSchemaRowsQuery("my_database_name"))
|
|
430
|
+
const parsed = MysqlTableStructureParser.parse(data);
|
|
431
|
+
CodeGenerator.generateAllToSingleFile(parsed, "./out/dir/");
|
|
432
|
+
// or
|
|
433
|
+
CodeGenerator.generateCustomTypesSupportedFiles(parsed, "./out/dir/");
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Simple approach
|
|
437
|
+
|
|
438
|
+
Just generate and keep regenerating everything. Positive is that this approach is very simple
|
|
439
|
+
Negative is that you can't use custom types, only library supported ones: number | string | vDate | vDateTime
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
export class MyDb {
|
|
443
|
+
|
|
444
|
+
public static user = new MysqlTable<"user", UserRow, UserRowForInsert>("user", {
|
|
445
|
+
id: {type: ColumnDataType.INT},
|
|
446
|
+
username: {type: ColumnDataType.VARCHAR},
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// This is precise structure of the row, ideal for SELECT queries.
|
|
451
|
+
export interface UserRow {
|
|
452
|
+
id: number;
|
|
453
|
+
created_at: vDateTime,
|
|
454
|
+
username: string;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// This interface is only used internally to understand database structure better. (for example id column is not something you usually set for insert calls).
|
|
458
|
+
export interface UserRowForInsert {
|
|
459
|
+
username: unknown;
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Convenience access constants
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
const tUser = MyDb.user.as("user");
|
|
467
|
+
const tArticle = MyDb.article.as("article");
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## 'I want custom types' approach (recommended):
|
|
471
|
+
|
|
472
|
+
1) Generate Row interfaces. We generate these files only once!
|
|
473
|
+
Later errors are discovered during compile time when other files are regenerated
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
|
|
477
|
+
export type tUserId = number & { tUserId: true };
|
|
478
|
+
|
|
479
|
+
export interface UserRow {
|
|
480
|
+
id: tUserId;
|
|
481
|
+
created_at: vDateTime,
|
|
482
|
+
username: string;
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
2) Generate Database structure file and "xxxRowForEdit" interfaces into a single file.
|
|
487
|
+
We keep regenerating these files in case database structure changes.
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
export class MyDb {
|
|
491
|
+
|
|
492
|
+
public static user = new MysqlTable<"user", UserRow, UserRowForInsert>("user", {
|
|
493
|
+
id: {type: ColumnDataType.INT},
|
|
494
|
+
username: {type: ColumnDataType.VARCHAR},
|
|
495
|
+
})
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export interface UserRowForInsert {
|
|
499
|
+
username: unknown;
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
|