prisma-sql 1.16.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generator.cjs +14 -5
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +14 -5
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +9 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/readme.md +435 -594
package/readme.md
CHANGED
|
@@ -2,17 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Speed up Prisma reads **2-7x** by executing queries via postgres.js or better-sqlite3 instead of Prisma's query engine.
|
|
4
4
|
|
|
5
|
+
**Same API. Same types. Just faster.**
|
|
6
|
+
|
|
5
7
|
```typescript
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
+
import { PrismaClient, Prisma } from '@prisma/client'
|
|
9
|
+
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
|
|
10
|
+
import postgres from 'postgres'
|
|
8
11
|
|
|
9
12
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
10
|
-
const sql = postgres(DATABASE_URL)
|
|
13
|
+
const sql = postgres(process.env.DATABASE_URL)
|
|
11
14
|
const prisma = new PrismaClient().$extends(
|
|
12
15
|
speedExtension({ postgres: sql, models }),
|
|
13
16
|
)
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
// Just use Prisma normally - queries are automatically faster
|
|
19
|
+
const users = await prisma.user.findMany({
|
|
20
|
+
where: { status: 'ACTIVE' },
|
|
21
|
+
include: { posts: true },
|
|
22
|
+
})
|
|
16
23
|
```
|
|
17
24
|
|
|
18
25
|
## Why?
|
|
@@ -44,81 +51,6 @@ npm install prisma-sql better-sqlite3
|
|
|
44
51
|
|
|
45
52
|
## Quick Start
|
|
46
53
|
|
|
47
|
-
You can use prisma-sql in two ways:
|
|
48
|
-
|
|
49
|
-
### 1. Generator Mode (Recommended - Fastest)
|
|
50
|
-
|
|
51
|
-
Prebake SQL queries at build time for maximum performance.
|
|
52
|
-
|
|
53
|
-
**Add generator to schema:**
|
|
54
|
-
|
|
55
|
-
```prisma
|
|
56
|
-
// schema.prisma
|
|
57
|
-
datasource db {
|
|
58
|
-
provider = "postgresql"
|
|
59
|
-
url = env("DATABASE_URL")
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
generator client {
|
|
63
|
-
provider = "prisma-client-js"
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
generator sql {
|
|
67
|
-
provider = "prisma-sql-generator"
|
|
68
|
-
// Dialect auto-detected from datasource.provider
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
model User {
|
|
72
|
-
id Int @id @default(autoincrement())
|
|
73
|
-
email String @unique
|
|
74
|
-
status String
|
|
75
|
-
posts Post[]
|
|
76
|
-
|
|
77
|
-
/// @sql.findMany({ where: { status: "ACTIVE" } })
|
|
78
|
-
/// @sql.findMany({ where: { status: "PENDING" } })
|
|
79
|
-
/// @sql.count({ where: { status: "ACTIVE" } })
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
model Post {
|
|
83
|
-
id Int @id @default(autoincrement())
|
|
84
|
-
title String
|
|
85
|
-
published Boolean
|
|
86
|
-
authorId Int
|
|
87
|
-
author User @relation(fields: [authorId], references: [id])
|
|
88
|
-
|
|
89
|
-
/// @sql.findMany({ where: { published: true }, include: { author: true } })
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Generate and use:**
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
npx prisma generate
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
import { PrismaClient } from '@prisma/client'
|
|
101
|
-
import { createExtension } from '.prisma/client/sql'
|
|
102
|
-
import postgres from 'postgres'
|
|
103
|
-
|
|
104
|
-
const sql = postgres(process.env.DATABASE_URL)
|
|
105
|
-
const prisma = new PrismaClient().$extends(createExtension({ postgres: sql }))
|
|
106
|
-
|
|
107
|
-
// ⚡ PREBAKED - Uses pre-generated SQL (instant)
|
|
108
|
-
const activeUsers = await prisma.user.findMany({
|
|
109
|
-
where: { status: 'ACTIVE' },
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
// 🔨 RUNTIME - Generates SQL on-the-fly (still fast)
|
|
113
|
-
const searchUsers = await prisma.user.findMany({
|
|
114
|
-
where: { email: { contains: '@example.com' } },
|
|
115
|
-
})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 2. Runtime Mode (Simpler, No Build Step)
|
|
119
|
-
|
|
120
|
-
Generate SQL on-the-fly without code generation.
|
|
121
|
-
|
|
122
54
|
**PostgreSQL:**
|
|
123
55
|
|
|
124
56
|
```typescript
|
|
@@ -128,10 +60,12 @@ import postgres from 'postgres'
|
|
|
128
60
|
|
|
129
61
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
130
62
|
const sql = postgres(process.env.DATABASE_URL)
|
|
63
|
+
|
|
131
64
|
const prisma = new PrismaClient().$extends(
|
|
132
65
|
speedExtension({ postgres: sql, models }),
|
|
133
66
|
)
|
|
134
67
|
|
|
68
|
+
// Use Prisma exactly as before - it's just faster now
|
|
135
69
|
const users = await prisma.user.findMany({
|
|
136
70
|
where: { status: 'ACTIVE' },
|
|
137
71
|
include: { posts: true },
|
|
@@ -147,6 +81,7 @@ import Database from 'better-sqlite3'
|
|
|
147
81
|
|
|
148
82
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
149
83
|
const db = new Database('./data.db')
|
|
84
|
+
|
|
150
85
|
const prisma = new PrismaClient().$extends(
|
|
151
86
|
speedExtension({ sqlite: db, models }),
|
|
152
87
|
)
|
|
@@ -154,192 +89,40 @@ const prisma = new PrismaClient().$extends(
|
|
|
154
89
|
const users = await prisma.user.findMany({ where: { status: 'ACTIVE' } })
|
|
155
90
|
```
|
|
156
91
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
### Generator Mode (Recommended)
|
|
160
|
-
|
|
161
|
-
**Pros:**
|
|
162
|
-
|
|
163
|
-
- ⚡ **Fastest** - SQL pre-generated at build time
|
|
164
|
-
- 🎯 **Zero overhead** for known queries
|
|
165
|
-
- 🔨 **Auto-fallback** to runtime for unknown queries
|
|
166
|
-
- 📊 **Track performance** with `prebaked` flag
|
|
167
|
-
|
|
168
|
-
**Cons:**
|
|
169
|
-
|
|
170
|
-
- Requires `npx prisma generate` step
|
|
171
|
-
- Need to define common queries upfront
|
|
172
|
-
|
|
173
|
-
**When to use:**
|
|
174
|
-
|
|
175
|
-
- ✅ Production applications
|
|
176
|
-
- ✅ Known query patterns (dashboards, APIs)
|
|
177
|
-
- ✅ Want maximum performance
|
|
178
|
-
- ✅ Build step is acceptable
|
|
179
|
-
|
|
180
|
-
### Runtime Mode
|
|
181
|
-
|
|
182
|
-
**Pros:**
|
|
183
|
-
|
|
184
|
-
- 🚀 **Simple** - no build step
|
|
185
|
-
- 🔧 **Flexible** - handles any query
|
|
186
|
-
- 📝 **Easy prototyping**
|
|
187
|
-
|
|
188
|
-
**Cons:**
|
|
189
|
-
|
|
190
|
-
- Small overhead (~0.2ms) for SQL generation
|
|
191
|
-
- Still 2-7x faster than Prisma
|
|
192
|
-
|
|
193
|
-
**When to use:**
|
|
194
|
-
|
|
195
|
-
- ✅ Rapid prototyping
|
|
196
|
-
- ✅ Fully dynamic queries
|
|
197
|
-
- ✅ No build step desired
|
|
198
|
-
- ✅ Query patterns unknown
|
|
199
|
-
|
|
200
|
-
## Generator Mode Details
|
|
201
|
-
|
|
202
|
-
### How It Works
|
|
203
|
-
|
|
204
|
-
```
|
|
205
|
-
Build Time:
|
|
206
|
-
schema.prisma
|
|
207
|
-
↓
|
|
208
|
-
/// @sql.findMany({ where: { status: "ACTIVE" } })
|
|
209
|
-
↓
|
|
210
|
-
npx prisma generate
|
|
211
|
-
↓
|
|
212
|
-
.prisma/client/sql/index.ts
|
|
213
|
-
↓
|
|
214
|
-
QUERIES = {
|
|
215
|
-
User: {
|
|
216
|
-
'{"where":{"status":"ACTIVE"}}': {
|
|
217
|
-
sql: 'SELECT * FROM users WHERE status = $1',
|
|
218
|
-
params: ['ACTIVE'],
|
|
219
|
-
dynamicKeys: []
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
Runtime:
|
|
225
|
-
prisma.user.findMany({ where: { status: 'ACTIVE' } })
|
|
226
|
-
↓
|
|
227
|
-
Normalize query → '{"where":{"status":"ACTIVE"}}'
|
|
228
|
-
↓
|
|
229
|
-
QUERIES.User[query] found?
|
|
230
|
-
↓
|
|
231
|
-
YES → ⚡ Use prebaked SQL (0.03ms overhead)
|
|
232
|
-
↓
|
|
233
|
-
NO → 🔨 Generate SQL runtime (0.2ms overhead)
|
|
234
|
-
↓
|
|
235
|
-
Execute via postgres.js/better-sqlite3
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Generator Configuration
|
|
239
|
-
|
|
240
|
-
```prisma
|
|
241
|
-
generator sql {
|
|
242
|
-
provider = "prisma-sql-generator"
|
|
243
|
-
|
|
244
|
-
// Optional: Override auto-detected dialect (rarely needed)
|
|
245
|
-
// dialect = "postgres" // or "sqlite"
|
|
246
|
-
|
|
247
|
-
// Optional: Output directory (default: ../node_modules/.prisma/client/sql)
|
|
248
|
-
// output = "./generated/sql"
|
|
249
|
-
|
|
250
|
-
// Optional: Skip invalid directives instead of failing (default: false)
|
|
251
|
-
// skipInvalid = "true"
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Supported Directive Patterns
|
|
256
|
-
|
|
257
|
-
```prisma
|
|
258
|
-
model User {
|
|
259
|
-
// Simple queries
|
|
260
|
-
/// @sql.findMany({ where: { status: "ACTIVE" } })
|
|
261
|
-
/// @sql.findFirst({ where: { email: "test@example.com" } })
|
|
262
|
-
/// @sql.findUnique({ where: { id: 1 } })
|
|
263
|
-
|
|
264
|
-
// With relations
|
|
265
|
-
/// @sql.findMany({ include: { posts: true } })
|
|
266
|
-
/// @sql.findMany({ include: { posts: { where: { published: true } } } })
|
|
267
|
-
|
|
268
|
-
// With pagination
|
|
269
|
-
/// @sql.findMany({ take: 10, skip: 20, orderBy: { createdAt: "desc" } })
|
|
270
|
-
|
|
271
|
-
// Aggregations
|
|
272
|
-
/// @sql.count({ where: { status: "ACTIVE" } })
|
|
273
|
-
/// @sql.aggregate({ _count: { _all: true }, _avg: { age: true } })
|
|
274
|
-
/// @sql.groupBy({ by: ["status"], _count: { _all: true } })
|
|
275
|
-
|
|
276
|
-
// Dynamic parameters
|
|
277
|
-
/// @sql.findMany({ where: { status: { $param: "status" } } })
|
|
278
|
-
/// @sql.findMany({ where: { age: { gte: { $param: "minAge" } } } })
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### Dynamic Parameters
|
|
283
|
-
|
|
284
|
-
Use `$param` for runtime values:
|
|
285
|
-
|
|
286
|
-
```prisma
|
|
287
|
-
model User {
|
|
288
|
-
/// @sql.findMany({ where: { status: { $param: "status" } } })
|
|
289
|
-
/// @sql.findMany({ where: { age: { gte: { $param: "minAge" } } } })
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
// Prebaked SQL with runtime parameters
|
|
295
|
-
const users = await prisma.user.findMany({
|
|
296
|
-
where: { status: 'ACTIVE' }, // Matches directive with $param
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
// SQL: SELECT * FROM users WHERE status = $1
|
|
300
|
-
// Params: ['ACTIVE']
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### Performance Tracking
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
const prisma = new PrismaClient().$extends(
|
|
307
|
-
createExtension({
|
|
308
|
-
postgres: sql,
|
|
309
|
-
debug: true,
|
|
310
|
-
onQuery: (info) => {
|
|
311
|
-
console.log(`${info.model}.${info.method}: ${info.duration}ms`)
|
|
312
|
-
console.log(info.prebaked ? '⚡ PREBAKED' : '🔨 RUNTIME')
|
|
313
|
-
},
|
|
314
|
-
}),
|
|
315
|
-
)
|
|
316
|
-
```
|
|
92
|
+
That's it! All your read queries are now 2-7x faster with zero code changes.
|
|
317
93
|
|
|
318
|
-
##
|
|
94
|
+
## Performance
|
|
319
95
|
|
|
320
|
-
|
|
96
|
+
Benchmarks from 137 E2E tests comparing identical queries:
|
|
321
97
|
|
|
322
|
-
|
|
323
|
-
- `count`
|
|
324
|
-
- `aggregate` (\_count, \_sum, \_avg, \_min, \_max)
|
|
325
|
-
- `groupBy` with having clauses
|
|
98
|
+
### PostgreSQL Results (Highlights)
|
|
326
99
|
|
|
327
|
-
|
|
100
|
+
| Query Type | Prisma v6 | Prisma v7 | This Extension | Speedup vs v7 |
|
|
101
|
+
| ------------------- | --------- | --------- | -------------- | ------------- |
|
|
102
|
+
| Simple where | 0.40ms | 0.34ms | 0.17ms | **2.0x** ⚡ |
|
|
103
|
+
| Complex conditions | 13.10ms | 6.90ms | 2.37ms | **2.9x** ⚡ |
|
|
104
|
+
| With relations | 0.83ms | 0.72ms | 0.41ms | **1.8x** ⚡ |
|
|
105
|
+
| Nested relations | 28.35ms | 14.34ms | 4.81ms | **3.0x** ⚡ |
|
|
106
|
+
| Aggregations | 0.42ms | 0.44ms | 0.24ms | **1.8x** ⚡ |
|
|
107
|
+
| Multi-field orderBy | 3.97ms | 2.38ms | 1.09ms | **2.2x** ⚡ |
|
|
328
108
|
|
|
329
|
-
|
|
330
|
-
- `createMany`, `updateMany`, `deleteMany`
|
|
331
|
-
- Transactions (`$transaction`)
|
|
332
|
-
- Middleware
|
|
109
|
+
**Overall:** 2.10x faster than Prisma v7, 2.39x faster than v6
|
|
333
110
|
|
|
334
|
-
|
|
111
|
+
### SQLite Results (Highlights)
|
|
335
112
|
|
|
336
|
-
|
|
113
|
+
| Query Type | Prisma v6 | Prisma v7 | This Extension | Speedup vs v7 |
|
|
114
|
+
| ------------------ | --------- | --------- | -------------- | ------------- |
|
|
115
|
+
| Simple where | 0.45ms | 0.23ms | 0.03ms | **7.7x** ⚡ |
|
|
116
|
+
| Complex conditions | 10.32ms | 3.87ms | 0.93ms | **4.2x** ⚡ |
|
|
117
|
+
| Relation filters | 166.62ms | 128.44ms | 2.40ms | **53.5x** ⚡ |
|
|
118
|
+
| Count queries | 0.17ms | 0.07ms | 0.01ms | **7.0x** ⚡ |
|
|
337
119
|
|
|
338
|
-
|
|
120
|
+
**Overall:** 5.48x faster than Prisma v7, 7.51x faster than v6
|
|
339
121
|
|
|
340
|
-
|
|
122
|
+
<details>
|
|
123
|
+
<summary><b>View Full Benchmark Results (137 queries)</b></summary>
|
|
341
124
|
|
|
342
|
-
|
|
125
|
+
### POSTGRES - Complete Results
|
|
343
126
|
|
|
344
127
|
| Test | Prisma v6 | Prisma v7 | Generated | Drizzle | v6 Speedup | v7 Speedup |
|
|
345
128
|
| ------------------------ | --------- | --------- | --------- | ------- | ---------- | ---------- |
|
|
@@ -403,17 +186,7 @@ Benchmarks from 137 E2E tests comparing identical queries against Prisma v6, Pri
|
|
|
403
186
|
| ILIKE special chars | 0.22ms | 0.26ms | 0.14ms | N/A | 1.54x | 1.76x |
|
|
404
187
|
| LIKE case sensitive | 0.19ms | 0.22ms | 0.12ms | N/A | 1.62x | 1.85x |
|
|
405
188
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
- Generated SQL vs Prisma v6: **2.39x faster**
|
|
409
|
-
- Generated SQL vs Prisma v7: **2.10x faster**
|
|
410
|
-
- Generated SQL vs Drizzle: **1.53x faster**
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
#### SQLITE:
|
|
415
|
-
|
|
416
|
-
---
|
|
189
|
+
### SQLITE - Complete Results
|
|
417
190
|
|
|
418
191
|
| Test | Prisma v6 | Prisma v7 | Generated | Drizzle | v6 Speedup | v7 Speedup |
|
|
419
192
|
| ------------------------ | --------- | --------- | --------- | ------- | ---------- | ---------- |
|
|
@@ -474,48 +247,27 @@ Benchmarks from 137 E2E tests comparing identical queries against Prisma v6, Pri
|
|
|
474
247
|
| \_count relation | 0.62ms | 0.46ms | 0.32ms | N/A | 1.93x | 1.44x |
|
|
475
248
|
| \_count multi-relation | 0.14ms | 0.17ms | 0.04ms | N/A | 3.22x | 4.09x |
|
|
476
249
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
- Generated SQL vs Prisma v6: **7.51x faster**
|
|
480
|
-
- Generated SQL vs Prisma v7: **5.48x faster**
|
|
481
|
-
- Generated SQL vs Drizzle: **2.61x faster**
|
|
482
|
-
|
|
483
|
-
> **Note on Prisma v7:** Prisma v7 introduced significant performance improvements (39% faster than v6 on PostgreSQL, 24% faster on SQLite), but this extension still provides 2-7x additional speedup over v7.
|
|
484
|
-
|
|
485
|
-
> **Benchmarks:** These are representative results from our test suite running on a MacBook Pro M1 with PostgreSQL 15 and SQLite 3.43. Your mileage may vary based on:
|
|
486
|
-
>
|
|
487
|
-
> - Database configuration and indexes
|
|
488
|
-
> - Query complexity and data volume
|
|
489
|
-
> - Hardware and network latency
|
|
490
|
-
> - Concurrent load
|
|
491
|
-
>
|
|
492
|
-
> Run benchmarks with your own schema and data for accurate measurements. See [Benchmarking](#benchmarking) section below.
|
|
250
|
+
</details>
|
|
493
251
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
## Configuration
|
|
497
|
-
|
|
498
|
-
### Basic Configuration
|
|
252
|
+
> **Note:** Benchmarks run on MacBook Pro M1 with PostgreSQL 15 and SQLite 3.43. Results vary based on database config, indexes, query complexity, and hardware. Run your own benchmarks for accurate measurements.
|
|
499
253
|
|
|
500
|
-
|
|
501
|
-
import { Prisma } from '@prisma/client'
|
|
502
|
-
import { convertDMMFToModels } from 'prisma-sql'
|
|
254
|
+
## What Gets Faster
|
|
503
255
|
|
|
504
|
-
|
|
256
|
+
**Accelerated (via raw SQL):**
|
|
505
257
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
258
|
+
- ✅ `findMany`, `findFirst`, `findUnique`
|
|
259
|
+
- ✅ `count`
|
|
260
|
+
- ✅ `aggregate` (\_count, \_sum, \_avg, \_min, \_max)
|
|
261
|
+
- ✅ `groupBy` with having clauses
|
|
509
262
|
|
|
510
|
-
|
|
263
|
+
**Unchanged (still uses Prisma):**
|
|
511
264
|
|
|
512
|
-
|
|
265
|
+
- `create`, `update`, `delete`, `upsert`
|
|
266
|
+
- `createMany`, `updateMany`, `deleteMany`
|
|
267
|
+
- Transactions (`$transaction`)
|
|
268
|
+
- Middleware
|
|
513
269
|
|
|
514
|
-
|
|
515
|
-
console.log(`${info.model}.${info.method}: ${info.duration}ms`)
|
|
516
|
-
},
|
|
517
|
-
})
|
|
518
|
-
```
|
|
270
|
+
## Configuration
|
|
519
271
|
|
|
520
272
|
### Debug Mode
|
|
521
273
|
|
|
@@ -527,25 +279,13 @@ import { Prisma } from '@prisma/client'
|
|
|
527
279
|
|
|
528
280
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
529
281
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
### Selective Models
|
|
538
|
-
|
|
539
|
-
Only accelerate specific models:
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
543
|
-
|
|
544
|
-
speedExtension({
|
|
545
|
-
postgres: sql,
|
|
546
|
-
models,
|
|
547
|
-
allowedModels: ['User', 'Post'],
|
|
548
|
-
})
|
|
282
|
+
const prisma = new PrismaClient().$extends(
|
|
283
|
+
speedExtension({
|
|
284
|
+
postgres: sql,
|
|
285
|
+
models,
|
|
286
|
+
debug: true, // Logs SQL for every query
|
|
287
|
+
}),
|
|
288
|
+
)
|
|
549
289
|
```
|
|
550
290
|
|
|
551
291
|
### Performance Monitoring
|
|
@@ -555,162 +295,76 @@ Track query performance:
|
|
|
555
295
|
```typescript
|
|
556
296
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
557
297
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
298
|
+
const prisma = new PrismaClient().$extends(
|
|
299
|
+
speedExtension({
|
|
300
|
+
postgres: sql,
|
|
301
|
+
models,
|
|
302
|
+
onQuery: (info) => {
|
|
303
|
+
console.log(`${info.model}.${info.method}: ${info.duration}ms`)
|
|
563
304
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
})
|
|
305
|
+
if (info.duration > 100) {
|
|
306
|
+
logger.warn('Slow query', {
|
|
307
|
+
model: info.model,
|
|
308
|
+
method: info.method,
|
|
309
|
+
sql: info.sql,
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
}),
|
|
314
|
+
)
|
|
573
315
|
```
|
|
574
316
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
### Read Replicas
|
|
578
|
-
|
|
579
|
-
Send writes to primary, reads to replica:
|
|
317
|
+
The `onQuery` callback receives:
|
|
580
318
|
|
|
581
319
|
```typescript
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const fastPrisma = new PrismaClient().$extends(
|
|
591
|
-
speedExtension({ postgres: replica, models })
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
await primary.user.create({ data: { ... } })
|
|
595
|
-
const users = await fastPrisma.user.findMany()
|
|
320
|
+
interface QueryInfo {
|
|
321
|
+
model: string // "User"
|
|
322
|
+
method: string // "findMany"
|
|
323
|
+
sql: string // The executed SQL
|
|
324
|
+
params: unknown[] // SQL parameters
|
|
325
|
+
duration: number // Query duration in ms
|
|
326
|
+
prebaked: boolean // true if using generated SQL (see Advanced Usage)
|
|
327
|
+
}
|
|
596
328
|
```
|
|
597
329
|
|
|
598
|
-
###
|
|
330
|
+
### Selective Models
|
|
599
331
|
|
|
600
|
-
|
|
332
|
+
Only accelerate specific models:
|
|
601
333
|
|
|
602
334
|
```typescript
|
|
603
335
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
604
336
|
|
|
605
|
-
const sql = postgres(process.env.DATABASE_URL, {
|
|
606
|
-
max: 20,
|
|
607
|
-
idle_timeout: 20,
|
|
608
|
-
connect_timeout: 10,
|
|
609
|
-
ssl: 'require',
|
|
610
|
-
})
|
|
611
|
-
|
|
612
337
|
const prisma = new PrismaClient().$extends(
|
|
613
|
-
speedExtension({
|
|
338
|
+
speedExtension({
|
|
339
|
+
postgres: sql,
|
|
340
|
+
models,
|
|
341
|
+
allowedModels: ['User', 'Post'], // Only speed up these models
|
|
342
|
+
}),
|
|
614
343
|
)
|
|
615
344
|
```
|
|
616
345
|
|
|
617
|
-
|
|
346
|
+
## Supported Queries
|
|
618
347
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
```typescript
|
|
622
|
-
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
623
|
-
const USE_FAST_READS = process.env.FAST_READS === 'true'
|
|
624
|
-
|
|
625
|
-
const sql = postgres(DATABASE_URL)
|
|
626
|
-
const prisma = new PrismaClient()
|
|
627
|
-
|
|
628
|
-
const db = USE_FAST_READS
|
|
629
|
-
? prisma.$extends(speedExtension({ postgres: sql, models }))
|
|
630
|
-
: prisma
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
### Access Original Prisma
|
|
634
|
-
|
|
635
|
-
```typescript
|
|
636
|
-
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
637
|
-
const prisma = new PrismaClient().$extends(
|
|
638
|
-
speedExtension({ postgres: sql, models }),
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
const fast = await prisma.user.findMany()
|
|
642
|
-
|
|
643
|
-
const slow = await prisma.$original.user.findMany()
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
## Edge Runtime
|
|
647
|
-
|
|
648
|
-
### Vercel Edge Functions
|
|
649
|
-
|
|
650
|
-
```typescript
|
|
651
|
-
import { PrismaClient, Prisma } from '@prisma/client'
|
|
652
|
-
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
|
|
653
|
-
import postgres from 'postgres'
|
|
654
|
-
|
|
655
|
-
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
656
|
-
const sql = postgres(process.env.DATABASE_URL)
|
|
657
|
-
const prisma = new PrismaClient().$extends(
|
|
658
|
-
speedExtension({ postgres: sql, models }),
|
|
659
|
-
)
|
|
660
|
-
|
|
661
|
-
export const config = { runtime: 'edge' }
|
|
662
|
-
|
|
663
|
-
export default async function handler(req: Request) {
|
|
664
|
-
const users = await prisma.user.findMany()
|
|
665
|
-
return Response.json(users)
|
|
666
|
-
}
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
### Cloudflare Workers
|
|
670
|
-
|
|
671
|
-
For Cloudflare Workers, use the standalone SQL generation API instead of the extension:
|
|
672
|
-
|
|
673
|
-
```typescript
|
|
674
|
-
import { createToSQL, convertDMMFToModels } from 'prisma-sql'
|
|
675
|
-
import { Prisma } from '@prisma/client'
|
|
676
|
-
|
|
677
|
-
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
678
|
-
const toSQL = createToSQL(models, 'sqlite')
|
|
679
|
-
|
|
680
|
-
export default {
|
|
681
|
-
async fetch(request: Request, env: Env) {
|
|
682
|
-
const { sql, params } = toSQL('User', 'findMany', {
|
|
683
|
-
where: { status: 'ACTIVE' },
|
|
684
|
-
})
|
|
685
|
-
|
|
686
|
-
const result = await env.DB.prepare(sql)
|
|
687
|
-
.bind(...params)
|
|
688
|
-
.all()
|
|
689
|
-
return Response.json(result.results)
|
|
690
|
-
},
|
|
691
|
-
}
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
> **Note:** The Prisma Client extension is not recommended for Cloudflare Workers due to cold start overhead. Use the `createToSQL` API for edge deployments.
|
|
695
|
-
|
|
696
|
-
## Supported Queries
|
|
697
|
-
|
|
698
|
-
### Filters
|
|
348
|
+
### Filters
|
|
699
349
|
|
|
700
350
|
```typescript
|
|
351
|
+
// Comparisons
|
|
701
352
|
{ age: { gt: 18, lte: 65 } }
|
|
702
353
|
{ status: { in: ['ACTIVE', 'PENDING'] } }
|
|
703
354
|
{ status: { notIn: ['DELETED'] } }
|
|
704
355
|
|
|
356
|
+
// String operations
|
|
705
357
|
{ email: { contains: '@example.com' } }
|
|
706
358
|
{ email: { startsWith: 'user' } }
|
|
707
359
|
{ email: { endsWith: '.com' } }
|
|
708
360
|
{ email: { contains: 'EXAMPLE', mode: 'insensitive' } }
|
|
709
361
|
|
|
362
|
+
// Boolean logic
|
|
710
363
|
{ AND: [{ status: 'ACTIVE' }, { verified: true }] }
|
|
711
364
|
{ OR: [{ role: 'ADMIN' }, { role: 'MODERATOR' }] }
|
|
712
365
|
{ NOT: { status: 'DELETED' } }
|
|
713
366
|
|
|
367
|
+
// Null checks
|
|
714
368
|
{ deletedAt: null }
|
|
715
369
|
{ deletedAt: { not: null } }
|
|
716
370
|
```
|
|
@@ -718,6 +372,7 @@ export default {
|
|
|
718
372
|
### Relations
|
|
719
373
|
|
|
720
374
|
```typescript
|
|
375
|
+
// Include relations
|
|
721
376
|
{
|
|
722
377
|
include: {
|
|
723
378
|
posts: true,
|
|
@@ -725,6 +380,7 @@ export default {
|
|
|
725
380
|
}
|
|
726
381
|
}
|
|
727
382
|
|
|
383
|
+
// Nested includes with filters
|
|
728
384
|
{
|
|
729
385
|
include: {
|
|
730
386
|
posts: {
|
|
@@ -736,27 +392,22 @@ export default {
|
|
|
736
392
|
}
|
|
737
393
|
}
|
|
738
394
|
|
|
395
|
+
// Relation filters
|
|
739
396
|
{
|
|
740
397
|
where: {
|
|
741
|
-
posts: {
|
|
742
|
-
some: { published: true }
|
|
743
|
-
}
|
|
398
|
+
posts: { some: { published: true } }
|
|
744
399
|
}
|
|
745
400
|
}
|
|
746
401
|
|
|
747
402
|
{
|
|
748
403
|
where: {
|
|
749
|
-
posts: {
|
|
750
|
-
every: { published: true }
|
|
751
|
-
}
|
|
404
|
+
posts: { every: { published: true } }
|
|
752
405
|
}
|
|
753
406
|
}
|
|
754
407
|
|
|
755
408
|
{
|
|
756
409
|
where: {
|
|
757
|
-
posts: {
|
|
758
|
-
none: { published: false }
|
|
759
|
-
}
|
|
410
|
+
posts: { none: { published: false } }
|
|
760
411
|
}
|
|
761
412
|
}
|
|
762
413
|
```
|
|
@@ -764,12 +415,14 @@ export default {
|
|
|
764
415
|
### Pagination & Ordering
|
|
765
416
|
|
|
766
417
|
```typescript
|
|
418
|
+
// Basic pagination
|
|
767
419
|
{
|
|
768
420
|
take: 10,
|
|
769
421
|
skip: 20,
|
|
770
422
|
orderBy: { createdAt: 'desc' }
|
|
771
423
|
}
|
|
772
424
|
|
|
425
|
+
// Cursor-based pagination
|
|
773
426
|
{
|
|
774
427
|
cursor: { id: 100 },
|
|
775
428
|
take: 10,
|
|
@@ -777,6 +430,7 @@ export default {
|
|
|
777
430
|
orderBy: { id: 'asc' }
|
|
778
431
|
}
|
|
779
432
|
|
|
433
|
+
// Multi-field ordering
|
|
780
434
|
{
|
|
781
435
|
orderBy: [
|
|
782
436
|
{ status: 'asc' },
|
|
@@ -784,22 +438,15 @@ export default {
|
|
|
784
438
|
{ createdAt: 'desc' }
|
|
785
439
|
]
|
|
786
440
|
}
|
|
787
|
-
|
|
788
|
-
{
|
|
789
|
-
orderBy: {
|
|
790
|
-
name: {
|
|
791
|
-
sort: 'asc',
|
|
792
|
-
nulls: 'last'
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
441
|
```
|
|
797
442
|
|
|
798
443
|
### Aggregations
|
|
799
444
|
|
|
800
445
|
```typescript
|
|
446
|
+
// Count
|
|
801
447
|
await prisma.user.count({ where: { status: 'ACTIVE' } })
|
|
802
448
|
|
|
449
|
+
// Aggregate
|
|
803
450
|
await prisma.task.aggregate({
|
|
804
451
|
where: { status: 'DONE' },
|
|
805
452
|
_count: { _all: true },
|
|
@@ -809,6 +456,7 @@ await prisma.task.aggregate({
|
|
|
809
456
|
_max: { completedAt: true },
|
|
810
457
|
})
|
|
811
458
|
|
|
459
|
+
// Group by
|
|
812
460
|
await prisma.task.groupBy({
|
|
813
461
|
by: ['status', 'priority'],
|
|
814
462
|
_count: { _all: true },
|
|
@@ -821,73 +469,311 @@ await prisma.task.groupBy({
|
|
|
821
469
|
})
|
|
822
470
|
```
|
|
823
471
|
|
|
824
|
-
|
|
472
|
+
## Advanced Usage
|
|
473
|
+
|
|
474
|
+
### Generator Mode - Prebaked SQL Queries
|
|
825
475
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
476
|
+
For maximum performance, prebake your most common queries at build time. This reduces overhead from ~0.2ms (runtime) to ~0.03ms.
|
|
477
|
+
|
|
478
|
+
**1. Add generator to your schema:**
|
|
479
|
+
|
|
480
|
+
```prisma
|
|
481
|
+
// schema.prisma
|
|
482
|
+
generator sql {
|
|
483
|
+
provider = "prisma-sql-generator"
|
|
830
484
|
}
|
|
485
|
+
```
|
|
831
486
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
487
|
+
**2. Add optimize directives to your models:**
|
|
488
|
+
|
|
489
|
+
```prisma
|
|
490
|
+
/// @optimize {
|
|
491
|
+
/// "method": "findMany",
|
|
492
|
+
/// "query": {
|
|
493
|
+
/// "skip": "$skip",
|
|
494
|
+
/// "take": "$take",
|
|
495
|
+
/// "orderBy": { "createdAt": "desc" },
|
|
496
|
+
/// "where": { "status": "ACTIVE" }
|
|
497
|
+
/// }
|
|
498
|
+
/// }
|
|
499
|
+
/// @optimize { "method": "count", "query": {} }
|
|
500
|
+
model User {
|
|
501
|
+
id Int @id @default(autoincrement())
|
|
502
|
+
email String @unique
|
|
503
|
+
status String
|
|
504
|
+
createdAt DateTime @default(now())
|
|
505
|
+
posts Post[]
|
|
838
506
|
}
|
|
839
507
|
```
|
|
840
508
|
|
|
841
|
-
|
|
509
|
+
**3. Generate:**
|
|
842
510
|
|
|
843
|
-
|
|
511
|
+
```bash
|
|
512
|
+
npx prisma generate
|
|
513
|
+
```
|
|
844
514
|
|
|
845
|
-
**
|
|
515
|
+
**4. Use the generated extension:**
|
|
846
516
|
|
|
847
517
|
```typescript
|
|
848
|
-
|
|
849
|
-
|
|
518
|
+
import { PrismaClient } from '@prisma/client'
|
|
519
|
+
import { createExtension } from '../path/to/generated/sql'
|
|
520
|
+
import postgres from 'postgres'
|
|
521
|
+
|
|
522
|
+
const sql = postgres(process.env.DATABASE_URL)
|
|
523
|
+
const prisma = new PrismaClient().$extends(createExtension({ postgres: sql }))
|
|
524
|
+
|
|
525
|
+
// ⚡ PREBAKED - Uses pre-generated SQL (~0.03ms overhead)
|
|
526
|
+
const activeUsers = await prisma.user.findMany({
|
|
527
|
+
where: { status: 'ACTIVE' },
|
|
528
|
+
skip: 0,
|
|
529
|
+
take: 10,
|
|
530
|
+
orderBy: { createdAt: 'desc' },
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
// 🔨 RUNTIME - Generates SQL on-the-fly (~0.2ms overhead, still fast!)
|
|
534
|
+
const searchUsers = await prisma.user.findMany({
|
|
535
|
+
where: { email: { contains: '@example.com' } },
|
|
536
|
+
})
|
|
850
537
|
```
|
|
851
538
|
|
|
852
|
-
|
|
539
|
+
The extension automatically:
|
|
540
|
+
|
|
541
|
+
- Uses prebaked SQL for matching queries (instant)
|
|
542
|
+
- Falls back to runtime generation for non-matching queries (still fast)
|
|
543
|
+
- Tracks which queries are prebaked via `onQuery` callback
|
|
544
|
+
|
|
545
|
+
**Dynamic Parameters:**
|
|
546
|
+
|
|
547
|
+
Use `$paramName` syntax for runtime values:
|
|
548
|
+
|
|
549
|
+
```prisma
|
|
550
|
+
/// @optimize {
|
|
551
|
+
/// "method": "findMany",
|
|
552
|
+
/// "query": {
|
|
553
|
+
/// "where": { "status": "$status" },
|
|
554
|
+
/// "skip": "$skip",
|
|
555
|
+
/// "take": "$take"
|
|
556
|
+
/// }
|
|
557
|
+
/// }
|
|
558
|
+
model User {
|
|
559
|
+
id Int @id
|
|
560
|
+
status String
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**Generator Configuration:**
|
|
853
565
|
|
|
854
566
|
```prisma
|
|
855
|
-
// schema.prisma
|
|
856
567
|
generator sql {
|
|
857
568
|
provider = "prisma-sql-generator"
|
|
858
|
-
}
|
|
859
569
|
|
|
860
|
-
|
|
861
|
-
|
|
570
|
+
// Optional: Override auto-detected dialect
|
|
571
|
+
// dialect = "postgres" // or "sqlite"
|
|
572
|
+
|
|
573
|
+
// Optional: Custom output directory
|
|
574
|
+
// output = "./generated/sql"
|
|
575
|
+
|
|
576
|
+
// Optional: Skip invalid directives instead of failing
|
|
577
|
+
// skipInvalid = "true"
|
|
862
578
|
}
|
|
863
579
|
```
|
|
864
580
|
|
|
581
|
+
### Access Original Prisma
|
|
582
|
+
|
|
865
583
|
```typescript
|
|
866
|
-
|
|
867
|
-
|
|
584
|
+
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
585
|
+
const prisma = new PrismaClient().$extends(
|
|
586
|
+
speedExtension({ postgres: sql, models }),
|
|
587
|
+
)
|
|
868
588
|
|
|
869
|
-
|
|
870
|
-
const
|
|
589
|
+
// Fast (via raw SQL)
|
|
590
|
+
const fast = await prisma.user.findMany()
|
|
871
591
|
|
|
872
|
-
|
|
592
|
+
// Slow (via Prisma engine)
|
|
593
|
+
const slow = await prisma.$original.user.findMany()
|
|
873
594
|
```
|
|
874
595
|
|
|
875
|
-
|
|
596
|
+
### Edge Runtime
|
|
597
|
+
|
|
598
|
+
**Vercel Edge Functions:**
|
|
876
599
|
|
|
877
600
|
```typescript
|
|
878
|
-
import
|
|
601
|
+
import { PrismaClient, Prisma } from '@prisma/client'
|
|
879
602
|
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
|
|
880
|
-
import
|
|
603
|
+
import postgres from 'postgres'
|
|
881
604
|
|
|
882
605
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
883
|
-
const sql = postgres(DATABASE_URL)
|
|
606
|
+
const sql = postgres(process.env.DATABASE_URL)
|
|
884
607
|
const prisma = new PrismaClient().$extends(
|
|
885
608
|
speedExtension({ postgres: sql, models }),
|
|
886
609
|
)
|
|
887
610
|
|
|
611
|
+
export const config = { runtime: 'edge' }
|
|
612
|
+
|
|
613
|
+
export default async function handler(req: Request) {
|
|
614
|
+
const users = await prisma.user.findMany()
|
|
615
|
+
return Response.json(users)
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**Cloudflare Workers:**
|
|
620
|
+
|
|
621
|
+
For Cloudflare Workers, use the standalone SQL generation API:
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
import { createToSQL, convertDMMFToModels } from 'prisma-sql'
|
|
625
|
+
import { Prisma } from '@prisma/client'
|
|
626
|
+
|
|
627
|
+
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
628
|
+
const toSQL = createToSQL(models, 'sqlite')
|
|
629
|
+
|
|
630
|
+
export default {
|
|
631
|
+
async fetch(request: Request, env: Env) {
|
|
632
|
+
const { sql, params } = toSQL('User', 'findMany', {
|
|
633
|
+
where: { status: 'ACTIVE' },
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
const result = await env.DB.prepare(sql)
|
|
637
|
+
.bind(...params)
|
|
638
|
+
.all()
|
|
639
|
+
return Response.json(result.results)
|
|
640
|
+
},
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
## Generator Mode Details
|
|
645
|
+
|
|
646
|
+
### How It Works
|
|
647
|
+
|
|
648
|
+
```
|
|
649
|
+
Build Time:
|
|
650
|
+
schema.prisma
|
|
651
|
+
↓
|
|
652
|
+
/// @optimize { "method": "findMany", "query": { "where": { "status": "ACTIVE" } } }
|
|
653
|
+
↓
|
|
654
|
+
npx prisma generate
|
|
655
|
+
↓
|
|
656
|
+
generated/sql/index.ts
|
|
657
|
+
↓
|
|
658
|
+
QUERIES = {
|
|
659
|
+
User: {
|
|
660
|
+
findMany: {
|
|
661
|
+
'{"where":{"status":"ACTIVE"}}': {
|
|
662
|
+
sql: 'SELECT * FROM users WHERE status = $1',
|
|
663
|
+
params: ['ACTIVE'],
|
|
664
|
+
dynamicKeys: []
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
Runtime:
|
|
671
|
+
prisma.user.findMany({ where: { status: 'ACTIVE' } })
|
|
672
|
+
↓
|
|
673
|
+
Normalize query → '{"where":{"status":"ACTIVE"}}'
|
|
674
|
+
↓
|
|
675
|
+
QUERIES.User.findMany[query] found?
|
|
676
|
+
↓
|
|
677
|
+
YES → ⚡ Use prebaked SQL (0.03ms overhead)
|
|
678
|
+
↓
|
|
679
|
+
NO → 🔨 Generate SQL runtime (0.2ms overhead)
|
|
680
|
+
↓
|
|
681
|
+
Execute via postgres.js/better-sqlite3
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
### Optimize Directive Examples
|
|
685
|
+
|
|
686
|
+
**Basic query:**
|
|
687
|
+
|
|
688
|
+
```prisma
|
|
689
|
+
/// @optimize {
|
|
690
|
+
/// "method": "findMany",
|
|
691
|
+
/// "query": {
|
|
692
|
+
/// "where": { "status": "ACTIVE" }
|
|
693
|
+
/// }
|
|
694
|
+
/// }
|
|
695
|
+
model User {
|
|
696
|
+
id Int @id
|
|
697
|
+
status String
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
**With pagination:**
|
|
702
|
+
|
|
703
|
+
```prisma
|
|
704
|
+
/// @optimize {
|
|
705
|
+
/// "method": "findMany",
|
|
706
|
+
/// "query": {
|
|
707
|
+
/// "skip": "$skip",
|
|
708
|
+
/// "take": "$take",
|
|
709
|
+
/// "orderBy": { "createdAt": "desc" }
|
|
710
|
+
/// }
|
|
711
|
+
/// }
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**With relations:**
|
|
715
|
+
|
|
716
|
+
```prisma
|
|
717
|
+
/// @optimize {
|
|
718
|
+
/// "method": "findMany",
|
|
719
|
+
/// "query": {
|
|
720
|
+
/// "include": {
|
|
721
|
+
/// "posts": {
|
|
722
|
+
/// "where": { "published": true },
|
|
723
|
+
/// "orderBy": { "createdAt": "desc" },
|
|
724
|
+
/// "take": 5
|
|
725
|
+
/// }
|
|
726
|
+
/// }
|
|
727
|
+
/// }
|
|
728
|
+
/// }
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
**Complex query:**
|
|
732
|
+
|
|
733
|
+
```prisma
|
|
734
|
+
/// @optimize {
|
|
735
|
+
/// "method": "findMany",
|
|
736
|
+
/// "query": {
|
|
737
|
+
/// "skip": "$skip",
|
|
738
|
+
/// "take": "$take",
|
|
739
|
+
/// "orderBy": { "createdAt": "desc" },
|
|
740
|
+
/// "include": {
|
|
741
|
+
/// "company": {
|
|
742
|
+
/// "where": { "deletedAt": null },
|
|
743
|
+
/// "select": { "id": true, "name": true }
|
|
744
|
+
/// }
|
|
745
|
+
/// }
|
|
746
|
+
/// }
|
|
747
|
+
/// }
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
## Migration Guide
|
|
751
|
+
|
|
752
|
+
### From Prisma Client
|
|
753
|
+
|
|
754
|
+
**Before:**
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
const prisma = new PrismaClient()
|
|
888
758
|
const users = await prisma.user.findMany()
|
|
889
759
|
```
|
|
890
760
|
|
|
761
|
+
**After:**
|
|
762
|
+
|
|
763
|
+
```typescript
|
|
764
|
+
import { PrismaClient, Prisma } from '@prisma/client'
|
|
765
|
+
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
|
|
766
|
+
import postgres from 'postgres'
|
|
767
|
+
|
|
768
|
+
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
769
|
+
const sql = postgres(process.env.DATABASE_URL)
|
|
770
|
+
const prisma = new PrismaClient().$extends(
|
|
771
|
+
speedExtension({ postgres: sql, models }),
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
const users = await prisma.user.findMany() // Same API, just faster
|
|
775
|
+
```
|
|
776
|
+
|
|
891
777
|
### From Drizzle
|
|
892
778
|
|
|
893
779
|
**Before:**
|
|
@@ -908,8 +794,9 @@ const users = await db
|
|
|
908
794
|
**After:**
|
|
909
795
|
|
|
910
796
|
```typescript
|
|
797
|
+
import { PrismaClient, Prisma } from '@prisma/client'
|
|
911
798
|
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
|
|
912
|
-
import
|
|
799
|
+
import postgres from 'postgres'
|
|
913
800
|
|
|
914
801
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
915
802
|
const sql = postgres(DATABASE_URL)
|
|
@@ -922,67 +809,25 @@ const users = await prisma.user.findMany({
|
|
|
922
809
|
})
|
|
923
810
|
```
|
|
924
811
|
|
|
925
|
-
## Local Development
|
|
926
|
-
|
|
927
|
-
### Testing the Generator Locally
|
|
928
|
-
|
|
929
|
-
**1. Build the package:**
|
|
930
|
-
|
|
931
|
-
```bash
|
|
932
|
-
cd prisma-sql
|
|
933
|
-
npm install
|
|
934
|
-
npm run build
|
|
935
|
-
```
|
|
936
|
-
|
|
937
|
-
**2. Link globally:**
|
|
938
|
-
|
|
939
|
-
```bash
|
|
940
|
-
npm link
|
|
941
|
-
```
|
|
942
|
-
|
|
943
|
-
**3. Use in your project:**
|
|
944
|
-
|
|
945
|
-
```bash
|
|
946
|
-
cd your-project
|
|
947
|
-
npm link prisma-sql
|
|
948
|
-
npx prisma generate
|
|
949
|
-
```
|
|
950
|
-
|
|
951
|
-
**Alternative: Use file path directly in schema:**
|
|
952
|
-
|
|
953
|
-
```prisma
|
|
954
|
-
generator sql {
|
|
955
|
-
provider = "node ../path/to/prisma-sql/dist/generator.cjs"
|
|
956
|
-
}
|
|
957
|
-
```
|
|
958
|
-
|
|
959
|
-
**Unlink when done:**
|
|
960
|
-
|
|
961
|
-
```bash
|
|
962
|
-
npm unlink prisma-sql # In your project
|
|
963
|
-
cd prisma-sql
|
|
964
|
-
npm unlink # In prisma-sql directory
|
|
965
|
-
```
|
|
966
|
-
|
|
967
812
|
## Limitations
|
|
968
813
|
|
|
969
814
|
### Partially Supported
|
|
970
815
|
|
|
971
816
|
These features work but have limitations:
|
|
972
817
|
|
|
973
|
-
- ⚠️ **Array operations**: Basic operations (`has`, `hasSome`, `hasEvery`, `isEmpty`) work. Advanced filtering
|
|
974
|
-
- ⚠️ **JSON operations**: Path-based filtering works
|
|
818
|
+
- ⚠️ **Array operations**: Basic operations (`has`, `hasSome`, `hasEvery`, `isEmpty`) work. Advanced filtering not yet supported.
|
|
819
|
+
- ⚠️ **JSON operations**: Path-based filtering works. Advanced JSON functions not yet supported.
|
|
975
820
|
|
|
976
821
|
### Not Yet Supported
|
|
977
822
|
|
|
978
|
-
These Prisma features
|
|
823
|
+
These Prisma features will fall back to Prisma Client:
|
|
979
824
|
|
|
980
825
|
- ❌ Full-text search (`search` operator)
|
|
981
826
|
- ❌ Composite types (MongoDB-style embedded documents)
|
|
982
827
|
- ❌ Raw database features (PostGIS, pg_trgm, etc.)
|
|
983
|
-
- ❌ Some advanced aggregations in `groupBy`
|
|
828
|
+
- ❌ Some advanced aggregations in `groupBy`
|
|
984
829
|
|
|
985
|
-
|
|
830
|
+
Enable `debug: true` to see which queries are accelerated vs fallback.
|
|
986
831
|
|
|
987
832
|
### Database Support
|
|
988
833
|
|
|
@@ -1013,23 +858,9 @@ const prisma = new PrismaClient().$extends(
|
|
|
1013
858
|
)
|
|
1014
859
|
```
|
|
1015
860
|
|
|
1016
|
-
### "Generator: Cannot find module"
|
|
1017
|
-
|
|
1018
|
-
The package hasn't been built or linked:
|
|
1019
|
-
|
|
1020
|
-
```bash
|
|
1021
|
-
# In prisma-sql directory
|
|
1022
|
-
npm run build
|
|
1023
|
-
npm link
|
|
1024
|
-
|
|
1025
|
-
# In your project
|
|
1026
|
-
npm link prisma-sql
|
|
1027
|
-
npx prisma generate
|
|
1028
|
-
```
|
|
1029
|
-
|
|
1030
861
|
### "Results don't match Prisma Client"
|
|
1031
862
|
|
|
1032
|
-
Enable debug mode
|
|
863
|
+
Enable debug mode and compare SQL:
|
|
1033
864
|
|
|
1034
865
|
```typescript
|
|
1035
866
|
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
@@ -1037,7 +868,7 @@ const models = convertDMMFToModels(Prisma.dmmf.datamodel)
|
|
|
1037
868
|
speedExtension({
|
|
1038
869
|
postgres: sql,
|
|
1039
870
|
models,
|
|
1040
|
-
debug: true,
|
|
871
|
+
debug: true, // Shows generated SQL
|
|
1041
872
|
})
|
|
1042
873
|
```
|
|
1043
874
|
|
|
@@ -1055,19 +886,10 @@ Increase postgres.js pool size:
|
|
|
1055
886
|
|
|
1056
887
|
```typescript
|
|
1057
888
|
const sql = postgres(DATABASE_URL, {
|
|
1058
|
-
max: 50,
|
|
889
|
+
max: 50, // Default is 10
|
|
1059
890
|
})
|
|
1060
891
|
```
|
|
1061
892
|
|
|
1062
|
-
### "Type errors after extending"
|
|
1063
|
-
|
|
1064
|
-
Ensure `@prisma/client` is up to date:
|
|
1065
|
-
|
|
1066
|
-
```bash
|
|
1067
|
-
npm update @prisma/client
|
|
1068
|
-
npx prisma generate
|
|
1069
|
-
```
|
|
1070
|
-
|
|
1071
893
|
### "Performance not improving"
|
|
1072
894
|
|
|
1073
895
|
Some queries won't see dramatic improvements:
|
|
@@ -1090,6 +912,48 @@ speedExtension({
|
|
|
1090
912
|
})
|
|
1091
913
|
```
|
|
1092
914
|
|
|
915
|
+
## Local Development
|
|
916
|
+
|
|
917
|
+
### Testing the Generator Locally
|
|
918
|
+
|
|
919
|
+
**1. Build the package:**
|
|
920
|
+
|
|
921
|
+
```bash
|
|
922
|
+
cd prisma-sql
|
|
923
|
+
npm install
|
|
924
|
+
npm run build
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
**2. Link globally:**
|
|
928
|
+
|
|
929
|
+
```bash
|
|
930
|
+
npm link
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
**3. Use in your project:**
|
|
934
|
+
|
|
935
|
+
```bash
|
|
936
|
+
cd your-project
|
|
937
|
+
npm link prisma-sql
|
|
938
|
+
npx prisma generate
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
**Alternative: Use file path directly in schema:**
|
|
942
|
+
|
|
943
|
+
```prisma
|
|
944
|
+
generator sql {
|
|
945
|
+
provider = "node ../path/to/prisma-sql/dist/generator.cjs"
|
|
946
|
+
}
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
**Unlink when done:**
|
|
950
|
+
|
|
951
|
+
```bash
|
|
952
|
+
npm unlink prisma-sql # In your project
|
|
953
|
+
cd prisma-sql
|
|
954
|
+
npm unlink # In prisma-sql directory
|
|
955
|
+
```
|
|
956
|
+
|
|
1093
957
|
## FAQ
|
|
1094
958
|
|
|
1095
959
|
**Q: Do I need to keep using Prisma Client?**
|
|
@@ -1099,7 +963,7 @@ A: Yes. You need Prisma for schema management, migrations, types, and write oper
|
|
|
1099
963
|
A: Yes. No schema changes required. It works with your existing Prisma schema and generated client.
|
|
1100
964
|
|
|
1101
965
|
**Q: What about writes (create, update, delete)?**
|
|
1102
|
-
A: Writes still use Prisma Client. This extension only accelerates reads.
|
|
966
|
+
A: Writes still use Prisma Client. This extension only accelerates reads.
|
|
1103
967
|
|
|
1104
968
|
**Q: Is it production ready?**
|
|
1105
969
|
A: Yes. 137 E2E tests verify exact parity with Prisma Client across both Prisma v6 and v7. Used in production.
|
|
@@ -1108,29 +972,22 @@ A: Yes. 137 E2E tests verify exact parity with Prisma Client across both Prisma
|
|
|
1108
972
|
A: Yes. Works with any PostgreSQL-compatible database. Just pass the connection string to postgres.js.
|
|
1109
973
|
|
|
1110
974
|
**Q: Does it support Prisma middlewares?**
|
|
1111
|
-
A: The extension runs after middlewares.
|
|
975
|
+
A: The extension runs after middlewares. For middleware to see actual SQL, use Prisma's query logging.
|
|
1112
976
|
|
|
1113
977
|
**Q: Can I still use `$queryRaw` and `$executeRaw`?**
|
|
1114
|
-
A: Yes. Those methods are unaffected.
|
|
978
|
+
A: Yes. Those methods are unaffected.
|
|
1115
979
|
|
|
1116
980
|
**Q: What's the overhead of SQL generation?**
|
|
1117
|
-
A:
|
|
1118
|
-
|
|
1119
|
-
**Q: How do I benchmark my own queries?**
|
|
1120
|
-
A: Use the `onQuery` callback to measure each query, or see the [Benchmarking](#benchmarking) section below.
|
|
1121
|
-
|
|
1122
|
-
**Q: How does performance compare to Prisma v7?**
|
|
1123
|
-
A: Prisma v7 introduced significant improvements (~39% faster than v6 on PostgreSQL, ~24% on SQLite), but this extension still provides 2-7x additional speedup over v7 depending on query complexity.
|
|
981
|
+
A: Runtime mode: ~0.2ms per query. Generator mode: ~0.03ms for prebaked queries. Still 2-7x faster than Prisma overall.
|
|
1124
982
|
|
|
1125
983
|
**Q: Should I use generator mode or runtime mode?**
|
|
1126
|
-
A:
|
|
984
|
+
A: Start with runtime mode (simpler). Add generator mode for your hottest queries later if needed.
|
|
1127
985
|
|
|
1128
986
|
## Examples
|
|
1129
987
|
|
|
1130
988
|
- [Generator Mode Example](./examples/generator-mode) - Complete working example
|
|
1131
989
|
- [PostgreSQL E2E Tests](./tests/e2e/postgres.test.ts) - Comprehensive query examples
|
|
1132
990
|
- [SQLite E2E Tests](./tests/e2e/sqlite.e2e.test.ts) - SQLite-specific queries
|
|
1133
|
-
- [Runtime API Tests](./tests/e2e/runtime-api.test.ts) - All three APIs
|
|
1134
991
|
|
|
1135
992
|
To run examples locally:
|
|
1136
993
|
|
|
@@ -1171,22 +1028,6 @@ await prisma.post.findMany({ include: { author: true } })
|
|
|
1171
1028
|
console.table(queries)
|
|
1172
1029
|
```
|
|
1173
1030
|
|
|
1174
|
-
Or run the full test suite benchmarks:
|
|
1175
|
-
|
|
1176
|
-
```bash
|
|
1177
|
-
git clone https://github.com/multipliedtwice/prisma-sql
|
|
1178
|
-
cd prisma-sql
|
|
1179
|
-
npm install
|
|
1180
|
-
|
|
1181
|
-
npx prisma db push
|
|
1182
|
-
|
|
1183
|
-
DATABASE_URL="postgresql://..." npm run test:e2e:postgres
|
|
1184
|
-
|
|
1185
|
-
npm run test:e2e:sqlite
|
|
1186
|
-
```
|
|
1187
|
-
|
|
1188
|
-
Results include timing for Prisma vs Extension vs Drizzle (where applicable).
|
|
1189
|
-
|
|
1190
1031
|
## Contributing
|
|
1191
1032
|
|
|
1192
1033
|
PRs welcome! Priority areas:
|