Skip to content

Commit

Permalink
Merge pull request #443 from supabase/issue_337
Browse files Browse the repository at this point in the history
add test case and docs for issue #337
  • Loading branch information
olirice authored Oct 30, 2023
2 parents 8858025 + d355791 commit c569b7c
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/computed_fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ For arbitrary computations that do not meet the requirements for [generated colu
--8<-- "test/expected/extend_type_with_function.out"
```

If the function is written in SQL, its volatility can impact freshness of data returned in mutations:

```sql
--8<-- "test/expected/issue_337.out"
```

## Computed Relationships

Expand Down
187 changes: 187 additions & 0 deletions test/expected/issue_337.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
begin;
-- A computed field function written in SQL and marked stable might return stale results.
-- Directly from the postgres docs(https://www.postgresql.org/docs/current/xfunc-volatility.html):
--For functions written in SQL or in any of the standard procedural languages,
--there is a second important property determined by the volatility category,
--namely the visibility of any data changes that have been made by the SQL
--command that is calling the function. A VOLATILE function will see such
--changes, a STABLE or IMMUTABLE function will not. This behavior is
--implemented using the snapshotting behavior of MVCC (see Chapter 13): STABLE
--and IMMUTABLE functions use a snapshot established as of the start of the
--calling query, whereas VOLATILE functions obtain a fresh snapshot at the
--start of each query they execute.
--The solution is to mark these functions as volatile
create table parent
(
id uuid primary key default gen_random_uuid(),
count int2
);
create table child
(
id uuid primary key default gen_random_uuid(),
parent_id uuid references parent not null,
count int2
);
-- note that the function is marked stable and in written in sql
create or replace function _count(rec parent)
returns smallint
stable
language sql
as
$$
select sum(count)
from child
where parent_id = rec.id
$$;
insert into parent (id, count)
values ('8bcf0ee4-95ed-445f-808f-17b8194727ca', 1);
insert into child (id, parent_id, count)
values ('57738181-3d0f-45ad-96dd-3ba799b2d21d', '8bcf0ee4-95ed-445f-808f-17b8194727ca', 2),
('cb5993ff-e693-49cd-9114-a6510707e628', '8bcf0ee4-95ed-445f-808f-17b8194727ca', 3);
select jsonb_pretty(
graphql.resolve($$
query ParentQuery {
parentCollection {
edges {
node {
id
count
childCollection {
edges {
node {
count
}
}
}
}
}
}
}
$$)
);
jsonb_pretty
-----------------------------------------------------------------------
{ +
"data": { +
"parentCollection": { +
"edges": [ +
{ +
"node": { +
"id": "8bcf0ee4-95ed-445f-808f-17b8194727ca",+
"count": 5, +
"childCollection": { +
"edges": [ +
{ +
"node": { +
"count": 2 +
} +
}, +
{ +
"node": { +
"count": 3 +
} +
} +
] +
} +
} +
} +
] +
} +
} +
}
(1 row)

-- since _count is stable, the value returned in parent.count field will be stale
-- i.e. parent.count is still 5 instead of (3 + 5) = 8
select jsonb_pretty(
graphql.resolve($$
mutation ChildMutation {
updateChildCollection(
filter: { id: { eq: "57738181-3d0f-45ad-96dd-3ba799b2d21d" } }
set: { count: 5 }
) {
records {
id
count
parent {
id
count
}
}
}
}
$$)
);
jsonb_pretty
-----------------------------------------------------------------------
{ +
"data": { +
"updateChildCollection": { +
"records": [ +
{ +
"id": "57738181-3d0f-45ad-96dd-3ba799b2d21d", +
"count": 5, +
"parent": { +
"id": "8bcf0ee4-95ed-445f-808f-17b8194727ca",+
"count": 5 +
} +
} +
] +
} +
} +
}
(1 row)

-- note that the function is marked volatile
create or replace function _count(rec parent)
returns smallint
volatile
language sql
as
$$
select sum(count)
from child
where parent_id = rec.id
$$;
-- since _count is volatile, the value returned in parent.count field will be fresh
-- i.e. parent.count is correctly at (3 + 7) 10
select jsonb_pretty(
graphql.resolve($$
mutation ChildMutation {
updateChildCollection(
filter: { id: { eq: "57738181-3d0f-45ad-96dd-3ba799b2d21d" } }
set: { count: 7 }
) {
records {
id
count
parent {
id
count
}
}
}
}
$$)
);
jsonb_pretty
-----------------------------------------------------------------------
{ +
"data": { +
"updateChildCollection": { +
"records": [ +
{ +
"id": "57738181-3d0f-45ad-96dd-3ba799b2d21d", +
"count": 7, +
"parent": { +
"id": "8bcf0ee4-95ed-445f-808f-17b8194727ca",+
"count": 10 +
} +
} +
] +
} +
} +
}
(1 row)

rollback;
128 changes: 128 additions & 0 deletions test/sql/issue_337.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
begin;

-- A computed field function written in SQL and marked stable might return stale results.
-- Directly from the postgres docs(https://www.postgresql.org/docs/current/xfunc-volatility.html):

--For functions written in SQL or in any of the standard procedural languages,
--there is a second important property determined by the volatility category,
--namely the visibility of any data changes that have been made by the SQL
--command that is calling the function. A VOLATILE function will see such
--changes, a STABLE or IMMUTABLE function will not. This behavior is
--implemented using the snapshotting behavior of MVCC (see Chapter 13): STABLE
--and IMMUTABLE functions use a snapshot established as of the start of the
--calling query, whereas VOLATILE functions obtain a fresh snapshot at the
--start of each query they execute.

--The solution is to mark these functions as volatile

create table parent
(
id uuid primary key default gen_random_uuid(),
count int2
);

create table child
(
id uuid primary key default gen_random_uuid(),
parent_id uuid references parent not null,
count int2
);

-- note that the function is marked stable and in written in sql
create or replace function _count(rec parent)
returns smallint
stable
language sql
as
$$
select sum(count)
from child
where parent_id = rec.id
$$;

insert into parent (id, count)
values ('8bcf0ee4-95ed-445f-808f-17b8194727ca', 1);

insert into child (id, parent_id, count)
values ('57738181-3d0f-45ad-96dd-3ba799b2d21d', '8bcf0ee4-95ed-445f-808f-17b8194727ca', 2),
('cb5993ff-e693-49cd-9114-a6510707e628', '8bcf0ee4-95ed-445f-808f-17b8194727ca', 3);

select jsonb_pretty(
graphql.resolve($$
query ParentQuery {
parentCollection {
edges {
node {
id
count
childCollection {
edges {
node {
count
}
}
}
}
}
}
}
$$)
);

-- since _count is stable, the value returned in parent.count field will be stale
-- i.e. parent.count is still 5 instead of (3 + 5) = 8
select jsonb_pretty(
graphql.resolve($$
mutation ChildMutation {
updateChildCollection(
filter: { id: { eq: "57738181-3d0f-45ad-96dd-3ba799b2d21d" } }
set: { count: 5 }
) {
records {
id
count
parent {
id
count
}
}
}
}
$$)
);

-- note that the function is marked volatile
create or replace function _count(rec parent)
returns smallint
volatile
language sql
as
$$
select sum(count)
from child
where parent_id = rec.id
$$;

-- since _count is volatile, the value returned in parent.count field will be fresh
-- i.e. parent.count is correctly at (3 + 7) 10
select jsonb_pretty(
graphql.resolve($$
mutation ChildMutation {
updateChildCollection(
filter: { id: { eq: "57738181-3d0f-45ad-96dd-3ba799b2d21d" } }
set: { count: 7 }
) {
records {
id
count
parent {
id
count
}
}
}
}
$$)
);

rollback;

0 comments on commit c569b7c

Please sign in to comment.