diff --git a/src/PostgREST/ApiRequest/Preferences.hs b/src/PostgREST/ApiRequest/Preferences.hs index 41d4e33596..c70fddfdb4 100644 --- a/src/PostgREST/ApiRequest/Preferences.hs +++ b/src/PostgREST/ApiRequest/Preferences.hs @@ -9,7 +9,7 @@ module PostgREST.ApiRequest.Preferences ( Preferences(..) , PreferCount(..) - , PreferDefaults(..) + , PreferUndefinedKeys(..) , PreferParameters(..) , PreferRepresentation(..) , PreferResolution(..) @@ -34,6 +34,7 @@ import Protolude -- >>> deriving instance Show PreferParameters -- >>> deriving instance Show PreferCount -- >>> deriving instance Show PreferTransaction +-- >>> deriving instance Show PreferUndefinedKeys -- >>> deriving instance Show Preferences -- | Preferences recognized by the application. @@ -44,7 +45,7 @@ data Preferences , preferParameters :: Maybe PreferParameters , preferCount :: Maybe PreferCount , preferTransaction :: Maybe PreferTransaction - , preferDefaults :: Maybe PreferDefaults + , preferUndefinedKeys :: Maybe PreferUndefinedKeys } -- | @@ -59,6 +60,7 @@ data Preferences -- , preferParameters = Nothing -- , preferCount = Just ExactCount -- , preferTransaction = Nothing +-- , preferUndefinedKeys = Nothing -- } -- -- Multiple headers can also be used: @@ -70,6 +72,7 @@ data Preferences -- , preferParameters = Nothing -- , preferCount = Just ExactCount -- , preferTransaction = Nothing +-- , preferUndefinedKeys = Nothing -- } -- -- If a preference is set more than once, only the first is used: @@ -94,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 , defaults=apply")] +-- >>> pPrint $ fromHeaders [("prefer", "count=exact, tx=commit ,return=representation , undefined-keys=apply-defaults")] -- Preferences -- { preferResolution = Nothing -- , preferRepresentation = Full -- , preferParameters = Nothing -- , preferCount = Just ExactCount -- , preferTransaction = Just Commit --- , preferDefaults = Just ApplyDefaults +-- , preferUndefinedKeys = Just ApplyDefaults -- } -- fromHeaders :: [HTTP.Header] -> Preferences @@ -112,7 +115,7 @@ fromHeaders headers = , preferParameters = parsePrefs [SingleObject, MultipleObjects] , preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount] , preferTransaction = parsePrefs [Commit, Rollback] - , preferDefaults = parsePrefs [ApplyDefaults, IgnoreDefaults] + , preferUndefinedKeys = parsePrefs [ApplyDefaults, IgnoreDefaults] } where prefHeaders = filter ((==) HTTP.hPrefer . fst) headers @@ -211,14 +214,14 @@ instance ToAppliedHeader PreferTransaction -- | -- How to handle the insertion/update when the keys specified in ?columns are not present --- in the json body. -data PreferDefaults +-- 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 + | IgnoreDefaults -- ^ Inserts: null values / Updates: the keys are not SET to any value deriving Eq -instance ToHeaderValue PreferDefaults where - toHeaderValue ApplyDefaults = "defaults=apply" - toHeaderValue IgnoreDefaults = "defaults=ignore" +instance ToHeaderValue PreferUndefinedKeys where + toHeaderValue ApplyDefaults = "undefined-keys=apply-defaults" + toHeaderValue IgnoreDefaults = "undefined-keys=ignore-defaults" -instance ToAppliedHeader PreferDefaults +instance ToAppliedHeader PreferUndefinedKeys diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index 1c839e82b2..8abdfec604 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -504,9 +504,9 @@ mutatePlan :: Mutation -> QualifiedIdentifier -> ApiRequest -> SchemaCache -> Re mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} sCache readReq = mapLeft ApiRequestError $ case mutation of MutationCreate -> - mapRight (\typedColumns -> Insert qi typedColumns body ((,) <$> preferResolution <*> Just confCols) [] returnings pkCols $ preferDefaults == Just ApplyDefaults) typedColumnsOrError + mapRight (\typedColumns -> Insert qi typedColumns body ((,) <$> preferResolution <*> Just confCols) [] returnings pkCols $ preferUndefinedKeys == Just ApplyDefaults) typedColumnsOrError MutationUpdate -> - mapRight (\typedColumns -> Update qi typedColumns body combinedLogic iTopLevelRange rootOrder returnings $ preferDefaults == Just ApplyDefaults) typedColumnsOrError + mapRight (\typedColumns -> Update qi typedColumns body combinedLogic iTopLevelRange rootOrder returnings $ preferUndefinedKeys == Just ApplyDefaults) typedColumnsOrError MutationSingleUpsert -> if null qsLogic && qsFilterFields == S.fromList pkCols && diff --git a/src/PostgREST/Response.hs b/src/PostgREST/Response.hs index 2d511173b1..ed01224c84 100644 --- a/src/PostgREST/Response.hs +++ b/src/PostgREST/Response.hs @@ -109,10 +109,7 @@ createResponse QualifiedIdentifier{..} MutateReadPlan{mrMutatePlan} ctxApiReques Nothing else toAppliedHeader <$> preferResolution - , if null iColumns || isNothing preferDefaults then - Nothing - else - toAppliedHeader <$> preferDefaults + , undefinedKeysHeader ctxApiRequest ] if preferRepresentation == Full then @@ -124,19 +121,14 @@ createResponse QualifiedIdentifier{..} MutateReadPlan{mrMutatePlan} ctxApiReques Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan updateResponse :: ApiRequest -> ResultSet -> Wai.Response -updateResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}, iColumns} resultSet = case resultSet of +updateResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} resultSet = case resultSet of RSStandard{..} -> do let response = gucResponse rsGucStatus rsGucHeaders contentRangeHeader = Just . RangeQuery.contentRangeH 0 (rsQueryTotal - 1) $ if shouldCount preferCount then Just rsQueryTotal else Nothing - preferDefaultsHeader = - if null iColumns || isNothing preferDefaults then - Nothing - else - toAppliedHeader <$> preferDefaults - headers = catMaybes [contentRangeHeader, preferDefaultsHeader] + headers = catMaybes [contentRangeHeader, undefinedKeysHeader ctxApiRequest] if preferRepresentation == Full then response HTTP.status200 @@ -271,6 +263,13 @@ profileHeader schema negotiatedByProfile = else Nothing +undefinedKeysHeader :: ApiRequest -> Maybe HTTP.Header +undefinedKeysHeader ApiRequest{iPreferences=Preferences{preferUndefinedKeys}, iColumns} = + if null iColumns || isNothing preferUndefinedKeys then + Nothing + else + toAppliedHeader <$> preferUndefinedKeys + addRetryHint :: Int -> Wai.Response -> Wai.Response addRetryHint delay response = do let h = ("Retry-After", BS.pack $ show delay) diff --git a/test/spec/Feature/Query/InsertSpec.hs b/test/spec/Feature/Query/InsertSpec.hs index e4592124ca..d148bb4f90 100644 --- a/test/spec/Feature/Query/InsertSpec.hs +++ b/test/spec/Feature/Query/InsertSpec.hs @@ -455,7 +455,7 @@ spec actualPgVersion = 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", "defaults=apply")] + 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}, @@ -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" <:> "defaults=apply"] + , 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", "defaults=apply")] + request methodPost "/complex_items_view?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")] [json|[ {"id": 7, "name": "Sieben"}, {"id": 8} @@ -483,7 +483,7 @@ spec actualPgVersion = do {"id": 8, "name": "Default", "field-with_sep": 1, "settings":null,"arr_data":null} ]|] { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "defaults=apply"] + , matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] } context "with unicode values" $ do diff --git a/test/spec/Feature/Query/UpdateSpec.hs b/test/spec/Feature/Query/UpdateSpec.hs index a070dcdc72..4274ccf668 100644 --- a/test/spec/Feature/Query/UpdateSpec.hs +++ b/test/spec/Feature/Query/UpdateSpec.hs @@ -333,43 +333,43 @@ spec = do 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", "defaults=apply")] + [("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" <:> "defaults=apply"] + , 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", "defaults=apply")] + [("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")] [json|{"name": "Tres"}|] `shouldRespondWith` [json|[ {"id":3,"name":"Tres"} ]|] { matchStatus = 200 - , matchHeaders = ["Preference-Applied" <:> "defaults=apply"] + , 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", "defaults=apply")] + [("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" <:> "defaults=apply"] + , 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", "defaults=apply")] + [("Prefer", "return=representation"), ("Prefer", "undefined-keys=apply-defaults")] [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" <:> "defaults=apply"] + , matchHeaders = ["Preference-Applied" <:> "undefined-keys=apply-defaults"] } context "tables with self reference foreign keys" $ do