refactor: add CallQuery type

Shortens requestToCallProcQuery, which should generate SQL in a more
direct way.
This commit is contained in:
steve-chavez
2021-08-30 18:17:59 -05:00
committed by Steve Chavez
parent c9a60373f6
commit caaa34b5de
6 changed files with 68 additions and 59 deletions
+5 -13
View File
@@ -429,25 +429,17 @@ handleInvoke invMethod proc context@RequestContext{..} = do
(pdSchema proc) (pdSchema proc)
(fromMaybe (pdName proc) $ Proc.procTableName proc) (fromMaybe (pdName proc) $ Proc.procTableName proc)
returnsSingle (ApiRequest.TargetProc target _) = Proc.procReturnsSingle target
returnsSingle _ = False
req <- readRequest identifier context req <- readRequest identifier context
bField <- binaryField context req bField <- binaryField context req
let callReq = ReqBuilder.callRequest proc ctxApiRequest req
(tableTotal, queryTotal, body, gucHeaders, gucStatus) <- (tableTotal, queryTotal, body, gucHeaders, gucStatus) <-
lift . SQL.statement mempty $ lift . SQL.statement mempty $
Statements.callProcStatement Statements.callProcStatement
(returnsScalar iTarget) (Proc.procReturnsScalar proc)
(returnsSingle iTarget) (Proc.procReturnsSingle proc)
(QueryBuilder.requestToCallProcQuery (QueryBuilder.requestToCallProcQuery callReq)
(QualifiedIdentifier (pdSchema proc) (pdName proc))
(Proc.specifiedProcParams iColumns proc)
iPayload
(returnsScalar iTarget)
iPreferParameters
(ReqBuilder.returningCols req [])
)
(QueryBuilder.readRequestToQuery req) (QueryBuilder.readRequestToQuery req)
(QueryBuilder.readRequestToCountQuery req) (QueryBuilder.readRequestToCountQuery req)
(shouldCount iPreferCount) (shouldCount iPreferCount)
+1 -12
View File
@@ -11,15 +11,12 @@ module PostgREST.DbStructure.Proc
, procReturnsScalar , procReturnsScalar
, procReturnsSingle , procReturnsSingle
, procTableName , procTableName
, specifiedProcParams
) where ) where
import qualified Data.Aeson as JSON import qualified Data.Aeson as JSON
import qualified Data.HashMap.Strict as M import qualified Data.HashMap.Strict as M
import qualified Data.Set as S
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..),
QualifiedIdentifier (..),
Schema, TableName) Schema, TableName)
import Protolude import Protolude
@@ -70,14 +67,6 @@ instance Ord ProcDescription where
-- | It uses a HashMap for a faster lookup. -- | It uses a HashMap for a faster lookup.
type ProcsMap = M.HashMap QualifiedIdentifier [ProcDescription] type ProcsMap = M.HashMap QualifiedIdentifier [ProcDescription]
{-|
Search the procedure parameters by matching them with the specified keys.
If the key doesn't match a parameter, a parameter with a default type "text" is assumed.
-}
specifiedProcParams :: S.Set FieldName -> ProcDescription -> [ProcParam]
specifiedProcParams keys proc =
(\k -> fromMaybe (ProcParam k "text" True False) (find ((==) k . ppName) (pdParams proc))) <$> S.toList keys
procReturnsScalar :: ProcDescription -> Bool procReturnsScalar :: ProcDescription -> Bool
procReturnsScalar proc = case proc of procReturnsScalar proc = case proc of
ProcDescription{pdReturnType = (Single Scalar)} -> True ProcDescription{pdReturnType = (Single Scalar)} -> True
+15 -22
View File
@@ -21,15 +21,12 @@ import qualified Hasql.DynamicStatements.Snippet as H
import Data.Tree (Tree (..)) import Data.Tree (Tree (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..))
QualifiedIdentifier (..))
import PostgREST.DbStructure.Proc (ProcParam (..)) import PostgREST.DbStructure.Proc (ProcParam (..))
import PostgREST.DbStructure.Relationship (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Relationship (..)) Relationship (..))
import PostgREST.DbStructure.Table (Table (..)) import PostgREST.DbStructure.Table (Table (..))
import PostgREST.Request.ApiRequest (PayloadJSON (..)) import PostgREST.Request.Preferences (PreferResolution (..))
import PostgREST.Request.Preferences (PreferParameters (..),
PreferResolution (..))
import PostgREST.Query.SqlFragment import PostgREST.Query.SqlFragment
import PostgREST.Request.Types import PostgREST.Request.Types
@@ -118,38 +115,34 @@ mutateRequestToQuery (Delete mainQi logicForest returnings) =
(if null logicForest then mempty else "WHERE " <> intercalateSnippet " AND " (map (pgFmtLogicTree mainQi) logicForest)) <> " " <> (if null logicForest then mempty else "WHERE " <> intercalateSnippet " AND " (map (pgFmtLogicTree mainQi) logicForest)) <> " " <>
H.sql (returningF mainQi returnings) H.sql (returningF mainQi returnings)
requestToCallProcQuery :: QualifiedIdentifier -> [ProcParam] -> Maybe PayloadJSON -> Bool -> Maybe PreferParameters -> [FieldName] -> H.Snippet requestToCallProcQuery :: CallRequest -> H.Snippet
requestToCallProcQuery qi procParams pj returnsScalar preferParams returnings = requestToCallProcQuery (FunctionCall qi params args returnsScalar multipleCall singleParam returnings) =
prmsCTE <> sourceBody prmsCTE <> argsBody
where where
body = pjRaw <$> pj (prmsCTE, argFrag)
paramsAsSingleObject = preferParams == Just SingleObject | null params = (mempty, mempty)
paramsAsMultipleObjects = preferParams == Just MultipleObjects | singleParam = ("WITH pgrst_args AS (SELECT NULL)", jsonPlaceHolder args)
(prmsCTE, args)
| null procParams = (mempty, mempty)
| paramsAsSingleObject = ("WITH pgrst_args AS (SELECT NULL)", jsonPlaceHolder body)
| otherwise = ( | otherwise = (
"WITH " <> normalizedBody body <> ", " <> "WITH " <> normalizedBody args <> ", " <>
H.sql ( H.sql (
BS.unwords [ BS.unwords [
"pgrst_args AS (", "pgrst_args AS (",
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtParams (const mempty) (\a -> " " <> encodeUtf8 (ppType a)) <> ")", "SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtParams (const mempty) (\a -> " " <> encodeUtf8 (ppType a)) <> ")",
")"]) ")"])
, H.sql $ if paramsAsMultipleObjects , H.sql $ if multipleCall
then fmtParams varadicPrefix (\a -> " := pgrst_args." <> pgFmtIdent (ppName a)) then fmtParams varadicPrefix (\a -> " := pgrst_args." <> pgFmtIdent (ppName a))
else fmtParams varadicPrefix (\a -> " := (SELECT " <> pgFmtIdent (ppName a) <> " FROM pgrst_args LIMIT 1)") else fmtParams varadicPrefix (\a -> " := (SELECT " <> pgFmtIdent (ppName a) <> " FROM pgrst_args LIMIT 1)")
) )
fmtParams :: (ProcParam -> SqlFragment) -> (ProcParam -> SqlFragment) -> SqlFragment fmtParams :: (ProcParam -> SqlFragment) -> (ProcParam -> SqlFragment) -> SqlFragment
fmtParams prmFragPre prmFragSuf = BS.intercalate ", " ((\a -> prmFragPre a <> pgFmtIdent (ppName a) <> prmFragSuf a) <$> procParams) fmtParams prmFragPre prmFragSuf = BS.intercalate ", " ((\a -> prmFragPre a <> pgFmtIdent (ppName a) <> prmFragSuf a) <$> params)
varadicPrefix :: ProcParam -> SqlFragment varadicPrefix :: ProcParam -> SqlFragment
varadicPrefix a = if ppVar a then "VARIADIC " else mempty varadicPrefix a = if ppVar a then "VARIADIC " else mempty
sourceBody :: H.Snippet argsBody :: H.Snippet
sourceBody argsBody
| paramsAsMultipleObjects = | multipleCall =
if returnsScalar if returnsScalar
then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args" then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args"
else "SELECT pgrst_lat_args.* FROM pgrst_args, " <> else "SELECT pgrst_lat_args.* FROM pgrst_args, " <>
@@ -160,7 +153,7 @@ requestToCallProcQuery qi procParams pj returnsScalar preferParams returnings =
else "SELECT " <> returnedColumns <> " FROM " <> callIt else "SELECT " <> returnedColumns <> " FROM " <> callIt
callIt :: H.Snippet callIt :: H.Snippet
callIt = H.sql (fromQi qi) <> "(" <> args <> ")" callIt = H.sql (fromQi qi) <> "(" <> argFrag <> ")"
returnedColumns :: H.Snippet returnedColumns :: H.Snippet
returnedColumns returnedColumns
+21 -1
View File
@@ -18,7 +18,7 @@ resource.
module PostgREST.Request.DbRequestBuilder module PostgREST.Request.DbRequestBuilder
( readRequest ( readRequest
, mutateRequest , mutateRequest
, returningCols , callRequest
) where ) where
import qualified Data.HashMap.Strict as M import qualified Data.HashMap.Strict as M
@@ -33,6 +33,9 @@ import Data.Tree (Tree (..))
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier (..), QualifiedIdentifier (..),
Schema, TableName) Schema, TableName)
import PostgREST.DbStructure.Proc (ProcDescription (..),
ProcParam (..),
procReturnsScalar)
import PostgREST.DbStructure.Relationship (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Junction (..), Junction (..),
Relationship (..)) Relationship (..))
@@ -343,6 +346,23 @@ mutateRequest schema tName apiRequest pkCols readReq = mapLeft ApiRequestError $
onlyRoot = filter (not . ( "." `isInfixOf` ) . fst) onlyRoot = filter (not . ( "." `isInfixOf` ) . fst)
body = pjRaw <$> iPayload apiRequest body = pjRaw <$> iPayload apiRequest
callRequest :: ProcDescription -> ApiRequest -> ReadRequest -> CallRequest
callRequest proc apiReq readReq = FunctionCall {
funCQi = QualifiedIdentifier (pdSchema proc) (pdName proc)
, funCParams = specifiedParams
, funCArgs = pjRaw <$> iPayload apiReq
, funCScalar = procReturnsScalar proc
, funCMultipleCall = iPreferParameters apiReq == Just MultipleObjects
, funCSingleParam = paramsAsSingleObject
, funCReturning = returningCols readReq []
}
where
paramsAsSingleObject = iPreferParameters apiReq == Just SingleObject
specifiedParams =
if paramsAsSingleObject
then pdParams proc
else filter (\x -> ppName x `S.member` iColumns apiReq) $ pdParams proc
returningCols :: ReadRequest -> [FieldName] -> [FieldName] returningCols :: ReadRequest -> [FieldName] -> [FieldName]
returningCols rr@(Node _ forest) pkCols returningCols rr@(Node _ forest) pkCols
-- if * is part of the select, we must not add pk or fk columns manually - -- if * is part of the select, we must not add pk or fk columns manually -
+14
View File
@@ -6,6 +6,8 @@ module PostgREST.Request.Types
, EmbedPath , EmbedPath
, Field , Field
, Filter(..) , Filter(..)
, CallQuery(..)
, CallRequest
, JoinCondition(..) , JoinCondition(..)
, JsonOperand(..) , JsonOperand(..)
, JsonOperation(..) , JsonOperation(..)
@@ -38,6 +40,7 @@ import qualified GHC.Show (show)
import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier) QualifiedIdentifier)
import PostgREST.DbStructure.Proc (ProcParam)
import PostgREST.DbStructure.Relationship (Relationship) import PostgREST.DbStructure.Relationship (Relationship)
import PostgREST.RangeQuery (NonnegRange) import PostgREST.RangeQuery (NonnegRange)
import PostgREST.Request.Preferences (PreferResolution) import PostgREST.Request.Preferences (PreferResolution)
@@ -47,6 +50,7 @@ import Protolude
type ReadRequest = Tree ReadNode type ReadRequest = Tree ReadNode
type MutateRequest = MutateQuery type MutateRequest = MutateQuery
type CallRequest = CallQuery
type ReadNode = type ReadNode =
(ReadQuery, (NodeName, Maybe Relationship, Maybe Alias, Maybe EmbedHint, Depth)) (ReadQuery, (NodeName, Maybe Relationship, Maybe Alias, Maybe EmbedHint, Depth))
@@ -121,6 +125,16 @@ data MutateQuery
, returning :: [FieldName] , returning :: [FieldName]
} }
data CallQuery = FunctionCall
{ funCQi :: QualifiedIdentifier
, funCParams :: [ProcParam]
, funCArgs :: Maybe BL.ByteString
, funCScalar :: Bool
, funCMultipleCall :: Bool
, funCSingleParam :: Bool
, funCReturning :: [FieldName]
}
-- | The select value in `/tbl?select=alias:field::cast` -- | The select value in `/tbl?select=alias:field::cast`
type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe EmbedHint) type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe EmbedHint)
+12 -11
View File
@@ -15,11 +15,10 @@ import Protolude hiding (get, toS)
import Protolude.Conv (toS) import Protolude.Conv (toS)
import PostgREST.Query.QueryBuilder (requestToCallProcQuery) import PostgREST.Query.QueryBuilder (requestToCallProcQuery)
import PostgREST.Request.ApiRequest (PayloadJSON (..)) import PostgREST.Request.Types (CallQuery (..))
import PostgREST.DbStructure.Identifiers import PostgREST.DbStructure.Identifiers
import PostgREST.DbStructure.Proc import PostgREST.DbStructure.Proc
import PostgREST.Request.Preferences
import SpecHelper (getEnvVarWithDefault) import SpecHelper (getEnvVarWithDefault)
@@ -34,29 +33,30 @@ main = do
context "call proc query" $ do context "call proc query" $ do
it "should not exceed cost when calling setof composite proc" $ do it "should not exceed cost when calling setof composite proc" $ do
cost <- exec pool $ cost <- exec pool $
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False] requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False]
(Just $ RawJSON [str| {"id": 3} |]) False Nothing [] (Just [str| {"id": 3} |]) False False False [])
liftIO $ liftIO $
cost `shouldSatisfy` (< Just 40) cost `shouldSatisfy` (< Just 40)
it "should not exceed cost when calling setof composite proc with empty params" $ do it "should not exceed cost when calling setof composite proc with empty params" $ do
cost <- exec pool $ cost <- exec pool $
requestToCallProcQuery (QualifiedIdentifier "test" "getallprojects") [] Nothing False Nothing [] requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "getallprojects") [] Nothing False False False [])
liftIO $ liftIO $
cost `shouldSatisfy` (< Just 30) cost `shouldSatisfy` (< Just 30)
it "should not exceed cost when calling scalar proc" $ do it "should not exceed cost when calling scalar proc" $ do
cost <- exec pool $ cost <- exec pool $
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [ProcParam "a" "int" True False, ProcParam "b" "int" True False] requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "add_them")
(Just $ RawJSON [str| {"a": 3, "b": 4} |]) True Nothing [] [ProcParam "a" "int" True False, ProcParam "b" "int" True False]
(Just [str| {"a": 3, "b": 4} |]) True False False [])
liftIO $ liftIO $
cost `shouldSatisfy` (< Just 10) cost `shouldSatisfy` (< Just 10)
context "params=multiple-objects" $ do context "params=multiple-objects" $ do
it "should not exceed cost when calling setof composite proc" $ do it "should not exceed cost when calling setof composite proc" $ do
cost <- exec pool $ cost <- exec pool $
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False] requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False]
(Just $ RawJSON [str| [{"id": 1}, {"id": 4}] |]) False (Just MultipleObjects) [] (Just [str| [{"id": 1}, {"id": 4}] |]) False True False [])
liftIO $ do liftIO $ do
-- lower bound needed for now to make sure that cost is not Nothing -- lower bound needed for now to make sure that cost is not Nothing
cost `shouldSatisfy` (> Just 2000) cost `shouldSatisfy` (> Just 2000)
@@ -64,8 +64,9 @@ main = do
it "should not exceed cost when calling scalar proc" $ do it "should not exceed cost when calling scalar proc" $ do
cost <- exec pool $ cost <- exec pool $
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [ProcParam "a" "int" True False, ProcParam "b" "int" True False] requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "add_them")
(Just $ RawJSON [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]) True Nothing [] [ProcParam "a" "int" True False, ProcParam "b" "int" True False]
(Just [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]) True False False [])
liftIO $ liftIO $
cost `shouldSatisfy` (< Just 10) cost `shouldSatisfy` (< Just 10)