Skip to content

Commit cf50260

Browse files
authored
Merge pull request #441 from supabase/fix/439
fix a bug in which a UDF call returned null
2 parents 4d475f9 + 80e5e3e commit cf50260

File tree

5 files changed

+245
-3
lines changed

5 files changed

+245
-3
lines changed

docs/functions.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Functions marked `immutable` or `stable` are available on the query type. Functi
8888
## Supported Return Types
8989

9090

91-
Built-in GraphQL scalar types `Int`, `Float`, `String`, `Boolean` and [custom scalar types](api.md#custom-scalars) are supported as function arguments and return types. Function types returning a table or view are supported as well:
91+
Built-in GraphQL scalar types `Int`, `Float`, `String`, `Boolean` and [custom scalar types](api.md#custom-scalars) are supported as function arguments and return types. Function types returning a table or view are supported as well. Such functions implement the [Node interface](api.md#node):
9292

9393
=== "Function"
9494

@@ -125,6 +125,7 @@ Built-in GraphQL scalar types `Int`, `Float`, `String`, `Boolean` and [custom sc
125125
accountById(accountId: 1) {
126126
id
127127
email
128+
nodeId
128129
}
129130
}
130131
```
@@ -137,11 +138,57 @@ Built-in GraphQL scalar types `Int`, `Float`, `String`, `Boolean` and [custom sc
137138
"accountById": {
138139
"id": 1,
139140
"email": "a@example.com"
141+
"nodeId": "WyJwdWJsaWMiLCAiYWNjb3VudCIsIDFd"
140142
}
141143
}
142144
}
143145
```
144146

147+
Since Postgres considers a row/composite type containing only null values to be null, the result can be a little surprising in this case. Instead of an object with all columns null, the top-level field is null:
148+
149+
=== "Function"
150+
151+
```sql
152+
create table account(
153+
id int,
154+
email varchar(255),
155+
name text null
156+
);
157+
158+
insert into account(id, email, name)
159+
values
160+
(1, 'aardvark@x.com', 'aardvark'),
161+
(2, 'bat@x.com', null),
162+
(null, null, null);
163+
164+
create function returns_account_with_all_null_columns()
165+
returns account language sql stable
166+
as $$ select id, email, name from account where id is null; $$;
167+
```
168+
169+
=== "Query"
170+
171+
```graphql
172+
query {
173+
returnsAccountWithAllNullColumns {
174+
id
175+
email
176+
name
177+
__typename
178+
}
179+
}
180+
```
181+
182+
=== "Response"
183+
184+
```json
185+
{
186+
"data": {
187+
"returnsAccountWithAllNullColumns": null
188+
}
189+
}
190+
```
191+
145192
Functions returning multiple rows of a table or view are exposed as [collections](api.md#collections).
146193

147194
=== "Function"

src/sql_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ impl Table {
491491
pub fn primary_key_columns(&self) -> Vec<&Arc<Column>> {
492492
self.primary_key()
493493
.map(|x| x.column_names)
494-
.unwrap_or(vec![])
494+
.unwrap_or_default()
495495
.iter()
496496
.map(|col_name| {
497497
self.columns

src/transpile.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ impl FunctionCallBuilder {
571571
} else {
572572
select_clause
573573
};
574-
format!("select coalesce((select {select_clause} from {func_schema}.{func_name}{args_clause} {block_name} where {block_name} is not null), null::jsonb);")
574+
format!("select coalesce((select {select_clause} from {func_schema}.{func_name}{args_clause} {block_name} where not ({block_name} is null)), null::jsonb);")
575575
}
576576
FuncCallReturnTypeBuilder::Connection(connection_builder) => {
577577
let from_clause = format!("{func_schema}.{func_name}{args_clause}");

test/expected/function_calls.out

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,4 +2195,118 @@ begin;
21952195
}
21962196
(1 row)
21972197

2198+
rollback to savepoint a;
2199+
create table account(
2200+
id int,
2201+
email varchar(255),
2202+
name text null
2203+
);
2204+
create function returns_all_columns_non_null_account()
2205+
returns account language sql stable
2206+
as $$ select id, email, name from account where id = 1; $$;
2207+
create function returns_one_column_null_account()
2208+
returns account language sql stable
2209+
as $$ select id, email, name from account where id = 2; $$;
2210+
create function returns_all_columns_null_account()
2211+
returns account language sql stable
2212+
as $$ select id, email, name from account where id is null; $$;
2213+
create function returns_null_account()
2214+
returns account language sql stable
2215+
as $$ select id, email, name from account where id = 9; $$;
2216+
insert into account(id, email, name)
2217+
values
2218+
(1, 'aardvark@x.com', 'aardvark'),--all columns non-null
2219+
(2, 'bat@x.com', null),--mixed: some null, some non-null
2220+
(null, null, null);--all columns null
2221+
-- comment on table account is e'@graphql({"totalCount": {"enabled": true}})';
2222+
select jsonb_pretty(graphql.resolve($$
2223+
query {
2224+
returnsAllColumnsNonNullAccount {
2225+
id
2226+
email
2227+
name
2228+
__typename
2229+
}
2230+
}
2231+
$$));
2232+
jsonb_pretty
2233+
----------------------------------------------
2234+
{ +
2235+
"data": { +
2236+
"returnsAllColumnsNonNullAccount": {+
2237+
"id": 1, +
2238+
"name": "aardvark", +
2239+
"email": "aardvark@x.com", +
2240+
"__typename": "Account" +
2241+
} +
2242+
} +
2243+
}
2244+
(1 row)
2245+
2246+
select jsonb_pretty(graphql.resolve($$
2247+
query {
2248+
returnsOneColumnNullAccount {
2249+
id
2250+
email
2251+
name
2252+
__typename
2253+
}
2254+
}
2255+
$$));
2256+
jsonb_pretty
2257+
------------------------------------------
2258+
{ +
2259+
"data": { +
2260+
"returnsOneColumnNullAccount": {+
2261+
"id": 2, +
2262+
"name": null, +
2263+
"email": "bat@x.com", +
2264+
"__typename": "Account" +
2265+
} +
2266+
} +
2267+
}
2268+
(1 row)
2269+
2270+
-- With current implementation we can't distinguish between
2271+
-- when all columns of a composite type are null and when
2272+
-- the composite type itself is null. In both these cases
2273+
-- the result will be null for the top-level field.
2274+
select jsonb_pretty(graphql.resolve($$
2275+
query {
2276+
returnsAllColumnsNullAccount {
2277+
id
2278+
email
2279+
name
2280+
__typename
2281+
}
2282+
}
2283+
$$));
2284+
jsonb_pretty
2285+
----------------------------------------------
2286+
{ +
2287+
"data": { +
2288+
"returnsAllColumnsNullAccount": null+
2289+
} +
2290+
}
2291+
(1 row)
2292+
2293+
select jsonb_pretty(graphql.resolve($$
2294+
query {
2295+
returnsNullAccount {
2296+
id
2297+
email
2298+
name
2299+
__typename
2300+
}
2301+
}
2302+
$$));
2303+
jsonb_pretty
2304+
------------------------------------
2305+
{ +
2306+
"data": { +
2307+
"returnsNullAccount": null+
2308+
} +
2309+
}
2310+
(1 row)
2311+
21982312
rollback;

test/sql/function_calls.sql

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,4 +782,85 @@ begin;
782782
returnsEventTrigger
783783
}
784784
$$));
785+
786+
rollback to savepoint a;
787+
788+
create table account(
789+
id int,
790+
email varchar(255),
791+
name text null
792+
);
793+
794+
create function returns_all_columns_non_null_account()
795+
returns account language sql stable
796+
as $$ select id, email, name from account where id = 1; $$;
797+
798+
create function returns_one_column_null_account()
799+
returns account language sql stable
800+
as $$ select id, email, name from account where id = 2; $$;
801+
802+
create function returns_all_columns_null_account()
803+
returns account language sql stable
804+
as $$ select id, email, name from account where id is null; $$;
805+
806+
create function returns_null_account()
807+
returns account language sql stable
808+
as $$ select id, email, name from account where id = 9; $$;
809+
810+
insert into account(id, email, name)
811+
values
812+
(1, 'aardvark@x.com', 'aardvark'),--all columns non-null
813+
(2, 'bat@x.com', null),--mixed: some null, some non-null
814+
(null, null, null);--all columns null
815+
816+
-- comment on table account is e'@graphql({"totalCount": {"enabled": true}})';
817+
818+
select jsonb_pretty(graphql.resolve($$
819+
query {
820+
returnsAllColumnsNonNullAccount {
821+
id
822+
email
823+
name
824+
__typename
825+
}
826+
}
827+
$$));
828+
829+
select jsonb_pretty(graphql.resolve($$
830+
query {
831+
returnsOneColumnNullAccount {
832+
id
833+
email
834+
name
835+
__typename
836+
}
837+
}
838+
$$));
839+
840+
-- With current implementation we can't distinguish between
841+
-- when all columns of a composite type are null and when
842+
-- the composite type itself is null. In both these cases
843+
-- the result will be null for the top-level field.
844+
select jsonb_pretty(graphql.resolve($$
845+
query {
846+
returnsAllColumnsNullAccount {
847+
id
848+
email
849+
name
850+
__typename
851+
}
852+
}
853+
$$));
854+
855+
select jsonb_pretty(graphql.resolve($$
856+
query {
857+
returnsNullAccount {
858+
id
859+
email
860+
name
861+
__typename
862+
}
863+
}
864+
$$));
865+
785866
rollback;

0 commit comments

Comments
 (0)