wip
This commit is contained in:
@@ -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