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.
- package/LICENSE +21 -0
- package/README.md +1242 -315
- package/assets/groupBy.gif +0 -0
- package/assets/joinUnsafeIgnoreType.gif +0 -0
- package/assets/joinUnsafeTypeEnforced.gif +0 -0
- package/assets/typesafe-join.gif +0 -0
- package/assets/typesafe-join.png +0 -0
- package/assets/whereNotNull.gif +0 -0
- package/assets/whereisNull.gif +0 -0
- package/dist/bin.cjs +1 -1
- package/dist/bin.js +1 -1
- package/dist/chunk-47KZVQLD.js +283 -0
- package/dist/chunk-54D2J5AR.cjs +291 -0
- package/dist/extend/dialects/index.d.ts +13 -0
- package/dist/extend/dialects/index.js +186 -0
- package/dist/extend/dialects/mysql-v6.d.ts +100 -0
- package/dist/extend/dialects/mysql-v6.js +152 -0
- package/dist/extend/dialects/mysql-v7.d.ts +6 -0
- package/dist/extend/dialects/mysql-v7.js +138 -0
- package/dist/extend/dialects/mysql.d.ts +90 -0
- package/dist/extend/dialects/mysql.js +156 -0
- package/dist/extend/dialects/postgresql-v6.d.ts +97 -0
- package/dist/extend/dialects/postgresql-v6.js +136 -0
- package/dist/extend/dialects/postgresql-v7.d.ts +97 -0
- package/dist/extend/dialects/postgresql-v7.js +136 -0
- package/dist/extend/dialects/postgresql.d.ts +89 -0
- package/dist/extend/dialects/postgresql.js +147 -0
- package/dist/extend/dialects/shared.d.ts +6 -0
- package/dist/extend/dialects/shared.js +5 -0
- package/dist/extend/dialects/sqlite.d.ts +63 -0
- package/dist/extend/dialects/sqlite.js +138 -0
- package/dist/extend/dialects/types.d.ts +12 -0
- package/dist/extend/dialects/types.js +4 -0
- package/dist/extend/extend.d.ts +286 -43
- package/dist/extend/extend.js +735 -163
- package/dist/extend/sql-expr-BaKWzJ-r.d.ts +10 -0
- package/dist/extend/types-D84lxYVc.d.ts +5 -0
- package/dist/generator.cjs +1 -1
- package/dist/generator.js +1 -1
- package/package.json +46 -42
- package/built/extend.cjs +0 -680
- package/built/extend.d.cts +0 -520
- package/built/extend.d.ts +0 -520
- package/built/extend.js +0 -678
- package/dist/chunk-TBO3MX7Q.cjs +0 -195
- package/dist/chunk-X3N5N5KQ.js +0 -187
- package/dist/extend/extend.cjs +0 -357
- package/dist/extend/extend.d.cts +0 -264
package/README.md
CHANGED
|
@@ -4,10 +4,21 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
## Test Matrix
|
|
8
|
+
|
|
9
|
+
| | SQLite | MySQL | PostgreSQL |
|
|
10
|
+
|----------------|--------|-------|------------|
|
|
11
|
+
| **Prisma v6** |  |  |  |
|
|
12
|
+
| **Prisma v7** |  |  |  |
|
|
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
|
-
>
|
|
124
|
-
>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
371
|
+
#### Example — CTE as base table
|
|
225
372
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
318
|
-
|
|
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
|
|
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
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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"
|
|
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>
|
|
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":
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
550
|
-
|
|
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
|
-
|
|
556
|
-
|
|
1088
|
+
.join("Post", "authorId", "User.id")
|
|
1089
|
+
.whereNotNull("User.name")
|
|
557
1090
|
```
|
|
558
1091
|

|
|
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
|
-
|
|
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
|
-
|
|
578
|
-
|
|
1107
|
+
.join("Post", "authorId", "User.id")
|
|
1108
|
+
.whereIsNull("Post.content")
|
|
579
1109
|
```
|
|
580
1110
|

|
|
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
|
-
|
|
599
|
-
|
|
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
|
|
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
|
-
|
|
620
|
-
|
|
1164
|
+
.join("Post", "authorId", "User.id")
|
|
1165
|
+
.groupBy(["name", "Post.content"]);
|
|
621
1166
|
```
|
|
622
1167
|

|
|
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
|
-
|
|
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
|
-
|
|
1208
|
+
.selectAll();
|
|
663
1209
|
```
|
|
664
1210
|
|
|
665
1211
|
##### SQL
|
|
666
1212
|
The resulting SQL will look like:
|
|
667
1213
|
|
|
668
|
-
```sql
|
|
669
|
-
SELECT
|
|
670
|
-
|
|
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
|
-
|
|
677
|
-
|
|
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
|
|
685
|
-
|
|
686
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1300
|
+
.select("User.*");
|
|
713
1301
|
```
|
|
714
1302
|
|
|
715
1303
|
##### SQL
|
|
716
1304
|
The resulting SQL will look like:
|
|
717
1305
|
|
|
718
|
-
```sql
|
|
719
|
-
SELECT
|
|
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
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
753
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
804
|
-
|
|
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
|
-
|
|
807
|
-
SELECT
|
|
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
|
-
|
|
810
|
-
SELECT
|
|
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
|
|
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`
|
|
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
|
-
####
|
|
1445
|
+
#### Criteria object
|
|
838
1446
|
|
|
839
|
-
```typescript
|
|
1447
|
+
```typescript file=../shared-tests/readme/having.ts region=with-groupby
|
|
840
1448
|
prisma.$from("User")
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
#####
|
|
1482
|
+
##### `count(col)` with bigint value
|
|
864
1483
|
|
|
865
|
-
```
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
```
|
|
873
|
-
FROM User
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1559
|
+
.join("Post", "authorId", "User.id")
|
|
1560
|
+
.limit(1)
|
|
1561
|
+
.offset(1);
|
|
928
1562
|
```
|
|
929
1563
|
|
|
930
1564
|
##### SQL
|
|
931
1565
|
|
|
932
|
-
```
|
|
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
|
-
##
|
|
1573
|
+
## Select Functions
|
|
940
1574
|
|
|
941
|
-
|
|
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
|
-
|
|
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
|
-
|
|
958
|
-
This project is licensed under the MIT License. See the LICENSE file for details.
|
|
1579
|
+
#### `lit(value)` — SQL literal
|
|
959
1580
|
|
|
960
|
-
|
|
1581
|
+
Produces a typed SQL literal from a JS value.
|
|
961
1582
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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
|
-
|
|
1611
|
+
#### `countDistinct(col)` — COUNT(DISTINCT col)
|
|
984
1612
|
|
|
985
|
-
|
|
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
|
-
|
|
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
|
|