perf: use LATERAL instead of CTE for json body

* add pgbench test for INSERT and RPC
* use LATERAL for RPC and INSERT
* add tests for inlining
This commit is contained in:
steve-chavez
2023-02-25 15:01:06 -05:00
committed by Steve Chavez
parent 253f2bf537
commit 0d5d209bfb
11 changed files with 144 additions and 97 deletions
+20
View File
@@ -0,0 +1,20 @@
WITH pgrst_source AS (
SELECT pgrst_call.*
FROM (
SELECT '{"id": 4}'::json as json_data
) pgrst_payload,
LATERAL (
SELECT CASE WHEN json_typeof(pgrst_payload.json_data) = 'array' THEN pgrst_payload.json_data ELSE json_build_array(pgrst_payload.json_data) END AS val
) pgrst_uniform_json,
LATERAL (
SELECT * FROM json_to_recordset(pgrst_uniform_json.val) AS _("id" integer) LIMIT 1
) pgrst_body,
LATERAL "test"."get_projects_below"("id" := pgrst_body.id) pgrst_call
)
SELECT
null::bigint AS total_result_set,
pg_catalog.count(_postgrest_t) AS page_total,
coalesce(json_agg(_postgrest_t), '[]')::character varying AS body,
nullif(current_setting('response.headers', true), '') AS response_headers,
nullif(current_setting('response.status', true), '') AS response_status
FROM (SELECT "projects".* FROM "pgrst_source" AS "projects") _postgrest_t;
+15
View File
@@ -0,0 +1,15 @@
WITH pgrst_source AS (
WITH
pgrst_payload AS (SELECT '{"id": 4}'::json AS json_data),
pgrst_body AS ( SELECT CASE WHEN json_typeof(json_data) = 'array' THEN json_data ELSE json_build_array(json_data) END AS val FROM pgrst_payload),
pgrst_args AS ( SELECT * FROM json_to_recordset((SELECT val FROM pgrst_body)) AS _("id" integer) )
SELECT "get_projects_below".*
FROM "test"."get_projects_below"("id" := (SELECT "id" FROM pgrst_args LIMIT 1))
)
SELECT
null::bigint AS total_result_set,
pg_catalog.count(_postgrest_t) AS page_total,
coalesce(json_agg(_postgrest_t), '[]')::character varying AS body,
nullif(current_setting('response.headers', true), '') AS response_headers,
nullif(current_setting('response.status', true), '') AS response_status
FROM (SELECT "projects".* FROM "pgrst_source" AS "projects") _postgrest_t;
+12
View File
@@ -0,0 +1,12 @@
INSERT INTO "test"."complex_items"("arr_data", "field-with_sep", "id", "name")
SELECT pgrst_body."arr_data", pgrst_body."field-with_sep", pgrst_body."id", pgrst_body."name"
FROM (
SELECT '[{"id": 4, "name": "Vier"}, {"id": 5, "name": "Funf", "arr_data": null}, {"id": 6, "name": "Sechs", "arr_data": [1, 2, 3], "field-with_sep": 6}]'::json as json_data
) pgrst_payload,
LATERAL (
SELECT CASE WHEN json_typeof(pgrst_payload.json_data) = 'array' THEN pgrst_payload.json_data ELSE json_build_array(pgrst_payload.json_data) END AS val
) pgrst_uniform_json,
LATERAL (
SELECT * FROM json_to_recordset (pgrst_uniform_json.val) AS _ ("arr_data" integer[], "field-with_sep" integer, "id" bigint, "name" text)
) pgrst_body
RETURNING "test"."complex_items".*
+7
View File
@@ -0,0 +1,7 @@
WITH
pgrst_payload AS (SELECT '[{"id": 4, "name": "Vier"}, {"id": 5, "name": "Funf", "arr_data": null}, {"id": 6, "name": "Sechs", "arr_data": [1, 2, 3], "field-with_sep": 6}]'::json AS json_data),
pgrst_body AS ( SELECT CASE WHEN json_typeof(json_data) = 'array' THEN json_data ELSE json_build_array(json_data) END AS val FROM pgrst_payload)
INSERT INTO "test"."complex_items"("arr_data", "field-with_sep", "id", "name")
SELECT "arr_data", "field-with_sep", "id", "name"
FROM json_to_recordset ((SELECT val FROM pgrst_body)) AS _ ("arr_data" integer[], "field-with_sep" integer, "id" bigint, "name" text)
RETURNING "test"."complex_items".*
+8
View File
@@ -0,0 +1,8 @@
## pgbench tests
Can be used as:
```
postgrest-with-postgresql-15 -f test/pgbench/fixtures.sql pgbench -n -T 10 -f test/pgbench/2677/old.sql
postgrest-with-postgresql-15 -f test/pgbench/fixtures.sql pgbench -n -T 10 -f test/pgbench/2677/new.sql
```
+7
View File
@@ -0,0 +1,7 @@
\ir ../spec/fixtures/load.sql
ALTER TABLE test.complex_items
DROP CONSTRAINT complex_items_pkey;
ALTER TABLE test.complex_items
ALTER COLUMN "field-with_sep" DROP NOT NULL;
+29 -13
View File
@@ -8,7 +8,9 @@ import Network.Wai.Test (SResponse (..))
import Data.Aeson.Lens
import Data.Aeson.QQ
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as T
import Network.HTTP.Types
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
@@ -166,10 +168,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; charset=utf-8")
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
totalCost `shouldBe`
if actualPgVersion > pgVersion120
then 3.28
else 3.33
totalCost `shouldBe` 3.27
it "outputs the total cost for an update" $ do
r <- request methodPatch "/projects?id=eq.3"
@@ -182,10 +181,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; charset=utf-8")
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
totalCost `shouldBe`
if actualPgVersion > pgVersion120
then 12.45
else 12.5
totalCost `shouldBe` 12.45
it "outputs the total cost for a delete" $ do
r <- request methodDelete "/projects?id=in.(1,2,3)"
@@ -212,10 +208,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; charset=utf-8")
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
totalCost `shouldBe`
if actualPgVersion >= pgVersion120
then 1.3
else 1.35
totalCost `shouldBe` 1.29
it "outputs the plan for application/vnd.pgrst.object" $ do
r <- request methodDelete "/projects?id=eq.6"
@@ -338,7 +331,7 @@ spec actualPgVersion = do
r <- request methodGet "/rpc/get_projects_below?id=3"
[planHdr] ""
liftIO $ planCost r `shouldSatisfy` (< 36.4)
liftIO $ planCost r `shouldSatisfy` (< 45.4)
it "should not exceed cost when calling setof composite proc with empty params" $ do
r <- request methodGet "/rpc/getallprojects"
@@ -367,6 +360,29 @@ spec actualPgVersion = do
liftIO $ planCost r `shouldSatisfy` (< 5.85)
context "function inlining" $ do
it "should inline a zero argument function" $ do
r <- request methodGet "/rpc/getallusers?id=eq.1"
[(hAccept, "application/vnd.pgrst.plan")] ""
let resBody = simpleBody r
liftIO $ do
resBody `shouldSatisfy` (
if actualPgVersion >= pgVersion120
then (\t -> T.isInfixOf "Index Scan using users_pkey on users" (decodeUtf8 $ BS.toStrict t))
else (\t -> T.isInfixOf "Seq Scan on users" (decodeUtf8 $ BS.toStrict t)))
it "should inline a function with arguments" $ do
r <- request methodGet "/rpc/getitemrange?min=10&max=15"
[(hAccept, "application/vnd.pgrst.plan")] ""
let resBody = simpleBody r
liftIO $ do
-- a Seq Scan ensures the function is inlined as the plan uses the underlying table
resBody `shouldSatisfy` (\t -> T.isInfixOf "Seq Scan on items" (decodeUtf8 $ BS.toStrict t))
disabledSpec :: SpecWith ((), Application)
disabledSpec =
it "doesn't work if db-plan-enabled=false(the default)" $ do
+3 -2
View File
@@ -15,7 +15,7 @@ import Text.Heredoc
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
pgVersion109, pgVersion110,
pgVersion112, pgVersion114,
pgVersion140)
pgVersion130, pgVersion140)
import Protolude hiding (get)
import SpecHelper
@@ -309,7 +309,8 @@ spec actualPgVersion =
]|]
{ matchHeaders = [matchContentTypeJson] }
when (actualPgVersion >= pgVersion110) $
-- https://github.com/PostgREST/postgrest/pull/2677#issuecomment-1444976849
when (actualPgVersion >= pgVersion130) $
it "can embed if rpc returns domain of table type" $ do
post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)"
[json| { "id": 1} |]