refactor: PgArg to ProcParam
Clarify the difference between arguments and parameters. Parameters are part of the function definition, arguments are the values passed to the function. Also clarify the findProc function comments and error message.
This commit is contained in:
committed by
Steve Chavez
parent
ed5072f4b1
commit
c9a60373f6
@@ -442,7 +442,7 @@ handleInvoke invMethod proc context@RequestContext{..} = do
|
|||||||
(returnsSingle iTarget)
|
(returnsSingle iTarget)
|
||||||
(QueryBuilder.requestToCallProcQuery
|
(QueryBuilder.requestToCallProcQuery
|
||||||
(QualifiedIdentifier (pdSchema proc) (pdName proc))
|
(QualifiedIdentifier (pdSchema proc) (pdName proc))
|
||||||
(Proc.specifiedProcArgs iColumns proc)
|
(Proc.specifiedProcParams iColumns proc)
|
||||||
iPayload
|
iPayload
|
||||||
(returnsScalar iTarget)
|
(returnsScalar iTarget)
|
||||||
iPreferParameters
|
iPreferParameters
|
||||||
|
|||||||
@@ -43,8 +43,9 @@ import Text.InterpolatedString.Perl6 (q)
|
|||||||
|
|
||||||
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..),
|
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..),
|
||||||
Schema, TableName)
|
Schema, TableName)
|
||||||
import PostgREST.DbStructure.Proc (PgArg (..), PgType (..),
|
import PostgREST.DbStructure.Proc (PgType (..),
|
||||||
ProcDescription (..),
|
ProcDescription (..),
|
||||||
|
ProcParam (..),
|
||||||
ProcVolatility (..),
|
ProcVolatility (..),
|
||||||
ProcsMap, RetType (..))
|
ProcsMap, RetType (..))
|
||||||
import PostgREST.DbStructure.Relationship (Cardinality (..),
|
import PostgREST.DbStructure.Relationship (Cardinality (..),
|
||||||
@@ -192,7 +193,7 @@ decodeProcs =
|
|||||||
<*> column HD.text
|
<*> column HD.text
|
||||||
<*> nullableColumn HD.text
|
<*> nullableColumn HD.text
|
||||||
<*> compositeArrayColumn
|
<*> compositeArrayColumn
|
||||||
(PgArg
|
(ProcParam
|
||||||
<$> compositeField HD.text
|
<$> compositeField HD.text
|
||||||
<*> compositeField HD.text
|
<*> compositeField HD.text
|
||||||
<*> compositeField HD.bool
|
<*> compositeField HD.bool
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
|
||||||
module PostgREST.DbStructure.Proc
|
module PostgREST.DbStructure.Proc
|
||||||
( PgArg(..)
|
( PgType(..)
|
||||||
, PgType(..)
|
|
||||||
, ProcDescription(..)
|
, ProcDescription(..)
|
||||||
|
, ProcParam(..)
|
||||||
, ProcVolatility(..)
|
, ProcVolatility(..)
|
||||||
, ProcsMap
|
, ProcsMap
|
||||||
, RetType(..)
|
, RetType(..)
|
||||||
, procReturnsScalar
|
, procReturnsScalar
|
||||||
, procReturnsSingle
|
, procReturnsSingle
|
||||||
, procTableName
|
, procTableName
|
||||||
, specifiedProcArgs
|
, specifiedProcParams
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -24,15 +24,6 @@ import PostgREST.DbStructure.Identifiers (FieldName,
|
|||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
|
|
||||||
data PgArg = PgArg
|
|
||||||
{ pgaName :: Text
|
|
||||||
, pgaType :: Text
|
|
||||||
, pgaReq :: Bool
|
|
||||||
, pgaVar :: Bool
|
|
||||||
}
|
|
||||||
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
|
||||||
|
|
||||||
data PgType
|
data PgType
|
||||||
= Scalar
|
= Scalar
|
||||||
| Composite QualifiedIdentifier
|
| Composite QualifiedIdentifier
|
||||||
@@ -53,19 +44,27 @@ data ProcDescription = ProcDescription
|
|||||||
{ pdSchema :: Schema
|
{ pdSchema :: Schema
|
||||||
, pdName :: Text
|
, pdName :: Text
|
||||||
, pdDescription :: Maybe Text
|
, pdDescription :: Maybe Text
|
||||||
, pdArgs :: [PgArg]
|
, pdParams :: [ProcParam]
|
||||||
, pdReturnType :: RetType
|
, pdReturnType :: RetType
|
||||||
, pdVolatility :: ProcVolatility
|
, pdVolatility :: ProcVolatility
|
||||||
, pdHasVariadic :: Bool
|
, pdHasVariadic :: Bool
|
||||||
}
|
}
|
||||||
deriving (Eq, Generic, JSON.ToJSON)
|
deriving (Eq, Generic, JSON.ToJSON)
|
||||||
|
|
||||||
-- Order by least number of args in the case of overloaded functions
|
data ProcParam = ProcParam
|
||||||
|
{ ppName :: Text
|
||||||
|
, ppType :: Text
|
||||||
|
, ppReq :: Bool
|
||||||
|
, ppVar :: Bool
|
||||||
|
}
|
||||||
|
deriving (Eq, Ord, Generic, JSON.ToJSON)
|
||||||
|
|
||||||
|
-- Order by least number of params in the case of overloaded functions
|
||||||
instance Ord ProcDescription where
|
instance Ord ProcDescription where
|
||||||
ProcDescription schema1 name1 des1 args1 rt1 vol1 hasVar1 `compare` ProcDescription schema2 name2 des2 args2 rt2 vol2 hasVar2
|
ProcDescription schema1 name1 des1 prms1 rt1 vol1 hasVar1 `compare` ProcDescription schema2 name2 des2 prms2 rt2 vol2 hasVar2
|
||||||
| schema1 == schema2 && name1 == name2 && length args1 < length args2 = LT
|
| schema1 == schema2 && name1 == name2 && length prms1 < length prms2 = LT
|
||||||
| schema2 == schema2 && name1 == name2 && length args1 > length args2 = GT
|
| schema2 == schema2 && name1 == name2 && length prms1 > length prms2 = GT
|
||||||
| otherwise = (schema1, name1, des1, args1, rt1, vol1, hasVar1) `compare` (schema2, name2, des2, args2, rt2, vol2, hasVar2)
|
| otherwise = (schema1, name1, des1, prms1, rt1, vol1, hasVar1) `compare` (schema2, name2, des2, prms2, rt2, vol2, hasVar2)
|
||||||
|
|
||||||
-- | A map of all procs, all of which can be overloaded(one entry will have more than one ProcDescription).
|
-- | A map of all procs, all of which can be overloaded(one entry will have more than one ProcDescription).
|
||||||
-- | It uses a HashMap for a faster lookup.
|
-- | It uses a HashMap for a faster lookup.
|
||||||
@@ -75,9 +74,9 @@ type ProcsMap = M.HashMap QualifiedIdentifier [ProcDescription]
|
|||||||
Search the procedure parameters by matching them with the specified keys.
|
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.
|
If the key doesn't match a parameter, a parameter with a default type "text" is assumed.
|
||||||
-}
|
-}
|
||||||
specifiedProcArgs :: S.Set FieldName -> ProcDescription -> [PgArg]
|
specifiedProcParams :: S.Set FieldName -> ProcDescription -> [ProcParam]
|
||||||
specifiedProcArgs keys proc =
|
specifiedProcParams keys proc =
|
||||||
(\k -> fromMaybe (PgArg k "text" True False) (find ((==) k . pgaName) (pdArgs proc))) <$> S.toList keys
|
(\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
|
||||||
|
|||||||
+10
-6
@@ -29,8 +29,8 @@ import Network.HTTP.Types.Header (Header)
|
|||||||
import PostgREST.ContentType (ContentType (..))
|
import PostgREST.ContentType (ContentType (..))
|
||||||
import qualified PostgREST.ContentType as ContentType
|
import qualified PostgREST.ContentType as ContentType
|
||||||
|
|
||||||
import PostgREST.DbStructure.Proc (PgArg (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcDescription (..))
|
ProcParam (..))
|
||||||
import PostgREST.DbStructure.Relationship (Cardinality (..),
|
import PostgREST.DbStructure.Relationship (Cardinality (..),
|
||||||
Junction (..),
|
Junction (..),
|
||||||
Relationship (..))
|
Relationship (..))
|
||||||
@@ -99,11 +99,15 @@ instance JSON.ToJSON ApiRequestError where
|
|||||||
"message" .= ("More than one relationship was found for " <> parent <> " and " <> child :: Text),
|
"message" .= ("More than one relationship was found for " <> parent <> " and " <> child :: Text),
|
||||||
"details" .= (compressedRel <$> rels) ]
|
"details" .= (compressedRel <$> rels) ]
|
||||||
toJSON (AmbiguousRpc procs) = JSON.object [
|
toJSON (AmbiguousRpc procs) = JSON.object [
|
||||||
"hint" .= ("Overloaded functions with the same argument name but different types are not supported" :: Text),
|
"hint" .= ("Overloaded functions with the same parameter name but different types are not supported" :: Text),
|
||||||
"message" .= ("Could not choose the best candidate function between: " <> T.intercalate ", " [pdSchema p <> "." <> pdName p <> "(" <> T.intercalate ", " [pgaName a <> " => " <> pgaType a | a <- pdArgs p] <> ")" | p <- procs])]
|
"message" .= ("Could not choose the best candidate function between: " <> T.intercalate ", " [pdSchema p <> "." <> pdName p <> "(" <> T.intercalate ", " [ppName a <> " => " <> ppType a | a <- pdParams p] <> ")" | p <- procs])]
|
||||||
toJSON (NoRpc schema procName payloadKeys hasPreferSingleObject) = JSON.object [
|
toJSON (NoRpc schema procName payloadKeys hasPreferSingleObject) = JSON.object [
|
||||||
"hint" .= ("If a new function was created in the database with this name and arguments, try reloading the schema cache." :: Text),
|
"hint" .= ("If a new function was created in the database with this name and parameters, try reloading the schema cache." :: Text),
|
||||||
"message" .= ("Could not find the " <> schema <> "." <> procName <> (if hasPreferSingleObject then " function with a single json or jsonb argument" else "(" <> T.intercalate ", " payloadKeys <> ")" <> " function") <> " in the schema cache")]
|
"message" .= ("Could not find the " <> schema <> "." <> procName <>
|
||||||
|
(if hasPreferSingleObject
|
||||||
|
then " function with a single json or jsonb parameter"
|
||||||
|
else "(" <> T.intercalate ", " payloadKeys <> ")" <> " function") <>
|
||||||
|
" in the schema cache")]
|
||||||
toJSON UnsupportedVerb = JSON.object [
|
toJSON UnsupportedVerb = JSON.object [
|
||||||
"message" .= ("Unsupported HTTP verb" :: Text)]
|
"message" .= ("Unsupported HTTP verb" :: Text)]
|
||||||
toJSON InvalidFilters = JSON.object [
|
toJSON InvalidFilters = JSON.object [
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ import PostgREST.Config (AppConfig (..), Proxy (..),
|
|||||||
isMalformedProxyUri, toURI)
|
isMalformedProxyUri, toURI)
|
||||||
import PostgREST.DbStructure (DbStructure (..),
|
import PostgREST.DbStructure (DbStructure (..),
|
||||||
tableCols, tablePKCols)
|
tableCols, tablePKCols)
|
||||||
import PostgREST.DbStructure.Proc (PgArg (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcDescription (..))
|
ProcParam (..))
|
||||||
import PostgREST.DbStructure.Relationship (Cardinality (..),
|
import PostgREST.DbStructure.Relationship (Cardinality (..),
|
||||||
PrimaryKey (..),
|
PrimaryKey (..),
|
||||||
Relationship (..))
|
Relationship (..))
|
||||||
@@ -130,11 +130,11 @@ makeProcSchema pd =
|
|||||||
(mempty :: Schema)
|
(mempty :: Schema)
|
||||||
& description .~ pdDescription pd
|
& description .~ pdDescription pd
|
||||||
& type_ ?~ SwaggerObject
|
& type_ ?~ SwaggerObject
|
||||||
& properties .~ fromList (fmap makeProcProperty (pdArgs pd))
|
& properties .~ fromList (fmap makeProcProperty (pdParams pd))
|
||||||
& required .~ fmap pgaName (filter pgaReq (pdArgs pd))
|
& required .~ fmap ppName (filter ppReq (pdParams pd))
|
||||||
|
|
||||||
makeProcProperty :: PgArg -> (Text, Referenced Schema)
|
makeProcProperty :: ProcParam -> (Text, Referenced Schema)
|
||||||
makeProcProperty (PgArg n t _ _) = (n, Inline s)
|
makeProcProperty (ProcParam n t _ _) = (n, Inline s)
|
||||||
where
|
where
|
||||||
s = (mempty :: Schema)
|
s = (mempty :: Schema)
|
||||||
& type_ ?~ toSwaggerType t
|
& type_ ?~ toSwaggerType t
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import Data.Tree (Tree (..))
|
|||||||
|
|
||||||
import PostgREST.DbStructure.Identifiers (FieldName,
|
import PostgREST.DbStructure.Identifiers (FieldName,
|
||||||
QualifiedIdentifier (..))
|
QualifiedIdentifier (..))
|
||||||
import PostgREST.DbStructure.Proc (PgArg (..))
|
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 (..))
|
||||||
@@ -118,34 +118,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 -> [PgArg] -> Maybe PayloadJSON -> Bool -> Maybe PreferParameters -> [FieldName] -> H.Snippet
|
requestToCallProcQuery :: QualifiedIdentifier -> [ProcParam] -> Maybe PayloadJSON -> Bool -> Maybe PreferParameters -> [FieldName] -> H.Snippet
|
||||||
requestToCallProcQuery qi pgArgs pj returnsScalar preferParams returnings =
|
requestToCallProcQuery qi procParams pj returnsScalar preferParams returnings =
|
||||||
argsCTE <> sourceBody
|
prmsCTE <> sourceBody
|
||||||
where
|
where
|
||||||
body = pjRaw <$> pj
|
body = pjRaw <$> pj
|
||||||
paramsAsSingleObject = preferParams == Just SingleObject
|
paramsAsSingleObject = preferParams == Just SingleObject
|
||||||
paramsAsMultipleObjects = preferParams == Just MultipleObjects
|
paramsAsMultipleObjects = preferParams == Just MultipleObjects
|
||||||
|
|
||||||
(argsCTE, args)
|
(prmsCTE, args)
|
||||||
| null pgArgs = (mempty, mempty)
|
| null procParams = (mempty, mempty)
|
||||||
| paramsAsSingleObject = ("WITH pgrst_args AS (SELECT NULL)", jsonPlaceHolder body)
|
| paramsAsSingleObject = ("WITH pgrst_args AS (SELECT NULL)", jsonPlaceHolder body)
|
||||||
| otherwise = (
|
| otherwise = (
|
||||||
"WITH " <> normalizedBody body <> ", " <>
|
"WITH " <> normalizedBody body <> ", " <>
|
||||||
H.sql (
|
H.sql (
|
||||||
BS.unwords [
|
BS.unwords [
|
||||||
"pgrst_args AS (",
|
"pgrst_args AS (",
|
||||||
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtArgs (const mempty) (\a -> " " <> encodeUtf8 (pgaType a)) <> ")",
|
"SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtParams (const mempty) (\a -> " " <> encodeUtf8 (ppType a)) <> ")",
|
||||||
")"])
|
")"])
|
||||||
, H.sql $ if paramsAsMultipleObjects
|
, H.sql $ if paramsAsMultipleObjects
|
||||||
then fmtArgs varadicPrefix (\a -> " := pgrst_args." <> pgFmtIdent (pgaName a))
|
then fmtParams varadicPrefix (\a -> " := pgrst_args." <> pgFmtIdent (ppName a))
|
||||||
else fmtArgs varadicPrefix (\a -> " := (SELECT " <> pgFmtIdent (pgaName a) <> " FROM pgrst_args LIMIT 1)")
|
else fmtParams varadicPrefix (\a -> " := (SELECT " <> pgFmtIdent (ppName a) <> " FROM pgrst_args LIMIT 1)")
|
||||||
)
|
)
|
||||||
|
|
||||||
fmtArgs :: (PgArg -> SqlFragment) -> (PgArg -> SqlFragment) -> SqlFragment
|
fmtParams :: (ProcParam -> SqlFragment) -> (ProcParam -> SqlFragment) -> SqlFragment
|
||||||
fmtArgs argFragPre argFragSuf = BS.intercalate ", " ((\a -> argFragPre a <> pgFmtIdent (pgaName a) <> argFragSuf a) <$> pgArgs)
|
fmtParams prmFragPre prmFragSuf = BS.intercalate ", " ((\a -> prmFragPre a <> pgFmtIdent (ppName a) <> prmFragSuf a) <$> procParams)
|
||||||
|
|
||||||
varadicPrefix :: PgArg -> SqlFragment
|
varadicPrefix :: ProcParam -> SqlFragment
|
||||||
varadicPrefix a = if pgaVar a then "VARIADIC " else mempty
|
varadicPrefix a = if ppVar a then "VARIADIC " else mempty
|
||||||
|
|
||||||
sourceBody :: H.Snippet
|
sourceBody :: H.Snippet
|
||||||
sourceBody
|
sourceBody
|
||||||
|
|||||||
@@ -51,9 +51,8 @@ import PostgREST.DbStructure (DbStructure (..))
|
|||||||
import PostgREST.DbStructure.Identifiers (FieldName,
|
import PostgREST.DbStructure.Identifiers (FieldName,
|
||||||
QualifiedIdentifier (..),
|
QualifiedIdentifier (..),
|
||||||
Schema)
|
Schema)
|
||||||
import PostgREST.DbStructure.Proc (PgArg (..),
|
import PostgREST.DbStructure.Proc (ProcDescription (..),
|
||||||
ProcDescription (..),
|
ProcParam (..), ProcsMap)
|
||||||
ProcsMap)
|
|
||||||
import PostgREST.Error (ApiRequestError (..))
|
import PostgREST.Error (ApiRequestError (..))
|
||||||
import PostgREST.Query.SqlFragment (ftsOperators, operators)
|
import PostgREST.Query.SqlFragment (ftsOperators, operators)
|
||||||
import PostgREST.RangeQuery (NonnegRange, allRange,
|
import PostgREST.RangeQuery (NonnegRange, allRange,
|
||||||
@@ -119,15 +118,15 @@ instance JSON.ToJSON RpcParamValue where
|
|||||||
toJSON (Variadic v) = JSON.toJSON v
|
toJSON (Variadic v) = JSON.toJSON v
|
||||||
|
|
||||||
toRpcParamValue :: ProcDescription -> (Text, Text) -> (Text, RpcParamValue)
|
toRpcParamValue :: ProcDescription -> (Text, Text) -> (Text, RpcParamValue)
|
||||||
toRpcParamValue proc (k, v) | argIsVariadic k = (k, Variadic [v])
|
toRpcParamValue proc (k, v) | prmIsVariadic k = (k, Variadic [v])
|
||||||
| otherwise = (k, Fixed v)
|
| otherwise = (k, Fixed v)
|
||||||
where
|
where
|
||||||
argIsVariadic arg = isJust $ find (\PgArg{pgaName, pgaVar} -> pgaName == arg && pgaVar) $ pdArgs proc
|
prmIsVariadic prm = isJust $ find (\ProcParam{ppName, ppVar} -> ppName == prm && ppVar) $ pdParams proc
|
||||||
|
|
||||||
-- | Convert rpc params `/rpc/func?a=val1&b=val2` to json `{"a": "val1", "b": "val2"}
|
-- | Convert rpc params `/rpc/func?a=val1&b=val2` to json `{"a": "val1", "b": "val2"}
|
||||||
jsonRpcParams :: ProcDescription -> [(Text, Text)] -> PayloadJSON
|
jsonRpcParams :: ProcDescription -> [(Text, Text)] -> PayloadJSON
|
||||||
jsonRpcParams proc prms =
|
jsonRpcParams proc prms =
|
||||||
if not $ pdHasVariadic proc then -- if proc has no variadic arg, save steps and directly convert to json
|
if not $ pdHasVariadic proc then -- if proc has no variadic param, save steps and directly convert to json
|
||||||
ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> prms) (S.fromList $ fst <$> prms)
|
ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> prms) (S.fromList $ fst <$> prms)
|
||||||
else
|
else
|
||||||
let paramsMap = M.fromListWith mergeParams $ toRpcParamValue proc <$> prms in
|
let paramsMap = M.fromListWith mergeParams $ toRpcParamValue proc <$> prms in
|
||||||
@@ -135,7 +134,7 @@ jsonRpcParams proc prms =
|
|||||||
where
|
where
|
||||||
mergeParams :: RpcParamValue -> RpcParamValue -> RpcParamValue
|
mergeParams :: RpcParamValue -> RpcParamValue -> RpcParamValue
|
||||||
mergeParams (Variadic a) (Variadic b) = Variadic $ b ++ a
|
mergeParams (Variadic a) (Variadic b) = Variadic $ b ++ a
|
||||||
mergeParams v _ = v -- repeated params for non-variadic arguments are not merged
|
mergeParams v _ = v -- repeated params for non-variadic parameters are not merged
|
||||||
|
|
||||||
targetToJsonRpcParams :: Maybe Target -> [(Text, Text)] -> Maybe PayloadJSON
|
targetToJsonRpcParams :: Maybe Target -> [(Text, Text)] -> Maybe PayloadJSON
|
||||||
targetToJsonRpcParams target params =
|
targetToJsonRpcParams target params =
|
||||||
@@ -480,37 +479,33 @@ rawContentTypes AppConfig{..} =
|
|||||||
(ContentType.decodeContentType <$> configRawMediaTypes) `union` [CTOctetStream, CTTextPlain]
|
(ContentType.decodeContentType <$> configRawMediaTypes) `union` [CTOctetStream, CTTextPlain]
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
Search a pg procedure by its parameters. Since a function can be overloaded, the name is not enough to find it.
|
Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded,
|
||||||
An overloaded function can have a different volatility or even a different return type.
|
the name is not enough to find it. An overloaded function can have a different volatility or even a different return type.
|
||||||
-}
|
-}
|
||||||
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> ProcsMap -> Either ApiRequestError ProcDescription
|
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> ProcsMap -> Either ApiRequestError ProcDescription
|
||||||
findProc qi payloadKeys paramsAsSingleObject allProcs =
|
findProc qi argumentsKeys paramsAsSingleObject allProcs =
|
||||||
case bestMatch of
|
case matchProc of
|
||||||
[] -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList payloadKeys) paramsAsSingleObject
|
[] -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) paramsAsSingleObject
|
||||||
[proc] -> Right proc
|
[proc] -> Right proc
|
||||||
procs -> Left $ AmbiguousRpc (toList procs)
|
procs -> Left $ AmbiguousRpc (toList procs)
|
||||||
where
|
where
|
||||||
bestMatch =
|
matchProc = filter matchesParams $ M.lookupDefault mempty qi allProcs -- first find the proc by name
|
||||||
case M.lookup qi allProcs of
|
matchesParams proc =
|
||||||
Nothing -> []
|
let params = pdParams proc in
|
||||||
Just [proc] -> [proc | matches proc]
|
-- here we don't match by argument key(there isn't one) but by the single parameter type
|
||||||
Just procs -> filter matches procs
|
if paramsAsSingleObject then
|
||||||
-- Find the exact arguments match
|
case params of
|
||||||
matches proc
|
[prm] -> ppType prm `elem` ["json", "jsonb"]
|
||||||
| paramsAsSingleObject = case pdArgs proc of
|
_ -> False
|
||||||
[arg] -> pgaType arg `elem` ["json", "jsonb"]
|
-- A function has optional and required parameters. Optional parameters have a default value and
|
||||||
_ -> False
|
-- don't require arguments for the function to be executed, required parameters must have an argument present.
|
||||||
| otherwise = case pdArgs proc of
|
else case L.partition ppReq params of
|
||||||
[] -> null payloadKeys
|
-- If the function has no parameters, the arguments keys must be empty as well
|
||||||
args -> matchesArg args
|
([], []) -> null argumentsKeys
|
||||||
matchesArg args =
|
-- If the function only has required parameters, the arguments keys must match those parameters
|
||||||
-- The function's required arguments are separated from the ones with a default value assigned.
|
(reqParams, []) -> argumentsKeys == S.fromList (ppName <$> reqParams)
|
||||||
-- The set of names of those arguments is compared to the set of keys supplied by the client
|
-- If the function only has optional parameters, the arguments keys can match none or any of them(a subset)
|
||||||
-- 1. If only required arguments are found, the keys must be exactly the same as those arguments
|
([], optParams) -> argumentsKeys `S.isSubsetOf` S.fromList (ppName <$> optParams)
|
||||||
-- 2. If only optional arguments are found, the keys must be a subset of those arguments
|
-- If the function has required and optional parameters, the arguments keys have to match the required parameters
|
||||||
-- 3. If both required and optional arguments are found, the result of taking away the optional arguments
|
-- and can match any or none of the default parameters.
|
||||||
-- from the keys must be exactly the same as the required arguments
|
(reqParams, optParams) -> argumentsKeys `S.difference` S.fromList (ppName <$> optParams) == S.fromList (ppName <$> reqParams)
|
||||||
case L.partition pgaReq args of
|
|
||||||
(reqArgs, []) -> payloadKeys == S.fromList (pgaName <$> reqArgs)
|
|
||||||
([], defArgs) -> payloadKeys `S.isSubsetOf` S.fromList (pgaName <$> defArgs)
|
|
||||||
(reqArgs, defArgs) -> payloadKeys `S.difference` S.fromList (pgaName <$> defArgs) == S.fromList (pgaName <$> reqArgs)
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ spec actualPgVersion =
|
|||||||
it "should not ignore unknown args and fail with 404" $
|
it "should not ignore unknown args and fail with 404" $
|
||||||
get "/rpc/add_them?a=1&b=2&smthelse=blabla" `shouldRespondWith`
|
get "/rpc/add_them?a=1&b=2&smthelse=blabla" `shouldRespondWith`
|
||||||
[json| {
|
[json| {
|
||||||
"hint":"If a new function was created in the database with this name and arguments, try reloading the schema cache.",
|
"hint":"If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||||
"message":"Could not find the test.add_them(a, b, smthelse) function in the schema cache" } |]
|
"message":"Could not find the test.add_them(a, b, smthelse) function in the schema cache" } |]
|
||||||
{ matchStatus = 404
|
{ matchStatus = 404
|
||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
@@ -119,8 +119,8 @@ spec actualPgVersion =
|
|||||||
[json|{}|]
|
[json|{}|]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json| {
|
[json| {
|
||||||
"hint":"If a new function was created in the database with this name and arguments, try reloading the schema cache.",
|
"hint":"If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||||
"message":"Could not find the test.sayhello function with a single json or jsonb argument in the schema cache" } |]
|
"message":"Could not find the test.sayhello function with a single json or jsonb parameter in the schema cache" } |]
|
||||||
{ matchStatus = 404
|
{ matchStatus = 404
|
||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
}
|
}
|
||||||
@@ -128,24 +128,24 @@ spec actualPgVersion =
|
|||||||
it "should fail with 404 for overloaded functions with unknown args" $ do
|
it "should fail with 404 for overloaded functions with unknown args" $ do
|
||||||
get "/rpc/overloaded?wrong_arg=value" `shouldRespondWith`
|
get "/rpc/overloaded?wrong_arg=value" `shouldRespondWith`
|
||||||
[json| {
|
[json| {
|
||||||
"hint":"If a new function was created in the database with this name and arguments, try reloading the schema cache.",
|
"hint":"If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||||
"message":"Could not find the test.overloaded(wrong_arg) function in the schema cache" } |]
|
"message":"Could not find the test.overloaded(wrong_arg) function in the schema cache" } |]
|
||||||
{ matchStatus = 404
|
{ matchStatus = 404
|
||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
}
|
}
|
||||||
get "/rpc/overloaded?a=1&b=2&wrong_arg=value" `shouldRespondWith`
|
get "/rpc/overloaded?a=1&b=2&wrong_arg=value" `shouldRespondWith`
|
||||||
[json| {
|
[json| {
|
||||||
"hint":"If a new function was created in the database with this name and arguments, try reloading the schema cache.",
|
"hint":"If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||||
"message":"Could not find the test.overloaded(a, b, wrong_arg) function in the schema cache" } |]
|
"message":"Could not find the test.overloaded(a, b, wrong_arg) function in the schema cache" } |]
|
||||||
{ matchStatus = 404
|
{ matchStatus = 404
|
||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
}
|
}
|
||||||
|
|
||||||
context "ambiguous overloaded functions with same arguments but different types" $ do
|
context "ambiguous overloaded functions with same parameters' names but different types" $ do
|
||||||
it "should fail with 300 Multiple Choices without explicit argument type casts" $
|
it "should fail with 300 Multiple Choices without explicit type casts" $
|
||||||
get "/rpc/overloaded_same_args?arg=value" `shouldRespondWith`
|
get "/rpc/overloaded_same_args?arg=value" `shouldRespondWith`
|
||||||
[json| {
|
[json| {
|
||||||
"hint":"Overloaded functions with the same argument name but different types are not supported",
|
"hint":"Overloaded functions with the same parameter name but different types are not supported",
|
||||||
"message":"Could not choose the best candidate function between: test.overloaded_same_args(arg => integer), test.overloaded_same_args(arg => xml), test.overloaded_same_args(arg => text, num => integer)" } |]
|
"message":"Could not choose the best candidate function between: test.overloaded_same_args(arg => integer), test.overloaded_same_args(arg => xml), test.overloaded_same_args(arg => text, num => integer)" } |]
|
||||||
{ matchStatus = 300
|
{ matchStatus = 300
|
||||||
, matchHeaders = [matchContentTypeJson]
|
, matchHeaders = [matchContentTypeJson]
|
||||||
|
|||||||
+4
-4
@@ -34,7 +34,7 @@ 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") [PgArg "id" "int" True False]
|
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False]
|
||||||
(Just $ RawJSON [str| {"id": 3} |]) False Nothing []
|
(Just $ RawJSON [str| {"id": 3} |]) False Nothing []
|
||||||
liftIO $
|
liftIO $
|
||||||
cost `shouldSatisfy` (< Just 40)
|
cost `shouldSatisfy` (< Just 40)
|
||||||
@@ -47,7 +47,7 @@ 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") [PgArg "a" "int" True False, PgArg "b" "int" True False]
|
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [ProcParam "a" "int" True False, ProcParam "b" "int" True False]
|
||||||
(Just $ RawJSON [str| {"a": 3, "b": 4} |]) True Nothing []
|
(Just $ RawJSON [str| {"a": 3, "b": 4} |]) True Nothing []
|
||||||
liftIO $
|
liftIO $
|
||||||
cost `shouldSatisfy` (< Just 10)
|
cost `shouldSatisfy` (< Just 10)
|
||||||
@@ -55,7 +55,7 @@ main = do
|
|||||||
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") [PgArg "id" "int" True False]
|
requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [ProcParam "id" "int" True False]
|
||||||
(Just $ RawJSON [str| [{"id": 1}, {"id": 4}] |]) False (Just MultipleObjects) []
|
(Just $ RawJSON [str| [{"id": 1}, {"id": 4}] |]) False (Just MultipleObjects) []
|
||||||
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
|
||||||
@@ -64,7 +64,7 @@ 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") [PgArg "a" "int" True False, PgArg "b" "int" True False]
|
requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [ProcParam "a" "int" True False, ProcParam "b" "int" True False]
|
||||||
(Just $ RawJSON [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]) True Nothing []
|
(Just $ RawJSON [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]) True Nothing []
|
||||||
liftIO $
|
liftIO $
|
||||||
cost `shouldSatisfy` (< Just 10)
|
cost `shouldSatisfy` (< Just 10)
|
||||||
|
|||||||
Reference in New Issue
Block a user