Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.

Commit b1369cd

Browse files
authored
Merge pull request #167 from supabase/feat/upsert
Make UPSERT its own function
2 parents 8ba6cb8 + f892424 commit b1369cd

File tree

3 files changed

+72
-21
lines changed

3 files changed

+72
-21
lines changed

src/lib/PostgrestQueryBuilder.ts

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,28 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
5858
* Performs an INSERT into the table.
5959
*
6060
* @param values The values to insert.
61-
* @param upsert If `true`, performs an UPSERT.
62-
* @param onConflict By specifying the `on_conflict` query parameter, you can make UPSERT work on a column(s) that has a UNIQUE constraint.
6361
* @param returning By default the new record is returned. Set this to 'minimal' if you don't need this value.
62+
* @param count Count algorithm to use to count rows in a table.
6463
*/
64+
insert(
65+
values: Partial<T> | Partial<T>[],
66+
options?: {
67+
returning?: 'minimal' | 'representation'
68+
count?: null | 'exact' | 'planned' | 'estimated'
69+
}
70+
): PostgrestFilterBuilder<T>
71+
/**
72+
* @deprecated Use `upsert()` instead.
73+
*/
74+
insert(
75+
values: Partial<T> | Partial<T>[],
76+
options?: {
77+
upsert?: boolean
78+
onConflict?: string
79+
returning?: 'minimal' | 'representation'
80+
count?: null | 'exact' | 'planned' | 'estimated'
81+
}
82+
): PostgrestFilterBuilder<T>
6583
insert(
6684
values: Partial<T> | Partial<T>[],
6785
{
@@ -78,18 +96,52 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
7896
): PostgrestFilterBuilder<T> {
7997
this.method = 'POST'
8098

81-
let prefersHeaders = []
82-
prefersHeaders.push(`return=${returning}`)
99+
const prefersHeaders = [`return=${returning}`]
83100
if (upsert) prefersHeaders.push('resolution=merge-duplicates')
84101

85102
if (upsert && onConflict !== undefined) this.url.searchParams.set('on_conflict', onConflict)
86103
this.body = values
87104
if (count) {
88105
prefersHeaders.push(`count=${count}`)
89106
}
90-
107+
91108
this.headers['Prefer'] = prefersHeaders.join(',')
92-
109+
110+
return new PostgrestFilterBuilder(this)
111+
}
112+
113+
/**
114+
* Performs an UPSERT into the table.
115+
*
116+
* @param values The values to insert.
117+
* @param onConflict By specifying the `on_conflict` query parameter, you can make UPSERT work on a column(s) that has a UNIQUE constraint.
118+
* @param returning By default the new record is returned. Set this to 'minimal' if you don't need this value.
119+
* @param count Count algorithm to use to count rows in a table.
120+
*/
121+
upsert(
122+
values: Partial<T> | Partial<T>[],
123+
{
124+
onConflict,
125+
returning = 'representation',
126+
count = null,
127+
}: {
128+
onConflict?: string
129+
returning?: 'minimal' | 'representation'
130+
count?: null | 'exact' | 'planned' | 'estimated'
131+
} = {}
132+
): PostgrestFilterBuilder<T> {
133+
this.method = 'POST'
134+
135+
const prefersHeaders = ['resolution=merge-duplicates', `return=${returning}`]
136+
137+
if (onConflict !== undefined) this.url.searchParams.set('on_conflict', onConflict)
138+
this.body = values
139+
if (count) {
140+
prefersHeaders.push(`count=${count}`)
141+
}
142+
143+
this.headers['Prefer'] = prefersHeaders.join(',')
144+
93145
return new PostgrestFilterBuilder(this)
94146
}
95147

@@ -98,6 +150,7 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
98150
*
99151
* @param values The values to update.
100152
* @param returning By default the updated record is returned. Set this to 'minimal' if you don't need this value.
153+
* @param count Count algorithm to use to count rows in a table.
101154
*/
102155
update(
103156
values: Partial<T>,
@@ -110,8 +163,7 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
110163
} = {}
111164
): PostgrestFilterBuilder<T> {
112165
this.method = 'PATCH'
113-
let prefersHeaders = []
114-
prefersHeaders.push(`return=${returning}`)
166+
const prefersHeaders = [`return=${returning}`]
115167
this.body = values
116168
if (count) {
117169
prefersHeaders.push(`count=${count}`)
@@ -124,6 +176,7 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
124176
* Performs a DELETE on the table.
125177
*
126178
* @param returning If `true`, return the deleted row(s) in the response.
179+
* @param count Count algorithm to use to count rows in a table.
127180
*/
128181
delete({
129182
returning = 'representation',
@@ -133,8 +186,7 @@ export default class PostgrestQueryBuilder<T> extends PostgrestBuilder<T> {
133186
count?: null | 'exact' | 'planned' | 'estimated'
134187
} = {}): PostgrestFilterBuilder<T> {
135188
this.method = 'DELETE'
136-
let prefersHeaders = []
137-
prefersHeaders.push(`return=${returning}`)
189+
const prefersHeaders = [`return=${returning}`]
138190
if (count) {
139191
prefersHeaders.push(`count=${count}`)
140192
}

test/basic.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test('switch schema', async () => {
3737
test('on_conflict insert', async () => {
3838
const res = await postgrest
3939
.from('users')
40-
.insert({ username: 'dragarcia' }, { upsert: true, onConflict: 'username' })
40+
.upsert({ username: 'dragarcia' }, { onConflict: 'username' })
4141
expect(res).toMatchSnapshot()
4242
})
4343

@@ -55,7 +55,7 @@ describe('basic insert, update, delete', () => {
5555
test('upsert', async () => {
5656
let res = await postgrest
5757
.from('messages')
58-
.insert({ id: 3, message: 'foo', username: 'supabot', channel_id: 2 }, { upsert: true })
58+
.upsert({ id: 3, message: 'foo', username: 'supabot', channel_id: 2 })
5959
expect(res).toMatchSnapshot()
6060

6161
res = await postgrest.from('messages').select()
@@ -176,7 +176,7 @@ test('select with count:exact', async () => {
176176
})
177177

178178
test("stored procedure with count: 'exact'", async () => {
179-
const res = await postgrest.rpc('get_status', { name_param: 'supabot'}, {count: 'exact' })
179+
const res = await postgrest.rpc('get_status', { name_param: 'supabot' }, { count: 'exact' })
180180
expect(res).toMatchSnapshot()
181181
})
182182

@@ -194,10 +194,7 @@ describe("insert, update, delete with count: 'exact'", () => {
194194
test("upsert with count: 'exact'", async () => {
195195
let res = await postgrest
196196
.from('messages')
197-
.insert(
198-
{ id: 3, message: 'foo', username: 'supabot', channel_id: 2 },
199-
{ upsert: true, count: 'exact' }
200-
)
197+
.upsert({ id: 3, message: 'foo', username: 'supabot', channel_id: 2 }, { count: 'exact' })
201198
expect(res).toMatchSnapshot()
202199

203200
res = await postgrest.from('messages').select()

test/resource-embedding.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ describe('embedded filters', () => {
2424
const res = await postgrest
2525
.from('users')
2626
.select('messages(*)')
27-
.or('channel_id.eq.2,and(message.eq.Hello World 👋,username.eq.supabot)', { foreignTable: 'messages' })
27+
.or('channel_id.eq.2,and(message.eq.Hello World 👋,username.eq.supabot)', {
28+
foreignTable: 'messages',
29+
})
2830
expect(res).toMatchSnapshot()
2931
})
3032
})
@@ -38,14 +40,14 @@ describe('embedded transforms', () => {
3840
expect(res).toMatchSnapshot()
3941
})
4042

41-
test('embedded order on multiple columns', async () => {
43+
test('embedded order on multiple columns', async () => {
4244
const res = await postgrest
4345
.from('users')
4446
.select('messages(*)')
4547
.order('channel_id', { foreignTable: 'messages', ascending: false })
4648
.order('username', { foreignTable: 'messages', ascending: false })
47-
expect(res).toMatchSnapshot()
48-
})
49+
expect(res).toMatchSnapshot()
50+
})
4951

5052
test('embedded limit', async () => {
5153
const res = await postgrest

0 commit comments

Comments
 (0)