prisma-sql 1.1.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/LICENSE +23 -0
- package/dist/index.cjs +4640 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +4631 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
- package/readme.md +994 -0
package/readme.md
ADDED
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
# @dee-wan/prisma-sql
|
|
2
|
+
|
|
3
|
+
Speed up Prisma reads **2-7x** by executing queries via postgres.js instead of Prisma's query engine.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const sql = postgres(DATABASE_URL)
|
|
7
|
+
const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
|
|
8
|
+
|
|
9
|
+
// Same Prisma API, 2-7x faster reads
|
|
10
|
+
const users = await prisma.user.findMany({ where: { status: 'ACTIVE' } })
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Why?
|
|
14
|
+
|
|
15
|
+
Prisma's query engine adds overhead even in v7:
|
|
16
|
+
|
|
17
|
+
- Query translation and validation layer
|
|
18
|
+
- Type checking and transformation
|
|
19
|
+
- Query planning and optimization
|
|
20
|
+
- Result serialization and mapping
|
|
21
|
+
|
|
22
|
+
This extension bypasses the engine for read queries and executes raw SQL directly via postgres.js or better-sqlite3.
|
|
23
|
+
|
|
24
|
+
**Result:** Same API, same types, 2-7x faster reads.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
**PostgreSQL:**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @dee-wan/prisma-sql postgres
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**SQLite:**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @dee-wan/prisma-sql better-sqlite3
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### PostgreSQL
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { PrismaClient } from '@prisma/client'
|
|
46
|
+
import { speedExtension } from '@dee-wan/prisma-sql'
|
|
47
|
+
import postgres from 'postgres'
|
|
48
|
+
|
|
49
|
+
const sql = postgres(process.env.DATABASE_URL)
|
|
50
|
+
const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
|
|
51
|
+
|
|
52
|
+
// All reads now execute via postgres.js
|
|
53
|
+
const users = await prisma.user.findMany({
|
|
54
|
+
where: { status: 'ACTIVE' },
|
|
55
|
+
include: { posts: true },
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### SQLite
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { PrismaClient } from '@prisma/client'
|
|
63
|
+
import { speedExtension } from '@dee-wan/prisma-sql'
|
|
64
|
+
import Database from 'better-sqlite3'
|
|
65
|
+
|
|
66
|
+
const db = new Database('./data.db')
|
|
67
|
+
const prisma = new PrismaClient().$extends(speedExtension({ sqlite: db }))
|
|
68
|
+
|
|
69
|
+
const users = await prisma.user.findMany({ where: { status: 'ACTIVE' } })
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Explicit DMMF (Edge Runtimes)
|
|
73
|
+
|
|
74
|
+
In some environments (Cloudflare Workers, Vercel Edge, bundlers), Prisma's DMMF may not be auto-detectable. Provide it explicitly:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { PrismaClient, Prisma } from '@prisma/client'
|
|
78
|
+
import { speedExtension } from '@dee-wan/prisma-sql'
|
|
79
|
+
import postgres from 'postgres'
|
|
80
|
+
|
|
81
|
+
const sql = postgres(process.env.DATABASE_URL)
|
|
82
|
+
const prisma = new PrismaClient().$extends(
|
|
83
|
+
speedExtension({
|
|
84
|
+
postgres: sql,
|
|
85
|
+
dmmf: Prisma.dmmf, // Required in edge runtimes
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## What Gets Faster
|
|
91
|
+
|
|
92
|
+
**Accelerated (via raw SQL):**
|
|
93
|
+
|
|
94
|
+
- `findMany`, `findFirst`, `findUnique`
|
|
95
|
+
- `count`
|
|
96
|
+
- `aggregate` (\_count, \_sum, \_avg, \_min, \_max)
|
|
97
|
+
- `groupBy` with having clauses
|
|
98
|
+
|
|
99
|
+
**Unchanged (still uses Prisma):**
|
|
100
|
+
|
|
101
|
+
- `create`, `update`, `delete`, `upsert`
|
|
102
|
+
- `createMany`, `updateMany`, `deleteMany`
|
|
103
|
+
- Transactions (`$transaction`)
|
|
104
|
+
- Middleware
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Performance
|
|
109
|
+
|
|
110
|
+
Benchmarks from 137 E2E tests comparing identical queries against Prisma v6, Prisma v7, and Drizzle:
|
|
111
|
+
|
|
112
|
+
### BENCHMARK RESULTS - Prisma v6 vs v7 vs Generated SQL
|
|
113
|
+
|
|
114
|
+
## POSTGRES Results:
|
|
115
|
+
|
|
116
|
+
| Test | Prisma v6 | Prisma v7 | Generated | Drizzle | v6 Speedup | v7 Speedup |
|
|
117
|
+
| ------------------------ | --------- | --------- | --------- | ------- | ---------- | ---------- |
|
|
118
|
+
| findMany basic | 0.45ms | 0.35ms | 0.18ms | 0.29ms | 2.53x | 1.74x |
|
|
119
|
+
| findMany where = | 0.40ms | 0.34ms | 0.17ms | 0.24ms | 2.39x | 1.55x |
|
|
120
|
+
| findMany where >= | 15.88ms | 8.03ms | 2.86ms | 6.18ms | 5.55x | 2.92x |
|
|
121
|
+
| findMany where IN | 0.52ms | 0.52ms | 0.29ms | 0.38ms | 1.79x | 1.38x |
|
|
122
|
+
| findMany where null | 0.23ms | 0.29ms | 0.10ms | 0.16ms | 2.20x | 2.56x |
|
|
123
|
+
| findMany ILIKE | 0.24ms | 0.22ms | 0.18ms | 0.17ms | 1.29x | 0.99x |
|
|
124
|
+
| findMany AND | 2.36ms | 1.20ms | 0.44ms | 1.42ms | 5.41x | 2.73x |
|
|
125
|
+
| findMany OR | 13.10ms | 6.90ms | 2.37ms | 5.58ms | 5.53x | 2.93x |
|
|
126
|
+
| findMany NOT | 0.51ms | 1.14ms | 0.30ms | 0.33ms | 1.73x | 3.74x |
|
|
127
|
+
| findMany orderBy | 2.19ms | 2.31ms | 0.85ms | 0.72ms | 2.58x | 2.05x |
|
|
128
|
+
| findMany pagination | 0.23ms | 0.28ms | 0.20ms | 0.19ms | 1.15x | 1.39x |
|
|
129
|
+
| findMany select | 0.23ms | 0.22ms | 0.09ms | 0.13ms | 2.57x | 2.21x |
|
|
130
|
+
| findMany relation some | 0.83ms | 0.72ms | 0.41ms | N/A | 2.04x | 1.72x |
|
|
131
|
+
| findMany relation every | 0.70ms | 0.79ms | 0.47ms | N/A | 1.50x | 1.70x |
|
|
132
|
+
| findMany relation none | 28.35ms | 14.34ms | 4.81ms | N/A | 5.90x | 2.75x |
|
|
133
|
+
| findMany nested relation | 0.70ms | 0.72ms | 0.71ms | N/A | 0.98x | 1.36x |
|
|
134
|
+
| findMany complex | 1.18ms | 1.19ms | 0.48ms | 0.64ms | 2.45x | 2.81x |
|
|
135
|
+
| findFirst | 0.22ms | 0.25ms | 0.15ms | 0.20ms | 1.45x | 3.05x |
|
|
136
|
+
| findFirst skip | 0.26ms | 0.32ms | 0.15ms | 0.23ms | 1.75x | 3.09x |
|
|
137
|
+
| findUnique id | 0.20ms | 0.21ms | 0.13ms | 0.13ms | 1.52x | 2.53x |
|
|
138
|
+
| findUnique email | 0.18ms | 0.19ms | 0.09ms | 0.12ms | 2.03x | 2.17x |
|
|
139
|
+
| count | 0.11ms | 0.12ms | 0.04ms | 0.07ms | 2.95x | 2.50x |
|
|
140
|
+
| count where | 0.43ms | 0.47ms | 0.26ms | 0.27ms | 1.62x | 1.90x |
|
|
141
|
+
| aggregate count | 0.22ms | 0.24ms | 0.13ms | N/A | 1.63x | 1.51x |
|
|
142
|
+
| aggregate sum/avg | 0.30ms | 0.32ms | 0.23ms | N/A | 1.32x | 1.35x |
|
|
143
|
+
| aggregate where | 0.42ms | 0.44ms | 0.24ms | N/A | 1.75x | 1.84x |
|
|
144
|
+
| aggregate min/max | 0.30ms | 0.32ms | 0.23ms | N/A | 1.29x | 1.32x |
|
|
145
|
+
| aggregate complete | 0.36ms | 0.41ms | 0.27ms | N/A | 1.36x | 1.39x |
|
|
146
|
+
| groupBy | 0.38ms | 0.41ms | 0.29ms | N/A | 1.33x | 1.36x |
|
|
147
|
+
| groupBy count | 0.44ms | 0.42ms | 0.32ms | N/A | 1.36x | 1.37x |
|
|
148
|
+
| groupBy multi | 0.53ms | 0.51ms | 0.37ms | N/A | 1.43x | 1.43x |
|
|
149
|
+
| groupBy having | 0.52ms | 0.50ms | 0.40ms | N/A | 1.29x | 1.40x |
|
|
150
|
+
| groupBy + where | 0.52ms | 0.49ms | 0.31ms | N/A | 1.65x | 1.88x |
|
|
151
|
+
| groupBy aggregates | 0.50ms | 0.48ms | 0.38ms | N/A | 1.31x | 1.32x |
|
|
152
|
+
| groupBy min/max | 0.49ms | 0.50ms | 0.38ms | N/A | 1.29x | 1.35x |
|
|
153
|
+
| include posts | 2.53ms | 1.59ms | 1.85ms | N/A | 1.37x | 0.81x |
|
|
154
|
+
| include profile | 0.47ms | 0.60ms | 0.21ms | N/A | 2.26x | 2.89x |
|
|
155
|
+
| include 3 levels | 1.56ms | 1.64ms | 1.15ms | N/A | 1.36x | 1.33x |
|
|
156
|
+
| include 4 levels | 1.80ms | 1.76ms | 0.99ms | N/A | 1.81x | 1.74x |
|
|
157
|
+
| include + where | 1.21ms | 1.06ms | 1.47ms | N/A | 0.82x | 0.69x |
|
|
158
|
+
| include + select nested | 1.23ms | 0.84ms | 1.43ms | N/A | 0.86x | 0.54x |
|
|
159
|
+
| findMany startsWith | 0.21ms | 0.24ms | 0.14ms | 0.17ms | 1.46x | 1.73x |
|
|
160
|
+
| findMany endsWith | 0.48ms | 0.38ms | 0.19ms | 0.28ms | 2.51x | 1.87x |
|
|
161
|
+
| findMany NOT contains | 0.47ms | 0.40ms | 0.18ms | 0.25ms | 2.62x | 2.49x |
|
|
162
|
+
| findMany LIKE | 0.19ms | 0.22ms | 0.09ms | 0.14ms | 2.19x | 2.61x |
|
|
163
|
+
| findMany < | 25.94ms | 14.16ms | 4.16ms | 9.59ms | 6.23x | 3.12x |
|
|
164
|
+
| findMany <= | 26.79ms | 13.87ms | 4.90ms | 9.73ms | 5.46x | 3.07x |
|
|
165
|
+
| findMany > | 15.22ms | 7.55ms | 2.69ms | 5.74ms | 5.66x | 2.71x |
|
|
166
|
+
| findMany NOT IN | 0.54ms | 0.42ms | 0.26ms | 0.36ms | 2.07x | 1.42x |
|
|
167
|
+
| findMany isNot null | 0.52ms | 0.39ms | 0.19ms | 0.24ms | 2.75x | 2.21x |
|
|
168
|
+
| orderBy multi-field | 3.97ms | 2.38ms | 1.09ms | 1.54ms | 3.65x | 3.71x |
|
|
169
|
+
| distinct status | 8.72ms | 7.84ms | 2.15ms | N/A | 4.06x | 4.68x |
|
|
170
|
+
| distinct multi | 11.71ms | 11.09ms | 2.09ms | N/A | 5.61x | 5.30x |
|
|
171
|
+
| cursor pagination | 0.29ms | 0.34ms | 0.23ms | N/A | 1.25x | 1.62x |
|
|
172
|
+
| select + include | 0.89ms | 0.70ms | 0.17ms | N/A | 5.19x | 3.46x |
|
|
173
|
+
| \_count relation | 0.71ms | 0.66ms | 0.55ms | N/A | 1.29x | 1.20x |
|
|
174
|
+
| \_count multi-relation | 0.24ms | 0.29ms | 0.16ms | N/A | 1.52x | 1.90x |
|
|
175
|
+
| ILIKE special chars | 0.22ms | 0.26ms | 0.14ms | N/A | 1.54x | 1.76x |
|
|
176
|
+
| LIKE case sensitive | 0.19ms | 0.22ms | 0.12ms | N/A | 1.62x | 1.85x |
|
|
177
|
+
|
|
178
|
+
##### Summary:
|
|
179
|
+
|
|
180
|
+
- Generated SQL vs Prisma v6: **2.39x faster**
|
|
181
|
+
- Generated SQL vs Prisma v7: **2.10x faster**
|
|
182
|
+
- Generated SQL vs Drizzle: **1.53x faster**
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
#### SQLITE:
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
| Test | Prisma v6 | Prisma v7 | Generated | Drizzle | v6 Speedup | v7 Speedup |
|
|
191
|
+
| ------------------------ | --------- | --------- | --------- | ------- | ---------- | ---------- |
|
|
192
|
+
| findMany basic | 0.44ms | 0.27ms | 0.04ms | 0.17ms | 9.59x | 5.47x |
|
|
193
|
+
| findMany where = | 0.45ms | 0.23ms | 0.03ms | 0.10ms | 14.14x | 6.25x |
|
|
194
|
+
| findMany where >= | 12.72ms | 4.70ms | 1.02ms | 2.09ms | 12.51x | 4.16x |
|
|
195
|
+
| findMany where IN | 0.40ms | 0.28ms | 0.04ms | 0.10ms | 10.35x | 6.55x |
|
|
196
|
+
| findMany where null | 0.15ms | 0.19ms | 0.01ms | 0.06ms | 10.97x | 12.56x |
|
|
197
|
+
| findMany LIKE | 0.15ms | 0.17ms | 0.02ms | 0.06ms | 8.64x | 9.41x |
|
|
198
|
+
| findMany AND | 1.49ms | 0.95ms | 0.26ms | 0.43ms | 5.75x | 3.45x |
|
|
199
|
+
| findMany OR | 10.32ms | 3.87ms | 0.93ms | 1.85ms | 11.09x | 3.64x |
|
|
200
|
+
| findMany NOT | 0.42ms | 0.28ms | 0.03ms | 0.09ms | 12.59x | 7.05x |
|
|
201
|
+
| findMany orderBy | 2.24ms | 1.92ms | 1.76ms | 1.81ms | 1.27x | 1.11x |
|
|
202
|
+
| findMany pagination | 0.13ms | 0.15ms | 0.02ms | 0.06ms | 5.69x | 6.24x |
|
|
203
|
+
| findMany select | 0.15ms | 0.11ms | 0.02ms | 0.04ms | 9.50x | 6.22x |
|
|
204
|
+
| findMany relation some | 4.50ms | 0.56ms | 0.40ms | N/A | 11.15x | 1.32x |
|
|
205
|
+
| findMany relation every | 9.53ms | 9.54ms | 6.38ms | N/A | 1.49x | 1.45x |
|
|
206
|
+
| findMany relation none | 166.62ms | 128.44ms | 2.40ms | N/A | 69.43x | 49.51x |
|
|
207
|
+
| findMany nested relation | 1.00ms | 0.51ms | 0.31ms | N/A | 3.28x | 1.70x |
|
|
208
|
+
| findMany complex | 0.79ms | 0.83ms | 0.43ms | 0.48ms | 1.84x | 1.74x |
|
|
209
|
+
| findFirst | 0.16ms | 0.17ms | 0.01ms | 0.06ms | 11.57x | 12.00x |
|
|
210
|
+
| findFirst skip | 0.25ms | 0.23ms | 0.03ms | 0.08ms | 8.62x | 13.31x |
|
|
211
|
+
| findUnique id | 0.12ms | 0.15ms | 0.01ms | 0.05ms | 9.92x | 11.62x |
|
|
212
|
+
| findUnique email | 0.12ms | 0.15ms | 0.01ms | 0.05ms | 8.73x | 11.43x |
|
|
213
|
+
| count | 0.17ms | 0.07ms | 0.01ms | 0.02ms | 13.33x | 10.73x |
|
|
214
|
+
| count where | 0.28ms | 0.28ms | 0.16ms | 0.17ms | 1.77x | 1.85x |
|
|
215
|
+
| aggregate count | 0.15ms | 0.11ms | 0.01ms | N/A | 14.80x | 9.69x |
|
|
216
|
+
| aggregate sum/avg | 0.27ms | 0.25ms | 0.15ms | N/A | 1.82x | 1.62x |
|
|
217
|
+
| aggregate where | 0.25ms | 0.26ms | 0.15ms | N/A | 1.66x | 1.73x |
|
|
218
|
+
| aggregate min/max | 0.28ms | 0.25ms | 0.16ms | N/A | 1.80x | 1.52x |
|
|
219
|
+
| aggregate complete | 0.39ms | 0.34ms | 0.21ms | N/A | 1.81x | 1.61x |
|
|
220
|
+
| groupBy | 0.56ms | 0.53ms | 0.44ms | N/A | 1.28x | 1.22x |
|
|
221
|
+
| groupBy count | 0.57ms | 0.57ms | 0.45ms | N/A | 1.28x | 1.27x |
|
|
222
|
+
| groupBy multi | 1.14ms | 1.08ms | 0.95ms | N/A | 1.20x | 1.17x |
|
|
223
|
+
| groupBy having | 0.64ms | 0.64ms | 0.47ms | N/A | 1.37x | 1.32x |
|
|
224
|
+
| groupBy + where | 0.31ms | 0.33ms | 0.18ms | N/A | 1.70x | 1.84x |
|
|
225
|
+
| groupBy aggregates | 0.71ms | 0.66ms | 0.54ms | N/A | 1.32x | 1.23x |
|
|
226
|
+
| groupBy min/max | 0.72ms | 0.70ms | 0.56ms | N/A | 1.29x | 1.25x |
|
|
227
|
+
| include posts | 1.88ms | 1.13ms | 0.90ms | N/A | 2.10x | 1.12x |
|
|
228
|
+
| include profile | 0.32ms | 0.41ms | 0.05ms | N/A | 6.17x | 6.48x |
|
|
229
|
+
| include 3 levels | 1.11ms | 1.08ms | 0.63ms | N/A | 1.77x | 1.86x |
|
|
230
|
+
| include 4 levels | 1.15ms | 1.10ms | 0.42ms | N/A | 2.72x | 2.70x |
|
|
231
|
+
| include + where | 0.77ms | 0.72ms | 0.11ms | N/A | 7.13x | 6.99x |
|
|
232
|
+
| include + select nested | 0.73ms | 0.53ms | 0.83ms | N/A | 0.88x | 0.64x |
|
|
233
|
+
| findMany startsWith | 0.15ms | 0.16ms | 0.02ms | 0.06ms | 6.73x | 6.98x |
|
|
234
|
+
| findMany endsWith | 0.43ms | 0.26ms | 0.04ms | 0.15ms | 9.74x | 5.22x |
|
|
235
|
+
| findMany NOT contains | 0.45ms | 0.28ms | 0.04ms | 0.11ms | 11.65x | 6.57x |
|
|
236
|
+
| findMany < | 21.60ms | 8.27ms | 1.88ms | 4.07ms | 11.49x | 4.24x |
|
|
237
|
+
| findMany <= | 22.34ms | 8.50ms | 1.97ms | 4.40ms | 11.36x | 4.25x |
|
|
238
|
+
| findMany > | 11.54ms | 4.33ms | 0.94ms | 2.13ms | 12.22x | 4.17x |
|
|
239
|
+
| findMany NOT IN | 0.42ms | 0.28ms | 0.04ms | 0.12ms | 10.40x | 6.23x |
|
|
240
|
+
| findMany isNot null | 0.45ms | 0.27ms | 0.03ms | 0.11ms | 13.03x | 6.91x |
|
|
241
|
+
| orderBy multi-field | 0.66ms | 0.59ms | 0.37ms | 0.43ms | 1.78x | 1.67x |
|
|
242
|
+
| distinct status | 10.61ms | 6.79ms | 4.09ms | N/A | 2.59x | 1.53x |
|
|
243
|
+
| distinct multi | 11.66ms | 7.12ms | 5.09ms | N/A | 2.29x | 1.34x |
|
|
244
|
+
| cursor pagination | 0.21ms | 0.26ms | 0.04ms | N/A | 4.60x | 5.52x |
|
|
245
|
+
| select + include | 0.51ms | 0.43ms | 0.04ms | N/A | 13.19x | 11.31x |
|
|
246
|
+
| \_count relation | 0.62ms | 0.46ms | 0.32ms | N/A | 1.93x | 1.44x |
|
|
247
|
+
| \_count multi-relation | 0.14ms | 0.17ms | 0.04ms | N/A | 3.22x | 4.09x |
|
|
248
|
+
|
|
249
|
+
##### Summary:
|
|
250
|
+
|
|
251
|
+
- Generated SQL vs Prisma v6: **7.51x faster**
|
|
252
|
+
- Generated SQL vs Prisma v7: **5.48x faster**
|
|
253
|
+
- Generated SQL vs Drizzle: **2.61x faster**
|
|
254
|
+
|
|
255
|
+
> **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.
|
|
256
|
+
|
|
257
|
+
> **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:
|
|
258
|
+
>
|
|
259
|
+
> - Database configuration and indexes
|
|
260
|
+
> - Query complexity and data volume
|
|
261
|
+
> - Hardware and network latency
|
|
262
|
+
> - Concurrent load
|
|
263
|
+
>
|
|
264
|
+
> Run benchmarks with your own schema and data for accurate measurements. See [Benchmarking](#benchmarking) section below.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Configuration
|
|
269
|
+
|
|
270
|
+
### Basic Configuration
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { Prisma } from '@prisma/client'
|
|
274
|
+
|
|
275
|
+
speedExtension({
|
|
276
|
+
// Database client (required - choose one)
|
|
277
|
+
postgres: sql, // For PostgreSQL via postgres.js
|
|
278
|
+
sqlite: db, // For SQLite via better-sqlite3
|
|
279
|
+
|
|
280
|
+
// DMMF (optional - auto-detected in most cases)
|
|
281
|
+
dmmf: Prisma.dmmf, // Required in edge runtimes/bundled apps
|
|
282
|
+
|
|
283
|
+
// Debug mode (optional)
|
|
284
|
+
debug: true, // Log all generated SQL
|
|
285
|
+
|
|
286
|
+
// Selective models (optional)
|
|
287
|
+
models: ['User', 'Post'], // Only accelerate these models
|
|
288
|
+
|
|
289
|
+
// Performance monitoring (optional)
|
|
290
|
+
onQuery: (info) => {
|
|
291
|
+
console.log(`${info.model}.${info.method}: ${info.duration}ms`)
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Debug Mode
|
|
297
|
+
|
|
298
|
+
See generated SQL for every query:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
speedExtension({
|
|
302
|
+
postgres: sql,
|
|
303
|
+
debug: true,
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
// Logs:
|
|
307
|
+
// [postgres] User.findMany
|
|
308
|
+
// SQL: SELECT ... FROM users WHERE status = $1
|
|
309
|
+
// Params: ['ACTIVE']
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Selective Models
|
|
313
|
+
|
|
314
|
+
Only accelerate specific models:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
speedExtension({
|
|
318
|
+
postgres: sql,
|
|
319
|
+
models: ['User', 'Post'], // Only User and Post get accelerated
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
// Order, Product, etc. still use Prisma
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Performance Monitoring
|
|
326
|
+
|
|
327
|
+
Track query performance:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
speedExtension({
|
|
331
|
+
postgres: sql,
|
|
332
|
+
onQuery: (info) => {
|
|
333
|
+
console.log(`${info.model}.${info.method} completed in ${info.duration}ms`)
|
|
334
|
+
|
|
335
|
+
if (info.duration > 100) {
|
|
336
|
+
logger.warn('Slow query detected', {
|
|
337
|
+
model: info.model,
|
|
338
|
+
method: info.method,
|
|
339
|
+
sql: info.sql,
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### When to Provide DMMF Explicitly
|
|
347
|
+
|
|
348
|
+
Provide `dmmf: Prisma.dmmf` if:
|
|
349
|
+
|
|
350
|
+
- Using Cloudflare Workers, Vercel Edge, or similar edge runtimes
|
|
351
|
+
- Bundling with webpack, esbuild, or Rollup
|
|
352
|
+
- In a monorepo with complex Prisma setup
|
|
353
|
+
- You see "Cannot access Prisma DMMF" error
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { Prisma } from '@prisma/client'
|
|
357
|
+
|
|
358
|
+
speedExtension({
|
|
359
|
+
postgres: sql,
|
|
360
|
+
dmmf: Prisma.dmmf, // Explicit DMMF
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Advanced Usage
|
|
365
|
+
|
|
366
|
+
### Read Replicas
|
|
367
|
+
|
|
368
|
+
Send writes to primary, reads to replica:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// Primary database for writes
|
|
372
|
+
const primary = new PrismaClient()
|
|
373
|
+
|
|
374
|
+
// Replica for fast reads
|
|
375
|
+
const replica = postgres(process.env.REPLICA_URL)
|
|
376
|
+
const fastPrisma = new PrismaClient().$extends(
|
|
377
|
+
speedExtension({ postgres: replica })
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
// Use appropriately
|
|
381
|
+
await primary.user.create({ data: { ... } }) // → Primary
|
|
382
|
+
const users = await fastPrisma.user.findMany() // → Replica
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Connection Pooling
|
|
386
|
+
|
|
387
|
+
Configure postgres.js connection pool:
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
const sql = postgres(process.env.DATABASE_URL, {
|
|
391
|
+
max: 20, // Pool size
|
|
392
|
+
idle_timeout: 20, // Close idle connections after 20s
|
|
393
|
+
connect_timeout: 10, // Connection timeout
|
|
394
|
+
ssl: 'require', // Force SSL
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Gradual Rollout
|
|
401
|
+
|
|
402
|
+
Feature-flag the extension for safe rollout:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
const USE_FAST_READS = process.env.FAST_READS === 'true'
|
|
406
|
+
|
|
407
|
+
const sql = postgres(DATABASE_URL)
|
|
408
|
+
const prisma = new PrismaClient()
|
|
409
|
+
|
|
410
|
+
const db = USE_FAST_READS
|
|
411
|
+
? prisma.$extends(speedExtension({ postgres: sql }))
|
|
412
|
+
: prisma
|
|
413
|
+
|
|
414
|
+
// Disable in production if issues arise:
|
|
415
|
+
// FAST_READS=false pm2 restart app
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Access Original Prisma
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
|
|
422
|
+
|
|
423
|
+
// Use extension (fast)
|
|
424
|
+
const fast = await prisma.user.findMany()
|
|
425
|
+
|
|
426
|
+
// Bypass extension (original Prisma)
|
|
427
|
+
const slow = await prisma.$original.user.findMany()
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Edge Runtime
|
|
431
|
+
|
|
432
|
+
### Vercel Edge Functions
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
import { PrismaClient, Prisma } from '@prisma/client'
|
|
436
|
+
import { speedExtension } from '@dee-wan/prisma-sql'
|
|
437
|
+
import postgres from 'postgres'
|
|
438
|
+
|
|
439
|
+
const sql = postgres(process.env.DATABASE_URL)
|
|
440
|
+
const prisma = new PrismaClient().$extends(
|
|
441
|
+
speedExtension({
|
|
442
|
+
postgres: sql,
|
|
443
|
+
dmmf: Prisma.dmmf, // Explicit dmmf required in edge runtime
|
|
444
|
+
}),
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
export const config = { runtime: 'edge' }
|
|
448
|
+
|
|
449
|
+
export default async function handler(req: Request) {
|
|
450
|
+
const users = await prisma.user.findMany()
|
|
451
|
+
return Response.json(users)
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Cloudflare Workers
|
|
456
|
+
|
|
457
|
+
For Cloudflare Workers, use the standalone SQL generation API instead of the extension:
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
import { createToSQL } from '@dee-wan/prisma-sql'
|
|
461
|
+
import { Prisma } from '@prisma/client'
|
|
462
|
+
|
|
463
|
+
const toSQL = createToSQL(Prisma.dmmf, 'sqlite')
|
|
464
|
+
|
|
465
|
+
export default {
|
|
466
|
+
async fetch(request: Request, env: Env) {
|
|
467
|
+
const { sql, params } = toSQL('User', 'findMany', {
|
|
468
|
+
where: { status: 'ACTIVE' },
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
const result = await env.DB.prepare(sql)
|
|
472
|
+
.bind(...params)
|
|
473
|
+
.all()
|
|
474
|
+
return Response.json(result.results)
|
|
475
|
+
},
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
> **Note:** The Prisma Client extension is not recommended for Cloudflare Workers due to cold start overhead. Use the `createToSQL` API for edge deployments.
|
|
480
|
+
|
|
481
|
+
## Supported Queries
|
|
482
|
+
|
|
483
|
+
### Filters
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
// Comparison operators
|
|
487
|
+
{ age: { gt: 18, lte: 65 } }
|
|
488
|
+
{ status: { in: ['ACTIVE', 'PENDING'] } }
|
|
489
|
+
{ status: { notIn: ['DELETED'] } }
|
|
490
|
+
|
|
491
|
+
// String operations
|
|
492
|
+
{ email: { contains: '@example.com' } }
|
|
493
|
+
{ email: { startsWith: 'user' } }
|
|
494
|
+
{ email: { endsWith: '.com' } }
|
|
495
|
+
{ email: { contains: 'EXAMPLE', mode: 'insensitive' } }
|
|
496
|
+
|
|
497
|
+
// Logical operators
|
|
498
|
+
{ AND: [{ status: 'ACTIVE' }, { verified: true }] }
|
|
499
|
+
{ OR: [{ role: 'ADMIN' }, { role: 'MODERATOR' }] }
|
|
500
|
+
{ NOT: { status: 'DELETED' } }
|
|
501
|
+
|
|
502
|
+
// Null checks
|
|
503
|
+
{ deletedAt: null }
|
|
504
|
+
{ deletedAt: { not: null } }
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Relations
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
// Include relations
|
|
511
|
+
{
|
|
512
|
+
include: {
|
|
513
|
+
posts: true,
|
|
514
|
+
profile: true
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Nested includes
|
|
519
|
+
{
|
|
520
|
+
include: {
|
|
521
|
+
posts: {
|
|
522
|
+
include: { comments: true },
|
|
523
|
+
where: { published: true },
|
|
524
|
+
orderBy: { createdAt: 'desc' },
|
|
525
|
+
take: 5
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Relation filters
|
|
531
|
+
{
|
|
532
|
+
where: {
|
|
533
|
+
posts: {
|
|
534
|
+
some: { published: true }
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
{
|
|
540
|
+
where: {
|
|
541
|
+
posts: {
|
|
542
|
+
every: { published: true }
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
{
|
|
548
|
+
where: {
|
|
549
|
+
posts: {
|
|
550
|
+
none: { published: false }
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Pagination & Ordering
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
// Limit/offset
|
|
560
|
+
{
|
|
561
|
+
take: 10,
|
|
562
|
+
skip: 20,
|
|
563
|
+
orderBy: { createdAt: 'desc' }
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Cursor-based pagination
|
|
567
|
+
{
|
|
568
|
+
cursor: { id: 100 },
|
|
569
|
+
take: 10,
|
|
570
|
+
skip: 1, // Skip cursor itself
|
|
571
|
+
orderBy: { id: 'asc' }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Multiple ordering
|
|
575
|
+
{
|
|
576
|
+
orderBy: [
|
|
577
|
+
{ status: 'asc' },
|
|
578
|
+
{ priority: 'desc' },
|
|
579
|
+
{ createdAt: 'desc' }
|
|
580
|
+
]
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Null positioning (PostgreSQL)
|
|
584
|
+
{
|
|
585
|
+
orderBy: {
|
|
586
|
+
name: {
|
|
587
|
+
sort: 'asc',
|
|
588
|
+
nulls: 'last'
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Aggregations
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// Count
|
|
598
|
+
await prisma.user.count({ where: { status: 'ACTIVE' } })
|
|
599
|
+
|
|
600
|
+
// Multiple aggregations
|
|
601
|
+
await prisma.task.aggregate({
|
|
602
|
+
where: { status: 'DONE' },
|
|
603
|
+
_count: { _all: true },
|
|
604
|
+
_sum: { estimatedHours: true },
|
|
605
|
+
_avg: { estimatedHours: true },
|
|
606
|
+
_min: { startedAt: true },
|
|
607
|
+
_max: { completedAt: true },
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
// Group by
|
|
611
|
+
await prisma.task.groupBy({
|
|
612
|
+
by: ['status', 'priority'],
|
|
613
|
+
_count: { _all: true },
|
|
614
|
+
_avg: { estimatedHours: true },
|
|
615
|
+
having: {
|
|
616
|
+
status: {
|
|
617
|
+
_count: { gte: 5 },
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
})
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Distinct
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
// Single field (PostgreSQL uses DISTINCT ON)
|
|
627
|
+
{
|
|
628
|
+
distinct: ['status'],
|
|
629
|
+
orderBy: { status: 'asc' }
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Multiple fields (SQLite uses window functions)
|
|
633
|
+
{
|
|
634
|
+
distinct: ['status', 'priority'],
|
|
635
|
+
orderBy: [
|
|
636
|
+
{ status: 'asc' },
|
|
637
|
+
{ priority: 'asc' }
|
|
638
|
+
]
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
## Migration Guide
|
|
643
|
+
|
|
644
|
+
### From Prisma Client
|
|
645
|
+
|
|
646
|
+
**Before:**
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
const prisma = new PrismaClient()
|
|
650
|
+
const users = await prisma.user.findMany()
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
**After:**
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import postgres from 'postgres'
|
|
657
|
+
import { speedExtension } from '@dee-wan/prisma-sql'
|
|
658
|
+
|
|
659
|
+
const sql = postgres(DATABASE_URL)
|
|
660
|
+
const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
|
|
661
|
+
|
|
662
|
+
const users = await prisma.user.findMany() // Same code, 2-7x faster
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### From Drizzle
|
|
666
|
+
|
|
667
|
+
**Before:**
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
671
|
+
import postgres from 'postgres'
|
|
672
|
+
|
|
673
|
+
const sql = postgres(DATABASE_URL)
|
|
674
|
+
const db = drizzle(sql)
|
|
675
|
+
|
|
676
|
+
const users = await db
|
|
677
|
+
.select()
|
|
678
|
+
.from(usersTable)
|
|
679
|
+
.where(eq(usersTable.status, 'ACTIVE'))
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**After:**
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
import { speedExtension } from '@dee-wan/prisma-sql'
|
|
686
|
+
|
|
687
|
+
const sql = postgres(DATABASE_URL)
|
|
688
|
+
const prisma = new PrismaClient().$extends(speedExtension({ postgres: sql }))
|
|
689
|
+
|
|
690
|
+
// Use Prisma's familiar API instead
|
|
691
|
+
const users = await prisma.user.findMany({
|
|
692
|
+
where: { status: 'ACTIVE' },
|
|
693
|
+
})
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
## Limitations
|
|
697
|
+
|
|
698
|
+
### Partially Supported
|
|
699
|
+
|
|
700
|
+
These features work but have limitations:
|
|
701
|
+
|
|
702
|
+
- ⚠️ **Array operations**: Basic operations (`has`, `hasSome`, `hasEvery`, `isEmpty`) work. Advanced filtering like `array_contains(array_field, [1,2,3])` not yet supported.
|
|
703
|
+
- ⚠️ **JSON operations**: Path-based filtering works (`json.path(['field'], { equals: 'value' })`). Advanced JSON functions not yet supported.
|
|
704
|
+
|
|
705
|
+
### Not Yet Supported
|
|
706
|
+
|
|
707
|
+
These Prisma features are not supported and will fall back to Prisma Client:
|
|
708
|
+
|
|
709
|
+
- ❌ Full-text search (`search` operator)
|
|
710
|
+
- ❌ Composite types (MongoDB-style embedded documents)
|
|
711
|
+
- ❌ Raw database features (PostGIS, pg_trgm, etc.)
|
|
712
|
+
- ❌ Some advanced aggregations in `groupBy` (nested aggregations)
|
|
713
|
+
|
|
714
|
+
If you encounter unsupported queries, enable `debug: true` to see which queries are being converted and which fall back to Prisma.
|
|
715
|
+
|
|
716
|
+
### Database Support
|
|
717
|
+
|
|
718
|
+
- ✅ PostgreSQL 12+
|
|
719
|
+
- ✅ SQLite 3.35+
|
|
720
|
+
- ❌ MySQL (not yet implemented)
|
|
721
|
+
- ❌ MongoDB (not applicable - document database)
|
|
722
|
+
- ❌ SQL Server (not yet implemented)
|
|
723
|
+
- ❌ CockroachDB (not yet tested)
|
|
724
|
+
|
|
725
|
+
## Troubleshooting
|
|
726
|
+
|
|
727
|
+
### "Results don't match Prisma Client"
|
|
728
|
+
|
|
729
|
+
Enable debug mode to inspect generated SQL:
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
speedExtension({
|
|
733
|
+
postgres: sql,
|
|
734
|
+
debug: true,
|
|
735
|
+
})
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
Compare with Prisma's query log:
|
|
739
|
+
|
|
740
|
+
```typescript
|
|
741
|
+
new PrismaClient({ log: ['query'] })
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
File an issue if results differ: https://github.com/dee-see/prisma-sql/issues
|
|
745
|
+
|
|
746
|
+
### "Connection pool exhausted"
|
|
747
|
+
|
|
748
|
+
Increase postgres.js pool size:
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
const sql = postgres(DATABASE_URL, {
|
|
752
|
+
max: 50, // Increase from default 10
|
|
753
|
+
})
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### "Cannot access Prisma DMMF" Error
|
|
757
|
+
|
|
758
|
+
If you see this error:
|
|
759
|
+
|
|
760
|
+
```
|
|
761
|
+
Cannot access Prisma DMMF. Please provide dmmf in config
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
Explicitly provide the DMMF:
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
import { Prisma } from '@prisma/client'
|
|
768
|
+
|
|
769
|
+
const prisma = new PrismaClient().$extends(
|
|
770
|
+
speedExtension({
|
|
771
|
+
postgres: sql,
|
|
772
|
+
dmmf: Prisma.dmmf, // Add this
|
|
773
|
+
}),
|
|
774
|
+
)
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
This is required in:
|
|
778
|
+
|
|
779
|
+
- Edge runtimes (Cloudflare Workers, Vercel Edge)
|
|
780
|
+
- Bundled applications (webpack, esbuild)
|
|
781
|
+
- Some monorepo setups
|
|
782
|
+
- When using Prisma Client programmatically
|
|
783
|
+
|
|
784
|
+
### "Type errors after extending"
|
|
785
|
+
|
|
786
|
+
Ensure `@prisma/client` is up to date:
|
|
787
|
+
|
|
788
|
+
```bash
|
|
789
|
+
npm update @prisma/client
|
|
790
|
+
npx prisma generate
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### "Performance not improving"
|
|
794
|
+
|
|
795
|
+
Some queries won't see dramatic improvements:
|
|
796
|
+
|
|
797
|
+
- Very simple `findUnique` by ID (already fast)
|
|
798
|
+
- Queries with no WHERE clause on small tables
|
|
799
|
+
- Aggregations on unindexed fields
|
|
800
|
+
|
|
801
|
+
Use `onQuery` to measure actual speedup:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
speedExtension({
|
|
805
|
+
postgres: sql,
|
|
806
|
+
onQuery: (info) => {
|
|
807
|
+
console.log(`${info.method} took ${info.duration}ms`)
|
|
808
|
+
},
|
|
809
|
+
})
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
## FAQ
|
|
813
|
+
|
|
814
|
+
**Q: Do I need to keep using Prisma Client?**
|
|
815
|
+
A: Yes. You need Prisma for schema management, migrations, types, and write operations. This extension only speeds up reads.
|
|
816
|
+
|
|
817
|
+
**Q: Does it work with my existing schema?**
|
|
818
|
+
A: Yes. No schema changes required. It works with your existing Prisma schema and generated client.
|
|
819
|
+
|
|
820
|
+
**Q: What about writes (create, update, delete)?**
|
|
821
|
+
A: Writes still use Prisma Client. This extension only accelerates reads. For write-heavy workloads, this provides less benefit.
|
|
822
|
+
|
|
823
|
+
**Q: Is it production ready?**
|
|
824
|
+
A: Yes. 137 E2E tests verify exact parity with Prisma Client across both Prisma v6 and v7. Used in production.
|
|
825
|
+
|
|
826
|
+
**Q: Can I use it with PlanetScale, Neon, Supabase?**
|
|
827
|
+
A: Yes. Works with any PostgreSQL-compatible database. Just pass the connection string to postgres.js.
|
|
828
|
+
|
|
829
|
+
**Q: Does it support Prisma middlewares?**
|
|
830
|
+
A: The extension runs after middlewares. If you need middleware to see the actual SQL, use Prisma's query logging.
|
|
831
|
+
|
|
832
|
+
**Q: Can I still use `$queryRaw` and `$executeRaw`?**
|
|
833
|
+
A: Yes. Those methods are unaffected. You also still have direct access to the postgres.js client.
|
|
834
|
+
|
|
835
|
+
**Q: Do I need to provide `dmmf` in the config?**
|
|
836
|
+
A: Usually no - it's auto-detected from Prisma Client. However, in edge runtimes (Cloudflare Workers, Vercel Edge) or bundled applications, you must provide it explicitly:
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
import { Prisma } from '@prisma/client'
|
|
840
|
+
|
|
841
|
+
speedExtension({
|
|
842
|
+
postgres: sql,
|
|
843
|
+
dmmf: Prisma.dmmf, // Required in edge runtimes
|
|
844
|
+
})
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
If you see "Cannot access Prisma DMMF" error, add this parameter.
|
|
848
|
+
|
|
849
|
+
**Q: What's the overhead of SQL generation?**
|
|
850
|
+
A: ~0.03-0.04ms per query. Even with this overhead, total time is 2-7x faster than Prisma.
|
|
851
|
+
|
|
852
|
+
**Q: How do I benchmark my own queries?**
|
|
853
|
+
A: Use the `onQuery` callback to measure each query, or see the [Benchmarking](#benchmarking) section below.
|
|
854
|
+
|
|
855
|
+
**Q: How does performance compare to Prisma v7?**
|
|
856
|
+
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.
|
|
857
|
+
|
|
858
|
+
## Examples
|
|
859
|
+
|
|
860
|
+
- [PostgreSQL E2E Tests](./tests/e2e/postgres.test.ts) - Comprehensive query examples
|
|
861
|
+
- [SQLite E2E Tests](./tests/e2e/sqlite.e2e.test.ts) - SQLite-specific queries
|
|
862
|
+
- [Runtime API Tests](./tests/e2e/runtime-api.test.ts) - All three APIs
|
|
863
|
+
|
|
864
|
+
To run examples locally:
|
|
865
|
+
|
|
866
|
+
```bash
|
|
867
|
+
git clone https://github.com/dee-see/prisma-sql
|
|
868
|
+
cd prisma-sql
|
|
869
|
+
npm install
|
|
870
|
+
npm test
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
## Benchmarking
|
|
874
|
+
|
|
875
|
+
Benchmark your own queries:
|
|
876
|
+
|
|
877
|
+
```typescript
|
|
878
|
+
import { speedExtension } from '@dee-wan/prisma-sql'
|
|
879
|
+
|
|
880
|
+
const queries: { name: string; duration: number }[] = []
|
|
881
|
+
|
|
882
|
+
const prisma = new PrismaClient().$extends(
|
|
883
|
+
speedExtension({
|
|
884
|
+
postgres: sql,
|
|
885
|
+
onQuery: (info) => {
|
|
886
|
+
queries.push({
|
|
887
|
+
name: `${info.model}.${info.method}`,
|
|
888
|
+
duration: info.duration,
|
|
889
|
+
})
|
|
890
|
+
},
|
|
891
|
+
}),
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
// Run your queries
|
|
895
|
+
await prisma.user.findMany({ where: { status: 'ACTIVE' } })
|
|
896
|
+
await prisma.post.findMany({ include: { author: true } })
|
|
897
|
+
|
|
898
|
+
// Analyze
|
|
899
|
+
console.table(queries)
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
Or run the full test suite benchmarks:
|
|
903
|
+
|
|
904
|
+
```bash
|
|
905
|
+
git clone https://github.com/dee-see/prisma-sql
|
|
906
|
+
cd prisma-sql
|
|
907
|
+
npm install
|
|
908
|
+
|
|
909
|
+
# Setup test database
|
|
910
|
+
npx prisma db push
|
|
911
|
+
|
|
912
|
+
# Run PostgreSQL benchmarks
|
|
913
|
+
DATABASE_URL="postgresql://..." npm run test:e2e:postgres
|
|
914
|
+
|
|
915
|
+
# Run SQLite benchmarks
|
|
916
|
+
npm run test:e2e:sqlite
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
Results include timing for Prisma vs Extension vs Drizzle (where applicable).
|
|
920
|
+
|
|
921
|
+
## Contributing
|
|
922
|
+
|
|
923
|
+
PRs welcome! Priority areas:
|
|
924
|
+
|
|
925
|
+
- MySQL support implementation
|
|
926
|
+
- Additional PostgreSQL/SQLite operators
|
|
927
|
+
- Performance optimizations
|
|
928
|
+
- Edge runtime compatibility
|
|
929
|
+
- Documentation improvements
|
|
930
|
+
|
|
931
|
+
Setup:
|
|
932
|
+
|
|
933
|
+
```bash
|
|
934
|
+
git clone https://github.com/dee-see/prisma-sql
|
|
935
|
+
cd prisma-sql
|
|
936
|
+
npm install
|
|
937
|
+
npm run generate
|
|
938
|
+
npm test
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
Please ensure:
|
|
942
|
+
|
|
943
|
+
- All tests pass (`npm test`)
|
|
944
|
+
- New features have tests
|
|
945
|
+
- Types are properly exported
|
|
946
|
+
- README is updated
|
|
947
|
+
|
|
948
|
+
## How It Works
|
|
949
|
+
|
|
950
|
+
```
|
|
951
|
+
┌─────────────────────────────────────────────────────┐
|
|
952
|
+
│ prisma.user.findMany({ where: { status: 'ACTIVE' }})│
|
|
953
|
+
└────────────────────┬────────────────────────────────┘
|
|
954
|
+
│
|
|
955
|
+
┌───────────▼──────────┐
|
|
956
|
+
│ Speed Extension │
|
|
957
|
+
│ Intercepts query │
|
|
958
|
+
└───────────┬──────────┘
|
|
959
|
+
│
|
|
960
|
+
┌───────────▼──────────┐
|
|
961
|
+
│ Generate SQL │
|
|
962
|
+
│ Parser + Builder │
|
|
963
|
+
└───────────┬──────────┘
|
|
964
|
+
│
|
|
965
|
+
┌───────────▼──────────┐
|
|
966
|
+
│ SELECT ... FROM users│
|
|
967
|
+
│ WHERE status = $1 │
|
|
968
|
+
└───────────┬──────────┘
|
|
969
|
+
│
|
|
970
|
+
┌───────────▼──────────┐
|
|
971
|
+
│ Execute via │
|
|
972
|
+
│ postgres.js │ ← Bypasses Prisma's query engine
|
|
973
|
+
└───────────┬──────────┘
|
|
974
|
+
│
|
|
975
|
+
┌───────────▼──────────┐
|
|
976
|
+
│ Return results │
|
|
977
|
+
│ (same format as │
|
|
978
|
+
│ Prisma) │
|
|
979
|
+
└──────────────────────┘
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
## License
|
|
983
|
+
|
|
984
|
+
MIT
|
|
985
|
+
|
|
986
|
+
## Links
|
|
987
|
+
|
|
988
|
+
- [NPM Package](https://www.npmjs.com/package/@dee-wan/prisma-sql)
|
|
989
|
+
- [GitHub Repository](https://github.com/dee-see/prisma-sql)
|
|
990
|
+
- [Issue Tracker](https://github.com/dee-see/prisma-sql/issues)
|
|
991
|
+
|
|
992
|
+
---
|
|
993
|
+
|
|
994
|
+
**Made for developers who need Prisma's DX with raw SQL performance.**
|