prisma-ts-select 0.0.33 → 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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1455 -263
  3. package/dist/bin.cjs +1 -1
  4. package/dist/bin.js +1 -1
  5. package/dist/chunk-47KZVQLD.js +283 -0
  6. package/dist/chunk-54D2J5AR.cjs +291 -0
  7. package/dist/extend/dialects/index.d.ts +13 -0
  8. package/dist/extend/dialects/index.js +186 -0
  9. package/dist/extend/dialects/mysql-v6.d.ts +100 -0
  10. package/dist/extend/dialects/mysql-v6.js +152 -0
  11. package/dist/extend/dialects/mysql-v7.d.ts +6 -0
  12. package/dist/extend/dialects/mysql-v7.js +138 -0
  13. package/dist/extend/dialects/mysql.d.ts +90 -0
  14. package/dist/extend/dialects/mysql.js +156 -0
  15. package/dist/extend/dialects/postgresql-v6.d.ts +97 -0
  16. package/dist/extend/dialects/postgresql-v6.js +136 -0
  17. package/dist/extend/dialects/postgresql-v7.d.ts +97 -0
  18. package/dist/extend/dialects/postgresql-v7.js +136 -0
  19. package/dist/extend/dialects/postgresql.d.ts +89 -0
  20. package/dist/extend/dialects/postgresql.js +147 -0
  21. package/dist/extend/dialects/shared.d.ts +6 -0
  22. package/dist/extend/dialects/shared.js +5 -0
  23. package/dist/extend/dialects/sqlite.d.ts +63 -0
  24. package/dist/extend/dialects/sqlite.js +138 -0
  25. package/dist/extend/dialects/types.d.ts +12 -0
  26. package/dist/extend/dialects/types.js +4 -0
  27. package/dist/extend/extend.d.ts +342 -57
  28. package/dist/extend/extend.js +813 -161
  29. package/dist/extend/sql-expr-BaKWzJ-r.d.ts +10 -0
  30. package/dist/extend/types-D84lxYVc.d.ts +5 -0
  31. package/dist/generator.cjs +1 -1
  32. package/dist/generator.js +1 -1
  33. package/package.json +52 -41
  34. package/built/extend.cjs +0 -565
  35. package/built/extend.d.cts +0 -451
  36. package/built/extend.d.ts +0 -451
  37. package/built/extend.js +0 -563
  38. package/dist/chunk-G66FOFCO.cjs +0 -195
  39. package/dist/chunk-GBXPF5FT.js +0 -187
  40. package/dist/extend/extend.cjs +0 -277
  41. package/dist/extend/extend.d.cts +0 -222
package/README.md CHANGED
@@ -1,31 +1,78 @@
1
1
  # prisma-ts-select
2
2
 
3
3
  ![npm version](https://img.shields.io/npm/v/prisma-ts-select)
4
+ ![build](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/CI.yml/badge.svg)
4
5
  ![license](https://img.shields.io/github/license/adrianbrowning/prisma-ts-select)
5
- <!--![build](https://github.com/adrianbrowning/prisma-ts-select/actions/workflows/build.yml/badge.svg)-->
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) |
6
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)
14
25
  * [API](#api)
15
26
  + [`.$from`](#from)
27
+ - [Example](#example)
28
+ - [Example - With Table Alias](#example---with-table-alias)
29
+ * [SQL](#sql)
30
+ - [Example - Inline Alias Syntax](#example---inline-alias-syntax)
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)
39
+ + [Table Aliases](#table-aliases)
40
+ - [Table Alias Syntax Options](#table-alias-syntax-options)
41
+ - [Basic Table Alias](#basic-table-alias)
42
+ * [SQL](#sql-2)
43
+ - [Table Aliases with Joins](#table-aliases-with-joins)
44
+ * [Inline Alias Syntax](#inline-alias-syntax)
45
+ * [Object Syntax](#object-syntax)
46
+ * [SQL](#sql-3)
47
+ - [Self-Joins with Aliases](#self-joins-with-aliases)
48
+ * [SQL](#sql-4)
49
+ - [Table.* with Aliases](#table-with-aliases)
50
+ * [SQL](#sql-5)
51
+ * [SQL](#sql-6)
16
52
  + [Joins](#joins)
53
+ - [Dialect Support](#dialect-support)
54
+ - [Nullability Semantics](#nullability-semantics)
17
55
  - [`.join`](#join)
18
- * [Example](#example)
19
- * [SQL](#sql)
56
+ * [Example](#example-1)
57
+ * [SQL](#sql-7)
20
58
  * [Parameters](#parameters)
21
59
  - [`.joinUnsafeTypeEnforced`](#joinunsafetypeenforced)
22
- * [Example](#example-1)
23
- * [SQL](#sql-1)
60
+ * [Example](#example-2)
61
+ * [SQL](#sql-8)
24
62
  * [Parameters](#parameters-1)
25
63
  - [`.joinUnsafeIgnoreType`](#joinunsafeignoretype)
26
- * [Example](#example-2)
27
- * [SQL](#sql-2)
64
+ * [Example](#example-3)
65
+ * [SQL](#sql-9)
28
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)
29
76
  + [Where](#where)
30
77
  - [`.where`](#where)
31
78
  * [TypeSyntax](#typesyntax)
@@ -37,54 +84,62 @@
37
84
  + [$NOT](#not)
38
85
  + [$NOR](#nor)
39
86
  - [`.whereNotNull`](#wherenotnull)
40
- * [Example](#example-3)
41
- * [SQL](#sql-3)
42
- - [`.whereIsNull`](#whereisnull)
43
87
  * [Example](#example-4)
44
- * [SQL](#sql-4)
45
- - [`.whereRaw`](#whereraw)
88
+ * [SQL](#sql-10)
89
+ - [`.whereIsNull`](#whereisnull)
46
90
  * [Example](#example-5)
47
- * [SQL](#sql-5)
91
+ * [SQL](#sql-11)
92
+ - [`.whereRaw`](#whereraw)
93
+ * [Example](#example-6)
94
+ * [SQL](#sql-12)
48
95
  + [Group By](#group-by)
49
- - [Example](#example-6)
50
- - [SQL](#sql-6)
96
+ - [Example](#example-7)
97
+ - [SQL](#sql-13)
51
98
  + [Selecting](#selecting)
52
99
  - [`.selectDistinct`](#selectdistinct)
53
- - [Example](#example-7)
54
- - [SQL](#sql-7)
100
+ - [Example](#example-8)
101
+ - [SQL](#sql-14)
55
102
  - [`.selectAll`](#selectall)
56
103
  - [Example - Single Table](#example---single-table)
57
- * [SQL](#sql-8)
104
+ * [SQL](#sql-15)
58
105
  - [Example - Join table](#example---join-table)
59
- * [SQL](#sql-9)
106
+ * [SQL](#sql-16)
107
+ - [`.selectAllOmit`](#selectallomit)
60
108
  - [`.select`](#select)
61
109
  - [Example - `*`](#example---)
62
- * [SQL](#sql-10)
110
+ * [SQL](#sql-17)
111
+ - [Example - `Table.*` (Single Table)](#example---table-single-table)
112
+ * [SQL](#sql-18)
113
+ - [Example - `Table.*` (With Join)](#example---table-with-join)
114
+ * [SQL](#sql-19)
63
115
  - [Example - Chained](#example---chained)
64
- * [SQL](#sql-11)
116
+ * [SQL](#sql-20)
65
117
  - [Example - Join + Chained](#example---join--chained)
66
- * [SQL](#sql-12)
118
+ * [SQL](#sql-21)
119
+ - [Example - Column Aliases](#example---column-aliases)
120
+ * [SQL](#sql-22)
121
+ - [Example - Aliases with Joins](#example---aliases-with-joins)
122
+ * [SQL](#sql-23)
67
123
  + [Having](#having)
68
- - [Example](#example-8)
69
- * [SQL](#sql-13)
70
- + [Order By](#order-by)
71
124
  - [Example](#example-9)
72
- * [SQL](#sql-14)
73
- + [Limit](#limit)
125
+ * [SQL](#sql-24)
126
+ + [Order By](#order-by)
74
127
  - [Example](#example-10)
75
- * [SQL](#sql-15)
76
- + [Offset](#offset)
128
+ * [SQL](#sql-25)
129
+ + [Limit](#limit)
77
130
  - [Example](#example-11)
78
- * [SQL](#sql-16)
131
+ * [SQL](#sql-26)
132
+ + [Offset](#offset)
133
+ - [Example](#example-12)
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)
79
140
  * [Future updates](#future-updates)
80
141
  * [Changelog / Versioning](#changelog--versioning)
81
142
  * [License](#license)
82
- - [prisma-ts-select](#prisma-ts-select)
83
- * [Install](#install)
84
- * [Setup](#setup)
85
- + [Extract](#extract)
86
- * [Usage](#usage-1)
87
-
88
143
  <!-- tocstop -->
89
144
 
90
145
  ## Summary
@@ -93,13 +148,9 @@
93
148
  It simplifies the selection of fields in Prisma queries, ensuring type safety and reducing boilerplate when working with nested fields.
94
149
  Ideal for developers seeking an efficient, type-safe way to select data with Prisma in TypeScript.
95
150
 
96
- [!NOTE]
97
- > This has been built mostly around MySQL. Most methods should work across the board.<br/>
98
- > Known exceptions include:
99
- > - HAVING
100
- > - SQLite
101
- > - Requires you to have either an aggregate function in the `SELECT` or make use of `GROUP BY`
102
- > - 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`.
103
154
 
104
155
 
105
156
  ## Installation
@@ -111,15 +162,108 @@ npm install prisma-ts-select
111
162
  pnpm add prisma-ts-select
112
163
  ```
113
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
+
114
261
  ## Supported DBs
115
262
 
116
- I have tested this currently on the following databases.
263
+ Fully tested on:
117
264
 
118
265
  - SQLite
119
266
  - MySQL
120
-
121
- Most items should also work for
122
-
123
267
  - PostgreSQL
124
268
 
125
269
  Other DBs will be added when I have chance.
@@ -180,13 +324,221 @@ The way the methods are chained, are heavily inspired by [Dr Milan Milanović](h
180
324
  ### `.$from`
181
325
  This takes the `base` table to work from.
182
326
 
327
+ #### Example
328
+ ```typescript file=../usage/tests/readme/from-basic.ts region=example
329
+ prisma.$from("User");
330
+ ```
331
+
332
+ #### Example - With Table Alias
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")
363
+ ```
364
+
365
+ ##### SQL
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;
369
+ ```
370
+
371
+ #### Example — CTE as base table
372
+
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")
382
+ ```
383
+
384
+ ##### SQL
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;
408
+ ```
409
+
410
+ ### Table Aliases
411
+
412
+ Table aliases allow you to give tables shorter or more meaningful names in your queries. This is especially useful for:
413
+ - Self-joins (joining a table to itself)
414
+ - Long table names
415
+ - Clearer query readability
416
+
417
+ #### Table Alias Syntax Options
418
+
419
+ Multiple syntaxes supported:
420
+ - **Inline in .$from()**: `prisma.$from("User u")` - Note: Second parameter syntax `.$from("User", "u")` is NOT supported
421
+ - **Inline in .join()**: `.join("Post p", "authorId", "User.id")`
422
+ - **Object syntax**: `.join({table: "Post", src: "authorId", on: "User.id", alias: "p"})`
423
+
424
+ #### Table Aliases with Joins
425
+
426
+ ##### Inline Alias Syntax
427
+ ```typescript file=../usage/tests/readme/table-alias.ts region=inline-join
428
+ prisma.$from("User u")
429
+ .join("Post p", "authorId", "u.id")
430
+ .select("u.name")
431
+ .select("p.title");
432
+ ```
433
+
434
+ ##### Object Syntax
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");
440
+ ```
441
+
442
+ ##### SQL
443
+ ```sql file=../usage/tests/readme/table-alias.ts region=inline-join-sql
444
+ SELECT name, title
445
+ FROM User AS `u`
446
+ JOIN Post AS `p` ON p.authorId = u.id;
447
+ ```
448
+
449
+ **Note:** The object syntax provides a foundation for future enhancements like multiple join conditions and complex WHERE-style conditions in joins.
450
+
451
+ #### Self-Joins with Aliases
452
+
453
+ Self-joins require aliases to distinguish between the different "instances" of the same table:
454
+
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");
460
+ ```
461
+
462
+ ##### SQL
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;
469
+ ```
470
+
471
+ #### Table.* with Aliases
472
+
473
+ You can use the `alias.*` syntax to select all columns from an aliased table:
474
+
475
+ ```typescript file=../usage/tests/readme/table-alias.ts region=star-single
476
+ prisma.$from("User u")
477
+ .select("u.*");
478
+ ```
479
+
480
+ ##### SQL
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`;
488
+ ```
489
+
490
+ With joins:
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.*");
496
+ ```
497
+
498
+ ##### SQL
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;
513
+ ```
514
+
183
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
+
184
536
  #### `.join`
185
537
 
186
538
  Using the defined links (foreign keys) defined in the schema, provides a type-safe way of joining on tables.
187
539
 
188
540
  ##### Example
189
- ```typescript
541
+ ```typescript file=../usage/tests/readme/join-basic.ts region=example
190
542
  prisma.$from("User")
191
543
  .join("Post", "authorId", "User.id");
192
544
  ```
@@ -197,24 +549,113 @@ prisma.$from("User")
197
549
 
198
550
  The resulting SQL will look like:
199
551
 
200
- ```sql
552
+ ```sql file=../usage/tests/readme/join-basic.ts region=join-basic-sql
201
553
  FROM User
202
- JOIN Post ON authorId = User.id;
554
+ JOIN Post ON Post.authorId = User.id;
203
555
  ```
204
556
 
205
557
  ##### Parameters
206
- | column | Description |
207
- |-------------|--------------------------------------------------------------------------------------------------------------------------------------|
208
- | `table` | The table to join on. <br/>TS autocomplete will show tables that can join with previously defined tables on. |
209
- | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
210
- | `reference` | `Table.Column` to a previously defined table (either the base, or another join), with a FK that is defined in the schema definition. |
558
+ | column | Description |
559
+ |-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
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. |
561
+ | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
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`. |
565
+
566
+ **Alternative Syntaxes:**
567
+ ```typescript
568
+ // Inline alias
569
+ .join("Post p", "authorId", "User.id")
570
+
571
+ // Object syntax
572
+ .join({
573
+ table: "Post",
574
+ src: "authorId",
575
+ on: "User.id",
576
+ alias: "p", // optional
577
+ joinType: "LEFT", // optional
578
+ where: { "Post.published": true } // optional
579
+ })
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.
211
652
 
212
653
  #### `.joinUnsafeTypeEnforced`
213
654
 
214
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.
215
656
 
216
657
  ##### Example
217
- ```typescript
658
+ ```typescript file=../usage/tests/readme/join-unsafe.ts region=type-enforced
218
659
  prisma.$from("User")
219
660
  .joinUnsafeTypeEnforced("Post", "title", "User.name");
220
661
  ```
@@ -223,24 +664,26 @@ prisma.$from("User")
223
664
  ##### SQL
224
665
  The resulting SQL will look like:
225
666
 
226
- ```sql
227
- FROM User
667
+ ```sql file=../usage/tests/readme/join-unsafe.ts region=type-enforced-sql
668
+ FROM User
228
669
  JOIN Post ON Post.title = User.name;
229
670
  ```
230
671
 
231
672
  ##### Parameters
232
- | column | Description |
233
- |-------------|----------------------------------------------------------------------------------------------------------------------------|
234
- | `table` | The table to join on. <br/>TS autocomplete will show tables that can join with previously defined tables on. |
235
- | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
236
- | `reference` | `Table.Column` to a previously defined table (either the base, or another join), with a column that is of the same type. |
673
+ | column | Description |
674
+ |-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
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. |
676
+ | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
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). |
237
680
 
238
681
  #### `.joinUnsafeIgnoreType`
239
682
 
240
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.
241
684
 
242
685
  ##### Example
243
- ```typescript
686
+ ```typescript file=../usage/tests/readme/join-unsafe.ts region=ignore-type
244
687
  prisma.$from("User")
245
688
  .joinUnsafeIgnoreType("Post", "id", "User.name");
246
689
  ```
@@ -249,17 +692,255 @@ prisma.$from("User")
249
692
  ##### SQL
250
693
  The resulting SQL will look like:
251
694
 
695
+ ```sql file=../usage/tests/readme/join-unsafe.ts region=ignore-type-sql
696
+ FROM User
697
+ JOIN Post ON Post.id = User.name;
698
+ ```
699
+
700
+ ##### Parameters
701
+ | column | Description |
702
+ |-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
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. |
704
+ | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
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
252
927
  ```sql
253
- FROM User
254
- JOIN Post ON Post.id = User.name
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")
255
941
  ```
256
942
 
257
- ##### Parameters
258
- | column | Description |
259
- |-------------|----------------------------------------------------------------------------------------------------------------------------|
260
- | `table` | The table to join on. <br/>TS autocomplete will show tables that can join with previously defined tables on. |
261
- | `field` | Column on table. <br/>TS autocomplete will show known columns that this table, can join with previously defined tables on. |
262
- | `reference` | `Table.Column` to a previously defined table (either the base, or another join). Referencing any column, of any type. |
943
+ ---
263
944
 
264
945
  ### Where
265
946
 
@@ -270,9 +951,12 @@ The `where` syntax takes inspiration from how mongoDB does queries.
270
951
  ##### TypeSyntax
271
952
  ```TypeScript
272
953
  type WhereClause = {
273
- "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
274
958
  "$AND": [WhereClause, ...Array<WhereClause>],
275
- "$OR": [WhereClause, ...Array<WhereClause>],
959
+ "$OR": [WhereClause, ...Array<WhereClause>],
276
960
  "$NOT": [WhereClause, ...Array<WhereClause>],
277
961
  "$NOR": [WhereClause, ...Array<WhereClause>]
278
962
  }
@@ -293,6 +977,7 @@ type WhereClause = {
293
977
  | < | | Numbers, Date |
294
978
  | <= | | Numbers, Date |
295
979
  | != | | Numbers, String, Date |
980
+ | = | | Numbers, String, Date |
296
981
 
297
982
 
298
983
  ##### Examples
@@ -303,155 +988,190 @@ type WhereClause = {
303
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%")` |
304
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")))` |
305
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%')` |
306
993
 
307
994
 
308
995
  ###### Columns
309
- ```typescript
996
+ ```typescript file=../usage/tests/readme/where.ts region=columns
310
997
  prisma.$from("User")
311
- .join("Post", "id", "User.name")
312
- .where({
313
- "User.age": 20,
314
- "User.name": {op: "LIKE", value: "Stuart%"},
315
- });
998
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
999
+ .where({
1000
+ "User.age": 20,
1001
+ "User.name": {op: "LIKE", value: "Stuart%"},
1002
+ });
316
1003
  ```
317
1004
 
318
1005
  ###### $AND
319
- ```typescript
1006
+ ```typescript file=../usage/tests/readme/where.ts region=and
320
1007
  prisma.$from("User")
321
- .join("Post", "id", "User.name")
322
- .where({
323
- $AND: [
324
- {"User.age": {op: ">", value: 20}},
325
- {"User.age": {op: "<", value: 60}},
326
- ]
327
- });
1008
+ .joinUnsafeIgnoreType("Post", "id", "User.name")
1009
+ .where({
1010
+ $AND: [
1011
+ {"User.age": {op: ">", value: 20}},
1012
+ {"User.age": {op: "<", value: 60}},
1013
+ ]
1014
+ });
328
1015
  ```
329
1016
 
330
1017
  ###### $OR
331
- ```typescript
1018
+ ```typescript file=../usage/tests/readme/where.ts region=or
332
1019
  prisma.$from("User")
333
- .join("Post", "id", "User.name")
334
- .where({
335
- $OR: [
336
- {"User.name": {op: "LIKE", value: "a%"}},
337
- {"User.name": {op: "LIKE", value: "d%"}},
338
- ]
339
- });
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
+ });
340
1027
  ```
341
1028
 
342
1029
  ###### $NOT
343
- ```typescript
1030
+ ```typescript file=../usage/tests/readme/where.ts region=not
344
1031
  prisma.$from("User")
345
- .join("Post", "id", "User.name")
346
- .where({
347
- $NOT: [
348
- {"User.age": 20},
349
- {
350
- "User.age": {op: "=", value: 60},
351
- "User.name": "Bob",
352
- },
353
- ]
354
- });
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
+ });
355
1042
  ```
356
1043
 
357
1044
  ###### $NOR
358
- ```typescript
1045
+ ```typescript file=../usage/tests/readme/where.ts region=nor
359
1046
  prisma.$from("User")
360
- .join("Post", "id", "User.name")
361
- .where({
362
- $NOR: [
363
- {"User.age": 20},
364
- {
365
- "User.age": {op: "!=", value: 60},
366
- "User.name": "Bob",
367
- },
368
- ]
369
- });
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
+ });
370
1078
  ```
371
1079
 
372
1080
  #### `.whereNotNull`
373
1081
 
374
- This will remove the `null` type from the union of types of the current table column.
375
- 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.
376
1084
 
377
1085
  ##### Example
378
- ```typescript
1086
+ ```typescript file=../usage-sqlite-v7/tests/readme/whereNotNull.ts region=whereNotNull
379
1087
  prisma.$from("User")
380
- .join("Post", "authorId", "User.id")
381
- .whereNotNull("User.name");
1088
+ .join("Post", "authorId", "User.id")
1089
+ .whereNotNull("User.name")
382
1090
  ```
383
1091
  ![whereNotNull](./assets/whereNotNull.gif)
384
1092
 
385
1093
  ##### SQL
386
1094
  The resulting SQL will look like:
387
1095
 
388
- ```sql
389
- FROM User
390
- JOIN Post ON authorId = User.id
391
- 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);
392
1098
  ```
393
1099
 
394
1100
  #### `.whereIsNull`
395
1101
 
396
- This will remove the NonNull type from the union of types of the current table column.
397
- 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.
398
1103
 
399
1104
  ##### Example
400
- ```typescript
1105
+ ```typescript file=../usage-sqlite-v7/tests/readme/whereNotNull.ts region=whereIsNull
401
1106
  prisma.$from("User")
402
- .join("Post", "authorId", "User.id")
403
- .whereIsNull("Post.content");
1107
+ .join("Post", "authorId", "User.id")
1108
+ .whereIsNull("Post.content")
404
1109
  ```
405
1110
  ![whereIsNull](./assets/whereIsNull.gif)
406
1111
 
407
1112
  ##### SQL
408
1113
  The resulting SQL will look like:
409
1114
 
410
- ```sql
411
- FROM User
412
- JOIN Post ON authorId = User.id
413
- 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);
1117
+ ```
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%';
414
1131
  ```
415
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
+
416
1135
  #### `.whereRaw`
417
1136
 
418
- 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`.
419
1138
 
420
1139
  ##### Example
421
- ```typescript
1140
+ ```typescript file=../usage/tests/readme/where.ts region=raw
422
1141
  prisma.$from("User")
423
- .join("Post", "authorId", "User.id")
424
- .whereRaw("this is a raw where statement");
1142
+ .join("Post", "authorId", "User.id")
1143
+ .whereRaw("this is a raw where statement");
425
1144
  ```
426
1145
 
427
1146
  ##### SQL
428
1147
  The resulting SQL will look like:
429
1148
 
430
- ```sql
431
- FROM User
432
- JOIN Post ON authorId = User.id
433
- 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;
434
1154
  ```
435
1155
 
436
1156
 
437
1157
  ### Group By
438
1158
 
439
- 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.
440
1160
 
441
1161
  #### Example
442
- ```typescript
1162
+ ```typescript file=../usage/tests/readme/groupby.ts region=basic
443
1163
  prisma.$from("User")
444
- .join("Post", "authorId", "User.id")
445
- .groupBy(["name", "Post.content"]);
1164
+ .join("Post", "authorId", "User.id")
1165
+ .groupBy(["name", "Post.content"]);
446
1166
  ```
447
1167
  ![groupBy](./assets/groupBy.gif)
448
1168
 
449
1169
  #### SQL
450
1170
  The resulting SQL will look like:
451
1171
 
452
- ```sql
453
- FROM User
454
- 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
455
1175
  GROUP BY name, Post.content;
456
1176
  ```
457
1177
 
@@ -462,16 +1182,17 @@ GROUP BY name, Post.content;
462
1182
  Will add the keyword `DISTINCT` after the select.
463
1183
 
464
1184
  #### Example
465
- ```typescript
1185
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=distinct
466
1186
  prisma.$from("User")
467
- .selectDistinct();
1187
+ .selectDistinct()
1188
+ .select("name");
468
1189
  ```
469
1190
 
470
1191
  #### SQL
471
1192
  The resulting SQL will look like:
472
1193
 
473
- ```sql
474
- SELECT DISTINCT
1194
+ ```sql file=../usage/tests/readme/select-advanced.ts region=distinct-sql
1195
+ SELECT DISTINCT name
475
1196
  FROM User;
476
1197
  ```
477
1198
 
@@ -482,151 +1203,329 @@ This method will explicitly list all the tables from the `$from` and `.join`. So
482
1203
 
483
1204
 
484
1205
  #### Example - Single Table
485
- ```typescript
1206
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=all-single
486
1207
  prisma.$from("User")
487
- .selectAll();
1208
+ .selectAll();
488
1209
  ```
489
1210
 
490
1211
  ##### SQL
491
1212
  The resulting SQL will look like:
492
1213
 
493
- ```sql
494
- SELECT id, email, name
495
- 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;
496
1221
  ```
497
1222
 
498
1223
  #### Example - Join table
499
- ```typescript
1224
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=all-join
500
1225
  prisma.$from("User")
501
- .join("Post", "authorId", "User.id")
502
- .selectAll();
1226
+ .join("Post", "authorId", "User.id")
1227
+ .selectAll();
503
1228
  ```
504
1229
 
505
1230
  ##### SQL
506
1231
  The resulting SQL will look like:
507
1232
 
508
- ```sql
509
- SELECT User.id, User. email, User.name, Post.id, Post.title, Post.content, Post.published, Post.author, Post.authorId, Post.LastModifiedBy, Post.lastModifiedById
510
- FROM User
511
- 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;
1247
+ ```
1248
+
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"]);
512
1275
  ```
513
1276
 
514
- [//]: # (#### `.selectAllOmit`)
1277
+ > **Note:** `*` and `Table.*` are not valid arguments — use `Table.column` or bare `column` references.
515
1278
 
516
1279
  #### `.select`
517
1280
 
518
- You can supply either; `*` OR `table.field` and then chain them together.
1281
+ You can supply either; `*`, `Table.*` OR `table.field` and then chain them together.
519
1282
 
520
1283
  #### Example - `*`
521
- ```typescript
1284
+ ```typescript file=../usage/tests/readme/select-star.ts region=example
522
1285
  prisma.$from("User")
523
- .select("*");
1286
+ .select("*");
524
1287
  ```
525
1288
 
526
1289
  ##### SQL
527
1290
  The resulting SQL will look like:
528
1291
 
529
- ```sql
1292
+ ```sql file=../usage/tests/readme/select-star.ts region=example-sql
530
1293
  SELECT *
531
- FROM User;
1294
+ FROM User;
1295
+ ```
1296
+
1297
+ #### Example - `Table.*` (Single Table)
1298
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=table-star-single
1299
+ prisma.$from("User")
1300
+ .select("User.*");
1301
+ ```
1302
+
1303
+ ##### SQL
1304
+ The resulting SQL will look like:
1305
+
1306
+ ```sql file=../usage/tests/readme/select-advanced.ts region=table-star-single-sql
1307
+ SELECT
1308
+ id,
1309
+ email,
1310
+ name,
1311
+ age
1312
+ FROM User;
1313
+ ```
1314
+
1315
+ #### Example - `Table.*` (With Join)
1316
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=table-star-join
1317
+ prisma.$from("User")
1318
+ .join("Post", "authorId", "User.id")
1319
+ .select("User.*")
1320
+ .select("Post.*");
1321
+ ```
1322
+
1323
+ ##### SQL
1324
+ The resulting SQL will look like:
1325
+
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`
1338
+ FROM User
1339
+ JOIN Post ON Post.authorId = User.id;
532
1340
  ```
533
1341
 
1342
+ > [!NOTE]
1343
+ > When using `Table.*` with joins, all columns are automatically aliased with the table name prefix to avoid column name conflicts.
1344
+
534
1345
  #### Example - Chained
535
- ```typescript
1346
+ ```typescript file=../usage/tests/readme/select-chained.ts region=example
536
1347
  prisma.$from("User")
537
- .select("name")
538
- .select("email");
1348
+ .select("name")
1349
+ .select("email");
539
1350
  ```
540
1351
 
541
1352
  ##### SQL
542
1353
  The resulting SQL will look like:
543
1354
 
544
- ```sql
1355
+ ```sql file=../usage/tests/readme/select-chained.ts region=example-sql
545
1356
  SELECT name, email
546
- FROM User;
1357
+ FROM User;
547
1358
  ```
548
1359
 
549
1360
  #### Example - Join + Chained
550
- ```typescript
1361
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=join-chained
551
1362
  prisma.$from("User")
552
- .join("Post", "authorId", "User.id")
1363
+ .join("Post", "authorId", "User.id")
553
1364
  .select("name")
554
1365
  .select("Post.title");
555
1366
  ```
556
1367
 
557
- [!NOTE]
558
- > Support for `Table.*` isn't complete yet. This will be tracked [here](https://github.com/adrianbrowning/prisma-ts-select/issues/31).
1368
+ ##### SQL
1369
+ The resulting SQL will look like:
1370
+
1371
+ ```sql file=../usage/tests/readme/select-advanced.ts region=join-chained-sql
1372
+ SELECT name, title
1373
+ FROM User
1374
+ JOIN Post ON Post.authorId = User.id;
1375
+ ```
1376
+
1377
+ #### Example - Column Aliases
1378
+ ```typescript file=../usage/tests/readme/select-column-alias.ts region=basic
1379
+ prisma.$from("User")
1380
+ .select("User.name", "username");
1381
+ ```
1382
+
1383
+ ```typescript file=../usage/tests/readme/select-column-alias.ts region=multiple
1384
+ prisma.$from("User")
1385
+ .select("User.id", "userId")
1386
+ .select("User.email", "emailAddress");
1387
+ ```
1388
+
1389
+ ```typescript file=../usage/tests/readme/select-column-alias.ts region=mixed
1390
+ prisma.$from("User")
1391
+ .select("User.id")
1392
+ .select("User.name", "username")
1393
+ .select("User.email");
1394
+ ```
559
1395
 
560
1396
  ##### SQL
561
1397
  The resulting SQL will look like:
562
1398
 
563
- ```sql
564
- SELECT name, email
565
- 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
+ ```
1403
+
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
+ ```
1410
+
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;
1417
+ ```
1418
+
1419
+ #### Example - Aliases with Joins
1420
+ ```typescript file=../usage/tests/readme/select-advanced.ts region=aliases-joins
1421
+ prisma.$from("User")
1422
+ .join("Post", "authorId", "User.id")
1423
+ .select("User.name", "authorName")
1424
+ .select("Post.title", "postTitle");
1425
+ ```
1426
+
1427
+ ##### SQL
1428
+ The resulting SQL will look like:
1429
+
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`
1434
+ FROM User
1435
+ JOIN Post ON Post.authorId = User.id;
566
1436
  ```
567
1437
 
1438
+ > [!NOTE]
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.
1440
+
568
1441
  ### Having
569
1442
 
570
- `.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.
571
1444
 
572
- #### Example
1445
+ #### Criteria object
573
1446
 
574
- ```typescript
1447
+ ```typescript file=../shared-tests/readme/having.ts region=with-groupby
575
1448
  prisma.$from("User")
576
- .join("Post", "authorId", "User.id")
577
- .groupBy(["name", "Post.content"])
578
- .having({
579
- "User.name": {
580
- "op": "LIKE",
581
- "value": "bob%"
582
- }
583
- });
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("*");
584
1458
  ```
585
1459
 
586
- ```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
587
1471
  prisma.$from("User")
588
- .join("Post", "authorId", "User.id")
589
- .having({
590
- "User.name": {
591
- "op": "LIKE",
592
- "value": "stuart%"
593
- }
594
- });
1472
+ .join("Post", "authorId", "User.id")
1473
+ .groupBy(["User.name"])
1474
+ .having(({ countAll }) => [[countAll(), { op: '>', value: 1 }]])
1475
+ .select("User.name")
595
1476
  ```
596
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
+ ```
597
1481
 
598
- ##### SQL
1482
+ ##### `count(col)` with bigint value
599
1483
 
600
- ```SQL
601
- FROM User
602
- JOIN Post ON authorId = User.id
603
- GROUP BY name, Post.content
604
- 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")
605
1490
  ```
606
1491
 
607
- ```SQL
608
- FROM User
609
- JOIN Post ON authorId = User.id
610
- 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;
611
1494
  ```
612
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%';
1508
+ ```
1509
+
1510
+ Multiple pairs in one `.having()` call are AND-ed together. `.having()` can also be chained — each call appends an AND condition.
1511
+
613
1512
  ### Order By
614
1513
 
615
1514
  `.orderBy`, takes an array of column names, with the optional suffix of `ASC` or `DESC`.
616
1515
 
617
1516
  #### Example
618
1517
 
619
- ```typescript
1518
+ ```typescript file=../usage/tests/readme/orderby.ts region=basic
620
1519
  prisma.$from("User")
621
1520
  .join("Post", "authorId", "User.id")
622
- .orderBy(["name", "Post.content DESC"]);
1521
+ .orderBy(["name", "Post.content DESC"]);
623
1522
  ```
624
1523
 
625
1524
  ##### SQL
626
1525
 
627
- ```sql
1526
+ ```sql file=../usage/tests/readme/orderby.ts region=basic-sql
628
1527
  FROM User
629
- JOIN Post ON authorId = User.id
1528
+ JOIN Post ON Post.authorId = User.id
630
1529
  ORDER BY name, Post.content DESC;
631
1530
  ```
632
1531
 
@@ -636,7 +1535,7 @@ ORDER BY name, Post.content DESC;
636
1535
 
637
1536
  #### Example
638
1537
 
639
- ```typescript
1538
+ ```typescript file=../usage/tests/readme/pagination.ts region=limit
640
1539
  prisma.$from("User")
641
1540
  .join("Post", "authorId", "User.id")
642
1541
  .limit(1);
@@ -644,9 +1543,9 @@ prisma.$from("User")
644
1543
 
645
1544
  ##### SQL
646
1545
 
647
- ```SQL
1546
+ ```sql file=../usage/tests/readme/pagination.ts region=limit-sql
648
1547
  FROM User
649
- JOIN Post ON authorId = User.id
1548
+ JOIN Post ON Post.authorId = User.id
650
1549
  LIMIT 1;
651
1550
  ```
652
1551
 
@@ -655,71 +1554,364 @@ LIMIT 1;
655
1554
  `.offSet`, the number of rows to skip. Requires `.limit` to have been used first.
656
1555
 
657
1556
  #### Example
658
- ```typescript
1557
+ ```typescript file=../usage/tests/readme/pagination.ts region=offset
659
1558
  prisma.$from("User")
660
- .join("Post", "authorId", "User.id")
661
- .limit(1)
662
- .offset(1);
1559
+ .join("Post", "authorId", "User.id")
1560
+ .limit(1)
1561
+ .offset(1);
663
1562
  ```
664
1563
 
665
1564
  ##### SQL
666
1565
 
667
- ```SQL
1566
+ ```sql file=../usage/tests/readme/pagination.ts region=offset-sql
668
1567
  FROM User
669
- JOIN Post ON authorId = User.id
1568
+ JOIN Post ON Post.authorId = User.id
670
1569
  LIMIT 1
671
- OFFSET 1
1570
+ OFFSET 1;
672
1571
  ```
673
1572
 
674
- ## Future updates
1573
+ ## Select Functions
675
1574
 
676
- - Support specifying `JOIN` type [issue#2](https://github.com/adrianbrowning/prisma-ts-select/issues/2)
677
- - Support Select Functions
678
- - [Aggregation #4](https://github.com/adrianbrowning/prisma-ts-select/issues/4)
679
- - [String #5](https://github.com/adrianbrowning/prisma-ts-select/issues/5)
680
- - [Date & Time #6](https://github.com/adrianbrowning/prisma-ts-select/issues/6)
681
- - [Math #7](https://github.com/adrianbrowning/prisma-ts-select/issues/7)
682
- - [Control Flow #8](https://github.com/adrianbrowning/prisma-ts-select/issues/8)
683
- - [JSON #9](https://github.com/adrianbrowning/prisma-ts-select/issues/9)
684
- - [Support a `Many-To-Many` join #19](https://github.com/adrianbrowning/prisma-ts-select/issues/19)
685
- - [Select column alias #27](https://github.com/adrianbrowning/prisma-ts-select/issues/27)
686
- - [Table name alias #28](https://github.com/adrianbrowning/prisma-ts-select/issues/28)
687
- - [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.
688
1576
 
689
- ## Changelog / Versioning
690
- 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)
691
1578
 
692
- ## License
693
- This project is licensed under the MIT License. See the LICENSE file for details.
1579
+ #### `lit(value)` — SQL literal
694
1580
 
695
- Things of note!!!!
1581
+ Produces a typed SQL literal from a JS value.
696
1582
 
697
- - remove typeof from
698
- - `type _db = DeepWriteable<typeof DB>;`
699
- - `}[keyof typeof DB];`
700
- - Merge Items missing //@ts-expect-error - might not be needed
701
- - groupBy -> having,
702
- - missing @deprecated
703
- - ts-exptect-error - might not be needed
704
- - GetColsFromTableType missing ts-expect-error - might not be needed
705
- - 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
+ ```
706
1588
 
1589
+ #### `countAll()` — COUNT(*)
707
1590
 
1591
+ The most common aggregate. Always produces `COUNT(*)`.
708
1592
 
709
- # 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
+ ```
710
1598
 
711
- ## 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
+ ```
712
1603
 
713
- ```shell
714
- npm i prisma-ts-select
715
- 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");
716
1609
  ```
717
1610
 
718
- ## Setup
1611
+ #### `countDistinct(col)` — COUNT(DISTINCT col)
1612
+
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
+ ```
719
1617
 
720
- ### Extract
1618
+ #### `sum(col)` / `avg(col)` / `min(col)` / `max(col)`
721
1619
 
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.
722
1621
 
723
- ## Usage
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.
724
1916
 
725
1917