feat: undefined json keys as defaults w/ Prefer:undefined-keys
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
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}]'::jsonb as json_data
|
||||
) pgrst_payload,
|
||||
LATERAL (
|
||||
SELECT CASE WHEN jsonb_typeof(pgrst_payload.json_data) = 'array' THEN pgrst_payload.json_data ELSE jsonb_build_array(pgrst_payload.json_data) END AS val
|
||||
) pgrst_uniform_json,
|
||||
LATERAL (
|
||||
SELECT jsonb_agg(jsonb_build_object('field-with_sep', 1) || elem) AS vals from jsonb_array_elements(pgrst_uniform_json.val) elem
|
||||
) pgrst_json_defs,
|
||||
LATERAL (
|
||||
SELECT * FROM jsonb_to_recordset (pgrst_json_defs.vals) AS _ ("arr_data" integer[], "field-with_sep" integer, "id" bigint, "name" text)
|
||||
) pgrst_body
|
||||
RETURNING "test"."complex_items".*;
|
||||
@@ -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}]'::jsonb as json_data
|
||||
) pgrst_payload,
|
||||
LATERAL (
|
||||
SELECT CASE WHEN jsonb_typeof(pgrst_payload.json_data) = 'array' THEN pgrst_payload.json_data ELSE jsonb_build_array(pgrst_payload.json_data) END AS val
|
||||
) pgrst_uniform_json,
|
||||
LATERAL (
|
||||
SELECT * FROM jsonb_to_recordset (pgrst_uniform_json.val) AS _ ("arr_data" integer[], "field-with_sep" integer, "id" bigint, "name" text)
|
||||
) pgrst_body
|
||||
RETURNING "test"."complex_items".*
|
||||
@@ -3,6 +3,11 @@
|
||||
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
|
||||
postgrest-with-postgresql-15 -f test/pgbench/fixtures.sql pgbench -n -T 10 -f test/pgbench/1567/old.sql
|
||||
|
||||
postgrest-with-postgresql-15 -f test/pgbench/fixtures.sql pgbench -n -T 10 -f test/pgbench/1567/new.sql
|
||||
```
|
||||
|
||||
## Directory structure
|
||||
|
||||
The directory name is the issue number on github.
|
||||
|
||||
@@ -11,8 +11,9 @@ import Test.Hspec.Wai
|
||||
import Test.Hspec.Wai.JSON
|
||||
import Text.Heredoc
|
||||
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
|
||||
pgVersion112, pgVersion130)
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||
pgVersion110, pgVersion112,
|
||||
pgVersion130)
|
||||
|
||||
import Protolude hiding (get)
|
||||
import SpecHelper
|
||||
@@ -448,14 +449,64 @@ spec actualPgVersion = do
|
||||
{"id": 204, "body": "yyy"},
|
||||
333,
|
||||
"asdf",
|
||||
{"id": 205, "body": "zzz"}]|] `shouldRespondWith`
|
||||
[json|{
|
||||
"code": "22023",
|
||||
"details": null,
|
||||
"hint": null,
|
||||
"message": "argument of json_to_recordset must be an array of objects"}|]
|
||||
{ matchStatus = 400
|
||||
, matchHeaders = []
|
||||
{"id": 205, "body": "zzz"}]|] `shouldRespondWith` 400
|
||||
|
||||
context "apply defaults on undefined keys" $ do
|
||||
-- inserting the array fails on pg 9.6, but the feature should work normally
|
||||
when (actualPgVersion >= pgVersion100) $
|
||||
it "inserts table default values(field-with_sep) when json keys are undefined" $
|
||||
request methodPost "/complex_items?columns=id,name,field-with_sep,arr_data" [("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")]
|
||||
[json|[
|
||||
{"id": 4, "name": "Vier"},
|
||||
{"id": 5, "name": "Funf", "arr_data": null},
|
||||
{"id": 6, "name": "Sechs", "field-with_sep": 6, "arr_data": "{1,2,3}"}
|
||||
]|]
|
||||
`shouldRespondWith`
|
||||
[json|[
|
||||
{"id": 4, "name": "Vier", "field-with_sep": 1, "settings":null,"arr_data":null},
|
||||
{"id": 5, "name": "Funf", "field-with_sep": 1, "settings":null,"arr_data":null},
|
||||
{"id": 6, "name": "Sechs", "field-with_sep": 6, "settings":null,"arr_data":[1,2,3]}
|
||||
]|]
|
||||
{ matchStatus = 201
|
||||
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"]
|
||||
}
|
||||
|
||||
it "inserts view default values(field-with_sep) when json keys are undefined" $
|
||||
request methodPost "/complex_items_view?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")]
|
||||
[json|[
|
||||
{"id": 7, "name": "Sieben"},
|
||||
{"id": 8}
|
||||
]|]
|
||||
`shouldRespondWith`
|
||||
[json|[
|
||||
{"id": 7, "name": "Sieben", "field-with_sep": 1, "settings":null,"arr_data":null},
|
||||
{"id": 8, "name": "Default", "field-with_sep": 1, "settings":null,"arr_data":null}
|
||||
]|]
|
||||
{ matchStatus = 201
|
||||
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"]
|
||||
}
|
||||
|
||||
it "doesn't insert json duplicate keys(since it uses jsonb)" $
|
||||
request methodPost "/tbl_w_json?columns=id,data" [("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")]
|
||||
[json| { "data": { "a": 1, "a": 2 }, "id": 3 } |]
|
||||
`shouldRespondWith`
|
||||
[json| [ { "data": { "a": 2 }, "id": 3 } ] |]
|
||||
{ matchStatus = 201
|
||||
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"]
|
||||
}
|
||||
|
||||
it "inserts json that has duplicate keys" $ do
|
||||
request methodPost "/tbl_w_json" [("Prefer", "return=representation")]
|
||||
[json| { "data": { "a": 1, "a": 2 }, "id": 3 } |]
|
||||
`shouldRespondWith`
|
||||
[json| [ { "data": { "a": 1, "a": 2 }, "id": 3 } ] |]
|
||||
{ matchStatus = 201
|
||||
}
|
||||
request methodPost "/tbl_w_json?columns=id,data" [("Prefer", "return=representation")]
|
||||
[json| { "data": { "a": 1, "a": 2 }, "id": 3 } |]
|
||||
`shouldRespondWith`
|
||||
[json| [ { "data": { "a": 1, "a": 2 }, "id": 3 } ] |]
|
||||
{ matchStatus = 201
|
||||
}
|
||||
|
||||
context "with unicode values" $ do
|
||||
|
||||
@@ -327,11 +327,11 @@ spec actualPgVersion = do
|
||||
describe "Shaping response with select parameter" $ do
|
||||
it "selectStar works in absense of parameter" $
|
||||
get "/complex_items?id=eq.3" `shouldRespondWith`
|
||||
[json|[{"id":3,"name":"Three","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}]|]
|
||||
[json|[{"id":3,"name":"Three","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":3}]|]
|
||||
|
||||
it "dash `-` in column names is accepted" $
|
||||
get "/complex_items?id=eq.3&select=id,field-with_sep" `shouldRespondWith`
|
||||
[json|[{"id":3,"field-with_sep":1}]|]
|
||||
[json|[{"id":3,"field-with_sep":3}]|]
|
||||
|
||||
it "one simple column" $
|
||||
get "/complex_items?select=id" `shouldRespondWith`
|
||||
|
||||
@@ -330,6 +330,57 @@ spec = do
|
||||
, matchHeaders = []
|
||||
}
|
||||
|
||||
context "apply defaults on undefined keys" $ do
|
||||
it "updates table using default values(field-with_sep) when json keys are undefined" $ do
|
||||
request methodPatch "/complex_items?id=eq.3&columns=name,field-with_sep"
|
||||
[("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")]
|
||||
[json|{"name": "Tres"}|]
|
||||
`shouldRespondWith`
|
||||
[json|[
|
||||
{"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"]
|
||||
}
|
||||
|
||||
it "updates with limit/offset using table default values(field-with_sep) when json keys are undefined" $ do
|
||||
request methodPatch "/complex_items?select=id,name&columns=name,field-with_sep&limit=1&offset=2&order=id"
|
||||
[("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")]
|
||||
[json|{"name": "Tres"}|]
|
||||
`shouldRespondWith`
|
||||
[json|[
|
||||
{"id":3,"name":"Tres"}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"]
|
||||
}
|
||||
|
||||
it "updates table default values(field-with_sep) when json keys are undefined" $ do
|
||||
request methodPatch "/complex_items?id=eq.3&columns=name,field-with_sep"
|
||||
[("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")]
|
||||
[json|{"name": "Tres"}|]
|
||||
`shouldRespondWith`
|
||||
[json|[
|
||||
{"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"]
|
||||
}
|
||||
|
||||
it "updates view default values(field-with_sep) when json keys are undefined" $
|
||||
request methodPatch "/complex_items_view?id=eq.3&columns=arr_data,name"
|
||||
[("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")]
|
||||
[json|
|
||||
{"arr_data":null}
|
||||
|]
|
||||
`shouldRespondWith`
|
||||
[json|[
|
||||
{"id":3,"name":"Default","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":null,"field-with_sep":3}
|
||||
]|]
|
||||
{ matchStatus = 200
|
||||
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"]
|
||||
}
|
||||
|
||||
context "tables with self reference foreign keys" $ do
|
||||
it "embeds children after update" $
|
||||
request methodPatch "/web_content?id=eq.0&select=id,name,web_content(name)"
|
||||
|
||||
Vendored
+1
-1
@@ -168,7 +168,7 @@ INSERT INTO touched_files VALUES
|
||||
TRUNCATE TABLE complex_items CASCADE;
|
||||
INSERT INTO complex_items VALUES (1, 'One', '{"foo":{"int":1,"bar":"baz"}}', '{1}');
|
||||
INSERT INTO complex_items VALUES (2, 'Two', '{"foo":{"int":1,"bar":"baz"}}', '{1,2}');
|
||||
INSERT INTO complex_items VALUES (3, 'Three', '{"foo":{"int":1,"bar":"baz"}}', '{1,2,3}');
|
||||
INSERT INTO complex_items VALUES (3, 'Three', '{"foo":{"int":1,"bar":"baz"}}', '{1,2,3}', 3);
|
||||
|
||||
|
||||
--
|
||||
|
||||
Vendored
+10
@@ -3100,3 +3100,13 @@ create view test.alpha_projects as
|
||||
create view test.zeta_projects as
|
||||
select c.id, p.name as pro_name, c.name as cli_name
|
||||
from projects p join clients c on p.client_id = c.id;
|
||||
|
||||
CREATE VIEW test.complex_items_view AS
|
||||
SELECT * FROM test.complex_items;
|
||||
|
||||
ALTER VIEW test.complex_items_view ALTER COLUMN name SET DEFAULT 'Default';
|
||||
|
||||
create table test.tbl_w_json(
|
||||
id int,
|
||||
data json
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user