Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/select-query-parser/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,9 @@ type ProcessEmbeddedResourceResult<
TablesAndViews<Schema>[CurrentTableOrView],
Resolved['relation']
> extends true
? ProcessedChildren | null
? Field extends { innerJoin: true }
? ProcessedChildren
: ProcessedChildren | null
: ProcessedChildren
}
: {
Expand Down
35 changes: 34 additions & 1 deletion test/db/01-dummy-data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,37 @@ INSERT INTO public.cornercase (id, array_column)
VALUES
(1, ARRAY['test', 'one']),
(2, ARRAY['another']),
(3, ARRAY['test2']);
(3, ARRAY['test2']);

create table
hotel (
id bigint generated by default as identity primary key,
name text null
);

create table
booking (
id bigint generated by default as identity primary key,
hotel_id bigint, -- nullable foreign key is needed to reproduce !inner supabase-js error
foreign key (hotel_id) references hotel (id)
);

-- Insert sample hotels
INSERT INTO hotel (id, name)
VALUES
(1, 'Sunset Resort'),
(2, 'Mountain View Hotel'),
(3, 'Beachfront Inn'),
(4, NULL);

-- Insert bookings with various relationship scenarios
INSERT INTO booking (id, hotel_id)
VALUES
(1, 1), -- Valid booking for Sunset Resort
(2, 1), -- Another booking for Sunset Resort (duplicate reference)
(3, 2), -- Booking for Mountain View Hotel
(4, NULL), -- Booking with no hotel (null reference)
(5, 3), -- Booking for Beachfront Inn
(6, 1), -- Third booking for Sunset Resort
(7, NULL), -- Another booking with no hotel
(8, 4); -- Booking for hotel with null name
63 changes: 63 additions & 0 deletions test/relationships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ export const selectParams = {
select:
'msgs:messages(id, ...message_details(created_at, channel!inner(id, slug, owner:users(*))))',
},
innerJoinOnNullableRelationship: {
from: 'booking',
select: 'id, hotel!inner(id, name)',
},
} as const

export const selectQueries = {
Expand Down Expand Up @@ -371,6 +375,9 @@ export const selectQueries = {
nestedQueryWithSelectiveFieldsAndInnerJoin: postgrest
.from(selectParams.nestedQueryWithSelectiveFieldsAndInnerJoin.from)
.select(selectParams.nestedQueryWithSelectiveFieldsAndInnerJoin.select),
innerJoinOnNullableRelationship: postgrest
.from(selectParams.innerJoinOnNullableRelationship.from)
.select(selectParams.innerJoinOnNullableRelationship.select),
} as const

test('nested query with selective fields', async () => {
Expand Down Expand Up @@ -518,6 +525,62 @@ test('!inner relationship', async () => {
`)
})

test('!inner relationship on nullable relation', async () => {
const res = await selectQueries.innerJoinOnNullableRelationship
expect(res).toMatchInlineSnapshot(`
Object {
"count": null,
"data": Array [
Object {
"hotel": Object {
"id": 1,
"name": "Sunset Resort",
},
"id": 1,
},
Object {
"hotel": Object {
"id": 1,
"name": "Sunset Resort",
},
"id": 2,
},
Object {
"hotel": Object {
"id": 2,
"name": "Mountain View Hotel",
},
"id": 3,
},
Object {
"hotel": Object {
"id": 3,
"name": "Beachfront Inn",
},
"id": 5,
},
Object {
"hotel": Object {
"id": 1,
"name": "Sunset Resort",
},
"id": 6,
},
Object {
"hotel": Object {
"id": 4,
"name": null,
},
"id": 8,
},
],
"error": null,
"status": 200,
"statusText": "OK",
}
`)
})

test('one-to-many relationship', async () => {
const res = await selectQueries.oneToMany.limit(1).single()
expect(res).toMatchInlineSnapshot(`
Expand Down
14 changes: 14 additions & 0 deletions test/select-query-parser/select.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ type Schema = Database['public']
expectType<TypeEqual<typeof result, typeof expected>>(true)
}

// !inner relationship on nullable relation
{
const { data } = await selectQueries.innerJoinOnNullableRelationship
let result: Exclude<typeof data, null>
let expected: Array<{
id: number
hotel: {
id: number
name: string | null
}
Comment on lines +94 to +97
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note

Since this come from an !inner join it should not be null as it's filtered at the join level.

}>
expectType<TypeEqual<typeof result, typeof expected>>(true)
}

// one-to-many relationship
{
const { data } = await selectQueries.oneToMany.limit(1).single()
Expand Down
38 changes: 38 additions & 0 deletions test/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,44 @@ export type Database = {
}
]
}
booking: {
Row: {
hotel_id: number | null
id: number
}
Insert: {
hotel_id?: number | null
id?: number
}
Update: {
hotel_id?: number | null
id?: number
}
Relationships: [
{
foreignKeyName: 'booking_hotel_id_fkey'
columns: ['hotel_id']
isOneToOne: false
referencedRelation: 'hotel'
referencedColumns: ['id']
}
]
}
hotel: {
Row: {
id: number
name: string | null
}
Insert: {
id?: number
name?: string | null
}
Update: {
id?: number
name?: string | null
}
Relationships: []
}
}
Views: {
non_updatable_view: {
Expand Down
Loading