From d3557915bcefac1f538bdeb94fcf4762e410ed0e Mon Sep 17 00:00:00 2001 From: Raminder Singh Date: Mon, 30 Oct 2023 12:07:04 +0530 Subject: [PATCH] add test case and docs for issue #337 --- docs/computed_fields.md | 5 + test/expected/issue_337.out | 187 ++++++++++++++++++++++++++++++++++++ test/sql/issue_337.sql | 128 ++++++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 test/expected/issue_337.out create mode 100644 test/sql/issue_337.sql diff --git a/docs/computed_fields.md b/docs/computed_fields.md index 76c825bc..bb23167b 100644 --- a/docs/computed_fields.md +++ b/docs/computed_fields.md @@ -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 diff --git a/test/expected/issue_337.out b/test/expected/issue_337.out new file mode 100644 index 00000000..5b818a00 --- /dev/null +++ b/test/expected/issue_337.out @@ -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; diff --git a/test/sql/issue_337.sql b/test/sql/issue_337.sql new file mode 100644 index 00000000..2d646758 --- /dev/null +++ b/test/sql/issue_337.sql @@ -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;