Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1a5234e

Browse filesBrowse files
committed
fix(realtime): handle null values in postgres changes filter comparison
the server may return null for optional fields (schema, table, filter) while the client has undefined. strict equality comparison fails when comparing null === undefined, causing false mismatch errors. this normalizes both values before comparison to handle the null/undefined discrepancy that occurs during json serialization. closes #1917
1 parent 2596a09 commit 1a5234e
Copy full SHA for 1a5234e

File tree

Expand file treeCollapse file tree

2 files changed

+88
-3
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+88
-3
lines changed
Open diff view settings
Collapse file

‎packages/core/realtime-js/src/RealtimeChannel.ts‎

Copy file name to clipboardExpand all lines: packages/core/realtime-js/src/RealtimeChannel.ts
+17-3Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,9 @@ export default class RealtimeChannel {
327327
if (
328328
serverPostgresFilter &&
329329
serverPostgresFilter.event === event &&
330-
serverPostgresFilter.schema === schema &&
331-
serverPostgresFilter.table === table &&
332-
serverPostgresFilter.filter === filter
330+
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.schema, schema) &&
331+
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.table, table) &&
332+
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.filter, filter)
333333
) {
334334
newPostgresBindings.push({
335335
...clientPostgresBinding,
@@ -949,6 +949,20 @@ export default class RealtimeChannel {
949949
return true
950950
}
951951

952+
/**
953+
* Compares two optional filter values for equality.
954+
* Treats undefined, null, and empty string as equivalent empty values.
955+
* @internal
956+
*/
957+
private static isFilterValueEqual(
958+
serverValue: string | undefined | null,
959+
clientValue: string | undefined
960+
): boolean {
961+
const normalizedServer = serverValue ?? undefined
962+
const normalizedClient = clientValue ?? undefined
963+
return normalizedServer === normalizedClient
964+
}
965+
952966
/** @internal */
953967
private _rejoinUntilConnected() {
954968
this.rejoinTimer.scheduleTimeout()
Collapse file

‎packages/core/realtime-js/test/RealtimeChannel.postgres.test.ts‎

Copy file name to clipboardExpand all lines: packages/core/realtime-js/test/RealtimeChannel.postgres.test.ts
+71Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,77 @@ describe('PostgreSQL binding matching behavior', () => {
247247
assert.equal(channel.bindings.postgres_changes[0].id, 'server-id-1')
248248
})
249249

250+
test('should match postgres changes when server returns null for optional fields', () => {
251+
const callbackSpy = vi.fn()
252+
253+
channel.on(
254+
'postgres_changes',
255+
{
256+
event: 'INSERT',
257+
schema: 'public',
258+
table: 'notifications',
259+
},
260+
callbackSpy
261+
)
262+
263+
channel.subscribe()
264+
265+
const mockServerResponse = {
266+
postgres_changes: [
267+
{
268+
event: 'INSERT',
269+
schema: 'public',
270+
table: 'notifications',
271+
filter: null,
272+
id: 'server-id-1',
273+
},
274+
],
275+
}
276+
277+
channel.joinPush._matchReceive({
278+
status: 'ok',
279+
response: mockServerResponse,
280+
})
281+
282+
assert.equal(channel.state, CHANNEL_STATES.joined)
283+
assert.equal(channel.bindings.postgres_changes[0].id, 'server-id-1')
284+
})
285+
286+
test('should match postgres changes when server omits optional filter field', () => {
287+
const callbackSpy = vi.fn()
288+
289+
channel.on(
290+
'postgres_changes',
291+
{
292+
event: '*',
293+
schema: 'public',
294+
table: 'notifications',
295+
},
296+
callbackSpy
297+
)
298+
299+
channel.subscribe()
300+
301+
const mockServerResponse = {
302+
postgres_changes: [
303+
{
304+
event: '*',
305+
schema: 'public',
306+
table: 'notifications',
307+
id: 'server-id-1',
308+
},
309+
],
310+
}
311+
312+
channel.joinPush._matchReceive({
313+
status: 'ok',
314+
response: mockServerResponse,
315+
})
316+
317+
assert.equal(channel.state, CHANNEL_STATES.joined)
318+
assert.equal(channel.bindings.postgres_changes[0].id, 'server-id-1')
319+
})
320+
250321
test.each([
251322
{
252323
description: 'should fail when event differs',

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.