@grest-ts/sql 0.0.5 → 0.0.7

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