From 7629eff51d7df69bfa7dedfabd5beea18cb7a8cc Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Mon, 27 Mar 2023 23:47:38 +0800 Subject: [PATCH] undefined-keys=apply-defaults to missing=default --- CHANGELOG.md | 2 +- src/PostgREST/ApiRequest/Preferences.hs | 32 ++++++++++++------------- src/PostgREST/Plan.hs | 2 +- src/PostgREST/Response.hs | 4 ++-- test/spec/Feature/Query/InsertSpec.hs | 14 +++++------ test/spec/Feature/Query/UpdateSpec.hs | 18 +++++++------- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f68f8d2f52..f9fc86d106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). + When the client sends the request header specified in the config it will be included in the response headers. - #2694, Make `db-root-spec` stable. - @steve-chavez + This can be used to override the OpenAPI spec with a custom database function - - #1567, On bulk inserts with `?columns`, undefined json keys can get columns' DEFAULT values by using the `Prefer: undefined-keys=apply-defaults` header - @steve-chavez + - #1567, On bulk inserts, missing values can get the column DEFAULT by using the `Prefer: missing=default` header - @steve-chavez ### Fixed diff --git a/src/PostgREST/ApiRequest/Preferences.hs b/src/PostgREST/ApiRequest/Preferences.hs index c70fddfdb4..c21bdb55b3 100644 --- a/src/PostgREST/ApiRequest/Preferences.hs +++ b/src/PostgREST/ApiRequest/Preferences.hs @@ -9,7 +9,7 @@ module PostgREST.ApiRequest.Preferences ( Preferences(..) , PreferCount(..) - , PreferUndefinedKeys(..) + , PreferMissing(..) , PreferParameters(..) , PreferRepresentation(..) , PreferResolution(..) @@ -34,7 +34,7 @@ import Protolude -- >>> deriving instance Show PreferParameters -- >>> deriving instance Show PreferCount -- >>> deriving instance Show PreferTransaction --- >>> deriving instance Show PreferUndefinedKeys +-- >>> deriving instance Show PreferMissing -- >>> deriving instance Show Preferences -- | Preferences recognized by the application. @@ -45,7 +45,7 @@ data Preferences , preferParameters :: Maybe PreferParameters , preferCount :: Maybe PreferCount , preferTransaction :: Maybe PreferTransaction - , preferUndefinedKeys :: Maybe PreferUndefinedKeys + , preferMissing :: Maybe PreferMissing } -- | @@ -60,19 +60,19 @@ data Preferences -- , preferParameters = Nothing -- , preferCount = Just ExactCount -- , preferTransaction = Nothing --- , preferUndefinedKeys = Nothing +-- , preferMissing = Nothing -- } -- -- Multiple headers can also be used: -- --- >>> pPrint $ fromHeaders [("Prefer", "resolution=ignore-duplicates"), ("Prefer", "count=exact")] +-- >>> pPrint $ fromHeaders [("Prefer", "resolution=ignore-duplicates"), ("Prefer", "count=exact"), ("Prefer", "missing=null")] -- Preferences -- { preferResolution = Just IgnoreDuplicates -- , preferRepresentation = None -- , preferParameters = Nothing -- , preferCount = Just ExactCount -- , preferTransaction = Nothing --- , preferUndefinedKeys = Nothing +-- , preferMissing = Just ApplyNulls -- } -- -- If a preference is set more than once, only the first is used: @@ -97,14 +97,14 @@ data Preferences -- -- Preferences can be separated by arbitrary amounts of space, lower-case header is also recognized: -- --- >>> pPrint $ fromHeaders [("prefer", "count=exact, tx=commit ,return=representation , undefined-keys=apply-defaults")] +-- >>> pPrint $ fromHeaders [("prefer", "count=exact, tx=commit ,return=representation , missing=default")] -- Preferences -- { preferResolution = Nothing -- , preferRepresentation = Full -- , preferParameters = Nothing -- , preferCount = Just ExactCount -- , preferTransaction = Just Commit --- , preferUndefinedKeys = Just ApplyDefaults +-- , preferMissing = Just ApplyDefaults -- } -- fromHeaders :: [HTTP.Header] -> Preferences @@ -115,7 +115,7 @@ fromHeaders headers = , preferParameters = parsePrefs [SingleObject, MultipleObjects] , preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount] , preferTransaction = parsePrefs [Commit, Rollback] - , preferUndefinedKeys = parsePrefs [ApplyDefaults, IgnoreDefaults] + , preferMissing = parsePrefs [ApplyDefaults, ApplyNulls] } where prefHeaders = filter ((==) HTTP.hPrefer . fst) headers @@ -215,13 +215,13 @@ instance ToAppliedHeader PreferTransaction -- | -- How to handle the insertion/update when the keys specified in ?columns are not present -- in the json body. -data PreferUndefinedKeys - = ApplyDefaults -- ^ Use the default column value for the unspecified keys. - | IgnoreDefaults -- ^ Inserts: null values / Updates: the keys are not SET to any value +data PreferMissing + = ApplyDefaults -- ^ Use the default column value for missing values. + | ApplyNulls -- ^ Use the null value for missing values. deriving Eq -instance ToHeaderValue PreferUndefinedKeys where - toHeaderValue ApplyDefaults = "undefined-keys=apply-defaults" - toHeaderValue IgnoreDefaults = "undefined-keys=ignore-defaults" +instance ToHeaderValue PreferMissing where + toHeaderValue ApplyDefaults = "missing=default" + toHeaderValue ApplyNulls = "missing=null" -instance ToAppliedHeader PreferUndefinedKeys +instance ToAppliedHeader PreferMissing diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index e52fc72a59..a1ae8753be 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -533,7 +533,7 @@ mutatePlan mutation qi ApiRequest{iPreferences=preferences, ..} sCache readReq = body = payRaw <$> iPayload -- the body is assumed to be json at this stage(ApiRequest validates) tbl = HM.lookup qi $ dbTables sCache typedColumnsOrError = resolveOrError tbl `traverse` S.toList iColumns - applyDefaults = preferences.preferUndefinedKeys == Just ApplyDefaults + applyDefaults = preferences.preferMissing == Just ApplyDefaults resolveOrError :: Maybe Table -> FieldName -> Either ApiRequestError TypedField resolveOrError Nothing _ = Left NotFound diff --git a/src/PostgREST/Response.hs b/src/PostgREST/Response.hs index 57f3ded045..398259bf5a 100644 --- a/src/PostgREST/Response.hs +++ b/src/PostgREST/Response.hs @@ -109,7 +109,7 @@ createResponse QualifiedIdentifier{..} MutateReadPlan{mrMutatePlan} ctxApiReques Nothing else toAppliedHeader <$> preferResolution - , toAppliedHeader <$> preferUndefinedKeys + , toAppliedHeader <$> preferMissing ] if preferRepresentation == Full then @@ -128,7 +128,7 @@ updateResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} resultSet contentRangeHeader = Just . RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $ if shouldCount preferCount then Just rsQueryTotal else Nothing - headers = catMaybes [contentRangeHeader, toAppliedHeader <$> preferUndefinedKeys] + headers = catMaybes [contentRangeHeader, toAppliedHeader <$> preferMissing] if preferRepresentation == Full then response HTTP.status200 diff --git a/test/spec/Feature/Query/InsertSpec.hs b/test/spec/Feature/Query/InsertSpec.hs index f1f17f0160..598990beb1 100644 --- a/test/spec/Feature/Query/InsertSpec.hs +++ b/test/spec/Feature/Query/InsertSpec.hs @@ -451,11 +451,11 @@ spec actualPgVersion = do "asdf", {"id": 205, "body": "zzz"}]|] `shouldRespondWith` 400 - context "apply defaults on undefined keys" $ do + context "apply defaults on missing values" $ 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")] + request methodPost "/complex_items?columns=id,name,field-with_sep,arr_data" [("Prefer", "return=representation"), ("Prefer", "missing=default")] [json|[ {"id": 4, "name": "Vier"}, {"id": 5, "name": "Funf", "arr_data": null}, @@ -468,11 +468,11 @@ spec actualPgVersion = do {"id": 6, "name": "Sechs", "field-with_sep": 6, "settings":null,"arr_data":[1,2,3]} ]|] { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] + , matchHeaders = ["Preference-Applied" <:> "missing=default"] } 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")] + request methodPost "/complex_items_view?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "missing=default")] [json|[ {"id": 7, "name": "Sieben"}, {"id": 8} @@ -483,16 +483,16 @@ spec actualPgVersion = do {"id": 8, "name": "Default", "field-with_sep": 1, "settings":null,"arr_data":null} ]|] { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] + , matchHeaders = ["Preference-Applied" <:> "missing=default"] } 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")] + request methodPost "/tbl_w_json?columns=id,data" [("Prefer", "return=representation"), ("Prefer", "missing=default")] [json| { "data": { "a": 1, "a": 2 }, "id": 3 } |] `shouldRespondWith` [json| [ { "data": { "a": 2 }, "id": 3 } ] |] { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] + , matchHeaders = ["Preference-Applied" <:> "missing=default"] } it "inserts json that has duplicate keys" $ do diff --git a/test/spec/Feature/Query/UpdateSpec.hs b/test/spec/Feature/Query/UpdateSpec.hs index 4274ccf668..205a36029d 100644 --- a/test/spec/Feature/Query/UpdateSpec.hs +++ b/test/spec/Feature/Query/UpdateSpec.hs @@ -330,46 +330,46 @@ spec = do , matchHeaders = [] } - context "apply defaults on undefined keys" $ do + context "apply defaults on missing values" $ 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")] + [("Prefer", "return=representation"), ("Prefer", "missing=default")] [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"] + , matchHeaders = ["Preference-Applied" <:> "missing=default"] } 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")] + [("Prefer", "return=representation"), ("Prefer", "missing=default")] [json|{"name": "Tres"}|] `shouldRespondWith` [json|[ {"id":3,"name":"Tres"} ]|] { matchStatus = 200 - , matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] + , matchHeaders = ["Preference-Applied" <:> "missing=default"] } 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")] + [("Prefer", "return=representation"), ("Prefer", "missing=default")] [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"] + , matchHeaders = ["Preference-Applied" <:> "missing=default"] } 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")] + [("Prefer", "return=representation"), ("Prefer", "missing=default")] [json| {"arr_data":null} |] @@ -378,7 +378,7 @@ spec = do {"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"] + , matchHeaders = ["Preference-Applied" <:> "missing=default"] } context "tables with self reference foreign keys" $ do