From 16f28497244b63fe1842c18b4621cc51b90f98a6 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Mon, 3 Apr 2023 18:54:33 -0500 Subject: [PATCH] refactor: wrap proc logic in asJsonF * add explicit logic for returning setof scalars --- src/PostgREST/Plan.hs | 7 +++++-- src/PostgREST/Plan/CallPlan.hs | 11 ++++++----- src/PostgREST/Query.hs | 3 ++- src/PostgREST/Query/QueryBuilder.hs | 4 ++-- src/PostgREST/Query/SqlFragment.hs | 12 +++++++----- src/PostgREST/Query/Statements.hs | 11 +++++------ src/PostgREST/SchemaCache/Proc.hs | 23 ++++++++++++++--------- 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index d421008d2..1f400df77 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -56,7 +56,8 @@ import PostgREST.SchemaCache.Identifiers (FieldName, Schema) import PostgREST.SchemaCache.Proc (ProcDescription (..), ProcParam (..), ProcsMap, - procReturnsScalar) + procReturnsScalar, + procReturnsSetOfScalar) import PostgREST.SchemaCache.Relationship (Cardinality (..), Junction (..), Relationship (..), @@ -548,6 +549,7 @@ callPlan proc ApiRequest{iPreferences=Preferences{..}} paramKeys args readReq = , funCParams = callParams , funCArgs = Just args , funCScalar = procReturnsScalar proc +, funCSetOfScalar = procReturnsSetOfScalar proc , funCReturning = inferColsEmbedNeeds readReq [] } where @@ -615,7 +617,8 @@ addFilterToLogicForest flt lf = Stmnt flt : lf binaryField :: AppConfig -> MediaType -> Maybe ProcDescription -> ReadPlanTree -> Either ApiRequestError (Maybe FieldName) binaryField AppConfig{configRawMediaTypes} acceptMediaType proc rpTree | isRawMediaType = - if (procReturnsScalar <$> proc) == Just True + if (procReturnsScalar <$> proc) == Just True || + (procReturnsSetOfScalar <$> proc) == Just True then Right $ Just "pgrst_scalar" else let diff --git a/src/PostgREST/Plan/CallPlan.hs b/src/PostgREST/Plan/CallPlan.hs index 8dbb446be..90c6405f3 100644 --- a/src/PostgREST/Plan/CallPlan.hs +++ b/src/PostgREST/Plan/CallPlan.hs @@ -17,11 +17,12 @@ import PostgREST.SchemaCache.Proc (ProcDescription (..), import Protolude data CallPlan = FunctionCall - { funCQi :: QualifiedIdentifier - , funCParams :: CallParams - , funCArgs :: Maybe LBS.ByteString - , funCScalar :: Bool - , funCReturning :: [FieldName] + { funCQi :: QualifiedIdentifier + , funCParams :: CallParams + , funCArgs :: Maybe LBS.ByteString + , funCScalar :: Bool + , funCSetOfScalar :: Bool + , funCReturning :: [FieldName] } data CallParams diff --git a/src/PostgREST/Query.hs b/src/PostgREST/Query.hs index e2f30f653..6f5f08d05 100644 --- a/src/PostgREST/Query.hs +++ b/src/PostgREST/Query.hs @@ -158,7 +158,8 @@ invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequ lift . SQL.statement mempty $ Statements.prepareCall (Proc.procReturnsScalar proc) - (Proc.procReturnsSingle proc) + (Proc.procReturnsSingleComposite proc) + (Proc.procReturnsSetOfScalar proc) (QueryBuilder.callPlanToQuery crCallPlan) (QueryBuilder.readPlanToQuery crReadPlan) (QueryBuilder.readPlanToCountQuery crReadPlan) diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index 7f1804e91..fa73e500e 100644 --- a/src/PostgREST/Query/QueryBuilder.hs +++ b/src/PostgREST/Query/QueryBuilder.hs @@ -164,8 +164,8 @@ mutatePlanToQuery (Delete mainQi logicForest range ordts returnings) (whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts) callPlanToQuery :: CallPlan -> SQL.Snippet -callPlanToQuery (FunctionCall qi params args returnsScalar returnings) = - "SELECT " <> (if returnsScalar then "pgrst_call AS pgrst_scalar " else returnedColumns) <> " " <> +callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar returnings) = + "SELECT " <> (if returnsScalar || returnsSetOfScalar then "pgrst_call AS pgrst_scalar " else returnedColumns) <> " " <> fromCall where fromCall = case params of diff --git a/src/PostgREST/Query/SqlFragment.hs b/src/PostgREST/Query/SqlFragment.hs index 334efe459..71cb23d93 100644 --- a/src/PostgREST/Query/SqlFragment.hs +++ b/src/PostgREST/Query/SqlFragment.hs @@ -178,16 +178,18 @@ asCsvF = asCsvHeaderF <> " || '\n' || " <> asCsvBodyF ")" asCsvBodyF = "coalesce(string_agg(substring(_postgrest_t::text, 2, length(_postgrest_t::text) - 2), '\n'), '')" -asJsonF :: Bool -> SqlFragment -asJsonF returnsScalar - | returnsScalar = "coalesce(json_agg(_postgrest_t.pgrst_scalar), '[]')" - | otherwise = "coalesce(json_agg(_postgrest_t), '[]')" - asJsonSingleF :: Bool -> SqlFragment asJsonSingleF returnsScalar | returnsScalar = "coalesce(json_agg(_postgrest_t.pgrst_scalar)->0, 'null')" | otherwise = "coalesce(json_agg(_postgrest_t)->0, 'null')" +asJsonF :: Bool -> Bool -> Bool -> SqlFragment +asJsonF returnsScalar returnsSetOfScalar returnsSingleComposite + | returnsSingleComposite = "coalesce(json_agg(_postgrest_t)->0, 'null')" + | returnsScalar = "coalesce(json_agg(_postgrest_t.pgrst_scalar)->0, 'null')" + | returnsSetOfScalar = "coalesce(json_agg(_postgrest_t.pgrst_scalar), '[]')" + | otherwise = "coalesce(json_agg(_postgrest_t), '[]')" + asXmlF :: FieldName -> SqlFragment asXmlF fieldName = "coalesce(xmlagg(_postgrest_t." <> pgFmtIdent fieldName <> "), '')" diff --git a/src/PostgREST/Query/Statements.hs b/src/PostgREST/Query/Statements.hs index 9ba90bd4b..145d3ce0f 100644 --- a/src/PostgREST/Query/Statements.hs +++ b/src/PostgREST/Query/Statements.hs @@ -87,7 +87,7 @@ prepareWrite selectQuery mutateQuery isInsert mt rep pKeys = | getMediaType mt == MTTextCSV = asCsvF | getMediaType mt == MTGeoJSON = asGeoJsonF | getMediaType mt == MTSingularJSON = asJsonSingleF False - | otherwise = asJsonF False + | otherwise = asJsonF False False False selectF -- prevent using any of the column names in ?select= when no response is returned from the CTE @@ -123,17 +123,17 @@ prepareRead selectQuery countQuery countTotal mt binaryField = | getMediaType mt == MTGeoJSON = asGeoJsonF | isJust binaryField && getMediaType mt == MTTextXML = asXmlF $ fromJust binaryField | isJust binaryField = asBinaryF $ fromJust binaryField - | otherwise = asJsonF False + | otherwise = asJsonF False False False decodeIt :: HD.Result ResultSet decodeIt = case mt of MTPlan{} -> planRow _ -> HD.singleRow $ standardRow True -prepareCall :: Bool -> Bool -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool -> +prepareCall :: Bool -> Bool -> Bool -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool -> MediaType -> Maybe FieldName -> Bool -> SQL.Statement () ResultSet -prepareCall returnsScalar returnsSingle callProcQuery selectQuery countQuery countTotal mt binaryField = +prepareCall returnsScalar returnsSingleComposite returnsSetOfScalar callProcQuery selectQuery countQuery countTotal mt binaryField = SQL.dynamicallyParameterized (mtSnippet mt snippet) decodeIt where snippet = @@ -156,8 +156,7 @@ prepareCall returnsScalar returnsSingle callProcQuery selectQuery countQuery cou | getMediaType mt == MTGeoJSON = asGeoJsonF | isJust binaryField && getMediaType mt == MTTextXML = asXmlF $ fromJust binaryField | isJust binaryField = asBinaryF $ fromJust binaryField - | returnsSingle = asJsonSingleF returnsScalar - | otherwise = asJsonF returnsScalar + | otherwise = asJsonF returnsScalar returnsSetOfScalar returnsSingleComposite decodeIt :: HD.Result ResultSet decodeIt = case mt of diff --git a/src/PostgREST/SchemaCache/Proc.hs b/src/PostgREST/SchemaCache/Proc.hs index eadbce34f..cf7921383 100644 --- a/src/PostgREST/SchemaCache/Proc.hs +++ b/src/PostgREST/SchemaCache/Proc.hs @@ -9,7 +9,8 @@ module PostgREST.SchemaCache.Proc , ProcsMap , RetType(..) , procReturnsScalar - , procReturnsSingle + , procReturnsSetOfScalar + , procReturnsSingleComposite , procReturnsVoid , procTableName ) where @@ -71,21 +72,25 @@ type ProcsMap = HM.HashMap QualifiedIdentifier [ProcDescription] procReturnsScalar :: ProcDescription -> Bool procReturnsScalar proc = case proc of ProcDescription{pdReturnType = Single (Scalar _)} -> True - ProcDescription{pdReturnType = SetOf (Scalar _)} -> True - _ -> False + _ -> False -procReturnsSingle :: ProcDescription -> Bool -procReturnsSingle proc = case proc of - ProcDescription{pdReturnType = Single _} -> True - _ -> False +procReturnsSetOfScalar :: ProcDescription -> Bool +procReturnsSetOfScalar proc = case proc of + ProcDescription{pdReturnType = SetOf (Scalar _)} -> True + _ -> False + +procReturnsSingleComposite :: ProcDescription -> Bool +procReturnsSingleComposite proc = case proc of + ProcDescription{pdReturnType = Single (Composite _)} -> True + _ -> False procReturnsVoid :: ProcDescription -> Bool procReturnsVoid proc = case proc of ProcDescription{pdReturnType = Single (Scalar True)} -> True - _ -> False + _ -> False procTableName :: ProcDescription -> Maybe TableName procTableName proc = case pdReturnType proc of SetOf (Composite qi) -> Just $ qiName qi Single (Composite qi) -> Just $ qiName qi - _ -> Nothing + _ -> Nothing