@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/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. ![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
-
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
+