wip
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
---
|
||||
name: prisma-client-api
|
||||
description: Prisma Client API reference covering model queries, filters, operators, and client methods. Use when writing database queries, using CRUD operations, filtering data, or configuring Prisma Client. Triggers on "prisma query", "findMany", "create", "update", "delete", "$transaction".
|
||||
license: MIT
|
||||
metadata:
|
||||
author: prisma
|
||||
version: "7.9.1"
|
||||
---
|
||||
|
||||
# Prisma Client API Reference
|
||||
|
||||
Complete API reference for Prisma Client. This skill provides guidance on model queries, filtering, relations, and client methods for current Prisma projects.
|
||||
|
||||
## When to Apply
|
||||
|
||||
Reference this skill when:
|
||||
- Writing database queries with Prisma Client
|
||||
- Performing CRUD operations (create, read, update, delete)
|
||||
- Filtering and sorting data
|
||||
- Working with relations
|
||||
- Using transactions
|
||||
- Configuring client options
|
||||
|
||||
## Rule Categories by Priority
|
||||
|
||||
| Priority | Category | Impact | Prefix |
|
||||
|----------|----------|--------|--------|
|
||||
| 1 | Client Construction | HIGH | `constructor` |
|
||||
| 2 | Model Queries | CRITICAL | `model-queries` |
|
||||
| 3 | Query Shape | HIGH | `query-options` |
|
||||
| 4 | Filtering | HIGH | `filters` |
|
||||
| 5 | Relations | HIGH | `relations` |
|
||||
| 6 | Transactions | CRITICAL | `transactions` |
|
||||
| 7 | Raw SQL | CRITICAL | `raw-queries` |
|
||||
| 8 | Client Methods | MEDIUM | `client-methods` |
|
||||
|
||||
## Quick Reference
|
||||
|
||||
- `constructor` - `PrismaClient` setup, adapter wiring, logging, and SQL commenter plugins
|
||||
- `model-queries` - CRUD operations and bulk operations
|
||||
- `query-options` - `select`, `include`, `omit`, sort, pagination
|
||||
- `filters` - scalar and logical filter operators
|
||||
- `relations` - relation reads and nested writes
|
||||
- `transactions` - array and interactive transaction patterns
|
||||
- `raw-queries` - `$queryRaw` and `$executeRaw` safety
|
||||
- `client-methods` - lifecycle methods, extensions, and `satisfies` patterns for `prisma-client`
|
||||
|
||||
## Client Instantiation
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '../generated/client'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
|
||||
const adapter = new PrismaPg({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
})
|
||||
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
```
|
||||
|
||||
## Model Query Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `findUnique()` | Find one record by unique field |
|
||||
| `findUniqueOrThrow()` | Find one or throw error |
|
||||
| `findFirst()` | Find first matching record |
|
||||
| `findFirstOrThrow()` | Find first or throw error |
|
||||
| `findMany()` | Find multiple records |
|
||||
| `create()` | Create a new record |
|
||||
| `createMany()` | Create multiple records |
|
||||
| `createManyAndReturn()` | Create multiple and return them |
|
||||
| `update()` | Update one record |
|
||||
| `updateMany()` | Update multiple records |
|
||||
| `updateManyAndReturn()` | Update multiple and return them |
|
||||
| `upsert()` | Update or create record |
|
||||
| `delete()` | Delete one record |
|
||||
| `deleteMany()` | Delete multiple records |
|
||||
| `count()` | Count matching records |
|
||||
| `aggregate()` | Aggregate values (sum, avg, etc.) |
|
||||
| `groupBy()` | Group and aggregate |
|
||||
|
||||
## Query Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `where` | Filter conditions |
|
||||
| `select` | Fields to include |
|
||||
| `include` | Relations to load |
|
||||
| `omit` | Fields to exclude |
|
||||
| `orderBy` | Sort order |
|
||||
| `take` | Limit results |
|
||||
| `skip` | Skip results (pagination) |
|
||||
| `cursor` | Cursor-based pagination |
|
||||
| `distinct` | Unique values only |
|
||||
|
||||
## Client Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `$connect()` | Explicitly connect to database |
|
||||
| `$disconnect()` | Disconnect from database |
|
||||
| `$transaction()` | Execute transaction |
|
||||
| `$queryRaw()` | Execute raw SQL query |
|
||||
| `$executeRaw()` | Execute raw SQL command |
|
||||
| `$on()` | Subscribe to events |
|
||||
| `$extends()` | Add extensions |
|
||||
|
||||
## Quick Examples
|
||||
|
||||
### Find records
|
||||
|
||||
```typescript
|
||||
// Find by unique field
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: 'alice@prisma.io' }
|
||||
})
|
||||
|
||||
// Find with filter
|
||||
const users = await prisma.user.findMany({
|
||||
where: { role: 'ADMIN' },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 10
|
||||
})
|
||||
```
|
||||
|
||||
### Create records
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'alice@prisma.io',
|
||||
name: 'Alice',
|
||||
posts: {
|
||||
create: { title: 'Hello World' }
|
||||
}
|
||||
},
|
||||
include: { posts: true }
|
||||
})
|
||||
```
|
||||
|
||||
### Update records
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: { name: 'Alice Smith' }
|
||||
})
|
||||
```
|
||||
|
||||
### Delete records
|
||||
|
||||
```typescript
|
||||
await prisma.user.delete({
|
||||
where: { id: 1 }
|
||||
})
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
```typescript
|
||||
const [user, post] = await prisma.$transaction([
|
||||
prisma.user.create({ data: { email: 'alice@prisma.io' } }),
|
||||
prisma.post.create({ data: { title: 'Hello', authorId: 1 } })
|
||||
])
|
||||
```
|
||||
|
||||
## Rule Files
|
||||
|
||||
Detailed API documentation:
|
||||
|
||||
```
|
||||
references/constructor.md - PrismaClient constructor options
|
||||
references/model-queries.md - CRUD operations
|
||||
references/query-options.md - select, include, omit, where, orderBy
|
||||
references/filters.md - Filter conditions and operators
|
||||
references/relations.md - Relation queries and nested operations
|
||||
references/transactions.md - Transaction API
|
||||
references/raw-queries.md - $queryRaw, $executeRaw
|
||||
references/client-methods.md - $connect, $disconnect, $on, $extends
|
||||
```
|
||||
|
||||
## Filter Operators
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `equals` | Exact match |
|
||||
| `not` | Not equal |
|
||||
| `in` | In array |
|
||||
| `notIn` | Not in array |
|
||||
| `lt`, `lte` | Less than |
|
||||
| `gt`, `gte` | Greater than |
|
||||
| `contains` | String contains |
|
||||
| `startsWith` | String starts with |
|
||||
| `endsWith` | String ends with |
|
||||
| `mode` | Case sensitivity |
|
||||
|
||||
## Relation Filters
|
||||
|
||||
| Operator | Description |
|
||||
|----------|-------------|
|
||||
| `some` | At least one related record matches |
|
||||
| `every` | All related records match |
|
||||
| `none` | No related records match |
|
||||
| `is` | Related record matches (1-to-1) |
|
||||
| `isNot` | Related record doesn't match |
|
||||
|
||||
## Resources
|
||||
|
||||
- [Prisma Client API Reference](https://www.prisma.io/docs/orm/reference/prisma-client-reference)
|
||||
- [CRUD Operations](https://www.prisma.io/docs/orm/prisma-client/queries/crud)
|
||||
- [Filtering and Sorting](https://www.prisma.io/docs/orm/prisma-client/queries/filtering-and-sorting)
|
||||
|
||||
## How to Use
|
||||
|
||||
Pick the category from the table above, then open the matching reference file for implementation details and examples.
|
||||
@@ -0,0 +1,223 @@
|
||||
# Client Methods
|
||||
|
||||
Prisma Client instance methods.
|
||||
|
||||
## $connect()
|
||||
|
||||
Explicitly connect to the database:
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
|
||||
// Explicit connection
|
||||
await prisma.$connect()
|
||||
```
|
||||
|
||||
### When to use
|
||||
|
||||
Usually not needed - Prisma connects automatically on first query. Use for:
|
||||
- Fail fast on startup
|
||||
- Health checks
|
||||
- Pre-warming connections
|
||||
|
||||
```typescript
|
||||
async function main() {
|
||||
try {
|
||||
await prisma.$connect()
|
||||
console.log('Database connected')
|
||||
} catch (e) {
|
||||
console.error('Failed to connect:', e)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## $disconnect()
|
||||
|
||||
Close database connection:
|
||||
|
||||
```typescript
|
||||
await prisma.$disconnect()
|
||||
```
|
||||
|
||||
### Graceful shutdown
|
||||
|
||||
```typescript
|
||||
process.on('beforeExit', async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
|
||||
// Or with SIGTERM
|
||||
process.on('SIGTERM', async () => {
|
||||
await prisma.$disconnect()
|
||||
process.exit(0)
|
||||
})
|
||||
```
|
||||
|
||||
### In tests
|
||||
|
||||
```typescript
|
||||
afterAll(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
```
|
||||
|
||||
## $on()
|
||||
|
||||
Subscribe to events:
|
||||
|
||||
### Query events
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
log: [{ level: 'query', emit: 'event' }]
|
||||
})
|
||||
|
||||
prisma.$on('query', (e) => {
|
||||
console.log('Query:', e.query)
|
||||
console.log('Params:', e.params)
|
||||
console.log('Duration:', e.duration, 'ms')
|
||||
})
|
||||
```
|
||||
|
||||
### Log events
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
log: [
|
||||
{ level: 'info', emit: 'event' },
|
||||
{ level: 'warn', emit: 'event' },
|
||||
{ level: 'error', emit: 'event' }
|
||||
]
|
||||
})
|
||||
|
||||
prisma.$on('info', (e) => console.log(e.message))
|
||||
prisma.$on('warn', (e) => console.warn(e.message))
|
||||
prisma.$on('error', (e) => console.error(e.message))
|
||||
```
|
||||
|
||||
## $extends()
|
||||
|
||||
Add extensions for custom behavior:
|
||||
|
||||
### Add custom methods
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({ adapter }).$extends({
|
||||
client: {
|
||||
$log: (message: string) => console.log(message)
|
||||
}
|
||||
})
|
||||
|
||||
prisma.$log('Hello!')
|
||||
```
|
||||
|
||||
### Add model methods
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({ adapter }).$extends({
|
||||
model: {
|
||||
user: {
|
||||
async findByEmail(email: string) {
|
||||
return prisma.user.findUnique({ where: { email } })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const user = await prisma.user.findByEmail('alice@prisma.io')
|
||||
```
|
||||
|
||||
### Query extensions
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({ adapter }).$extends({
|
||||
query: {
|
||||
user: {
|
||||
async findMany({ args, query }) {
|
||||
// Add default filter
|
||||
args.where = { ...args.where, deletedAt: null }
|
||||
return query(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Result extensions
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({ adapter }).$extends({
|
||||
result: {
|
||||
user: {
|
||||
fullName: {
|
||||
needs: { firstName: true, lastName: true },
|
||||
compute(user) {
|
||||
return `${user.firstName} ${user.lastName}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const user = await prisma.user.findFirst()
|
||||
console.log(user.fullName) // Computed field
|
||||
```
|
||||
|
||||
### Chain extensions
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
.$extends(loggingExtension)
|
||||
.$extends(softDeleteExtension)
|
||||
.$extends(computedFieldsExtension)
|
||||
```
|
||||
|
||||
## $transaction()
|
||||
|
||||
See `transactions.md` for details.
|
||||
|
||||
## $queryRaw() / $executeRaw()
|
||||
|
||||
See `raw-queries.md` for details.
|
||||
|
||||
## Type utilities
|
||||
|
||||
### Prisma namespace
|
||||
|
||||
```typescript
|
||||
import { Prisma } from '../generated/client'
|
||||
|
||||
// Input types
|
||||
type UserCreateInput = Prisma.UserCreateInput
|
||||
type UserWhereInput = Prisma.UserWhereInput
|
||||
|
||||
// Output types
|
||||
type User = Prisma.UserGetPayload<{}>
|
||||
type UserWithPosts = Prisma.UserGetPayload<{
|
||||
include: { posts: true }
|
||||
}>
|
||||
```
|
||||
|
||||
### Type-safe query fragments with satisfies
|
||||
|
||||
Type-safe query fragments:
|
||||
|
||||
```typescript
|
||||
import { Prisma } from '../generated/client'
|
||||
|
||||
const userSelect = {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true
|
||||
} satisfies Prisma.UserSelect
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
select: userSelect
|
||||
})
|
||||
```
|
||||
|
||||
With the `prisma-client` generator, use TypeScript `satisfies` for typed query fragments. You may still see older examples that use `Prisma.validator()` with `prisma-client-js`.
|
||||
@@ -0,0 +1,221 @@
|
||||
# PrismaClient Constructor
|
||||
|
||||
Configure Prisma Client when instantiating.
|
||||
|
||||
## Basic Instantiation
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '../generated/client'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
|
||||
const adapter = new PrismaPg({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
})
|
||||
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
```
|
||||
|
||||
## Constructor Options
|
||||
|
||||
### adapter (Required for the SQL provider workflow)
|
||||
|
||||
Driver adapter instance:
|
||||
|
||||
```typescript
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
|
||||
const adapter = new PrismaPg({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
})
|
||||
|
||||
const prisma = new PrismaClient({ adapter })
|
||||
```
|
||||
|
||||
### accelerateUrl (For Accelerate users)
|
||||
|
||||
```typescript
|
||||
import { withAccelerate } from '@prisma/extension-accelerate'
|
||||
|
||||
const prisma = new PrismaClient({
|
||||
accelerateUrl: process.env.DATABASE_URL, // prisma:// URL
|
||||
}).$extends(withAccelerate())
|
||||
```
|
||||
|
||||
### log
|
||||
|
||||
Configure logging:
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
log: ['query', 'info', 'warn', 'error'],
|
||||
})
|
||||
```
|
||||
|
||||
#### Log levels
|
||||
|
||||
| Level | Description |
|
||||
|-------|-------------|
|
||||
| `query` | All SQL queries |
|
||||
| `info` | Informational messages |
|
||||
| `warn` | Warnings |
|
||||
| `error` | Errors |
|
||||
|
||||
#### Log to events
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
log: [
|
||||
{ level: 'query', emit: 'event' },
|
||||
{ level: 'error', emit: 'stdout' },
|
||||
],
|
||||
})
|
||||
|
||||
prisma.$on('query', (e) => {
|
||||
console.log('Query:', e.query)
|
||||
console.log('Duration:', e.duration, 'ms')
|
||||
})
|
||||
```
|
||||
|
||||
### errorFormat
|
||||
|
||||
Control error formatting:
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
errorFormat: 'pretty', // 'pretty' | 'colorless' | 'minimal'
|
||||
})
|
||||
```
|
||||
|
||||
### comments
|
||||
|
||||
Attach SQL commenter plugins for observability, tracing, or query insights:
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '../generated/client'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
import { prismaQueryInsights } from '@prisma/sqlcommenter-query-insights'
|
||||
import { queryTags, withQueryTags } from '@prisma/sqlcommenter-query-tags'
|
||||
import { traceContext } from '@prisma/sqlcommenter-trace-context'
|
||||
|
||||
const prisma = new PrismaClient({
|
||||
adapter: new PrismaPg(process.env.DATABASE_URL!),
|
||||
comments: [prismaQueryInsights(), traceContext(), queryTags()],
|
||||
})
|
||||
|
||||
await withQueryTags({ route: '/api/users', requestId: 'req-123' }, () =>
|
||||
prisma.user.findMany(),
|
||||
)
|
||||
```
|
||||
|
||||
Use `comments` only for SQL providers. This is the clean way to add trace or query-shape metadata without changing your query calls.
|
||||
|
||||
### transactionOptions
|
||||
|
||||
Default transaction settings:
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
transactionOptions: {
|
||||
maxWait: 5000, // Max wait to acquire transaction (ms)
|
||||
timeout: 10000, // Max transaction duration (ms)
|
||||
isolationLevel: 'Serializable',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### queryPlanCacheMaxSize
|
||||
|
||||
Use `queryPlanCacheMaxSize` to limit the in-memory query-plan cache:
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
queryPlanCacheMaxSize: 2_000,
|
||||
})
|
||||
```
|
||||
|
||||
The value must be a non-negative integer. Set it to `0` to disable query-plan caching; omit it to use Prisma's default. Treat this as a process-local memory/performance control, not a database prepared-statement setting.
|
||||
|
||||
## Singleton Pattern
|
||||
|
||||
Prevent multiple client instances in development:
|
||||
|
||||
```typescript
|
||||
// lib/prisma.ts
|
||||
import { PrismaClient } from '../generated/client'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined
|
||||
}
|
||||
|
||||
function createPrismaClient() {
|
||||
const adapter = new PrismaPg({
|
||||
connectionString: process.env.DATABASE_URL!
|
||||
})
|
||||
return new PrismaClient({ adapter })
|
||||
}
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? createPrismaClient()
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalForPrisma.prisma = prisma
|
||||
}
|
||||
```
|
||||
|
||||
## Next.js Pattern
|
||||
|
||||
```typescript
|
||||
// lib/prisma.ts
|
||||
import { PrismaClient } from '@/generated/client'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
|
||||
const createAdapter = () => new PrismaPg({
|
||||
connectionString: process.env.DATABASE_URL!
|
||||
})
|
||||
|
||||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient({ adapter: createAdapter() })
|
||||
}
|
||||
|
||||
declare const globalThis: {
|
||||
prismaGlobal: ReturnType<typeof prismaClientSingleton>
|
||||
} & typeof global
|
||||
|
||||
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
|
||||
|
||||
export default prisma
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalThis.prismaGlobal = prisma
|
||||
}
|
||||
```
|
||||
|
||||
## Query Events
|
||||
|
||||
Listen to query events:
|
||||
|
||||
```typescript
|
||||
const prisma = new PrismaClient({
|
||||
adapter,
|
||||
log: [{ level: 'query', emit: 'event' }],
|
||||
})
|
||||
|
||||
prisma.$on('query', (e) => {
|
||||
console.log('Query:', e.query)
|
||||
console.log('Params:', e.params)
|
||||
console.log('Duration:', e.duration)
|
||||
})
|
||||
```
|
||||
|
||||
## Log Events
|
||||
|
||||
```typescript
|
||||
prisma.$on('info', (e) => console.log(e.message))
|
||||
prisma.$on('warn', (e) => console.warn(e.message))
|
||||
prisma.$on('error', (e) => console.error(e.message))
|
||||
```
|
||||
@@ -0,0 +1,256 @@
|
||||
# Filter Conditions and Operators
|
||||
|
||||
Filter operators for the `where` clause.
|
||||
|
||||
## Equality
|
||||
|
||||
```typescript
|
||||
// Exact match (implicit)
|
||||
where: { email: 'alice@prisma.io' }
|
||||
|
||||
// Explicit equals
|
||||
where: { email: { equals: 'alice@prisma.io' } }
|
||||
|
||||
// Not equal
|
||||
where: { email: { not: 'alice@prisma.io' } }
|
||||
```
|
||||
|
||||
## Comparison
|
||||
|
||||
```typescript
|
||||
// Greater than
|
||||
where: { age: { gt: 18 } }
|
||||
|
||||
// Greater than or equal
|
||||
where: { age: { gte: 18 } }
|
||||
|
||||
// Less than
|
||||
where: { age: { lt: 65 } }
|
||||
|
||||
// Less than or equal
|
||||
where: { age: { lte: 65 } }
|
||||
|
||||
// Combined
|
||||
where: { age: { gte: 18, lte: 65 } }
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
||||
```typescript
|
||||
// In array
|
||||
where: { role: { in: ['ADMIN', 'MODERATOR'] } }
|
||||
|
||||
// Not in array
|
||||
where: { role: { notIn: ['GUEST', 'BANNED'] } }
|
||||
```
|
||||
|
||||
## String Filters
|
||||
|
||||
```typescript
|
||||
// Contains
|
||||
where: { email: { contains: 'prisma' } }
|
||||
|
||||
// Starts with
|
||||
where: { email: { startsWith: 'alice' } }
|
||||
|
||||
// Ends with
|
||||
where: { email: { endsWith: '@prisma.io' } }
|
||||
|
||||
// Case-insensitive (default for some databases)
|
||||
where: {
|
||||
email: {
|
||||
contains: 'PRISMA',
|
||||
mode: 'insensitive'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Null Checks
|
||||
|
||||
```typescript
|
||||
// Is null
|
||||
where: { deletedAt: null }
|
||||
|
||||
// Is not null
|
||||
where: { deletedAt: { not: null } }
|
||||
|
||||
// Using isSet (for optional fields)
|
||||
where: { middleName: { isSet: true } }
|
||||
```
|
||||
|
||||
## Logical Operators
|
||||
|
||||
### AND (implicit)
|
||||
|
||||
```typescript
|
||||
// Multiple conditions = AND
|
||||
where: {
|
||||
email: { contains: '@prisma.io' },
|
||||
role: 'ADMIN'
|
||||
}
|
||||
```
|
||||
|
||||
### AND (explicit)
|
||||
|
||||
```typescript
|
||||
where: {
|
||||
AND: [
|
||||
{ email: { contains: '@prisma.io' } },
|
||||
{ role: 'ADMIN' }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### OR
|
||||
|
||||
```typescript
|
||||
where: {
|
||||
OR: [
|
||||
{ email: { contains: '@gmail.com' } },
|
||||
{ email: { contains: '@prisma.io' } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### NOT
|
||||
|
||||
```typescript
|
||||
where: {
|
||||
NOT: {
|
||||
role: 'GUEST'
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple NOT conditions
|
||||
where: {
|
||||
NOT: [
|
||||
{ role: 'GUEST' },
|
||||
{ verified: false }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Combined
|
||||
|
||||
```typescript
|
||||
where: {
|
||||
AND: [
|
||||
{ verified: true },
|
||||
{
|
||||
OR: [
|
||||
{ role: 'ADMIN' },
|
||||
{ role: 'MODERATOR' }
|
||||
]
|
||||
}
|
||||
],
|
||||
NOT: { deletedAt: { not: null } }
|
||||
}
|
||||
```
|
||||
|
||||
## Relation Filters
|
||||
|
||||
### some
|
||||
|
||||
At least one related record matches:
|
||||
|
||||
```typescript
|
||||
// Users with at least one published post
|
||||
where: {
|
||||
posts: {
|
||||
some: { published: true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### every
|
||||
|
||||
All related records match:
|
||||
|
||||
```typescript
|
||||
// Users where all posts are published
|
||||
where: {
|
||||
posts: {
|
||||
every: { published: true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### none
|
||||
|
||||
No related records match:
|
||||
|
||||
```typescript
|
||||
// Users with no published posts
|
||||
where: {
|
||||
posts: {
|
||||
none: { published: true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### is / isNot (1-to-1)
|
||||
|
||||
```typescript
|
||||
// Users with profile in specific country
|
||||
where: {
|
||||
profile: {
|
||||
is: { country: 'USA' }
|
||||
}
|
||||
}
|
||||
|
||||
// Users without profile
|
||||
where: {
|
||||
profile: {
|
||||
isNot: null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Array Field Filters
|
||||
|
||||
For fields like `String[]`:
|
||||
|
||||
```typescript
|
||||
// Has element
|
||||
where: { tags: { has: 'typescript' } }
|
||||
|
||||
// Has some elements
|
||||
where: { tags: { hasSome: ['typescript', 'javascript'] } }
|
||||
|
||||
// Has every element
|
||||
where: { tags: { hasEvery: ['typescript', 'prisma'] } }
|
||||
|
||||
// Is empty
|
||||
where: { tags: { isEmpty: true } }
|
||||
```
|
||||
|
||||
## JSON Filters
|
||||
|
||||
```typescript
|
||||
// Path-based filter
|
||||
where: {
|
||||
metadata: {
|
||||
path: ['settings', 'theme'],
|
||||
equals: 'dark'
|
||||
}
|
||||
}
|
||||
|
||||
// String contains in JSON
|
||||
where: {
|
||||
metadata: {
|
||||
path: ['bio'],
|
||||
string_contains: 'developer'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Full-Text Search
|
||||
|
||||
```typescript
|
||||
// Requires @@fulltext index
|
||||
where: {
|
||||
content: {
|
||||
search: 'prisma database'
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,281 @@
|
||||
# Model Queries
|
||||
|
||||
CRUD operations for your Prisma models.
|
||||
|
||||
## Read Operations
|
||||
|
||||
### findUnique
|
||||
|
||||
Find a single record by unique field:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 }
|
||||
})
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: 'alice@prisma.io' }
|
||||
})
|
||||
```
|
||||
|
||||
#### With composite unique key
|
||||
|
||||
```typescript
|
||||
// Model with @@unique([firstName, lastName])
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
firstName_lastName: {
|
||||
firstName: 'Alice',
|
||||
lastName: 'Smith'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### findUniqueOrThrow
|
||||
|
||||
Same as findUnique but throws if not found:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUniqueOrThrow({
|
||||
where: { id: 1 }
|
||||
})
|
||||
// Throws PrismaClientKnownRequestError if not found
|
||||
```
|
||||
|
||||
### findFirst
|
||||
|
||||
Find first matching record:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { role: 'ADMIN' },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
```
|
||||
|
||||
### findFirstOrThrow
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: { role: 'ADMIN' }
|
||||
})
|
||||
```
|
||||
|
||||
### findMany
|
||||
|
||||
Find multiple records:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
where: { role: 'USER' },
|
||||
orderBy: { name: 'asc' },
|
||||
take: 10,
|
||||
skip: 0
|
||||
})
|
||||
```
|
||||
|
||||
## Create Operations
|
||||
|
||||
### create
|
||||
|
||||
Create a single record:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'alice@prisma.io',
|
||||
name: 'Alice'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### With relations
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'alice@prisma.io',
|
||||
posts: {
|
||||
create: [
|
||||
{ title: 'First Post' },
|
||||
{ title: 'Second Post' }
|
||||
]
|
||||
}
|
||||
},
|
||||
include: { posts: true }
|
||||
})
|
||||
```
|
||||
|
||||
### createMany
|
||||
|
||||
Create multiple records:
|
||||
|
||||
```typescript
|
||||
const result = await prisma.user.createMany({
|
||||
data: [
|
||||
{ email: 'alice@prisma.io', name: 'Alice' },
|
||||
{ email: 'bob@prisma.io', name: 'Bob' }
|
||||
],
|
||||
skipDuplicates: true // Skip records with duplicate unique fields
|
||||
})
|
||||
// Returns { count: 2 }
|
||||
```
|
||||
|
||||
### createManyAndReturn
|
||||
|
||||
Create multiple and return them:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.createManyAndReturn({
|
||||
data: [
|
||||
{ email: 'alice@prisma.io', name: 'Alice' },
|
||||
{ email: 'bob@prisma.io', name: 'Bob' }
|
||||
]
|
||||
})
|
||||
// Returns array of created users
|
||||
```
|
||||
|
||||
## Update Operations
|
||||
|
||||
### update
|
||||
|
||||
Update a single record:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: { name: 'Alice Smith' }
|
||||
})
|
||||
```
|
||||
|
||||
#### Atomic operations
|
||||
|
||||
```typescript
|
||||
const post = await prisma.post.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
views: { increment: 1 },
|
||||
likes: { decrement: 1 },
|
||||
score: { multiply: 2 },
|
||||
rating: { divide: 2 },
|
||||
version: { set: 5 }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### updateMany
|
||||
|
||||
Update multiple records:
|
||||
|
||||
```typescript
|
||||
const result = await prisma.user.updateMany({
|
||||
where: { role: 'USER' },
|
||||
data: { verified: true }
|
||||
})
|
||||
// Returns { count: 42 }
|
||||
```
|
||||
|
||||
### updateManyAndReturn
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.updateManyAndReturn({
|
||||
where: { role: 'USER' },
|
||||
data: { verified: true }
|
||||
})
|
||||
// Returns array of updated users
|
||||
```
|
||||
|
||||
### upsert
|
||||
|
||||
Update or create:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.upsert({
|
||||
where: { email: 'alice@prisma.io' },
|
||||
update: { name: 'Alice Smith' },
|
||||
create: { email: 'alice@prisma.io', name: 'Alice' }
|
||||
})
|
||||
```
|
||||
|
||||
## Delete Operations
|
||||
|
||||
### delete
|
||||
|
||||
Delete a single record:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.delete({
|
||||
where: { id: 1 }
|
||||
})
|
||||
// Returns deleted record
|
||||
```
|
||||
|
||||
### deleteMany
|
||||
|
||||
Delete multiple records:
|
||||
|
||||
```typescript
|
||||
const result = await prisma.user.deleteMany({
|
||||
where: { role: 'GUEST' }
|
||||
})
|
||||
// Returns { count: 5 }
|
||||
|
||||
// Delete all
|
||||
const result = await prisma.user.deleteMany({})
|
||||
```
|
||||
|
||||
## Aggregation Operations
|
||||
|
||||
### count
|
||||
|
||||
```typescript
|
||||
const count = await prisma.user.count({
|
||||
where: { role: 'ADMIN' }
|
||||
})
|
||||
```
|
||||
|
||||
### aggregate
|
||||
|
||||
```typescript
|
||||
const result = await prisma.post.aggregate({
|
||||
_avg: { views: true },
|
||||
_sum: { views: true },
|
||||
_min: { views: true },
|
||||
_max: { views: true },
|
||||
_count: { _all: true }
|
||||
})
|
||||
```
|
||||
|
||||
### groupBy
|
||||
|
||||
```typescript
|
||||
const groups = await prisma.user.groupBy({
|
||||
by: ['country'],
|
||||
_count: { _all: true },
|
||||
_avg: { age: true },
|
||||
having: {
|
||||
age: { _avg: { gt: 30 } }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Return Types
|
||||
|
||||
| Method | Returns |
|
||||
|--------|---------|
|
||||
| `findUnique` | Record \| null |
|
||||
| `findUniqueOrThrow` | Record (throws if not found) |
|
||||
| `findFirst` | Record \| null |
|
||||
| `findFirstOrThrow` | Record (throws if not found) |
|
||||
| `findMany` | Record[] |
|
||||
| `create` | Record |
|
||||
| `createMany` | { count: number } |
|
||||
| `createManyAndReturn` | Record[] |
|
||||
| `update` | Record |
|
||||
| `updateMany` | { count: number } |
|
||||
| `delete` | Record |
|
||||
| `deleteMany` | { count: number } |
|
||||
| `count` | number |
|
||||
| `aggregate` | Aggregate result |
|
||||
| `groupBy` | Group result[] |
|
||||
@@ -0,0 +1,276 @@
|
||||
# Query Options
|
||||
|
||||
Options for controlling query behavior.
|
||||
|
||||
## select
|
||||
|
||||
Choose specific fields to return:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
// password: false (excluded by not including)
|
||||
}
|
||||
})
|
||||
// Returns: { id: 1, name: 'Alice', email: 'alice@prisma.io' }
|
||||
```
|
||||
|
||||
### Select relations
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
select: {
|
||||
name: true,
|
||||
posts: {
|
||||
select: {
|
||||
title: true,
|
||||
published: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Select with include inside
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
posts: {
|
||||
include: {
|
||||
comments: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Select relation count
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
_count: {
|
||||
select: { posts: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
// Returns: { name: 'Alice', _count: { posts: 5 } }
|
||||
```
|
||||
|
||||
## include
|
||||
|
||||
Include related records:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
include: {
|
||||
posts: true,
|
||||
profile: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Filtered include
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
include: {
|
||||
posts: {
|
||||
where: { published: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 5
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Nested include
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
include: {
|
||||
posts: {
|
||||
include: {
|
||||
comments: {
|
||||
include: {
|
||||
author: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Include relation count
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: { posts: true, followers: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## omit
|
||||
|
||||
Exclude specific fields:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
omit: {
|
||||
password: true
|
||||
}
|
||||
})
|
||||
// Returns all fields except password
|
||||
```
|
||||
|
||||
### Omit in relations
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
omit: { password: true },
|
||||
include: {
|
||||
posts: {
|
||||
omit: { content: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Note:** Cannot use `select` and `omit` together.
|
||||
|
||||
## where
|
||||
|
||||
Filter records:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
email: { contains: '@prisma.io' },
|
||||
role: 'ADMIN'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See `filters.md` for detailed filter operators.
|
||||
|
||||
## orderBy
|
||||
|
||||
Sort results:
|
||||
|
||||
```typescript
|
||||
// Single field
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: { name: 'asc' }
|
||||
})
|
||||
|
||||
// Multiple fields
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: [
|
||||
{ role: 'desc' },
|
||||
{ name: 'asc' }
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### Order by relation
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: {
|
||||
posts: { _count: 'desc' }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Null handling
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: {
|
||||
name: { sort: 'asc', nulls: 'last' }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## take & skip
|
||||
|
||||
Pagination:
|
||||
|
||||
```typescript
|
||||
// First page
|
||||
const users = await prisma.user.findMany({
|
||||
take: 10,
|
||||
skip: 0
|
||||
})
|
||||
|
||||
// Second page
|
||||
const users = await prisma.user.findMany({
|
||||
take: 10,
|
||||
skip: 10
|
||||
})
|
||||
```
|
||||
|
||||
### Negative take (reverse)
|
||||
|
||||
```typescript
|
||||
const lastUsers = await prisma.user.findMany({
|
||||
take: -10,
|
||||
orderBy: { id: 'asc' }
|
||||
})
|
||||
// Returns last 10 users
|
||||
```
|
||||
|
||||
## cursor
|
||||
|
||||
Cursor-based pagination:
|
||||
|
||||
```typescript
|
||||
// First page
|
||||
const firstPage = await prisma.user.findMany({
|
||||
take: 10,
|
||||
orderBy: { id: 'asc' }
|
||||
})
|
||||
|
||||
// Next page using cursor
|
||||
const nextPage = await prisma.user.findMany({
|
||||
take: 10,
|
||||
skip: 1, // Skip the cursor record
|
||||
cursor: { id: firstPage[firstPage.length - 1].id },
|
||||
orderBy: { id: 'asc' }
|
||||
})
|
||||
```
|
||||
|
||||
## distinct
|
||||
|
||||
Return unique values:
|
||||
|
||||
```typescript
|
||||
const cities = await prisma.user.findMany({
|
||||
distinct: ['city'],
|
||||
select: { city: true }
|
||||
})
|
||||
```
|
||||
|
||||
### Multiple distinct fields
|
||||
|
||||
```typescript
|
||||
const locations = await prisma.user.findMany({
|
||||
distinct: ['city', 'country']
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,198 @@
|
||||
# Raw Queries
|
||||
|
||||
Execute raw SQL when Prisma's query API isn't sufficient.
|
||||
|
||||
## $queryRaw
|
||||
|
||||
Execute SELECT queries and get typed results:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT * FROM "User" WHERE email LIKE ${'%@prisma.io'}
|
||||
`
|
||||
```
|
||||
|
||||
### With type
|
||||
|
||||
```typescript
|
||||
type User = { id: number; email: string; name: string | null }
|
||||
|
||||
const users = await prisma.$queryRaw<User[]>`
|
||||
SELECT id, email, name FROM "User" WHERE role = ${'ADMIN'}
|
||||
`
|
||||
```
|
||||
|
||||
### Dynamic table/column names
|
||||
|
||||
Use `Prisma.raw()` for identifiers (not safe for user input):
|
||||
|
||||
```typescript
|
||||
import { Prisma } from '../generated/client'
|
||||
|
||||
const column = 'email'
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT ${Prisma.raw(column)} FROM "User"
|
||||
`
|
||||
```
|
||||
|
||||
### With Prisma.sql
|
||||
|
||||
Build queries dynamically:
|
||||
|
||||
```typescript
|
||||
import { Prisma } from '../generated/client'
|
||||
|
||||
const email = 'alice@prisma.io'
|
||||
const query = Prisma.sql`SELECT * FROM "User" WHERE email = ${email}`
|
||||
const users = await prisma.$queryRaw(query)
|
||||
```
|
||||
|
||||
### Join multiple SQL fragments
|
||||
|
||||
```typescript
|
||||
import { Prisma } from '../generated/client'
|
||||
|
||||
const conditions = [
|
||||
Prisma.sql`role = ${'ADMIN'}`,
|
||||
Prisma.sql`verified = ${true}`
|
||||
]
|
||||
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT * FROM "User"
|
||||
WHERE ${Prisma.join(conditions, ' AND ')}
|
||||
`
|
||||
```
|
||||
|
||||
## $executeRaw
|
||||
|
||||
Execute INSERT, UPDATE, DELETE (returns affected count):
|
||||
|
||||
```typescript
|
||||
const count = await prisma.$executeRaw`
|
||||
UPDATE "User" SET verified = true WHERE email LIKE ${'%@prisma.io'}
|
||||
`
|
||||
console.log(`Updated ${count} users`)
|
||||
```
|
||||
|
||||
### Delete example
|
||||
|
||||
```typescript
|
||||
const deleted = await prisma.$executeRaw`
|
||||
DELETE FROM "User" WHERE "deletedAt" < ${thirtyDaysAgo}
|
||||
`
|
||||
```
|
||||
|
||||
### Insert example
|
||||
|
||||
```typescript
|
||||
const inserted = await prisma.$executeRaw`
|
||||
INSERT INTO "Log" (message, level, timestamp)
|
||||
VALUES (${message}, ${level}, ${new Date()})
|
||||
`
|
||||
```
|
||||
|
||||
## $queryRawUnsafe / $executeRawUnsafe
|
||||
|
||||
For fully dynamic queries (use with caution!):
|
||||
|
||||
```typescript
|
||||
// ⚠️ SQL injection risk - only use with trusted input
|
||||
const table = 'User'
|
||||
const users = await prisma.$queryRawUnsafe(
|
||||
`SELECT * FROM "${table}" WHERE id = $1`,
|
||||
userId
|
||||
)
|
||||
```
|
||||
|
||||
### Parameterized unsafe query
|
||||
|
||||
```typescript
|
||||
const result = await prisma.$executeRawUnsafe(
|
||||
'UPDATE "User" SET name = $1 WHERE id = $2',
|
||||
'Alice',
|
||||
1
|
||||
)
|
||||
```
|
||||
|
||||
## SQL Injection Prevention
|
||||
|
||||
### Safe (parameterized)
|
||||
|
||||
```typescript
|
||||
// ✅ User input is parameterized
|
||||
const email = userInput
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT * FROM "User" WHERE email = ${email}
|
||||
`
|
||||
```
|
||||
|
||||
### Unsafe (concatenation)
|
||||
|
||||
```typescript
|
||||
// ❌ SQL injection vulnerability!
|
||||
const email = userInput
|
||||
const users = await prisma.$queryRawUnsafe(
|
||||
`SELECT * FROM "User" WHERE email = '${email}'`
|
||||
)
|
||||
```
|
||||
|
||||
## Database-Specific Features
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```typescript
|
||||
// Array operations
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT * FROM "User" WHERE 'admin' = ANY(roles)
|
||||
`
|
||||
|
||||
// JSON operations
|
||||
const users = await prisma.$queryRaw`
|
||||
SELECT * FROM "User" WHERE metadata->>'theme' = 'dark'
|
||||
`
|
||||
```
|
||||
|
||||
### MySQL
|
||||
|
||||
```typescript
|
||||
// Full-text search
|
||||
const posts = await prisma.$queryRaw`
|
||||
SELECT * FROM Post WHERE MATCH(title, content) AGAINST(${searchTerm})
|
||||
`
|
||||
```
|
||||
|
||||
## Transactions with Raw Queries
|
||||
|
||||
```typescript
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.$executeRaw`UPDATE "Account" SET balance = balance - ${amount} WHERE id = ${senderId}`
|
||||
await tx.$executeRaw`UPDATE "Account" SET balance = balance + ${amount} WHERE id = ${recipientId}`
|
||||
})
|
||||
```
|
||||
|
||||
## Handling Results
|
||||
|
||||
### BigInt handling
|
||||
|
||||
PostgreSQL returns BigInt for COUNT:
|
||||
|
||||
```typescript
|
||||
const result = await prisma.$queryRaw<[{ count: bigint }]>`
|
||||
SELECT COUNT(*) as count FROM "User"
|
||||
`
|
||||
const count = Number(result[0].count)
|
||||
```
|
||||
|
||||
### Date handling
|
||||
|
||||
```typescript
|
||||
type Result = { createdAt: Date }
|
||||
const users = await prisma.$queryRaw<Result[]>`
|
||||
SELECT "createdAt" FROM "User"
|
||||
`
|
||||
// createdAt is already a Date object
|
||||
```
|
||||
|
||||
Invalid JavaScript `Date` values passed to raw queries fail validation instead of being silently serialized as `null`. Validate date input at the application boundary; do not rely on `new Date(badValue)` reaching the database.
|
||||
|
||||
When a driver adapter returns an unmapped database-specific error, Prisma surfaces `P2039` with the adapter's preserved original code/message. If those details are missing, fix the adapter mapping rather than parsing rendered error text.
|
||||
@@ -0,0 +1,308 @@
|
||||
# Relation Queries
|
||||
|
||||
Query and modify related records.
|
||||
|
||||
## Include Relations
|
||||
|
||||
Load related records:
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
include: {
|
||||
posts: true,
|
||||
profile: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Filtered include
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
include: {
|
||||
posts: {
|
||||
where: { published: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 5,
|
||||
select: { id: true, title: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Nested include
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
include: {
|
||||
posts: {
|
||||
include: {
|
||||
comments: {
|
||||
include: { author: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Select Relations
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: 1 },
|
||||
select: {
|
||||
name: true,
|
||||
posts: {
|
||||
select: { title: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Nested Writes
|
||||
|
||||
### Create with relations
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'alice@prisma.io',
|
||||
posts: {
|
||||
create: [
|
||||
{ title: 'Post 1' },
|
||||
{ title: 'Post 2' }
|
||||
]
|
||||
},
|
||||
profile: {
|
||||
create: { bio: 'Hello!' }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Create or connect
|
||||
|
||||
```typescript
|
||||
const post = await prisma.post.create({
|
||||
data: {
|
||||
title: 'New Post',
|
||||
author: {
|
||||
connectOrCreate: {
|
||||
where: { email: 'alice@prisma.io' },
|
||||
create: { email: 'alice@prisma.io', name: 'Alice' }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Connect existing
|
||||
|
||||
```typescript
|
||||
const post = await prisma.post.create({
|
||||
data: {
|
||||
title: 'New Post',
|
||||
author: {
|
||||
connect: { id: 1 }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Shorthand for foreign key
|
||||
const post = await prisma.post.create({
|
||||
data: {
|
||||
title: 'New Post',
|
||||
authorId: 1
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Update Relations
|
||||
|
||||
### Update related records
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
posts: {
|
||||
update: {
|
||||
where: { id: 1 },
|
||||
data: { title: 'Updated Title' }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Update many related
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
posts: {
|
||||
updateMany: {
|
||||
where: { published: false },
|
||||
data: { published: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Upsert related
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
profile: {
|
||||
upsert: {
|
||||
create: { bio: 'New bio' },
|
||||
update: { bio: 'Updated bio' }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Disconnect
|
||||
|
||||
```typescript
|
||||
// 1-to-1 optional
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
profile: { disconnect: true }
|
||||
}
|
||||
})
|
||||
|
||||
// Many-to-many
|
||||
const post = await prisma.post.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
tags: {
|
||||
disconnect: [{ id: 1 }, { id: 2 }]
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Delete related
|
||||
|
||||
```typescript
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
posts: {
|
||||
delete: { id: 1 }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Delete many
|
||||
const user = await prisma.user.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
posts: {
|
||||
deleteMany: { published: false }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Set (replace all)
|
||||
|
||||
```typescript
|
||||
// Replace all related records
|
||||
const post = await prisma.post.update({
|
||||
where: { id: 1 },
|
||||
data: {
|
||||
tags: {
|
||||
set: [{ id: 1 }, { id: 2 }]
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Relation Filters
|
||||
|
||||
### some
|
||||
|
||||
At least one matches:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
posts: { some: { published: true } }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### every
|
||||
|
||||
All match:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
posts: { every: { published: true } }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### none
|
||||
|
||||
None match:
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
posts: { none: { published: true } }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### is / isNot (1-to-1)
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
where: {
|
||||
profile: { is: { country: 'USA' } }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Count Relations
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
_count: {
|
||||
select: { posts: true, followers: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
// { name: 'Alice', _count: { posts: 5, followers: 100 } }
|
||||
```
|
||||
|
||||
### Filter counted relations
|
||||
|
||||
```typescript
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
_count: {
|
||||
select: {
|
||||
posts: { where: { published: true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,184 @@
|
||||
# Transactions
|
||||
|
||||
Execute multiple operations atomically.
|
||||
|
||||
## Sequential Transactions
|
||||
|
||||
Array of operations executed in order:
|
||||
|
||||
```typescript
|
||||
const [user, post] = await prisma.$transaction([
|
||||
prisma.user.create({ data: { email: 'alice@prisma.io' } }),
|
||||
prisma.post.create({ data: { title: 'Hello', authorId: 1 } })
|
||||
])
|
||||
```
|
||||
|
||||
### All or nothing
|
||||
|
||||
If any operation fails, all are rolled back:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await prisma.$transaction([
|
||||
prisma.user.create({ data: { email: 'alice@prisma.io' } }),
|
||||
prisma.user.create({ data: { email: 'alice@prisma.io' } }) // Duplicate!
|
||||
])
|
||||
} catch (e) {
|
||||
// Both operations rolled back
|
||||
}
|
||||
```
|
||||
|
||||
## Interactive Transactions
|
||||
|
||||
For complex logic and dependent operations:
|
||||
|
||||
```typescript
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Decrement sender balance
|
||||
const sender = await tx.account.update({
|
||||
where: { id: senderId },
|
||||
data: { balance: { decrement: amount } }
|
||||
})
|
||||
|
||||
// Check balance
|
||||
if (sender.balance < 0) {
|
||||
throw new Error('Insufficient funds')
|
||||
}
|
||||
|
||||
// Increment recipient balance
|
||||
await tx.account.update({
|
||||
where: { id: recipientId },
|
||||
data: { balance: { increment: amount } }
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Transaction options
|
||||
|
||||
```typescript
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
// operations
|
||||
},
|
||||
{
|
||||
maxWait: 5000, // Max wait to acquire lock (ms)
|
||||
timeout: 10000, // Max transaction duration (ms)
|
||||
isolationLevel: 'Serializable' // Isolation level
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Isolation levels
|
||||
|
||||
| Level | Description |
|
||||
|-------|-------------|
|
||||
| `ReadUncommitted` | Lowest isolation, can read uncommitted changes |
|
||||
| `ReadCommitted` | Only read committed changes |
|
||||
| `RepeatableRead` | Consistent reads within transaction |
|
||||
| `Serializable` | Highest isolation, serialized execution |
|
||||
|
||||
## Nested Writes
|
||||
|
||||
Automatic transactions for nested operations:
|
||||
|
||||
```typescript
|
||||
// This is automatically a transaction
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: 'alice@prisma.io',
|
||||
posts: {
|
||||
create: [
|
||||
{ title: 'Post 1' },
|
||||
{ title: 'Post 2' }
|
||||
]
|
||||
},
|
||||
profile: {
|
||||
create: { bio: 'Hello!' }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Transaction Client
|
||||
|
||||
The `tx` parameter is a Prisma Client scoped to the transaction:
|
||||
|
||||
```typescript
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Use tx instead of prisma
|
||||
await tx.user.create({ ... })
|
||||
await tx.post.create({ ... })
|
||||
|
||||
// Can call methods
|
||||
const count = await tx.user.count()
|
||||
})
|
||||
```
|
||||
|
||||
## OrThrow in Transactions
|
||||
|
||||
Use with interactive transactions:
|
||||
|
||||
```typescript
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// If not found, throws and rolls back entire transaction
|
||||
const user = await tx.user.findUniqueOrThrow({
|
||||
where: { id: 1 }
|
||||
})
|
||||
|
||||
await tx.post.create({
|
||||
data: { title: 'New Post', authorId: user.id }
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Keep transactions short
|
||||
|
||||
```typescript
|
||||
// Good - only DB operations in transaction
|
||||
const data = prepareData() // Outside transaction
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.user.create({ data })
|
||||
})
|
||||
```
|
||||
|
||||
### Handle errors
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// operations
|
||||
})
|
||||
} catch (e) {
|
||||
if (e.code === 'P2002') {
|
||||
// Handle unique constraint violation
|
||||
}
|
||||
throw e
|
||||
}
|
||||
```
|
||||
|
||||
### Use appropriate isolation
|
||||
|
||||
```typescript
|
||||
// Default is fine for most cases
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// operations
|
||||
})
|
||||
|
||||
// Use Serializable for strict consistency
|
||||
await prisma.$transaction(
|
||||
async (tx) => { /* operations */ },
|
||||
{ isolationLevel: 'Serializable' }
|
||||
)
|
||||
```
|
||||
|
||||
## Sequential vs Interactive
|
||||
|
||||
| Feature | Sequential | Interactive |
|
||||
|---------|------------|-------------|
|
||||
| Syntax | Array | Async function |
|
||||
| Dependent ops | No | Yes |
|
||||
| Conditional logic | No | Yes |
|
||||
| Performance | Better | More flexible |
|
||||
| Use case | Simple batch | Complex logic |
|
||||
Reference in New Issue
Block a user