prisma-ts-select 0.0.34 → 0.1.2

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1242 -315
  3. package/assets/groupBy.gif +0 -0
  4. package/assets/joinUnsafeIgnoreType.gif +0 -0
  5. package/assets/joinUnsafeTypeEnforced.gif +0 -0
  6. package/assets/typesafe-join.gif +0 -0
  7. package/assets/typesafe-join.png +0 -0
  8. package/assets/whereNotNull.gif +0 -0
  9. package/assets/whereisNull.gif +0 -0
  10. package/dist/bin.cjs +1 -1
  11. package/dist/bin.js +1 -1
  12. package/dist/chunk-47KZVQLD.js +283 -0
  13. package/dist/chunk-54D2J5AR.cjs +291 -0
  14. package/dist/extend/dialects/index.d.ts +13 -0
  15. package/dist/extend/dialects/index.js +186 -0
  16. package/dist/extend/dialects/mysql-v6.d.ts +100 -0
  17. package/dist/extend/dialects/mysql-v6.js +152 -0
  18. package/dist/extend/dialects/mysql-v7.d.ts +6 -0
  19. package/dist/extend/dialects/mysql-v7.js +138 -0
  20. package/dist/extend/dialects/mysql.d.ts +90 -0
  21. package/dist/extend/dialects/mysql.js +156 -0
  22. package/dist/extend/dialects/postgresql-v6.d.ts +97 -0
  23. package/dist/extend/dialects/postgresql-v6.js +136 -0
  24. package/dist/extend/dialects/postgresql-v7.d.ts +97 -0
  25. package/dist/extend/dialects/postgresql-v7.js +136 -0
  26. package/dist/extend/dialects/postgresql.d.ts +89 -0
  27. package/dist/extend/dialects/postgresql.js +147 -0
  28. package/dist/extend/dialects/shared.d.ts +6 -0
  29. package/dist/extend/dialects/shared.js +5 -0
  30. package/dist/extend/dialects/sqlite.d.ts +63 -0
  31. package/dist/extend/dialects/sqlite.js +138 -0
  32. package/dist/extend/dialects/types.d.ts +12 -0
  33. package/dist/extend/dialects/types.js +4 -0
  34. package/dist/extend/extend.d.ts +286 -43
  35. package/dist/extend/extend.js +735 -163
  36. package/dist/extend/sql-expr-BaKWzJ-r.d.ts +10 -0
  37. package/dist/extend/types-D84lxYVc.d.ts +5 -0
  38. package/dist/generator.cjs +1 -1
  39. package/dist/generator.js +1 -1
  40. package/package.json +46 -42
  41. package/built/extend.cjs +0 -680
  42. package/built/extend.d.cts +0 -520
  43. package/built/extend.d.ts +0 -520
  44. package/built/extend.js +0 -678
  45. package/dist/chunk-TBO3MX7Q.cjs +0 -195
  46. package/dist/chunk-X3N5N5KQ.js +0 -187
  47. package/dist/extend/extend.cjs +0 -357
  48. package/dist/extend/extend.d.cts +0 -264
package/README.md CHANGED
@@ -4,10 +4,21 @@
4
4
  ![build](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/CI.yml/badge.svg)
5
5
  ![license](https://img.shields.io/github/license/adrianbrowning/prisma-ts-select)
6
6
 
7
+ ## Test Matrix
8
+
9
+ | | SQLite | MySQL | PostgreSQL |
10
+ |----------------|--------|-------|------------|
11
+ | **Prisma v6** | ![v6-sqlite](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/test-v6-sqlite.yml/badge.svg) | ![v6-mysql](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/test-v6-mysql.yml/badge.svg) | ![v6-postgresql](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/test-v6-pg.yml/badge.svg) |
12
+ | **Prisma v7** | ![v7-sqlite](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/test-v7-sqlite.yml/badge.svg) | ![v7-mysql](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/test-v7-mysql.yml/badge.svg) | ![v7-postgresql](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/test-v7-pg.yml/badge.svg) |
13
+
7
14
  <!-- toc -->
8
15
 
9
16
  * [Summary](#summary)
10
17
  * [Installation](#installation)
18
+ * [Setup](#setup)
19
+ + [Schema](#schema)
20
+ + [Client — Prisma v6](#client--prisma-v6)
21
+ + [Client — Prisma v7](#client--prisma-v7)
11
22
  * [Supported DBs](#supported-dbs)
12
23
  * [Usage](#usage)
13
24
  + [Generator](#generator)
@@ -18,6 +29,13 @@
18
29
  * [SQL](#sql)
19
30
  - [Example - Inline Alias Syntax](#example---inline-alias-syntax)
20
31
  * [SQL](#sql-1)
32
+ + [`.$with`](#with)
33
+ - [Example — CTE as joined table](#example--cte-as-joined-table)
34
+ * [SQL](#sql-2)
35
+ - [Example — CTE as base table](#example--cte-as-base-table)
36
+ * [SQL](#sql-3)
37
+ - [Example — Multiple CTEs](#example--multiple-ctes)
38
+ * [SQL](#sql-4)
21
39
  + [Table Aliases](#table-aliases)
22
40
  - [Table Alias Syntax Options](#table-alias-syntax-options)
23
41
  - [Basic Table Alias](#basic-table-alias)
@@ -32,6 +50,8 @@
32
50
  * [SQL](#sql-5)
33
51
  * [SQL](#sql-6)
34
52
  + [Joins](#joins)
53
+ - [Dialect Support](#dialect-support)
54
+ - [Nullability Semantics](#nullability-semantics)
35
55
  - [`.join`](#join)
36
56
  * [Example](#example-1)
37
57
  * [SQL](#sql-7)
@@ -44,6 +64,15 @@
44
64
  * [Example](#example-3)
45
65
  * [SQL](#sql-9)
46
66
  * [Parameters](#parameters-2)
67
+ - [`.innerJoin`](#innerjoin)
68
+ - [`.leftJoin`](#leftjoin)
69
+ - [`.crossJoin`](#crossjoin)
70
+ - [`.rightJoin`](#rightjoin) *(MySQL / PostgreSQL)*
71
+ - [`.fullJoin`](#fulljoin) *(PostgreSQL only)*
72
+ - [`.manyToManyJoin`](#manytomanyjoin)
73
+ * [Example](#example-4)
74
+ * [SQL](#sql-10)
75
+ * [Parameters](#parameters-3)
47
76
  + [Where](#where)
48
77
  - [`.where`](#where)
49
78
  * [TypeSyntax](#typesyntax)
@@ -75,6 +104,7 @@
75
104
  * [SQL](#sql-15)
76
105
  - [Example - Join table](#example---join-table)
77
106
  * [SQL](#sql-16)
107
+ - [`.selectAllOmit`](#selectallomit)
78
108
  - [`.select`](#select)
79
109
  - [Example - `*`](#example---)
80
110
  * [SQL](#sql-17)
@@ -102,15 +132,14 @@
102
132
  + [Offset](#offset)
103
133
  - [Example](#example-12)
104
134
  * [SQL](#sql-27)
135
+ * [Select Functions](#select-functions)
136
+ + [Shared (all dialects)](#shared-all-dialects)
137
+ + [MySQL-specific](#mysql-specific)
138
+ + [PostgreSQL-specific](#postgresql-specific)
139
+ + [SQLite-specific](#sqlite-specific)
105
140
  * [Future updates](#future-updates)
106
141
  * [Changelog / Versioning](#changelog--versioning)
107
142
  * [License](#license)
108
- - [prisma-ts-select](#prisma-ts-select)
109
- * [Install](#install)
110
- * [Setup](#setup)
111
- + [Extract](#extract)
112
- * [Usage](#usage-1)
113
-
114
143
  <!-- tocstop -->
115
144
 
116
145
  ## Summary
@@ -119,13 +148,9 @@
119
148
  It simplifies the selection of fields in Prisma queries, ensuring type safety and reducing boilerplate when working with nested fields.
120
149
  Ideal for developers seeking an efficient, type-safe way to select data with Prisma in TypeScript.
121
150
 
122
- [!NOTE]
123
- > This has been built mostly around MySQL. Most methods should work across the board.<br/>
124
- > Known exceptions include:
125
- > - HAVING
126
- > - SQLite
127
- > - Requires you to have either an aggregate function in the `SELECT` or make use of `GROUP BY`
128
- > - Can only use columns that are specified in `SELECT` or `GROUP BY`
151
+ > [!NOTE]
152
+ > Fully tested on SQLite, MySQL, and PostgreSQL. Known exceptions:
153
+ > - HAVING on SQLite requires either an aggregate function in `SELECT` or a `GROUP BY` clause, and can only reference columns from `SELECT` or `GROUP BY`.
129
154
 
130
155
 
131
156
  ## Installation
@@ -137,15 +162,108 @@ npm install prisma-ts-select
137
162
  pnpm add prisma-ts-select
138
163
  ```
139
164
 
165
+ ## Setup
166
+
167
+ ### Schema
168
+
169
+ Add both generators to `prisma/schema.prisma`. The `output` path is relative to the schema file.
170
+
171
+ ```prisma
172
+ generator prisma-ts-select {
173
+ provider = "prisma-ts-select"
174
+ output = "../generated/prisma-ts-select"
175
+ }
176
+ ```
177
+
178
+ The client generator differs between Prisma versions:
179
+
180
+ **Prisma v6**
181
+ ```prisma
182
+ generator client {
183
+ provider = "prisma-client-js"
184
+ output = "../generated/prisma"
185
+ }
186
+ ```
187
+
188
+ **Prisma v7**
189
+ ```prisma
190
+ generator client {
191
+ provider = "prisma-client"
192
+ output = "../generated/prisma"
193
+ }
194
+ ```
195
+
196
+ Then generate:
197
+
198
+ ```shell
199
+ pnpm exec prisma generate
200
+ ```
201
+
202
+ ### Client — Prisma v6
203
+
204
+ No driver adapter needed. Import from the generated `extend-v6.js`:
205
+
206
+ ```typescript
207
+ import { PrismaClient } from './generated/prisma/index.js'
208
+ import tsSelectExtend from './generated/prisma-ts-select/extend-v6.js'
209
+
210
+ export const prisma = new PrismaClient().$extends(tsSelectExtend)
211
+ ```
212
+
213
+ ### Client — Prisma v7
214
+
215
+ Prisma v7 requires a driver adapter. Install the adapter for your database and import from the generated `extend-v7.js`:
216
+
217
+ **SQLite**
218
+ ```shell
219
+ pnpm add @prisma/adapter-better-sqlite3
220
+ ```
221
+ ```typescript
222
+ import { PrismaClient } from './generated/prisma/client.ts'
223
+ import tsSelectExtend from './generated/prisma-ts-select/extend-v7.js'
224
+ import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
225
+
226
+ const adapter = new PrismaBetterSqlite3({ url: process.env.DATABASE_URL })
227
+ export const prisma = new PrismaClient({ adapter }).$extends(tsSelectExtend)
228
+ ```
229
+
230
+ **MySQL**
231
+ ```shell
232
+ pnpm add @prisma/adapter-mariadb
233
+ ```
234
+ ```typescript
235
+ import { PrismaClient } from './generated/prisma/client.ts'
236
+ import tsSelectExtend from './generated/prisma-ts-select/extend-v7.js'
237
+ import { PrismaMariaDb } from '@prisma/adapter-mariadb'
238
+
239
+ const url = new URL(process.env.DATABASE_URL!)
240
+ const adapter = new PrismaMariaDb({
241
+ host: url.hostname, port: +url.port,
242
+ user: url.username, password: url.password,
243
+ database: url.pathname.slice(1),
244
+ })
245
+ export const prisma = new PrismaClient({ adapter }).$extends(tsSelectExtend)
246
+ ```
247
+
248
+ **PostgreSQL**
249
+ ```shell
250
+ pnpm add @prisma/adapter-pg
251
+ ```
252
+ ```typescript
253
+ import { PrismaClient } from './generated/prisma/client.ts'
254
+ import tsSelectExtend from './generated/prisma-ts-select/extend-v7.js'
255
+ import { PrismaPg } from '@prisma/adapter-pg'
256
+
257
+ const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
258
+ export const prisma = new PrismaClient({ adapter }).$extends(tsSelectExtend)
259
+ ```
260
+
140
261
  ## Supported DBs
141
262
 
142
- I have tested this currently on the following databases.
263
+ Fully tested on:
143
264
 
144
265
  - SQLite
145
266
  - MySQL
146
-
147
- Most items should also work for
148
-
149
267
  - PostgreSQL
150
268
 
151
269
  Other DBs will be added when I have chance.
@@ -207,31 +325,87 @@ The way the methods are chained, are heavily inspired by [Dr Milan Milanović](h
207
325
  This takes the `base` table to work from.
208
326
 
209
327
  #### Example
210
- ```typescript
211
- prisma.$from("User")
328
+ ```typescript file=../usage/tests/readme/from-basic.ts region=example
329
+ prisma.$from("User");
212
330
  ```
213
331
 
214
332
  #### Example - With Table Alias
215
- ```typescript
216
- prisma.$from("User", "u")
333
+ ```typescript file=../usage/tests/readme/from-inline-alias.ts region=example
334
+ prisma.$from("User u");
335
+ ```
336
+ ##### SQL
337
+ ```sql file=../usage/tests/readme/from-inline-alias.ts region=inline-alias-sql
338
+ FROM User AS `u`;
339
+ ```
340
+ **Note:** Alias can be inline (space-separated) or as second parameter.
341
+ **Note:** Table aliases are particularly useful for self-joins where you need to join a table to itself with different aliases.
342
+
343
+
344
+ ### `.$with`
345
+
346
+ Defines one or more Common Table Expressions (CTEs) that can be referenced in `.join()` calls or used directly as the base table via `.from('cteName')`.
347
+
348
+ | Param | Description |
349
+ |-------|-------------|
350
+ | `name` | CTE name — used to reference the CTE in `join()` or `from()` |
351
+ | `query` | Any query built with `.$from()` |
352
+
353
+ Chain `.with(name, query)` before `.from()` to define additional CTEs.
354
+
355
+ #### Example — CTE as joined table
356
+
357
+ ```typescript file=../shared-tests/readme/with-cte.ts region=join
358
+ const posts = prisma.$from("Post").select("id").select("authorId").select("title");
359
+
360
+ prisma.$with("pp", posts)
361
+ .from("User")
362
+ .join("pp", "authorId", "User.id")
217
363
  ```
218
364
 
219
365
  ##### SQL
220
- ```sql
221
- FROM User AS u
366
+
367
+ ```sql file=../shared-tests/readme/with-cte.ts region=join-sql
368
+ WITH pp AS (SELECT id, authorId, title FROM Post) FROM User JOIN pp ON pp.authorId = User.id;
222
369
  ```
223
370
 
224
- **Note:** Table aliases are particularly useful for self-joins where you need to join a table to itself with different aliases.
371
+ #### Example CTE as base table
225
372
 
226
- #### Example - Inline Alias Syntax
227
- ```typescript
228
- prisma.$from("User u")
373
+ Use `.from('cteName')` to query a CTE directly, without a real table as the base.
374
+
375
+ ```typescript file=../shared-tests/readme/with-cte.ts region=cte-base
376
+ const posts = prisma.$from("Post").select("id").select("title");
377
+
378
+ prisma.$with("pp", posts)
379
+ .from("pp")
380
+ .select("pp.id")
381
+ .select("pp.title")
229
382
  ```
383
+
230
384
  ##### SQL
231
- ```sql
232
- FROM User AS u
385
+
386
+ ```sql file=../shared-tests/readme/with-cte.ts region=cte-base-sql
387
+ WITH pp AS (SELECT id, title FROM Post) SELECT pp.id AS `pp.id`, pp.title AS `pp.title` FROM pp;
388
+ ```
389
+
390
+ **Type safety:** only CTEs declared in `.$with()` / `.with()` are accepted by `.from()`. Unknown CTE names are rejected at compile time.
391
+
392
+ #### Example — Multiple CTEs
393
+
394
+ ```typescript file=../shared-tests/readme/with-cte.ts region=multi-cte
395
+ const posts = prisma.$from("Post").select("id").select("authorId").select("title");
396
+ const users = prisma.$from("User").select("id").select("name");
397
+
398
+ prisma.$with("pp", posts)
399
+ .with("uu", users)
400
+ .from("User")
401
+ .join("pp", "authorId", "User.id")
402
+ ```
403
+
404
+ ##### SQL
405
+
406
+ ```sql file=../shared-tests/readme/with-cte.ts region=multi-cte-sql
407
+ WITH pp AS (SELECT id, authorId, title FROM Post), uu AS (SELECT id, name FROM User) FROM User JOIN pp ON pp.authorId = User.id;
233
408
  ```
234
- **Note:** Alias can be inline (space-separated) or as second parameter.
235
409
 
236
410
  ### Table Aliases
237
411
 
@@ -243,49 +417,33 @@ Table aliases allow you to give tables shorter or more meaningful names in your
243
417
  #### Table Alias Syntax Options
244
418
 
245
419
  Multiple syntaxes supported:
246
- - **Inline in .$from()**: `prisma.$from("User u")``
420
+ - **Inline in .$from()**: `prisma.$from("User u")` - Note: Second parameter syntax `.$from("User", "u")` is NOT supported
247
421
  - **Inline in .join()**: `.join("Post p", "authorId", "User.id")`
248
422
  - **Object syntax**: `.join({table: "Post", src: "authorId", on: "User.id", alias: "p"})`
249
423
 
250
- #### Basic Table Alias
251
-
252
- ```typescript
253
- prisma.$from("User", "u")
254
- .select("u.name")
255
- .select("u.email")
256
- .run();
257
- ```
258
-
259
- ##### SQL
260
- ```sql
261
- SELECT name, email FROM User AS u;
262
- ```
263
-
264
424
  #### Table Aliases with Joins
265
425
 
266
426
  ##### Inline Alias Syntax
267
- ```typescript
427
+ ```typescript file=../usage/tests/readme/table-alias.ts region=inline-join
268
428
  prisma.$from("User u")
269
- .join("Post p", "authorId", "u.id")
270
- .select("u.name")
271
- .select("p.title")
272
- .run();
429
+ .join("Post p", "authorId", "u.id")
430
+ .select("u.name")
431
+ .select("p.title");
273
432
  ```
274
433
 
275
434
  ##### Object Syntax
276
- ```typescript
277
- prisma.$from("User", "u")
278
- .join({table: "Post", src: "authorId", on: "u.id", alias: "p"})
279
- .select("u.name")
280
- .select("p.title")
281
- .run();
435
+ ```typescript file=../usage/tests/readme/table-alias.ts region=object-join
436
+ prisma.$from("User u")
437
+ .join({table: "Post", src: "authorId", on: "u.id", alias: "p"})
438
+ .select("u.name")
439
+ .select("p.title");
282
440
  ```
283
441
 
284
442
  ##### SQL
285
- ```sql
443
+ ```sql file=../usage/tests/readme/table-alias.ts region=inline-join-sql
286
444
  SELECT name, title
287
- FROM User AS u
288
- JOIN Post AS p ON authorId = u.id;
445
+ FROM User AS `u`
446
+ JOIN Post AS `p` ON p.authorId = u.id;
289
447
  ```
290
448
 
291
449
  **Note:** The object syntax provides a foundation for future enhancements like multiple join conditions and complex WHERE-style conditions in joins.
@@ -294,60 +452,93 @@ JOIN Post AS p ON authorId = u.id;
294
452
 
295
453
  Self-joins require aliases to distinguish between the different "instances" of the same table:
296
454
 
297
- ```typescript
298
- prisma.$from("User", "u1")
299
- .joinUnsafeTypeEnforced("User", "id", "u1.id", "u2")
300
- .select("u1.name", "user1Name")
301
- .select("u2.name", "user2Name")
302
- .run();
455
+ ```typescript file=../usage/tests/readme/table-alias.ts region=self-join
456
+ prisma.$from("User u1")
457
+ .joinUnsafeTypeEnforced("User u2", "id", "u1.id")
458
+ .select("u1.name", "user1Name")
459
+ .select("u2.name", "user2Name");
303
460
  ```
304
461
 
305
462
  ##### SQL
306
- ```sql
307
- SELECT u1.name AS `user1Name`, u2.name AS `user2Name`
308
- FROM User AS u1
309
- JOIN User AS u2 ON User.id = u1.id;
463
+ ```sql file=../usage/tests/readme/table-alias.ts region=self-join-sql
464
+ SELECT
465
+ u1.name AS `user1Name`,
466
+ u2.name AS `user2Name`
467
+ FROM User AS `u1`
468
+ JOIN User AS `u2` ON u2.id = u1.id;
310
469
  ```
311
470
 
312
471
  #### Table.* with Aliases
313
472
 
314
473
  You can use the `alias.*` syntax to select all columns from an aliased table:
315
474
 
316
- ```typescript
317
- prisma.$from("User", "u")
318
- .select("u.*")
319
- .run();
475
+ ```typescript file=../usage/tests/readme/table-alias.ts region=star-single
476
+ prisma.$from("User u")
477
+ .select("u.*");
320
478
  ```
321
479
 
322
480
  ##### SQL
323
- ```sql
324
- SELECT u.id, u.email, u.name FROM User AS u;
481
+ ```sql file=../usage/tests/readme/table-alias.ts region=star-single-sql
482
+ SELECT
483
+ id,
484
+ email,
485
+ name,
486
+ age
487
+ FROM User AS `u`;
325
488
  ```
326
489
 
327
490
  With joins:
328
- ```typescript
329
- prisma.$from("User", "u")
330
- .join("Post", "authorId", "u.id", "p")
331
- .select("u.*")
332
- .select("p.*")
333
- .run();
491
+ ```typescript file=../usage/tests/readme/table-alias.ts region=star-join
492
+ prisma.$from("User u")
493
+ .join("Post p", "authorId", "u.id")
494
+ .select("u.*")
495
+ .select("p.*");
334
496
  ```
335
497
 
336
498
  ##### SQL
337
- ```sql
338
- SELECT u.id AS `u.id`, u.email AS `u.email`, u.name AS `u.name`,
339
- p.id AS `p.id`, p.title AS `p.title`, p.content AS `p.content`
340
- FROM User AS u
341
- JOIN Post AS p ON authorId = u.id;
499
+ ```sql file=../usage/tests/readme/table-alias.ts region=star-join-sql
500
+ SELECT
501
+ u.id AS `u.id`,
502
+ u.email AS `u.email`,
503
+ u.name AS `u.name`,
504
+ u.age AS `u.age`,
505
+ p.id AS `p.id`,
506
+ p.title AS `p.title`,
507
+ p.content AS `p.content`,
508
+ p.published AS `p.published`,
509
+ p.authorId AS `p.authorId`,
510
+ p.lastModifiedById AS `p.lastModifiedById`
511
+ FROM User AS `u`
512
+ JOIN Post AS `p` ON p.authorId = u.id;
342
513
  ```
343
514
 
344
515
  ### Joins
516
+
517
+ #### Dialect Support
518
+
519
+ | Method | SQLite | MySQL | PostgreSQL |
520
+ |--------|--------|-------|------------|
521
+ | `join` / `innerJoin` / `crossJoin` / `leftJoin` | ✓ | ✓ | ✓ |
522
+ | `rightJoin` | ✗ | ✓ | ✓ |
523
+ | `fullJoin` | ✗ | ✗ | ✓ |
524
+
525
+ Each method has `*UnsafeTypeEnforced` and `*UnsafeIgnoreType` variants with the same dialect restrictions.
526
+
527
+ #### Nullability Semantics
528
+
529
+ | Join type | Effect on result type |
530
+ |-----------|----------------------|
531
+ | `join` / `innerJoin` / `crossJoin` | No nullable change — both sides guaranteed to match |
532
+ | `leftJoin` | Joined table fields become `T \| null` |
533
+ | `rightJoin` | Base table fields become `T \| null` |
534
+ | `fullJoin` | Both sides become `T \| null` |
535
+
345
536
  #### `.join`
346
537
 
347
538
  Using the defined links (foreign keys) defined in the schema, provides a type-safe way of joining on tables.
348
539
 
349
540
  ##### Example
350
- ```typescript
541
+ ```typescript file=../usage/tests/readme/join-basic.ts region=example
351
542
  prisma.$from("User")
352
543
  .join("Post", "authorId", "User.id");
353
544
  ```
@@ -358,9 +549,9 @@ prisma.$from("User")
358
549
 
359
550
  The resulting SQL will look like:
360
551
 
361
- ```sql
552
+ ```sql file=../usage/tests/readme/join-basic.ts region=join-basic-sql
362
553
  FROM User
363
- JOIN Post ON authorId = User.id;
554
+ JOIN Post ON Post.authorId = User.id;
364
555
  ```
365
556
 
366
557
  ##### Parameters
@@ -369,27 +560,102 @@ JOIN Post ON authorId = User.id;
369
560
  | `table` | The table to join on (supports inline alias: `"Post p"` or `"Post", "p"`). <br/>TS autocomplete will show tables that can join with previously defined tables on. |
370
561
  | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
371
562
  | `reference` | `Table.Column` to a previously defined table (either the base, or another join), with a FK that is defined in the schema definition. |
563
+ | `where` | *(optional)* Criteria added to the `ON` clause (`ON a = b AND ...`). Same syntax as `.where()`. Keys scoped to the joined table only. |
564
+ | `joinType` | *(optional)* Join variant. One of `"INNER"`, `"LEFT"`, `"LEFT OUTER"`, `"RIGHT"`, `"RIGHT OUTER"`, `"FULL"`, `"FULL OUTER"`, `"CROSS"`. Default: plain `JOIN`. |
372
565
 
373
566
  **Alternative Syntaxes:**
374
567
  ```typescript
375
568
  // Inline alias
376
569
  .join("Post p", "authorId", "User.id")
377
-
570
+
378
571
  // Object syntax
379
572
  .join({
380
573
  table: "Post",
381
574
  src: "authorId",
382
575
  on: "User.id",
383
- alias: "p" // optional
576
+ alias: "p", // optional
577
+ joinType: "LEFT", // optional
578
+ where: { "Post.published": true } // optional
384
579
  })
385
- ```
580
+ ```
581
+
582
+ ##### Join Type
583
+
584
+ Control the SQL join variant via the `joinType` option:
585
+
586
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-type.ts region=join-type-left
587
+ prisma.$from("User")
588
+ .join("Post", "authorId", "User.id", { joinType: "LEFT" })
589
+ ```
590
+
591
+ ```sql file=../usage-sqlite-v7/tests/readme/join-type.ts region=join-type-left-sql
592
+ FROM User LEFT JOIN Post ON Post.authorId = User.id;
593
+ ```
594
+
595
+ `CROSS JOIN` has no `ON` clause — it is suppressed automatically:
596
+
597
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-type.ts region=join-type-cross
598
+ prisma.$from("User")
599
+ .joinUnsafeIgnoreType("Post", "id", "User.id", { joinType: "CROSS" })
600
+ ```
601
+
602
+ ```sql file=../usage-sqlite-v7/tests/readme/join-type.ts region=join-type-cross-sql
603
+ FROM User CROSS JOIN Post;
604
+ ```
605
+
606
+ `joinType` and `where` can be combined — `where` is ignored for `CROSS`:
607
+
608
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-type.ts region=join-type-with-where
609
+ prisma.$from("User")
610
+ .join("Post", "authorId", "User.id", {
611
+ joinType: "LEFT",
612
+ where: { "Post.published": true }
613
+ })
614
+ ```
615
+
616
+ ```sql file=../usage-sqlite-v7/tests/readme/join-type.ts region=join-type-with-where-sql
617
+ FROM User LEFT JOIN Post ON Post.authorId = User.id AND Post.published = true;
618
+ ```
619
+
620
+ ##### Join-level WHERE
621
+
622
+ Conditions placed on the `ON` clause instead of the top-level `WHERE`:
623
+
624
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-where.ts region=join-where-example
625
+ prisma.$from("User")
626
+ .join("Post", "authorId", "User.id", { where: { "Post.published": true } })
627
+ ```
628
+
629
+ ```sql file=../usage-sqlite-v7/tests/readme/join-where.ts region=join-where-sql
630
+ FROM User JOIN Post ON Post.authorId = User.id AND Post.published = true;
631
+ ```
632
+
633
+ Supports the same MongoDB-inspired operators as `.where()` — `$AND`, `$OR`, `$NOT`, `$NOR`:
634
+
635
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-where.ts region=join-where-ops-example
636
+ prisma.$from("User")
637
+ .join("Post", "authorId", "User.id", {
638
+ where: {
639
+ $AND: [
640
+ { "Post.published": true },
641
+ { "Post.id": { op: ">", value: 0 } }
642
+ ]
643
+ }
644
+ })
645
+ ```
646
+
647
+ ```sql file=../usage-sqlite-v7/tests/readme/join-where.ts region=join-where-ops-sql
648
+ FROM User JOIN Post ON Post.authorId = User.id AND (Post.published = true AND Post.id > 0);
649
+ ```
650
+
651
+ > **Type safety**: only `"JoinedTable.field"` keys are accepted — other tables' fields are rejected at compile time.
386
652
 
387
653
  #### `.joinUnsafeTypeEnforced`
388
654
 
389
655
  Unlike the `.join` command, this will allow you to join on columns that are not explicitly linked by a FK, but have the same type.
390
656
 
391
657
  ##### Example
392
- ```typescript
658
+ ```typescript file=../usage/tests/readme/join-unsafe.ts region=type-enforced
393
659
  prisma.$from("User")
394
660
  .joinUnsafeTypeEnforced("Post", "title", "User.name");
395
661
  ```
@@ -398,8 +664,8 @@ prisma.$from("User")
398
664
  ##### SQL
399
665
  The resulting SQL will look like:
400
666
 
401
- ```sql
402
- FROM User
667
+ ```sql file=../usage/tests/readme/join-unsafe.ts region=type-enforced-sql
668
+ FROM User
403
669
  JOIN Post ON Post.title = User.name;
404
670
  ```
405
671
 
@@ -409,13 +675,15 @@ JOIN Post ON Post.title = User.name;
409
675
  | `table` | The table to join on (supports inline alias: `"Post p"` or `"Post", "p"`). <br/>TS autocomplete will show tables that can join with previously defined tables on. |
410
676
  | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
411
677
  | `reference` | `Table.Column` to a previously defined table (either the base, or another join), with a column that is of the same type. |
678
+ | `where` | *(optional)* Criteria added to the `ON` clause. See [Join-level WHERE](#join-level-where). |
679
+ | `joinType` | *(optional)* Join variant. See [Join Type](#join-type). |
412
680
 
413
681
  #### `.joinUnsafeIgnoreType`
414
682
 
415
683
  Unlike the `.joinUnsafeIgnoreType` command, this will allow you to join on columns that are not explicitly linked by a FK, and do not have the same type.
416
684
 
417
685
  ##### Example
418
- ```typescript
686
+ ```typescript file=../usage/tests/readme/join-unsafe.ts region=ignore-type
419
687
  prisma.$from("User")
420
688
  .joinUnsafeIgnoreType("Post", "id", "User.name");
421
689
  ```
@@ -424,9 +692,9 @@ prisma.$from("User")
424
692
  ##### SQL
425
693
  The resulting SQL will look like:
426
694
 
427
- ```sql
428
- FROM User
429
- JOIN Post ON Post.id = User.name
695
+ ```sql file=../usage/tests/readme/join-unsafe.ts region=ignore-type-sql
696
+ FROM User
697
+ JOIN Post ON Post.id = User.name;
430
698
  ```
431
699
 
432
700
  ##### Parameters
@@ -435,6 +703,244 @@ JOIN Post ON Post.id = User.name
435
703
  | `table` | The table to join on (supports inline alias: `"Post p"` or `"Post", "p"`). <br/>TS autocomplete will show tables that can join with previously defined tables on. |
436
704
  | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
437
705
  | `reference` | `Table.Column` to a previously defined table (either the base, or another join). Referencing any column, of any type. |
706
+ | `where` | *(optional)* Criteria added to the `ON` clause. See [Join-level WHERE](#join-level-where). |
707
+ | `joinType` | *(optional)* Join variant. See [Join Type](#join-type). |
708
+
709
+ #### `.manyToManyJoin`
710
+
711
+ Joins through Prisma's implicit or explicit many-to-many junction tables. Automatically detects the junction table and join columns from the generated schema.
712
+
713
+ ##### Example
714
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-basic
715
+ prisma.$from("M2M_Post")
716
+ .manyToManyJoin("M2M_Category");
717
+ ```
718
+
719
+ ##### SQL
720
+ ```sql file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-basic-sql
721
+ FROM M2M_Post JOIN _M2M_CategoryToM2M_Post ON _M2M_CategoryToM2M_Post.B = M2M_Post.id JOIN M2M_Category ON M2M_Category.id = _M2M_CategoryToM2M_Post.A;
722
+ ```
723
+
724
+ ##### Parameters
725
+ | Param | Type | Description |
726
+ |-------|------|-------------|
727
+ | `targetTable` | `string` | Target table, optionally with alias: `"M2M_Category"` or `"M2M_Category mc"` |
728
+ | `options.refName` | `string?` | Junction ref name — required when multiple M2M relations point to the same target |
729
+ | `options.source` | `string?` | Explicit source as `"alias.column"` — useful when the source table is aliased |
730
+
731
+ ##### With Alias
732
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-alias
733
+ prisma.$from("M2M_Post")
734
+ .manyToManyJoin("M2M_Category mc");
735
+ ```
736
+
737
+ ```sql file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-alias-sql
738
+ FROM M2M_Post JOIN _M2M_CategoryToM2M_Post ON _M2M_CategoryToM2M_Post.B = M2M_Post.id JOIN M2M_Category AS `mc` ON mc.id = _M2M_CategoryToM2M_Post.A;
739
+ ```
740
+
741
+ ##### Named Junction (`refName`)
742
+
743
+ Use `refName` when a model has multiple M2M relations to the same target:
744
+
745
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-refname
746
+ prisma.$from("MMM_Post")
747
+ .manyToManyJoin("MMM_Category", { refName: "M2M_NC_M1" });
748
+ ```
749
+
750
+ ```sql file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-refname-sql
751
+ FROM MMM_Post JOIN _M2M_NC_M1 ON _M2M_NC_M1.B = MMM_Post.id JOIN MMM_Category ON MMM_Category.id = _M2M_NC_M1.A;
752
+ ```
753
+
754
+ ##### Explicit Source (`source`)
755
+
756
+ Use `source` to pin the source alias and column when the source table is aliased:
757
+
758
+ ```typescript file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-source
759
+ prisma.$from("M2M_Post mp")
760
+ .manyToManyJoin("M2M_Category mc", { source: "mp.id" });
761
+ ```
762
+
763
+ ```sql file=../usage-sqlite-v7/tests/readme/join-many-to-many.ts region=m2m-source-sql
764
+ FROM M2M_Post AS `mp` JOIN _M2M_CategoryToM2M_Post ON _M2M_CategoryToM2M_Post.B = mp.id JOIN M2M_Category AS `mc` ON mc.id = _M2M_CategoryToM2M_Post.A;
765
+ ```
766
+
767
+ #### `.innerJoin`
768
+
769
+ Alias for `.join` — explicitly emits `INNER JOIN`. Same type-safe FK constraints.
770
+
771
+ ##### Example
772
+ ```typescript file=../shared-tests/readme/join-inner.ts region=example
773
+ prisma.$from("User")
774
+ .innerJoin("Post", "authorId", "User.id")
775
+ ```
776
+
777
+ ##### SQL
778
+ ```sql file=../shared-tests/readme/join-inner.ts region=sql
779
+ FROM User INNER JOIN Post ON Post.authorId = User.id;
780
+ ```
781
+
782
+ ##### `.innerJoinUnsafeTypeEnforced`
783
+
784
+ Same-type column join, INNER semantics.
785
+
786
+ ```typescript file=../shared-tests/readme/join-inner.ts region=type-enforced
787
+ prisma.$from("User")
788
+ .innerJoinUnsafeTypeEnforced("Post", "title", "User.name")
789
+ ```
790
+
791
+ ```sql file=../shared-tests/readme/join-inner.ts region=type-enforced-sql
792
+ FROM User INNER JOIN Post ON Post.title = User.name;
793
+ ```
794
+
795
+ ##### `.innerJoinUnsafeIgnoreType`
796
+
797
+ Any-column join, INNER semantics.
798
+
799
+ ```typescript file=../shared-tests/readme/join-inner.ts region=ignore-type
800
+ prisma.$from("User")
801
+ .innerJoinUnsafeIgnoreType("Post", "id", "User.name")
802
+ ```
803
+
804
+ ```sql file=../shared-tests/readme/join-inner.ts region=ignore-type-sql
805
+ FROM User INNER JOIN Post ON Post.id = User.name;
806
+ ```
807
+
808
+ ---
809
+
810
+ #### `.leftJoin`
811
+
812
+ FK-safe LEFT JOIN. Joined table fields become `T | null` in the result type.
813
+
814
+ ##### Example
815
+ ```typescript file=../shared-tests/readme/join-left.ts region=example
816
+ prisma.$from("User")
817
+ .leftJoin("Post", "authorId", "User.id")
818
+ ```
819
+
820
+ ##### SQL
821
+ ```sql file=../shared-tests/readme/join-left.ts region=sql
822
+ FROM User LEFT JOIN Post ON Post.authorId = User.id;
823
+ ```
824
+
825
+ ##### `.leftJoinUnsafeTypeEnforced`
826
+
827
+ Same-type column join, LEFT semantics.
828
+
829
+ ```typescript file=../shared-tests/readme/join-left.ts region=type-enforced
830
+ prisma.$from("User")
831
+ .leftJoinUnsafeTypeEnforced("Post", "title", "User.name")
832
+ ```
833
+
834
+ ```sql file=../shared-tests/readme/join-left.ts region=type-enforced-sql
835
+ FROM User LEFT JOIN Post ON Post.title = User.name;
836
+ ```
837
+
838
+ ##### `.leftJoinUnsafeIgnoreType`
839
+
840
+ Any-column join, LEFT semantics.
841
+
842
+ ```typescript file=../shared-tests/readme/join-left.ts region=ignore-type
843
+ prisma.$from("User")
844
+ .leftJoinUnsafeIgnoreType("Post", "id", "User.name")
845
+ ```
846
+
847
+ ```sql file=../shared-tests/readme/join-left.ts region=ignore-type-sql
848
+ FROM User LEFT JOIN Post ON Post.id = User.name;
849
+ ```
850
+
851
+ ---
852
+
853
+ #### `.crossJoin`
854
+
855
+ Produces a cartesian product — no `ON` clause. All dialects supported.
856
+
857
+ ##### Example
858
+ ```typescript file=../shared-tests/readme/join-cross.ts region=example
859
+ prisma.$from("User")
860
+ .crossJoin("Post")
861
+ ```
862
+
863
+ ##### SQL
864
+ ```sql file=../shared-tests/readme/join-cross.ts region=sql
865
+ FROM User CROSS JOIN Post;
866
+ ```
867
+
868
+ ##### `.crossJoinUnsafeTypeEnforced` / `.crossJoinUnsafeIgnoreType`
869
+
870
+ Type-permission variants — still emit `CROSS JOIN` with no `ON` clause (takes only a table argument).
871
+
872
+ ```typescript file=../shared-tests/readme/join-cross.ts region=type-enforced
873
+ prisma.$from("User")
874
+ .crossJoinUnsafeTypeEnforced("Post")
875
+ ```
876
+
877
+ ```sql file=../shared-tests/readme/join-cross.ts region=type-enforced-sql
878
+ FROM User CROSS JOIN Post;
879
+ ```
880
+
881
+ ---
882
+
883
+ #### `.rightJoin`
884
+
885
+ > **MySQL / PostgreSQL only** — not supported by SQLite.
886
+
887
+ Base table fields become `T | null`. Use when the joined table drives the result set.
888
+
889
+ ##### Example
890
+ ```typescript
891
+ prisma.$from("Post")
892
+ .rightJoin("User", "id", "Post.authorId")
893
+ ```
894
+
895
+ ##### SQL
896
+ ```sql
897
+ FROM Post RIGHT JOIN User ON User.id = Post.authorId;
898
+ ```
899
+
900
+ ##### `.rightJoinUnsafeTypeEnforced`
901
+ ```typescript
902
+ prisma.$from("Post")
903
+ .rightJoinUnsafeTypeEnforced("User", "name", "Post.title")
904
+ ```
905
+
906
+ ##### `.rightJoinUnsafeIgnoreType`
907
+ ```typescript
908
+ prisma.$from("Post")
909
+ .rightJoinUnsafeIgnoreType("User", "id", "Post.title")
910
+ ```
911
+
912
+ ---
913
+
914
+ #### `.fullJoin`
915
+
916
+ > **PostgreSQL only** — not supported by SQLite or MySQL.
917
+
918
+ Both sides become `T | null`. Use for outer joins where either side may have no match.
919
+
920
+ ##### Example
921
+ ```typescript
922
+ prisma.$from("User")
923
+ .fullJoin("Post", "authorId", "User.id")
924
+ ```
925
+
926
+ ##### SQL
927
+ ```sql
928
+ FROM User FULL JOIN Post ON Post.authorId = User.id;
929
+ ```
930
+
931
+ ##### `.fullJoinUnsafeTypeEnforced`
932
+ ```typescript
933
+ prisma.$from("User")
934
+ .fullJoinUnsafeTypeEnforced("Post", "title", "User.name")
935
+ ```
936
+
937
+ ##### `.fullJoinUnsafeIgnoreType`
938
+ ```typescript
939
+ prisma.$from("User")
940
+ .fullJoinUnsafeIgnoreType("Post", "id", "User.name")
941
+ ```
942
+
943
+ ---
438
944
 
439
945
  ### Where
440
946
 
@@ -445,9 +951,12 @@ The `where` syntax takes inspiration from how mongoDB does queries.
445
951
  ##### TypeSyntax
446
952
  ```TypeScript
447
953
  type WhereClause = {
448
- "Table.Column": <value> | { "op": "<condition>", "value": <value> }
954
+ "Table.Column": <value>
955
+ | [<value>, ...<value>[]] // scalar array → IN
956
+ | { "op": "<condition>", "value": <value> }
957
+ | [{ "op": "<condition>", "value": <value> }, ...] // op-array → OR
449
958
  "$AND": [WhereClause, ...Array<WhereClause>],
450
- "$OR": [WhereClause, ...Array<WhereClause>],
959
+ "$OR": [WhereClause, ...Array<WhereClause>],
451
960
  "$NOT": [WhereClause, ...Array<WhereClause>],
452
961
  "$NOR": [WhereClause, ...Array<WhereClause>]
453
962
  }
@@ -468,6 +977,7 @@ type WhereClause = {
468
977
  | < | | Numbers, Date |
469
978
  | <= | | Numbers, Date |
470
979
  | != | | Numbers, String, Date |
980
+ | = | | Numbers, String, Date |
471
981
 
472
982
 
473
983
  ##### Examples
@@ -478,155 +988,190 @@ type WhereClause = {
478
988
  | $OR | Will join all items with a `OR` | <pre>.where({ <br /> $OR:[<br /> {"User.name": {op: "LIKE", value:"a%"}},<br /> {"User.name": {op: "LIKE", value:"d%"}},<br />]})</pre> | `(User.name LIKE "a%" OR User.name LIKE "d%")` |
479
989
  | $NOT | Will wrap statement in a `NOT (/*...*/)` and join any items with a `AND` | <pre>.where({ <br /> $NOT:[<br /> {"User.age": 20 },<br /> {<br /> "User.age": {op: "=", value:60},<br /> "User.name": "Bob",<br /> },<br />]})</pre> | `(NOT (User.age = 20 AND (User.age = 60 AND User.name = "Bob")))` |
480
990
  | $NOR | Will wrap statement in a `NOT (/*...*/)` and join any items with a `OR` | <pre>.where({ <br /> $NOR:[<br /> {"User.age": 20 },<br /> {<br /> "User.age": {op: "!=", value:60},<br /> "User.name": "Bob",<br /> },<br />]})</pre> | `(NOT (User.age = 20 OR (User.age != 60 AND User.name = "Bob")))` |
991
+ | `Array (scalar)` | Non-empty array of values → SQL `IN` | `.where({ "User.name": ["Alice", "Bob"] })` | `User.name IN ('Alice', 'Bob')` |
992
+ | `Array (op-objects)` | Non-empty array of op-objects → `OR` chain | `.where({ "User.name": [{ op: "LIKE", value: "A%" }, { op: "LIKE", value: "B%" }] })` | `(User.name LIKE 'A%' OR User.name LIKE 'B%')` |
481
993
 
482
994
 
483
995
  ###### Columns
484
- ```typescript
996
+ ```typescript file=../usage/tests/readme/where.ts region=columns
485
997
  prisma.$from("User")
486
- .join("Post", "id", "User.name")
487
- .where({
488
- "User.age": 20,
489
- "User.name": {op: "LIKE", value: "Stuart%"},
490
- });
998
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
999
+ .where({
1000
+ "User.age": 20,
1001
+ "User.name": {op: "LIKE", value: "Stuart%"},
1002
+ });
491
1003
  ```
492
1004
 
493
1005
  ###### $AND
494
- ```typescript
1006
+ ```typescript file=../usage/tests/readme/where.ts region=and
495
1007
  prisma.$from("User")
496
- .join("Post", "id", "User.name")
497
- .where({
498
- $AND: [
499
- {"User.age": {op: ">", value: 20}},
500
- {"User.age": {op: "<", value: 60}},
501
- ]
502
- });
1008
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
1009
+ .where({
1010
+ $AND: [
1011
+ {"User.age": {op: ">", value: 20}},
1012
+ {"User.age": {op: "<", value: 60}},
1013
+ ]
1014
+ });
503
1015
  ```
504
1016
 
505
1017
  ###### $OR
506
- ```typescript
1018
+ ```typescript file=../usage/tests/readme/where.ts region=or
507
1019
  prisma.$from("User")
508
- .join("Post", "id", "User.name")
509
- .where({
510
- $OR: [
511
- {"User.name": {op: "LIKE", value: "a%"}},
512
- {"User.name": {op: "LIKE", value: "d%"}},
513
- ]
514
- });
1020
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
1021
+ .where({
1022
+ $OR: [
1023
+ {"User.name": {op: "LIKE", value: "a%"}},
1024
+ {"User.name": {op: "LIKE", value: "d%"}},
1025
+ ]
1026
+ });
515
1027
  ```
516
1028
 
517
1029
  ###### $NOT
518
- ```typescript
1030
+ ```typescript file=../usage/tests/readme/where.ts region=not
519
1031
  prisma.$from("User")
520
- .join("Post", "id", "User.name")
521
- .where({
522
- $NOT: [
523
- {"User.age": 20},
524
- {
525
- "User.age": {op: "=", value: 60},
526
- "User.name": "Bob",
527
- },
528
- ]
529
- });
1032
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
1033
+ .where({
1034
+ $NOT: [
1035
+ {"User.age": 20},
1036
+ {
1037
+ "User.age": {op: "=", value: 60},
1038
+ "User.name": "Bob",
1039
+ },
1040
+ ]
1041
+ });
530
1042
  ```
531
1043
 
532
1044
  ###### $NOR
533
- ```typescript
1045
+ ```typescript file=../usage/tests/readme/where.ts region=nor
534
1046
  prisma.$from("User")
535
- .join("Post", "id", "User.name")
536
- .where({
537
- $NOR: [
538
- {"User.age": 20},
539
- {
540
- "User.age": {op: "!=", value: 60},
541
- "User.name": "Bob",
542
- },
543
- ]
544
- });
1047
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
1048
+ .where({
1049
+ $NOR: [
1050
+ {"User.age": 20},
1051
+ {
1052
+ "User.age": {op: "!=", value: 60},
1053
+ "User.name": "Bob",
1054
+ },
1055
+ ]
1056
+ });
1057
+ ```
1058
+
1059
+ ###### Array (Scalar → IN)
1060
+ ```typescript file=../usage/tests/readme/where.ts region=array-scalar
1061
+ prisma.$from("User")
1062
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
1063
+ .where({
1064
+ "User.name": ["Alice", "Bob"],
1065
+ });
1066
+ ```
1067
+
1068
+ ###### Array (Op-Object → OR)
1069
+ ```typescript file=../usage/tests/readme/where.ts region=array-op
1070
+ prisma.$from("User")
1071
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
1072
+ .where({
1073
+ "User.name": [
1074
+ { op: "LIKE", value: "A%" },
1075
+ { op: "LIKE", value: "B%" },
1076
+ ],
1077
+ });
545
1078
  ```
546
1079
 
547
1080
  #### `.whereNotNull`
548
1081
 
549
- This will remove the `null` type from the union of types of the current table column.
550
- To use `.whereNotNull`, you need to add it before a `.where`.
1082
+ Removes `null` from the column's type union and adds an `IS NOT NULL` condition to the WHERE clause.
1083
+ Type narrowing is reflected in all downstream `.select()` calls.
551
1084
 
552
1085
  ##### Example
553
- ```typescript
1086
+ ```typescript file=../usage-sqlite-v7/tests/readme/whereNotNull.ts region=whereNotNull
554
1087
  prisma.$from("User")
555
- .join("Post", "authorId", "User.id")
556
- .whereNotNull("User.name");
1088
+ .join("Post", "authorId", "User.id")
1089
+ .whereNotNull("User.name")
557
1090
  ```
558
1091
  ![whereNotNull](./assets/whereNotNull.gif)
559
1092
 
560
1093
  ##### SQL
561
1094
  The resulting SQL will look like:
562
1095
 
563
- ```sql
564
- FROM User
565
- JOIN Post ON authorId = User.id
566
- WHERE User.name IS NOT NULL;
1096
+ ```sql file=../usage-sqlite-v7/tests/readme/whereNotNull.ts region=whereNotNull-sql
1097
+ FROM User JOIN Post ON Post.authorId = User.id WHERE (User.name IS NOT NULL);
567
1098
  ```
568
1099
 
569
1100
  #### `.whereIsNull`
570
1101
 
571
- This will remove the NonNull type from the union of types of the current table column.
572
- To use `.whereIsNull`, you need to add it before a `.where`.
1102
+ Narrows the column's type to exactly `null` and adds an `IS NULL` condition to the WHERE clause.
573
1103
 
574
1104
  ##### Example
575
- ```typescript
1105
+ ```typescript file=../usage-sqlite-v7/tests/readme/whereNotNull.ts region=whereIsNull
576
1106
  prisma.$from("User")
577
- .join("Post", "authorId", "User.id")
578
- .whereIsNull("Post.content");
1107
+ .join("Post", "authorId", "User.id")
1108
+ .whereIsNull("Post.content")
579
1109
  ```
580
1110
  ![whereIsNull](./assets/whereIsNull.gif)
581
1111
 
582
1112
  ##### SQL
583
1113
  The resulting SQL will look like:
584
1114
 
585
- ```sql
586
- FROM User
587
- JOIN Post ON authorId = User.id
588
- WHERE Post.content IS NULL;
1115
+ ```sql file=../usage-sqlite-v7/tests/readme/whereNotNull.ts region=whereIsNull-sql
1116
+ FROM User JOIN Post ON Post.authorId = User.id WHERE (Post.content IS NULL);
589
1117
  ```
590
1118
 
1119
+ #### `.where` — fn overload (SQL expressions)
1120
+
1121
+ Pass a callback instead of a criteria object to apply SQL functions as conditions. The callback receives the same select-fn context as `.select()`, giving access to `upper`, `lower`, `length`, `count`, `avg`, etc.
1122
+
1123
+ ```typescript file=../shared-tests/readme/where.ts region=fn-upper-like
1124
+ prisma.$from("User")
1125
+ .where(({ upper }) => [[upper('name'), { op: 'LIKE', value: 'John%' }]])
1126
+ .select("name")
1127
+ ```
1128
+
1129
+ ```sql file=../shared-tests/readme/where.ts region=fn-upper-like-sql
1130
+ SELECT name FROM User WHERE UPPER(name) LIKE 'John%';
1131
+ ```
1132
+
1133
+ Each array element is an `[SQLExpr<T>, condition]` pair — multiple pairs are AND-ed. The condition type is inferred from `SQLExpr<T>`: string expressions accept LIKE/NOT LIKE, numeric expressions accept `>`, `<`, BETWEEN, etc.
1134
+
591
1135
  #### `.whereRaw`
592
1136
 
593
- When you want to write a complex `where`, or you just don't want the TypeSafety offered by the other methods, you can use `.whereRaw`.
1137
+ When you want to write a complex `where`, or you just don't want the TypeSafety offered by the other methods, you can use `.whereRaw`.
594
1138
 
595
1139
  ##### Example
596
- ```typescript
1140
+ ```typescript file=../usage/tests/readme/where.ts region=raw
597
1141
  prisma.$from("User")
598
- .join("Post", "authorId", "User.id")
599
- .whereRaw("this is a raw where statement");
1142
+ .join("Post", "authorId", "User.id")
1143
+ .whereRaw("this is a raw where statement");
600
1144
  ```
601
1145
 
602
1146
  ##### SQL
603
1147
  The resulting SQL will look like:
604
1148
 
605
- ```sql
606
- FROM User
607
- JOIN Post ON authorId = User.id
608
- WHERE this is a raw where statement;
1149
+ ```sql file=../usage/tests/readme/where.ts region=raw-sql
1150
+ FROM User
1151
+ JOIN Post ON Post.authorId = User.id
1152
+ WHERE this is a raw
1153
+ WHERE statement;
609
1154
  ```
610
1155
 
611
1156
 
612
1157
  ### Group By
613
1158
 
614
- Will allow you to pass a list of columns, that haven been specified from the `.$from` and any `.join` methods.
1159
+ Will allow you to pass a list of columns, that haven been specified from the `.$from` and any `.join` methods.
615
1160
 
616
1161
  #### Example
617
- ```typescript
1162
+ ```typescript file=../usage/tests/readme/groupby.ts region=basic
618
1163
  prisma.$from("User")
619
- .join("Post", "authorId", "User.id")
620
- .groupBy(["name", "Post.content"]);
1164
+ .join("Post", "authorId", "User.id")
1165
+ .groupBy(["name", "Post.content"]);
621
1166
  ```
622
1167
  ![groupBy](./assets/groupBy.gif)
623
1168
 
624
1169
  #### SQL
625
1170
  The resulting SQL will look like:
626
1171
 
627
- ```sql
628
- FROM User
629
- JOIN Post ON authorId = User.id
1172
+ ```sql file=../usage/tests/readme/groupby.ts region=basic-sql
1173
+ FROM User
1174
+ JOIN Post ON Post.authorId = User.id
630
1175
  GROUP BY name, Post.content;
631
1176
  ```
632
1177
 
@@ -637,16 +1182,17 @@ GROUP BY name, Post.content;
637
1182
  Will add the keyword `DISTINCT` after the select.
638
1183
 
639
1184
  #### Example
640
- ```typescript
1185
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=distinct
641
1186
  prisma.$from("User")
642
- .selectDistinct();
1187
+ .selectDistinct()
1188
+ .select("name");
643
1189
  ```
644
1190
 
645
1191
  #### SQL
646
1192
  The resulting SQL will look like:
647
1193
 
648
- ```sql
649
- SELECT DISTINCT
1194
+ ```sql file=../usage/tests/readme/select-advanced.ts region=distinct-sql
1195
+ SELECT DISTINCT name
650
1196
  FROM User;
651
1197
  ```
652
1198
 
@@ -657,71 +1203,117 @@ This method will explicitly list all the tables from the `$from` and `.join`. So
657
1203
 
658
1204
 
659
1205
  #### Example - Single Table
660
- ```typescript
1206
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=all-single
661
1207
  prisma.$from("User")
662
- .selectAll();
1208
+ .selectAll();
663
1209
  ```
664
1210
 
665
1211
  ##### SQL
666
1212
  The resulting SQL will look like:
667
1213
 
668
- ```sql
669
- SELECT id, email, name
670
- FROM User
1214
+ ```sql file=../usage/tests/readme/select-advanced.ts region=all-single-sql
1215
+ SELECT
1216
+ id,
1217
+ email,
1218
+ name,
1219
+ age
1220
+ FROM User;
671
1221
  ```
672
1222
 
673
1223
  #### Example - Join table
674
- ```typescript
1224
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=all-join
675
1225
  prisma.$from("User")
676
- .join("Post", "authorId", "User.id")
677
- .selectAll();
1226
+ .join("Post", "authorId", "User.id")
1227
+ .selectAll();
678
1228
  ```
679
1229
 
680
1230
  ##### SQL
681
1231
  The resulting SQL will look like:
682
1232
 
683
- ```sql
684
- SELECT User.id, User. email, User.name, Post.id, Post.title, Post.content, Post.published, Post.author, Post.authorId, Post.LastModifiedBy, Post.lastModifiedById
685
- FROM User
686
- JOIN Post ON authorId = User.id
1233
+ ```sql file=../usage/tests/readme/select-advanced.ts region=all-join-sql
1234
+ SELECT
1235
+ User.id AS `User.id`,
1236
+ User.email AS `User.email`,
1237
+ User.name AS `User.name`,
1238
+ User.age AS `User.age`,
1239
+ Post.id AS `Post.id`,
1240
+ Post.title AS `Post.title`,
1241
+ Post.content AS `Post.content`,
1242
+ Post.published AS `Post.published`,
1243
+ Post.authorId AS `Post.authorId`,
1244
+ Post.lastModifiedById AS `Post.lastModifiedById`
1245
+ FROM User
1246
+ JOIN Post ON Post.authorId = User.id;
687
1247
  ```
688
1248
 
689
- [//]: # (#### `.selectAllOmit`)
1249
+ #### `.selectAllOmit`
1250
+
1251
+ Like `.selectAll`, but excludes specific columns. Accepts `Table.column` or bare `column` references.
1252
+
1253
+ #### Example - Single Table
1254
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-all-omit.ts region=single-omit
1255
+ prisma.$from("User")
1256
+ .selectAllOmit(["User.email"]);
1257
+ ```
1258
+
1259
+ ##### SQL
1260
+ ```sql file=../usage-sqlite-v7/tests/readme/select-all-omit.ts region=single-omit-sql
1261
+ SELECT id, name, age FROM User;
1262
+ ```
1263
+
1264
+ #### Example - Multiple Columns
1265
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-all-omit.ts region=multi-omit
1266
+ prisma.$from("User")
1267
+ .selectAllOmit(["User.email", "User.age"]);
1268
+ ```
1269
+
1270
+ #### Example - With Join
1271
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-all-omit.ts region=join-omit
1272
+ prisma.$from("User")
1273
+ .join("Post", "authorId", "User.id")
1274
+ .selectAllOmit(["User.email", "Post.content"]);
1275
+ ```
1276
+
1277
+ > **Note:** `*` and `Table.*` are not valid arguments — use `Table.column` or bare `column` references.
690
1278
 
691
1279
  #### `.select`
692
1280
 
693
1281
  You can supply either; `*`, `Table.*` OR `table.field` and then chain them together.
694
1282
 
695
1283
  #### Example - `*`
696
- ```typescript
1284
+ ```typescript file=../usage/tests/readme/select-star.ts region=example
697
1285
  prisma.$from("User")
698
- .select("*");
1286
+ .select("*");
699
1287
  ```
700
1288
 
701
1289
  ##### SQL
702
1290
  The resulting SQL will look like:
703
1291
 
704
- ```sql
1292
+ ```sql file=../usage/tests/readme/select-star.ts region=example-sql
705
1293
  SELECT *
706
1294
  FROM User;
707
1295
  ```
708
1296
 
709
1297
  #### Example - `Table.*` (Single Table)
710
- ```typescript
1298
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=table-star-single
711
1299
  prisma.$from("User")
712
- .select("User.*");
1300
+ .select("User.*");
713
1301
  ```
714
1302
 
715
1303
  ##### SQL
716
1304
  The resulting SQL will look like:
717
1305
 
718
- ```sql
719
- SELECT User.id, User.email, User.name
1306
+ ```sql file=../usage/tests/readme/select-advanced.ts region=table-star-single-sql
1307
+ SELECT
1308
+ id,
1309
+ email,
1310
+ name,
1311
+ age
720
1312
  FROM User;
721
1313
  ```
722
1314
 
723
1315
  #### Example - `Table.*` (With Join)
724
- ```typescript
1316
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=table-star-join
725
1317
  prisma.$from("User")
726
1318
  .join("Post", "authorId", "User.id")
727
1319
  .select("User.*")
@@ -731,38 +1323,42 @@ prisma.$from("User")
731
1323
  ##### SQL
732
1324
  The resulting SQL will look like:
733
1325
 
734
- ```sql
735
- SELECT User.id AS `User.id`,
736
- User.email AS `User.email`,
737
- User.name AS `User.name`,
738
- Post.id AS `Post.id`,
739
- Post.title AS `Post.title`,
740
- Post.content AS `Post.content`,
741
- Post.published AS `Post.published`
1326
+ ```sql file=../usage/tests/readme/select-advanced.ts region=table-star-join-sql
1327
+ SELECT
1328
+ User.id AS `User.id`,
1329
+ User.email AS `User.email`,
1330
+ User.name AS `User.name`,
1331
+ User.age AS `User.age`,
1332
+ Post.id AS `Post.id`,
1333
+ Post.title AS `Post.title`,
1334
+ Post.content AS `Post.content`,
1335
+ Post.published AS `Post.published`,
1336
+ Post.authorId AS `Post.authorId`,
1337
+ Post.lastModifiedById AS `Post.lastModifiedById`
742
1338
  FROM User
743
- JOIN Post ON authorId = User.id;
1339
+ JOIN Post ON Post.authorId = User.id;
744
1340
  ```
745
1341
 
746
- [!NOTE]
1342
+ > [!NOTE]
747
1343
  > When using `Table.*` with joins, all columns are automatically aliased with the table name prefix to avoid column name conflicts.
748
1344
 
749
1345
  #### Example - Chained
750
- ```typescript
1346
+ ```typescript file=../usage/tests/readme/select-chained.ts region=example
751
1347
  prisma.$from("User")
752
- .select("name")
753
- .select("email");
1348
+ .select("name")
1349
+ .select("email");
754
1350
  ```
755
1351
 
756
1352
  ##### SQL
757
1353
  The resulting SQL will look like:
758
1354
 
759
- ```sql
1355
+ ```sql file=../usage/tests/readme/select-chained.ts region=example-sql
760
1356
  SELECT name, email
761
1357
  FROM User;
762
1358
  ```
763
1359
 
764
1360
  #### Example - Join + Chained
765
- ```typescript
1361
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=join-chained
766
1362
  prisma.$from("User")
767
1363
  .join("Post", "authorId", "User.id")
768
1364
  .select("name")
@@ -772,24 +1368,25 @@ prisma.$from("User")
772
1368
  ##### SQL
773
1369
  The resulting SQL will look like:
774
1370
 
775
- ```sql
776
- SELECT name, Post.title
1371
+ ```sql file=../usage/tests/readme/select-advanced.ts region=join-chained-sql
1372
+ SELECT name, title
777
1373
  FROM User
778
- JOIN Post ON authorId = User.id;
1374
+ JOIN Post ON Post.authorId = User.id;
779
1375
  ```
780
1376
 
781
1377
  #### Example - Column Aliases
782
- ```typescript
783
- // Basic alias
1378
+ ```typescript file=../usage/tests/readme/select-column-alias.ts region=basic
784
1379
  prisma.$from("User")
785
1380
  .select("User.name", "username");
1381
+ ```
786
1382
 
787
- // Multiple aliases
1383
+ ```typescript file=../usage/tests/readme/select-column-alias.ts region=multiple
788
1384
  prisma.$from("User")
789
1385
  .select("User.id", "userId")
790
1386
  .select("User.email", "emailAddress");
1387
+ ```
791
1388
 
792
- // Mixing aliased and non-aliased columns
1389
+ ```typescript file=../usage/tests/readme/select-column-alias.ts region=mixed
793
1390
  prisma.$from("User")
794
1391
  .select("User.id")
795
1392
  .select("User.name", "username")
@@ -799,19 +1396,28 @@ prisma.$from("User")
799
1396
  ##### SQL
800
1397
  The resulting SQL will look like:
801
1398
 
802
- ```sql
803
- -- Basic alias
804
- SELECT User.name AS `username` FROM User;
1399
+ ```sql file=../usage/tests/readme/select-column-alias.ts region=basic-sql
1400
+ SELECT User.name AS `username`
1401
+ FROM User;
1402
+ ```
805
1403
 
806
- -- Multiple aliases
807
- SELECT User.id AS `userId`, User.email AS `emailAddress` FROM User;
1404
+ ```sql file=../usage/tests/readme/select-column-alias.ts region=multiple-sql
1405
+ SELECT
1406
+ User.id AS `userId`,
1407
+ User.email AS `emailAddress`
1408
+ FROM User;
1409
+ ```
808
1410
 
809
- -- Mixed
810
- SELECT User.id, User.name AS `username`, User.email FROM User;
1411
+ ```sql file=../usage/tests/readme/select-column-alias.ts region=mixed-sql
1412
+ SELECT
1413
+ id,
1414
+ User.name AS `username`,
1415
+ email
1416
+ FROM User;
811
1417
  ```
812
1418
 
813
1419
  #### Example - Aliases with Joins
814
- ```typescript
1420
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=aliases-joins
815
1421
  prisma.$from("User")
816
1422
  .join("Post", "authorId", "User.id")
817
1423
  .select("User.name", "authorName")
@@ -821,77 +1427,105 @@ prisma.$from("User")
821
1427
  ##### SQL
822
1428
  The resulting SQL will look like:
823
1429
 
824
- ```sql
825
- SELECT User.name AS `authorName`, Post.title AS `postTitle`
1430
+ ```sql file=../usage/tests/readme/select-advanced.ts region=aliases-joins-sql
1431
+ SELECT
1432
+ User.name AS `authorName`,
1433
+ Post.title AS `postTitle`
826
1434
  FROM User
827
- JOIN Post ON authorId = User.id;
1435
+ JOIN Post ON Post.authorId = User.id;
828
1436
  ```
829
1437
 
830
- [!NOTE]
1438
+ > [!NOTE]
831
1439
  > When using column aliases, you can reference the alias in `ORDER BY` clauses. The returned type will use the alias names instead of the original column names.
832
1440
 
833
1441
  ### Having
834
1442
 
835
- `.having` uses the same syntax as [`.where`](#where). Please see the previous section for details.
1443
+ `.having` accepts two overloads — a criteria object (same syntax as [`.where`](#where)) or a fn callback for SQL expressions and aggregate functions.
836
1444
 
837
- #### Example
1445
+ #### Criteria object
838
1446
 
839
- ```typescript
1447
+ ```typescript file=../shared-tests/readme/having.ts region=with-groupby
840
1448
  prisma.$from("User")
841
- .join("Post", "authorId", "User.id")
842
- .groupBy(["name", "Post.content"])
843
- .having({
844
- "User.name": {
845
- "op": "LIKE",
846
- "value": "bob%"
847
- }
848
- });
1449
+ .join("Post", "authorId", "User.id")
1450
+ .groupBy(["name", "Post.content"])
1451
+ .having({
1452
+ "User.name": {
1453
+ "op": "LIKE",
1454
+ "value": "bob%"
1455
+ }
1456
+ })
1457
+ .select("*");
849
1458
  ```
850
1459
 
851
- ```typescript
1460
+ ```sql file=../shared-tests/readme/having.ts region=with-groupby-sql
1461
+ SELECT * FROM User JOIN Post ON Post.authorId = User.id GROUP BY name, Post.content HAVING User.name LIKE 'bob%';
1462
+ ```
1463
+
1464
+ #### fn overload — aggregate functions
1465
+
1466
+ Pass a callback returning `Array<[SQLExpr<T>, condition]>` pairs. The callback receives the full select-fn context, including all aggregate and string functions.
1467
+
1468
+ ##### `countAll()` with comparison op
1469
+
1470
+ ```typescript file=../shared-tests/readme/having.ts region=agg-fn-tuple-countall
852
1471
  prisma.$from("User")
853
- .join("Post", "authorId", "User.id")
854
- .having({
855
- "User.name": {
856
- "op": "LIKE",
857
- "value": "stuart%"
858
- }
859
- });
1472
+ .join("Post", "authorId", "User.id")
1473
+ .groupBy(["User.name"])
1474
+ .having(({ countAll }) => [[countAll(), { op: '>', value: 1 }]])
1475
+ .select("User.name")
860
1476
  ```
861
1477
 
1478
+ ```sql file=../shared-tests/readme/having.ts region=agg-fn-tuple-countall-sql
1479
+ SELECT name FROM User JOIN Post ON Post.authorId = User.id GROUP BY User.name HAVING COUNT(*) > 1;
1480
+ ```
862
1481
 
863
- ##### SQL
1482
+ ##### `count(col)` with bigint value
864
1483
 
865
- ```SQL
866
- FROM User
867
- JOIN Post ON authorId = User.id
868
- GROUP BY name, Post.content
869
- HAVING (User.name LIKE 'bob%');
1484
+ ```typescript file=../shared-tests/readme/having.ts region=agg-fn-tuple-count
1485
+ prisma.$from("User")
1486
+ .join("Post", "authorId", "User.id")
1487
+ .groupBy(["User.name"])
1488
+ .having(({ count }) => [[count('User.id'), { op: '>=', value: 2n }]])
1489
+ .select("User.name")
870
1490
  ```
871
1491
 
872
- ```SQL
873
- FROM User
874
- JOIN Post ON authorId = User.id
875
- HAVING (User.name LIKE 'stuart%');
1492
+ ```sql file=../shared-tests/readme/having.ts region=agg-fn-tuple-count-sql
1493
+ SELECT name FROM User JOIN Post ON Post.authorId = User.id GROUP BY User.name HAVING COUNT(User.id) >= 2;
1494
+ ```
1495
+
1496
+ ##### String expr — `upper(col)` LIKE
1497
+
1498
+ ```typescript file=../shared-tests/readme/having.ts region=agg-fn-string-upper
1499
+ prisma.$from("User")
1500
+ .join("Post", "authorId", "User.id")
1501
+ .groupBy(["User.name"])
1502
+ .having(({ upper }) => [[upper('User.name'), { op: 'LIKE', value: 'John%' }]])
1503
+ .select("User.name")
1504
+ ```
1505
+
1506
+ ```sql file=../shared-tests/readme/having.ts region=agg-fn-string-upper-sql
1507
+ SELECT name FROM User JOIN Post ON Post.authorId = User.id GROUP BY User.name HAVING UPPER(User.name) LIKE 'John%';
876
1508
  ```
877
1509
 
1510
+ Multiple pairs in one `.having()` call are AND-ed together. `.having()` can also be chained — each call appends an AND condition.
1511
+
878
1512
  ### Order By
879
1513
 
880
1514
  `.orderBy`, takes an array of column names, with the optional suffix of `ASC` or `DESC`.
881
1515
 
882
1516
  #### Example
883
1517
 
884
- ```typescript
1518
+ ```typescript file=../usage/tests/readme/orderby.ts region=basic
885
1519
  prisma.$from("User")
886
1520
  .join("Post", "authorId", "User.id")
887
- .orderBy(["name", "Post.content DESC"]);
1521
+ .orderBy(["name", "Post.content DESC"]);
888
1522
  ```
889
1523
 
890
1524
  ##### SQL
891
1525
 
892
- ```sql
1526
+ ```sql file=../usage/tests/readme/orderby.ts region=basic-sql
893
1527
  FROM User
894
- JOIN Post ON authorId = User.id
1528
+ JOIN Post ON Post.authorId = User.id
895
1529
  ORDER BY name, Post.content DESC;
896
1530
  ```
897
1531
 
@@ -901,7 +1535,7 @@ ORDER BY name, Post.content DESC;
901
1535
 
902
1536
  #### Example
903
1537
 
904
- ```typescript
1538
+ ```typescript file=../usage/tests/readme/pagination.ts region=limit
905
1539
  prisma.$from("User")
906
1540
  .join("Post", "authorId", "User.id")
907
1541
  .limit(1);
@@ -909,9 +1543,9 @@ prisma.$from("User")
909
1543
 
910
1544
  ##### SQL
911
1545
 
912
- ```SQL
1546
+ ```sql file=../usage/tests/readme/pagination.ts region=limit-sql
913
1547
  FROM User
914
- JOIN Post ON authorId = User.id
1548
+ JOIN Post ON Post.authorId = User.id
915
1549
  LIMIT 1;
916
1550
  ```
917
1551
 
@@ -920,71 +1554,364 @@ LIMIT 1;
920
1554
  `.offSet`, the number of rows to skip. Requires `.limit` to have been used first.
921
1555
 
922
1556
  #### Example
923
- ```typescript
1557
+ ```typescript file=../usage/tests/readme/pagination.ts region=offset
924
1558
  prisma.$from("User")
925
- .join("Post", "authorId", "User.id")
926
- .limit(1)
927
- .offset(1);
1559
+ .join("Post", "authorId", "User.id")
1560
+ .limit(1)
1561
+ .offset(1);
928
1562
  ```
929
1563
 
930
1564
  ##### SQL
931
1565
 
932
- ```SQL
1566
+ ```sql file=../usage/tests/readme/pagination.ts region=offset-sql
933
1567
  FROM User
934
- JOIN Post ON authorId = User.id
1568
+ JOIN Post ON Post.authorId = User.id
935
1569
  LIMIT 1
936
- OFFSET 1
1570
+ OFFSET 1;
937
1571
  ```
938
1572
 
939
- ## Future updates
1573
+ ## Select Functions
940
1574
 
941
- - Support specifying `JOIN` type [issue#2](https://github.com/adrianbrowning/prisma-ts-select/issues/2)
942
- - Support Select Functions
943
- - [Aggregation #4](https://github.com/adrianbrowning/prisma-ts-select/issues/4)
944
- - [String #5](https://github.com/adrianbrowning/prisma-ts-select/issues/5)
945
- - [Date & Time #6](https://github.com/adrianbrowning/prisma-ts-select/issues/6)
946
- - [Math #7](https://github.com/adrianbrowning/prisma-ts-select/issues/7)
947
- - [Control Flow #8](https://github.com/adrianbrowning/prisma-ts-select/issues/8)
948
- - [JSON #9](https://github.com/adrianbrowning/prisma-ts-select/issues/9)
949
- - [Support a `Many-To-Many` join #19](https://github.com/adrianbrowning/prisma-ts-select/issues/19)
950
- - [Select column alias #27](https://github.com/adrianbrowning/prisma-ts-select/issues/27)
951
- - [Table name alias #28](https://github.com/adrianbrowning/prisma-ts-select/issues/28)
952
- - [whereRaw supporting Prisma.sql](https://github.com/adrianbrowning/prisma-ts-select/issues/29)
1575
+ Pass a callback to `.select()` to use SQL expressions and aggregate functions. The callback receives a context object with all available functions for the active dialect.
953
1576
 
954
- ## Changelog / Versioning
955
- Changelog is available [here](https://github.com/adrianbrowning/prisma-ts-select/releases). We use [semantic versioning](https://semver.org/) for versioning.
1577
+ ### Shared (all dialects)
956
1578
 
957
- ## License
958
- This project is licensed under the MIT License. See the LICENSE file for details.
1579
+ #### `lit(value)` — SQL literal
959
1580
 
960
- Things of note!!!!
1581
+ Produces a typed SQL literal from a JS value.
961
1582
 
962
- - remove typeof from
963
- - `type _db = DeepWriteable<typeof DB>;`
964
- - `}[keyof typeof DB];`
965
- - Merge Items missing //@ts-expect-error - might not be needed
966
- - groupBy -> having,
967
- - missing @deprecated
968
- - ts-exptect-error - might not be needed
969
- - GetColsFromTableType missing ts-expect-error - might not be needed
970
- - DB needs to be in the same file.
1583
+ ##### Example
1584
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=lit-string
1585
+ prisma.$from("User")
1586
+ .select(({ lit }) => lit("hello"), "greeting");
1587
+ ```
971
1588
 
1589
+ #### `countAll()` — COUNT(*)
972
1590
 
1591
+ The most common aggregate. Always produces `COUNT(*)`.
973
1592
 
974
- # prisma-ts-select
1593
+ ##### Example
1594
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=count-all
1595
+ prisma.$from("User")
1596
+ .select(({ countAll }) => countAll(), "total");
1597
+ ```
975
1598
 
976
- ## Install
1599
+ ##### SQL
1600
+ ```sql file=../usage-sqlite-v7/tests/readme/select-fns.ts region=count-all-sql
1601
+ SELECT COUNT(*) AS `total` FROM User;
1602
+ ```
977
1603
 
978
- ```shell
979
- npm i prisma-ts-select
980
- pnpm add prisma-ts-select
1604
+ #### `count(col)` — COUNT(col)
1605
+
1606
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=count-col
1607
+ prisma.$from("User")
1608
+ .select(({ count }) => count("User.id"), "cnt");
981
1609
  ```
982
1610
 
983
- ## Setup
1611
+ #### `countDistinct(col)` — COUNT(DISTINCT col)
984
1612
 
985
- ### Extract
1613
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=count-distinct
1614
+ prisma.$from("User")
1615
+ .select(({ countDistinct }) => countDistinct("User.id"), "cnt");
1616
+ ```
986
1617
 
1618
+ #### `sum(col)` / `avg(col)` / `min(col)` / `max(col)`
987
1619
 
988
- ## Usage
1620
+ Standard numeric aggregates. **Return types vary by dialect** — `sum` and `avg` return `Decimal` on MySQL (matching Prisma's numeric precision model), `number` on SQLite and PostgreSQL. `min`/`max` always return `T | null` (NULL for empty sets) where `T` is the column's TypeScript type.
1621
+
1622
+ | Function | SQLite | MySQL | PostgreSQL |
1623
+ |---|---|---|---|
1624
+ | `sum(col)` | `number` | `Decimal` | `number` |
1625
+ | `avg(col)` | `number` | `Decimal` | `number` |
1626
+ | `min(col)` | `T \| null` | `T \| null` | `T \| null` |
1627
+ | `max(col)` | `T \| null` | `T \| null` | `T \| null` |
1628
+
1629
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=sum
1630
+ prisma.$from("User")
1631
+ .select(({ sum }) => sum("User.age"), "total");
1632
+ ```
1633
+
1634
+ #### String Functions (all dialects)
1635
+
1636
+ | Function | SQL | Returns |
1637
+ |---|---|---|
1638
+ | `upper(col)` | `UPPER(col)` | `string` |
1639
+ | `lower(col)` | `LOWER(col)` | `string` |
1640
+ | `length(col)` | `LENGTH(col)` | `number` |
1641
+ | `trim(col)` | `TRIM(col)` | `string` |
1642
+ | `ltrim(col)` | `LTRIM(col)` | `string` |
1643
+ | `rtrim(col)` | `RTRIM(col)` | `string` |
1644
+ | `replace(col, from, to)` | `REPLACE(col, 'from', 'to')` | `string` |
1645
+
1646
+ > **Note:** MySQL `LENGTH()` returns byte-length (not char-length). For character-length on multi-byte strings use a dialect-specific fn.
1647
+
1648
+ #### DateTime Functions (all dialects)
1649
+
1650
+ All dialects provide these functions. Return types differ for `year`/`month`/`day`/`hour`/`minute`/`second` — see note below.
1651
+
1652
+ | Function | SQL (MySQL / PG / SQLite) | Returns |
1653
+ |---|---|---|
1654
+ | `now()` | `NOW()` / `NOW()` / `datetime('now')` | `Date` |
1655
+ | `curDate()` | `CURDATE()` / `CURRENT_DATE` / `date('now')` | `Date` |
1656
+ | `year(col)` | `YEAR(col)` / `EXTRACT(YEAR FROM col)::integer` / `strftime('%Y', col)` | `number` (SQLite: `string`) |
1657
+ | `month(col)` | `MONTH(col)` / `EXTRACT(MONTH FROM col)::integer` / `strftime('%m', col)` | `number` (SQLite: `string`) |
1658
+ | `day(col)` | `DAY(col)` / `EXTRACT(DAY FROM col)::integer` / `strftime('%d', col)` | `number` (SQLite: `string`) |
1659
+ | `hour(col)` | `HOUR(col)` / `EXTRACT(HOUR FROM col)::integer` / `strftime('%H', col)` | `number` (SQLite: `string`) |
1660
+ | `minute(col)` | `MINUTE(col)` / `EXTRACT(MINUTE FROM col)::integer` / `strftime('%M', col)` | `number` (SQLite: `string`) |
1661
+ | `second(col)` | `SECOND(col)` / `EXTRACT(SECOND FROM col)::integer` / `strftime('%S', col)` | `number` (SQLite: `string`) |
1662
+
1663
+ > **Note:** `year`, `month`, `day`, `hour`, `minute`, and `second` return `string` on SQLite because `strftime()` always returns text (e.g. `'2024'`, `'03'`). MySQL and PostgreSQL return `number`.
1664
+
1665
+ DateTime column args also accept `SQLExpr<Date>`, enabling composition:
1666
+
1667
+ ```typescript
1668
+ prisma.$from("Post").select(({ year, now }) => year(now()), "y");
1669
+ ```
1670
+
1671
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=upper
1672
+ prisma.$from("User")
1673
+ .select(({ upper }) => upper("User.name"), "uname");
1674
+ ```
1675
+
1676
+ String fns accept a `SQLExpr<string>` as input, enabling composition:
1677
+
1678
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=lower
1679
+ prisma.$from("User")
1680
+ .select(({ lower }) => lower("User.name"), "lname");
1681
+ ```
1682
+
1683
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=replace
1684
+ prisma.$from("User")
1685
+ .select(({ replace }) => replace("User.email", "@example.com", ""), "handle");
1686
+ ```
1687
+
1688
+ #### Math Functions (all dialects)
1689
+
1690
+ | Function | SQL | Returns |
1691
+ |---|---|---|
1692
+ | `abs(col)` | `ABS(col)` | `number` |
1693
+ | `ceil(col)` | `CEIL(col)` | `number` |
1694
+ | `floor(col)` | `FLOOR(col)` | `number` |
1695
+ | `round(col, decimals?)` | `ROUND(col)` / `ROUND(col, n)` | `number` |
1696
+ | `power(base, exp)` | `POWER(base, exp)` | `number` |
1697
+ | `sqrt(col)` | `SQRT(col)` | `number` |
1698
+ | `mod(col, divisor)` | `MOD(col, divisor)` | `number` |
1699
+ | `sign(col)` | `SIGN(col)` | `number` |
1700
+ | `exp(col)` | `EXP(col)` | `number` |
1701
+
1702
+ Math fns accept `SQLExpr<number>` or a column reference, enabling composition:
1703
+
1704
+ ```typescript
1705
+ // Absolute value of a literal
1706
+ prisma.$from("User")
1707
+ .select(({ abs, lit }) => abs(lit(-5)), "absVal");
1708
+
1709
+ // Round to 2 decimal places
1710
+ prisma.$from("User")
1711
+ .select(({ round, lit }) => round(lit(4.567), 2), "val");
1712
+
1713
+ // Compose: sqrt(power(x, 2))
1714
+ prisma.$from("User")
1715
+ .select(({ sqrt, power }) => sqrt(power("User.age", 2)), "val");
1716
+ ```
1717
+
1718
+ #### Control Flow Functions (all dialects)
1719
+
1720
+ | Function | SQL | Returns |
1721
+ |---|---|---|
1722
+ | `cond(criteria)` | *(WhereCriteria → SQL condition string)* | `SQLExpr<boolean>` |
1723
+ | `coalesce(...args)` | `COALESCE(a, b, ...)` | `SQLExpr<T>` |
1724
+ | `nullif(expr1, expr2)` | `NULLIF(a, b)` | `SQLExpr<T \| null>` |
1725
+ | `caseWhen(cases, elseVal?)` | `CASE WHEN ... THEN ... END` | `SQLExpr<T \| null>` |
1726
+
1727
+ `cond()` converts a `WhereCriteria` object into a `SQLExpr<unknown>` — useful when you need a condition expression outside of a dedicated function. Note: `$if()`/`iif()` and `caseWhen()` all accept `WhereCriteria` directly, so `cond()` is rarely needed.
1728
+
1729
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns-control-flow.ts region=coalesce
1730
+ prisma.$from("User")
1731
+ .select(({ coalesce, lit }) => coalesce("User.email", lit("unknown")), "contact")
1732
+ ```
1733
+
1734
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns-control-flow.ts region=nullif
1735
+ prisma.$from("User")
1736
+ .select(({ nullif, lit }) => nullif(lit(0), lit(0)), "val")
1737
+ ```
1738
+
1739
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns-control-flow.ts region=case-when
1740
+ prisma.$from("User")
1741
+ .select(({ caseWhen, lit }) => caseWhen([
1742
+ { when: { age: { op: ">=", value: 18 } }, then: lit("adult") },
1743
+ ], lit("minor")), "status")
1744
+ ```
1745
+
1746
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns-control-flow.ts region=cond
1747
+ prisma.$from("User")
1748
+ .select(({ cond }) => cond({ age: { op: ">", value: 0 } }), "flag")
1749
+ ```
1750
+
1751
+ #### Combining with `.groupBy()`
1752
+
1753
+ ```typescript file=../usage-sqlite-v7/tests/readme/select-fns.ts region=count-groupby
1754
+ prisma.$from("User")
1755
+ .join("Post", "authorId", "User.id")
1756
+ .groupBy(["User.name"])
1757
+ .select("User.name")
1758
+ .select(({ countAll }) => countAll(), "postCount");
1759
+ ```
1760
+
1761
+ ##### SQL
1762
+ ```sql
1763
+ SELECT User.name, COUNT(*) AS `postCount`
1764
+ FROM User
1765
+ JOIN Post ON Post.authorId = User.id
1766
+ GROUP BY User.name;
1767
+ ```
1768
+
1769
+ ---
1770
+
1771
+ ### MySQL-specific
1772
+
1773
+ | Function | SQL | Returns |
1774
+ |---|---|---|
1775
+ | `groupConcat(col, sep?)` | `GROUP_CONCAT(col SEPARATOR sep)` | `string` |
1776
+ | `bitAnd(col)` | `BIT_AND(col)` | `number` |
1777
+ | `bitOr(col)` | `BIT_OR(col)` | `number` |
1778
+ | `bitXor(col)` | `BIT_XOR(col)` | `number` |
1779
+ | `stddev(col)` | `STDDEV(col)` | `number` |
1780
+ | `stddevSamp(col)` | `STDDEV_SAMP(col)` | `number` |
1781
+ | `variance(col)` | `VARIANCE(col)` | `number` |
1782
+ | `varSamp(col)` | `VAR_SAMP(col)` | `number` |
1783
+ | `jsonArrayAgg(col)` | `JSON_ARRAYAGG(col)` | `JSONValue` |
1784
+ | `jsonObjectAgg(key, val)` | `JSON_OBJECTAGG(key, val)` | `JSONValue` |
1785
+ | `concat(...cols)` | `CONCAT(a, b, ...)` | `string` |
1786
+ | `substring(col, start, len?)` | `SUBSTRING(col, start, len)` | `string` |
1787
+ | `left(col, n)` | `LEFT(col, n)` | `string` |
1788
+ | `right(col, n)` | `RIGHT(col, n)` | `string` |
1789
+ | `repeat(col, n)` | `REPEAT(col, n)` | `string` |
1790
+ | `reverse(col)` | `REVERSE(col)` | `string` |
1791
+ | `lpad(col, len, pad)` | `LPAD(col, len, 'pad')` | `string` |
1792
+ | `rpad(col, len, pad)` | `RPAD(col, len, 'pad')` | `string` |
1793
+ | `locate(substr, col)` | `LOCATE('substr', col)` | `number` |
1794
+ | `space(n)` | `SPACE(n)` | `string` |
1795
+ | `$if(cond, trueVal, falseVal)` | `IF(cond, a, b)` | `T` |
1796
+ | `ifNull(col, fallback)` | `IFNULL(col, fallback)` | `NonNullable<T>` |
1797
+ | `greatest(...args)` | `GREATEST(a, b, ...)` | `T \| null` |
1798
+ | `least(...args)` | `LEAST(a, b, ...)` | `T \| null` |
1799
+ | `dateAdd(col, n, unit)` | `DATE_ADD(col, INTERVAL n unit)` | `Date` |
1800
+ | `dateSub(col, n, unit)` | `DATE_SUB(col, INTERVAL n unit)` | `Date` |
1801
+ | `dateFormat(col, fmt)` | `DATE_FORMAT(col, 'fmt')` | `string` |
1802
+ | `dateDiff(d1, d2)` | `DATEDIFF(d1, d2)` | `number` |
1803
+ | `quarter(col)` | `QUARTER(col)` | `number` |
1804
+ | `weekOfYear(col)` | `WEEKOFYEAR(col)` | `number` |
1805
+ | `dayName(col)` | `DAYNAME(col)` | `string` |
1806
+ | `lastDay(col)` | `LAST_DAY(col)` | `Date` |
1807
+ | `pi()` | `PI()` | `number` |
1808
+ | `ln(x)` | `LN(x)` | `number` |
1809
+ | `log(x)` | `LOG(x)` | `number` |
1810
+ | `log2(x)` | `LOG2(x)` | `number` |
1811
+ | `log10(x)` | `LOG10(x)` | `number` |
1812
+ | `truncate(x, n)` | `TRUNCATE(x, n)` | `number` |
1813
+ | `rand(seed?)` | `RAND()` / `RAND(seed)` | `number` |
1814
+
1815
+ > **Note:** MySQL `LOG(x)` is natural log (ln). `rand()` returns a float in [0, 1).
1816
+
1817
+ `unit` is one of: `'MICROSECOND' | 'SECOND' | 'MINUTE' | 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'QUARTER' | 'YEAR'`
1818
+
1819
+ > **Note:** `jsonArrayAgg` and `jsonObjectAgg` require MySQL 5.7.22+.
1820
+
1821
+ ---
1822
+
1823
+ ### PostgreSQL-specific
1824
+
1825
+ | Function | SQL | Returns |
1826
+ |---|---|---|
1827
+ | `greatest(...args)` | `GREATEST(a, b, ...)` | `T` |
1828
+ | `least(...args)` | `LEAST(a, b, ...)` | `T` |
1829
+ | `stringAgg(col, sep)` | `STRING_AGG(col, sep)` | `string` |
1830
+ | `arrayAgg(col)` | `ARRAY_AGG(col)` | `unknown[]` |
1831
+ | `stddevPop(col)` | `STDDEV_POP(col)` | `number` |
1832
+ | `stddevSamp(col)` | `STDDEV_SAMP(col)` | `number` |
1833
+ | `varPop(col)` | `VAR_POP(col)` | `number` |
1834
+ | `varSamp(col)` | `VAR_SAMP(col)` | `number` |
1835
+ | `boolAnd(col)` | `BOOL_AND(col)` | `boolean` |
1836
+ | `boolOr(col)` | `BOOL_OR(col)` | `boolean` |
1837
+ | `jsonAgg(col)` | `JSON_AGG(col)` | `JSONValue[]` |
1838
+ | `bitAnd(col)` | `BIT_AND(col)` | `number` |
1839
+ | `bitOr(col)` | `BIT_OR(col)` | `number` |
1840
+ | `jsonObjectAgg(key, val)` | `JSON_OBJECT_AGG(key, val)` | `JSONValue` |
1841
+ | `concat(...cols)` | `CONCAT(a, b, ...)` | `string` |
1842
+ | `substring(col, start, len?)` | `SUBSTRING(col, start, len)` | `string` |
1843
+ | `left(col, n)` | `LEFT(col, n)` | `string` |
1844
+ | `right(col, n)` | `RIGHT(col, n)` | `string` |
1845
+ | `repeat(col, n)` | `REPEAT(col, n)` | `string` |
1846
+ | `reverse(col)` | `REVERSE(col)` | `string` |
1847
+ | `lpad(col, len, pad)` | `LPAD(col, len, 'pad')` | `string` |
1848
+ | `rpad(col, len, pad)` | `RPAD(col, len, 'pad')` | `string` |
1849
+ | `initcap(col)` | `INITCAP(col)` | `string` |
1850
+ | `strpos(col, substr)` | `STRPOS(col, 'substr')` | `number` |
1851
+ | `splitPart(col, delimiter, field)` | `SPLIT_PART(col, 'delimiter', field)` | `string` |
1852
+ | `btrim(col, chars?)` | `BTRIM(col)` / `BTRIM(col, 'chars')` | `string` |
1853
+ | `md5(col)` | `MD5(col)` | `string` |
1854
+ | `extract(field, col)` | `EXTRACT(field FROM col)` | `number` |
1855
+ | `dateTrunc(unit, col)` | `DATE_TRUNC('unit', col)` | `Date` |
1856
+ | `age(ts1, ts2?)` | `AGE(ts1)` / `AGE(ts1, ts2)` | `string` (PG `interval` mapped to string) |
1857
+ | `toDate(text, fmt)` | `TO_DATE(text, 'fmt')` | `Date` |
1858
+ | `pi()` | `PI()` | `number` |
1859
+ | `ln(x)` | `LN(x)` | `number` |
1860
+ | `log(x)` | `LOG(x)` | `number` |
1861
+ | `logBase(base, x)` | `LOG(base, x)` | `number` |
1862
+ | `trunc(x, n?)` | `TRUNC(x)` / `TRUNC(x, n)` | `number` |
1863
+ | `div(x, y)` | `DIV(x, y)` | `number` |
1864
+ | `random()` | `RANDOM()` | `number` |
1865
+
1866
+ > **Note:** PG `LOG(x)` is log base 10 (unlike MySQL where it is natural log). `random()` returns a float in [0, 1).
1867
+
1868
+ `field` for `extract` is one of: `'YEAR' | 'MONTH' | 'DAY' | 'HOUR' | 'MINUTE' | 'SECOND' | 'DOW' | 'DOY' | 'EPOCH' | 'WEEK' | 'QUARTER'`
1869
+
1870
+ `unit` for `dateTrunc` is one of: `'microseconds' | 'milliseconds' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | 'decade' | 'century' | 'millennium'`
1871
+
1872
+ ---
1873
+
1874
+ ### SQLite-specific
1875
+
1876
+ | Function | SQL | Returns |
1877
+ |---|---|---|
1878
+ | `iif(cond, trueVal, falseVal)` | `IIF(cond, a, b)` | `T` |
1879
+ | `ifNull(col, fallback)` | `IFNULL(col, fallback)` | `NonNullable<T>` |
1880
+ | `groupConcat(col, sep?)` | `GROUP_CONCAT(col, sep)` | `string` |
1881
+ | `total(col)` | `TOTAL(col)` | `number` |
1882
+ | `concat(...cols)` | `a \|\| b \|\| ...` | `string` |
1883
+ | `substr(col, start, len?)` | `SUBSTR(col, start, len)` | `string` |
1884
+ | `instr(col, substr)` | `INSTR(col, 'substr')` | `number` |
1885
+ | `char(...codes)` | `CHAR(n1, n2, ...)` | `string` |
1886
+ | `hex(col)` | `HEX(col)` | `string` |
1887
+ | `unicode(col)` | `UNICODE(col)` | `number` |
1888
+ | `strftime(fmt, col)` | `strftime('fmt', col)` | `string` |
1889
+ | `julianday(col)` | `julianday(col)` | `number` |
1890
+ | `date(col)` | `date(col)` | `string` |
1891
+ | `datetime(col)` | `datetime(col)` | `string` |
1892
+ | `random()` | `RANDOM()` | `number` |
1893
+ | `log(x)` | `LOG(x)` | `number` |
1894
+ | `log2(x)` | `LOG2(x)` | `number` |
1895
+ | `log10(x)` | `LOG10(x)` | `number` |
1896
+
1897
+ > **Note:** SQLite `random()` returns a random integer (not float). `log`, `log2`, `log10` require SQLite 3.35+. `total()` behaves like `SUM()` but returns `0.0` instead of `NULL` for empty sets. SQLite uses the `||` operator for string concatenation.
1898
+
1899
+ > **Note:** SQLite stores `DateTime` differently between Prisma v6 (integer milliseconds) and v7 (ISO 8601 text). All SQLite datetime fns automatically normalise both formats via a `CASE WHEN typeof(...) = 'integer' THEN datetime(.../1000, 'unixepoch') ELSE ... END` wrapper, so they work correctly on both versions.
1900
+
1901
+ ---
1902
+
1903
+ ## Future updates
1904
+
1905
+ - Support specifying `JOIN` type [issue#2](https://github.com/adrianbrowning/prisma-ts-select/issues/2)
1906
+ - Support additional Select Functions
1907
+ - [JSON #9](https://github.com/adrianbrowning/prisma-ts-select/issues/9)
1908
+ - [CAST #71](https://github.com/adrianbrowning/prisma-ts-select/issues/71)
1909
+ - [whereRaw supporting Prisma.sql](https://github.com/adrianbrowning/prisma-ts-select/issues/29)
1910
+
1911
+ ## Changelog / Versioning
1912
+ Changelog is available [here](https://github.com/adrianbrowning/prisma-ts-select/releases). We use [semantic versioning](https://semver.org/) for versioning.
1913
+
1914
+ ## License
1915
+ This project is licensed under the MIT License. See the LICENSE file for details.
989
1916
 
990
1917