undefined-keys=apply-defaults to missing=default

This commit is contained in:
steve-chavez
2023-03-29 05:33:38 -05:00
committed by Steve Chavez
parent 46fd856fc6
commit 7629eff51d
6 changed files with 36 additions and 36 deletions
+1 -1
View File
@@ -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. + 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 - #2694, Make `db-root-spec` stable. - @steve-chavez
+ This can be used to override the OpenAPI spec with a custom database function + 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 ### Fixed
+16 -16
View File
@@ -9,7 +9,7 @@
module PostgREST.ApiRequest.Preferences module PostgREST.ApiRequest.Preferences
( Preferences(..) ( Preferences(..)
, PreferCount(..) , PreferCount(..)
, PreferUndefinedKeys(..) , PreferMissing(..)
, PreferParameters(..) , PreferParameters(..)
, PreferRepresentation(..) , PreferRepresentation(..)
, PreferResolution(..) , PreferResolution(..)
@@ -34,7 +34,7 @@ import Protolude
-- >>> deriving instance Show PreferParameters -- >>> deriving instance Show PreferParameters
-- >>> deriving instance Show PreferCount -- >>> deriving instance Show PreferCount
-- >>> deriving instance Show PreferTransaction -- >>> deriving instance Show PreferTransaction
-- >>> deriving instance Show PreferUndefinedKeys -- >>> deriving instance Show PreferMissing
-- >>> deriving instance Show Preferences -- >>> deriving instance Show Preferences
-- | Preferences recognized by the application. -- | Preferences recognized by the application.
@@ -45,7 +45,7 @@ data Preferences
, preferParameters :: Maybe PreferParameters , preferParameters :: Maybe PreferParameters
, preferCount :: Maybe PreferCount , preferCount :: Maybe PreferCount
, preferTransaction :: Maybe PreferTransaction , preferTransaction :: Maybe PreferTransaction
, preferUndefinedKeys :: Maybe PreferUndefinedKeys , preferMissing :: Maybe PreferMissing
} }
-- | -- |
@@ -60,19 +60,19 @@ data Preferences
-- , preferParameters = Nothing -- , preferParameters = Nothing
-- , preferCount = Just ExactCount -- , preferCount = Just ExactCount
-- , preferTransaction = Nothing -- , preferTransaction = Nothing
-- , preferUndefinedKeys = Nothing -- , preferMissing = Nothing
-- } -- }
-- --
-- Multiple headers can also be used: -- 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 -- Preferences
-- { preferResolution = Just IgnoreDuplicates -- { preferResolution = Just IgnoreDuplicates
-- , preferRepresentation = None -- , preferRepresentation = None
-- , preferParameters = Nothing -- , preferParameters = Nothing
-- , preferCount = Just ExactCount -- , preferCount = Just ExactCount
-- , preferTransaction = Nothing -- , preferTransaction = Nothing
-- , preferUndefinedKeys = Nothing -- , preferMissing = Just ApplyNulls
-- } -- }
-- --
-- If a preference is set more than once, only the first is used: -- 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: -- 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 -- Preferences
-- { preferResolution = Nothing -- { preferResolution = Nothing
-- , preferRepresentation = Full -- , preferRepresentation = Full
-- , preferParameters = Nothing -- , preferParameters = Nothing
-- , preferCount = Just ExactCount -- , preferCount = Just ExactCount
-- , preferTransaction = Just Commit -- , preferTransaction = Just Commit
-- , preferUndefinedKeys = Just ApplyDefaults -- , preferMissing = Just ApplyDefaults
-- } -- }
-- --
fromHeaders :: [HTTP.Header] -> Preferences fromHeaders :: [HTTP.Header] -> Preferences
@@ -115,7 +115,7 @@ fromHeaders headers =
, preferParameters = parsePrefs [SingleObject, MultipleObjects] , preferParameters = parsePrefs [SingleObject, MultipleObjects]
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount] , preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
, preferTransaction = parsePrefs [Commit, Rollback] , preferTransaction = parsePrefs [Commit, Rollback]
, preferUndefinedKeys = parsePrefs [ApplyDefaults, IgnoreDefaults] , preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
} }
where where
prefHeaders = filter ((==) HTTP.hPrefer . fst) headers 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 -- How to handle the insertion/update when the keys specified in ?columns are not present
-- in the json body. -- in the json body.
data PreferUndefinedKeys data PreferMissing
= ApplyDefaults -- ^ Use the default column value for the unspecified keys. = ApplyDefaults -- ^ Use the default column value for missing values.
| IgnoreDefaults -- ^ Inserts: null values / Updates: the keys are not SET to any value | ApplyNulls -- ^ Use the null value for missing values.
deriving Eq deriving Eq
instance ToHeaderValue PreferUndefinedKeys where instance ToHeaderValue PreferMissing where
toHeaderValue ApplyDefaults = "undefined-keys=apply-defaults" toHeaderValue ApplyDefaults = "missing=default"
toHeaderValue IgnoreDefaults = "undefined-keys=ignore-defaults" toHeaderValue ApplyNulls = "missing=null"
instance ToAppliedHeader PreferUndefinedKeys instance ToAppliedHeader PreferMissing
+1 -1
View File
@@ -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) body = payRaw <$> iPayload -- the body is assumed to be json at this stage(ApiRequest validates)
tbl = HM.lookup qi $ dbTables sCache tbl = HM.lookup qi $ dbTables sCache
typedColumnsOrError = resolveOrError tbl `traverse` S.toList iColumns 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 :: Maybe Table -> FieldName -> Either ApiRequestError TypedField
resolveOrError Nothing _ = Left NotFound resolveOrError Nothing _ = Left NotFound
+2 -2
View File
@@ -109,7 +109,7 @@ createResponse QualifiedIdentifier{..} MutateReadPlan{mrMutatePlan} ctxApiReques
Nothing Nothing
else else
toAppliedHeader <$> preferResolution toAppliedHeader <$> preferResolution
, toAppliedHeader <$> preferUndefinedKeys , toAppliedHeader <$> preferMissing
] ]
if preferRepresentation == Full then if preferRepresentation == Full then
@@ -128,7 +128,7 @@ updateResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} resultSet
contentRangeHeader = contentRangeHeader =
Just . RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $ Just . RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $
if shouldCount preferCount then Just rsQueryTotal else Nothing if shouldCount preferCount then Just rsQueryTotal else Nothing
headers = catMaybes [contentRangeHeader, toAppliedHeader <$> preferUndefinedKeys] headers = catMaybes [contentRangeHeader, toAppliedHeader <$> preferMissing]
if preferRepresentation == Full then if preferRepresentation == Full then
response HTTP.status200 response HTTP.status200
+7 -7
View File
@@ -451,11 +451,11 @@ spec actualPgVersion = do
"asdf", "asdf",
{"id": 205, "body": "zzz"}]|] `shouldRespondWith` 400 {"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 -- inserting the array fails on pg 9.6, but the feature should work normally
when (actualPgVersion >= pgVersion100) $ when (actualPgVersion >= pgVersion100) $
it "inserts table default values(field-with_sep) when json keys are undefined" $ 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|[ [json|[
{"id": 4, "name": "Vier"}, {"id": 4, "name": "Vier"},
{"id": 5, "name": "Funf", "arr_data": null}, {"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]} {"id": 6, "name": "Sechs", "field-with_sep": 6, "settings":null,"arr_data":[1,2,3]}
]|] ]|]
{ matchStatus = 201 { 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" $ 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|[ [json|[
{"id": 7, "name": "Sieben"}, {"id": 7, "name": "Sieben"},
{"id": 8} {"id": 8}
@@ -483,16 +483,16 @@ spec actualPgVersion = do
{"id": 8, "name": "Default", "field-with_sep": 1, "settings":null,"arr_data":null} {"id": 8, "name": "Default", "field-with_sep": 1, "settings":null,"arr_data":null}
]|] ]|]
{ matchStatus = 201 { 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)" $ 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 } |] [json| { "data": { "a": 1, "a": 2 }, "id": 3 } |]
`shouldRespondWith` `shouldRespondWith`
[json| [ { "data": { "a": 2 }, "id": 3 } ] |] [json| [ { "data": { "a": 2 }, "id": 3 } ] |]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] , matchHeaders = ["Preference-Applied" <:> "missing=default"]
} }
it "inserts json that has duplicate keys" $ do it "inserts json that has duplicate keys" $ do
+9 -9
View File
@@ -330,46 +330,46 @@ spec = do
, matchHeaders = [] , 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 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" 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"}|] [json|{"name": "Tres"}|]
`shouldRespondWith` `shouldRespondWith`
[json|[ [json|[
{"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1} {"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}
]|] ]|]
{ matchStatus = 200 { 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 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" 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"}|] [json|{"name": "Tres"}|]
`shouldRespondWith` `shouldRespondWith`
[json|[ [json|[
{"id":3,"name":"Tres"} {"id":3,"name":"Tres"}
]|] ]|]
{ matchStatus = 200 { 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 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" 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"}|] [json|{"name": "Tres"}|]
`shouldRespondWith` `shouldRespondWith`
[json|[ [json|[
{"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1} {"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}
]|] ]|]
{ matchStatus = 200 { 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" $ 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" 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| [json|
{"arr_data":null} {"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} {"id":3,"name":"Default","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":null,"field-with_sep":3}
]|] ]|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] , matchHeaders = ["Preference-Applied" <:> "missing=default"]
} }
context "tables with self reference foreign keys" $ do context "tables with self reference foreign keys" $ do