diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ab69d67..e6dc7da03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Fixed +- Prevent duplicate call to stored procs (regression) - @begriffs - Allow SQL functions to generate registered JWT claims - @begriffs - Terminate gracefully on SIGTERM (for use in Docker) - @recmo - Relation detection fix for views that depend on multiple tables - @ruslantalpa diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index 732e749b1..87487ba32 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -208,18 +208,22 @@ callProc :: QualifiedIdentifier -> JSON.Object -> NonnegRange -> Bool -> H.Query callProc qi params range countTotal = H.statement sql HE.unit decodeProc True where - sql = [qc| SELECT - {countQuery} as countTotal, - {countResult} as countResult, + sql = [qc| + WITH t AS (select * {_callSql}) + SELECT + {_countExpr} as countTotal, + pg_catalog.count(1) as countResult, array_to_json( - coalesce(array_agg(row_to_json(t)), '\{}') + coalesce(array_agg(row_to_json(r)), '\{}') )::character varying - from (select * {_callSql} {limitF range}) t |] + FROM (select * from t {limitF range}) r; + |] _args = intercalate "," $ map _assignment (HM.toList params) _assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v _callSql = [qc| from {fromQi qi}({_args}) |] :: BS.ByteString - countQuery = if countTotal then [qc| (select pg_catalog.count(1) {_callSql} c) |] else "null::bigint" :: BS.ByteString - countResult = "pg_catalog.count(t)" :: BS.ByteString + _countExpr = if countTotal + then "(select pg_catalog.count(1) from t)" + else "null::bigint" :: BS.ByteString decodeProc = HD.maybeRow procRow procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8 <*> HD.value HD.json diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index f374e53cd..7e43f465d 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -422,6 +422,12 @@ spec = do it "GET with 405 on known procs" $ get "/rpc/sayhello" `shouldRespondWith` 405 + it "executes the proc exactly once per request" $ do + post "/rpc/callcounter" [json| {} |] `shouldRespondWith` + [json| [{"callcounter":1}] |] + post "/rpc/callcounter" [json| {} |] `shouldRespondWith` + [json| [{"callcounter":2}] |] + describe "weird requests" $ do it "can query as normal" $ do get "/Escap3e;" `shouldRespondWith` diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index cb5a62495..884d2de00 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -43,6 +43,7 @@ GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous; GRANT USAGE ON SEQUENCE auto_incrementing_pk_id_seq , items_id_seq + , callcounter_count TO postgrest_test_anonymous; -- Privileges for non anonymous users diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 033576dc8..af949d004 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -261,6 +261,18 @@ CREATE FUNCTION sayhello(name text) RETURNS text $_$; +-- +-- Name: callcounter(); Type: FUNCTION; Schema: test; Owner: - +-- + +CREATE SEQUENCE callcounter_count START 1; + +CREATE FUNCTION callcounter() RETURNS bigint + LANGUAGE sql + AS $_$ + SELECT nextval('test.callcounter_count'); +$_$; + -- -- Name: test_empty_rowset(); Type: FUNCTION; Schema: test; Owner: - --