Skip to content

Commit c265837

Browse files
steve-chavezsoedirgo
authored andcommitted
feat: add explain transform
1 parent 7060ab7 commit c265837

File tree

3 files changed

+133
-1
lines changed

3 files changed

+133
-1
lines changed

src/lib/PostgrestTransformBuilder.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PostgrestBuilder, PostgrestMaybeSingleResponse, PostgrestSingleResponse } from './types'
1+
import { PostgrestBuilder, PostgrestMaybeSingleResponse, PostgrestResponse, PostgrestSingleResponse } from './types'
22

33
/**
44
* Post-filters (transforms)
@@ -128,4 +128,43 @@ export default class PostgrestTransformBuilder<T> extends PostgrestBuilder<T> {
128128
this.headers['Accept'] = 'application/geo+json'
129129
return this as PromiseLike<PostgrestSingleResponse<Record<string, unknown>>>
130130
}
131+
132+
/**
133+
* Obtains the EXPLAIN plan for this request.
134+
*
135+
* @param analyze If `true`, the query will be executed and the actual run time will be displayed.
136+
* @param verbose If `true`, the query identifier will be displayed and the result will include the output columns of the query.
137+
* @param settings If `true`, include information on configuration parameters that affect query planning.
138+
* @param buffers If `true`, include information on buffer usage.
139+
* @param wal If `true`, include information on WAL record generation
140+
*/
141+
explain({
142+
analyze = false,
143+
verbose = false,
144+
settings = false,
145+
buffers = false,
146+
wal = false,
147+
}: {
148+
analyze?: boolean
149+
verbose?: boolean
150+
settings?: boolean
151+
buffers?: boolean
152+
wal?: boolean
153+
} = {}): PromiseLike<PostgrestResponse<Record<string, unknown>>> {
154+
const options = [
155+
analyze ? 'analyze' : null,
156+
verbose ? 'verbose' : null,
157+
settings ? 'settings' : null,
158+
buffers ? 'buffers' : null,
159+
wal ? 'wal' : null,
160+
]
161+
.filter(Boolean)
162+
.join('|')
163+
// An Accept header can carry multiple media types but postgrest-js always sends one
164+
const forMediatype = this.headers['Accept']
165+
this.headers[
166+
'Accept'
167+
] = `application/vnd.pgrst.plan+json; for="${forMediatype}"; options=${options};`
168+
return this as PromiseLike<PostgrestResponse<Record<string, unknown>>>
169+
}
131170
}

test/db/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ services:
1010
PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres
1111
PGRST_DB_SCHEMAS: public,personal
1212
PGRST_DB_ANON_ROLE: postgres
13+
PGRST_DB_PLAN_ENABLED: 1
1314
depends_on:
1415
- db
1516
db:

test/transforms.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,95 @@ test('geojson', async () => {
130130
}
131131
`)
132132
})
133+
134+
test('explain', async () => {
135+
const res = await postgrest
136+
.from('users')
137+
.select()
138+
.explain()
139+
.then((res) => res.data)
140+
expect(res).toMatchInlineSnapshot(`
141+
Array [
142+
Object {
143+
"Plan": Object {
144+
"Node Type": "Aggregate",
145+
"Parallel Aware": false,
146+
"Partial Mode": "Simple",
147+
"Plan Rows": 1,
148+
"Plan Width": 112,
149+
"Plans": Array [
150+
Object {
151+
"Alias": "users",
152+
"Node Type": "Seq Scan",
153+
"Parallel Aware": false,
154+
"Parent Relationship": "Outer",
155+
"Plan Rows": 510,
156+
"Plan Width": 132,
157+
"Relation Name": "users",
158+
"Startup Cost": 0,
159+
"Total Cost": 15.1,
160+
},
161+
],
162+
"Startup Cost": 17.65,
163+
"Strategy": "Plain",
164+
"Total Cost": 17.68,
165+
},
166+
},
167+
]
168+
`)
169+
})
170+
171+
test('explain with options', async () => {
172+
const res = await postgrest
173+
.from('users')
174+
.select()
175+
.explain({ verbose: true, settings: true })
176+
.then((res) => res.data)
177+
expect(res).toMatchInlineSnapshot(`
178+
Array [
179+
Object {
180+
"Plan": Object {
181+
"Node Type": "Aggregate",
182+
"Output": Array [
183+
"NULL::bigint",
184+
"count(ROW(users.username, users.data, users.age_range, users.status, users.catchphrase))",
185+
"(COALESCE(json_agg(ROW(users.username, users.data, users.age_range, users.status, users.catchphrase)), '[]'::json))::character varying",
186+
"NULLIF(current_setting('response.headers'::text, true), ''::text)",
187+
"NULLIF(current_setting('response.status'::text, true), ''::text)",
188+
],
189+
"Parallel Aware": false,
190+
"Partial Mode": "Simple",
191+
"Plan Rows": 1,
192+
"Plan Width": 112,
193+
"Plans": Array [
194+
Object {
195+
"Alias": "users",
196+
"Node Type": "Seq Scan",
197+
"Output": Array [
198+
"users.username",
199+
"users.data",
200+
"users.age_range",
201+
"users.status",
202+
"users.catchphrase",
203+
],
204+
"Parallel Aware": false,
205+
"Parent Relationship": "Outer",
206+
"Plan Rows": 510,
207+
"Plan Width": 132,
208+
"Relation Name": "users",
209+
"Schema": "public",
210+
"Startup Cost": 0,
211+
"Total Cost": 15.1,
212+
},
213+
],
214+
"Startup Cost": 17.65,
215+
"Strategy": "Plain",
216+
"Total Cost": 17.68,
217+
},
218+
"Settings": Object {
219+
"search_path": "\\\"public\\\", \\\"public\\\"",
220+
},
221+
},
222+
]
223+
`)
224+
})

0 commit comments

Comments
 (0)