Skip to content

Commit 1ffef35

Browse files
feat(postgrest): add stripNulls method for null value stripping (#2189)
1 parent 1ec22ca commit 1ffef35

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

packages/core/postgrest-js/src/PostgrestBuilder.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export default abstract class PostgrestBuilder<
8383
protected signal?: AbortSignal
8484
protected fetch: Fetch
8585
protected isMaybeSingle: boolean
86+
protected shouldStripNulls: boolean
8687
protected urlLengthLimit: number
8788

8889
// Retry configuration - enabled by default
@@ -123,6 +124,7 @@ export default abstract class PostgrestBuilder<
123124
signal?: AbortSignal
124125
fetch?: Fetch
125126
isMaybeSingle?: boolean
127+
shouldStripNulls?: boolean
126128
urlLengthLimit?: number
127129
// Retry option
128130
retry?: boolean
@@ -135,6 +137,7 @@ export default abstract class PostgrestBuilder<
135137
this.shouldThrowOnError = builder.shouldThrowOnError ?? false
136138
this.signal = builder.signal
137139
this.isMaybeSingle = builder.isMaybeSingle ?? false
140+
this.shouldStripNulls = builder.shouldStripNulls ?? false
138141
this.urlLengthLimit = builder.urlLengthLimit ?? 8000
139142
this.retryEnabled = builder.retry ?? true
140143

@@ -158,6 +161,63 @@ export default abstract class PostgrestBuilder<
158161
return this as this & PostgrestBuilder<ClientOptions, Result, true>
159162
}
160163

164+
/**
165+
* Strip null values from the response data. Properties with `null` values
166+
* will be omitted from the returned JSON objects.
167+
*
168+
* Requires PostgREST 11.2.0+.
169+
*
170+
* {@link https://docs.postgrest.org/en/stable/references/api/resource_representation.html#stripped-nulls}
171+
*
172+
* @category Database
173+
*
174+
* @example With `select()`
175+
* ```ts
176+
* const { data, error } = await supabase
177+
* .from('characters')
178+
* .select()
179+
* .stripNulls()
180+
* ```
181+
*
182+
* @exampleSql With `select()`
183+
* ```sql
184+
* create table
185+
* characters (id int8 primary key, name text, bio text);
186+
*
187+
* insert into
188+
* characters (id, name, bio)
189+
* values
190+
* (1, 'Luke', null),
191+
* (2, 'Leia', 'Princess of Alderaan');
192+
* ```
193+
*
194+
* @exampleResponse With `select()`
195+
* ```json
196+
* {
197+
* "data": [
198+
* {
199+
* "id": 1,
200+
* "name": "Luke"
201+
* },
202+
* {
203+
* "id": 2,
204+
* "name": "Leia",
205+
* "bio": "Princess of Alderaan"
206+
* }
207+
* ],
208+
* "status": 200,
209+
* "statusText": "OK"
210+
* }
211+
* ```
212+
*/
213+
stripNulls(): this {
214+
if (this.headers.get('Accept') === 'text/csv') {
215+
throw new Error('stripNulls() cannot be used with csv()')
216+
}
217+
this.shouldStripNulls = true
218+
return this
219+
}
220+
161221
/**
162222
* Set an HTTP header for the request.
163223
*
@@ -222,6 +282,16 @@ export default abstract class PostgrestBuilder<
222282
this.headers.set('Content-Type', 'application/json')
223283
}
224284

285+
// https://docs.postgrest.org/en/stable/references/api/resource_representation.html#stripped-nulls
286+
if (this.shouldStripNulls) {
287+
const currentAccept = this.headers.get('Accept')
288+
if (currentAccept === 'application/vnd.pgrst.object+json') {
289+
this.headers.set('Accept', 'application/vnd.pgrst.object+json;nulls=stripped')
290+
} else if (!currentAccept || currentAccept === 'application/json') {
291+
this.headers.set('Accept', 'application/vnd.pgrst.array+json;nulls=stripped')
292+
}
293+
}
294+
225295
// NOTE: Invoke w/o `this` to avoid illegal invocation error.
226296
// https://github.com/supabase/postgrest-js/pull/247
227297
const _fetch = this.fetch

packages/core/postgrest-js/test/transforms.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,43 @@ test('csv', async () => {
315315
`)
316316
})
317317

318+
test('stripNulls', async () => {
319+
const res = await postgrest.from('users').select().stripNulls()
320+
// Null fields (data, age_range, catchphrase) should be stripped from rows where they are null
321+
expect(res.error).toBeNull()
322+
expect(res.status).toBe(200)
323+
expect(Array.isArray(res.data)).toBe(true)
324+
// Check that a row with null `data` does not have the `data` key
325+
const supabot = (res.data as any[])?.find((u: any) => u.username === 'supabot')
326+
expect(supabot).toBeDefined()
327+
expect(supabot).not.toHaveProperty('data')
328+
expect(supabot).toHaveProperty('username')
329+
// Check that a row with non-null `data` still has it
330+
const jsonuser = (res.data as any[])?.find((u: any) => u.username === 'jsonuser')
331+
expect(jsonuser).toBeDefined()
332+
expect(jsonuser).toHaveProperty('data')
333+
})
334+
335+
test('stripNulls with single', async () => {
336+
const res = await postgrest
337+
.from('users')
338+
.select()
339+
.eq('username', 'supabot')
340+
.limit(1)
341+
.single()
342+
.stripNulls()
343+
expect(res.error).toBeNull()
344+
expect(res.status).toBe(200)
345+
expect(res.data).not.toHaveProperty('data')
346+
expect((res.data as any)?.username).toBe('supabot')
347+
})
348+
349+
test('stripNulls throws when used with csv', () => {
350+
expect(() => postgrest.from('users').select().csv().stripNulls()).toThrow(
351+
'stripNulls() cannot be used with csv()'
352+
)
353+
})
354+
318355
test('geojson', async () => {
319356
const res = await postgrest.from('shops').select().geojson()
320357
expect(res).toMatchInlineSnapshot(`

0 commit comments

Comments
 (0)