diff --git a/postgrest.cabal b/postgrest.cabal index 0ad33e626..9ebcf5d2c 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -46,7 +46,7 @@ library PostgREST.Cors PostgREST.SchemaCache PostgREST.SchemaCache.Identifiers - PostgREST.SchemaCache.Proc + PostgREST.SchemaCache.Routine PostgREST.SchemaCache.Relationship PostgREST.SchemaCache.Table PostgREST.Error diff --git a/src/PostgREST/ApiRequest/Types.hs b/src/PostgREST/ApiRequest/Types.hs index 99436247e..ecee3de96 100644 --- a/src/PostgREST/ApiRequest/Types.hs +++ b/src/PostgREST/ApiRequest/Types.hs @@ -36,9 +36,9 @@ module PostgREST.ApiRequest.Types import PostgREST.MediaType (MediaType (..)) import PostgREST.SchemaCache.Identifiers (FieldName, QualifiedIdentifier) -import PostgREST.SchemaCache.Proc (ProcDescription (..)) import PostgREST.SchemaCache.Relationship (Relationship, RelationshipsMap) +import PostgREST.SchemaCache.Routine (Routine (..)) import Protolude @@ -66,7 +66,7 @@ data SelectItem data ApiRequestError = AmbiguousRelBetween Text Text [Relationship] - | AmbiguousRpc [ProcDescription] + | AmbiguousRpc [Routine] | BinaryFieldError MediaType | MediaTypeError [ByteString] | InvalidBody ByteString @@ -76,7 +76,7 @@ data ApiRequestError | LimitNoOrderError | NotFound | NoRelBetween Text Text (Maybe Text) Text RelationshipsMap - | NoRpc Text Text [Text] Bool MediaType Bool [QualifiedIdentifier] [ProcDescription] + | NoRpc Text Text [Text] Bool MediaType Bool [QualifiedIdentifier] [Routine] | NotEmbedded Text | PutLimitNotAllowedError | QueryParamError QPError diff --git a/src/PostgREST/Error.hs b/src/PostgREST/Error.hs index a02629b54..5aab087b9 100644 --- a/src/PostgREST/Error.hs +++ b/src/PostgREST/Error.hs @@ -39,12 +39,12 @@ import qualified PostgREST.MediaType as MediaType import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..), Schema) -import PostgREST.SchemaCache.Proc (ProcDescription (..), - ProcParam (..)) import PostgREST.SchemaCache.Relationship (Cardinality (..), Junction (..), Relationship (..), RelationshipsMap) +import PostgREST.SchemaCache.Routine (Routine (..), + RoutineParam (..)) import Protolude @@ -283,7 +283,7 @@ noRelBetweenHint parent child schema allRels = ("Perhaps you meant '" <>) <$> -- to all the overloaded functions' params using the form "param1, param2, param3, ..." -- and shows the best match as hint. -- --- >>> let procsDesc = [ProcDescription {pdParams = [ProcParam {ppName="val"}, ProcParam {ppName="param"}, ProcParam {ppName="name"}]}, ProcDescription {pdParams = [ProcParam {ppName="id"}, ProcParam {ppName="attr"}]}] +-- >>> let procsDesc = [Routine {pdParams = [RoutineParam {ppName="val"}, RoutineParam {ppName="param"}, RoutineParam {ppName="name"}]}, Routine {pdParams = [RoutineParam {ppName="id"}, RoutineParam {ppName="attr"}]}] -- -- >>> noRpcHint "api" "test" ["vall", "pqaram", "nam"] procs procsDesc -- Just "Perhaps you meant to call the function api.test(name, param, val)" @@ -300,7 +300,7 @@ noRelBetweenHint parent child schema allRels = ("Perhaps you meant '" <>) <$> -- >>> noRpcHint "api" "test" ["noclosealternative"] procs procsDesc -- Nothing -- -noRpcHint :: Text -> Text -> [Text] -> [QualifiedIdentifier] -> [ProcDescription] -> Maybe Text +noRpcHint :: Text -> Text -> [Text] -> [QualifiedIdentifier] -> [Routine] -> Maybe Text noRpcHint schema procName params allProcs overloadedProcs = fmap (("Perhaps you meant to call the function " <> schema <> ".") <>) possibleProcs where diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index 824f24b5e..4714e8f15 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -27,11 +27,11 @@ module PostgREST.Plan ) where -import qualified Data.ByteString.Lazy as LBS -import qualified Data.HashMap.Strict as HM -import qualified Data.List as L -import qualified Data.Set as S -import qualified PostgREST.SchemaCache.Proc as Proc +import qualified Data.ByteString.Lazy as LBS +import qualified Data.HashMap.Strict as HM +import qualified Data.List as L +import qualified Data.Set as S +import qualified PostgREST.SchemaCache.Routine as Routine import Data.Either.Combinators (mapLeft, mapRight) import Data.List (delete) @@ -54,16 +54,16 @@ import PostgREST.SchemaCache (SchemaCache (..)) import PostgREST.SchemaCache.Identifiers (FieldName, QualifiedIdentifier (..), Schema) -import PostgREST.SchemaCache.Proc (ProcDescription (..), - ProcParam (..), ProcsMap, - procReturnsCompositeAlias, - procReturnsScalar, - procReturnsSetOfScalar) import PostgREST.SchemaCache.Relationship (Cardinality (..), Junction (..), Relationship (..), RelationshipsMap, relIsToOne) +import PostgREST.SchemaCache.Routine (Routine (..), RoutineMap, + RoutineParam (..), + funcReturnsCompositeAlias, + funcReturnsScalar, + funcReturnsSetOfScalar) import PostgREST.SchemaCache.Table (Table (tableName), tablePKCols) @@ -95,7 +95,7 @@ data CallReadPlan = CallReadPlan { crReadPlan :: ReadPlanTree , crCallPlan :: CallPlan , crTxMode :: SQL.Mode -, crProc :: ProcDescription +, crProc :: Routine , crBinField :: Maybe FieldName } @@ -117,9 +117,9 @@ callReadPlan identifier conf sCache apiRequest invMethod = do InvGet -> S.fromList $ fst <$> qsParams' InvHead -> S.fromList $ fst <$> qsParams' InvPost -> iColumns apiRequest - proc@ProcDescription{..} <- mapLeft ApiRequestError $ - findProc identifier paramKeys (preferParameters == Just SingleObject) (dbProcs sCache) (iContentMediaType apiRequest) (invMethod == InvPost) - let relIdentifier = QualifiedIdentifier pdSchema (fromMaybe pdName $ Proc.procTableName proc) -- done so a set returning function can embed other relations + proc@Function{..} <- mapLeft ApiRequestError $ + findProc identifier paramKeys (preferParameters == Just SingleObject) (dbRoutines sCache) (iContentMediaType apiRequest) (invMethod == InvPost) + let relIdentifier = QualifiedIdentifier pdSchema (fromMaybe pdName $ Routine.funcTableName proc) -- done so a set returning function can embed other relations rPlan <- readPlan relIdentifier conf sCache apiRequest let args = case (invMethod, iContentMediaType apiRequest) of (InvGet, _) -> jsonRpcParams proc qsParams' @@ -127,11 +127,11 @@ callReadPlan identifier conf sCache apiRequest invMethod = do (InvPost, MTUrlEncoded) -> maybe mempty (jsonRpcParams proc . payArray) $ iPayload apiRequest (InvPost, _) -> maybe mempty payRaw $ iPayload apiRequest txMode = case (invMethod, pdVolatility) of - (InvGet, _) -> SQL.Read - (InvHead, _) -> SQL.Read - (InvPost, Proc.Stable) -> SQL.Read - (InvPost, Proc.Immutable) -> SQL.Read - (InvPost, Proc.Volatile) -> SQL.Write + (InvGet, _) -> SQL.Read + (InvHead, _) -> SQL.Read + (InvPost, Routine.Stable) -> SQL.Read + (InvPost, Routine.Immutable) -> SQL.Read + (InvPost, Routine.Volatile) -> SQL.Write cPlan = callPlan proc apiRequest paramKeys args rPlan binField <- mapLeft ApiRequestError $ binaryField conf (iAcceptMediaType apiRequest) (Just proc) rPlan return $ CallReadPlan rPlan cPlan txMode proc binField @@ -143,7 +143,7 @@ callReadPlan identifier conf sCache apiRequest invMethod = do Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded, 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 -> MediaType -> Bool -> Either ApiRequestError ProcDescription +findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> RoutineMap -> MediaType -> Bool -> Either ApiRequestError Routine findProc qi argumentsKeys paramsAsSingleObject allProcs contentMediaType isInvPost = case matchProc of ([], []) -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) paramsAsSingleObject contentMediaType isInvPost (HM.keys allProcs) lookupProcName @@ -166,7 +166,7 @@ findProc qi argumentsKeys paramsAsSingleObject allProcs contentMediaType isInvPo | otherwise = (ts,fs) -- If the function is called with post and has a single unnamed parameter -- it can be called depending on content type and the parameter type - hasSingleUnnamedParam ProcDescription{pdParams=[ProcParam{ppType}]} = isInvPost && case (contentMediaType, ppType) of + hasSingleUnnamedParam Function{pdParams=[RoutineParam{ppType}]} = isInvPost && case (contentMediaType, ppType) of (MTApplicationJSON, "json") -> True (MTApplicationJSON, "jsonb") -> True (MTTextPlain, "text") -> True @@ -544,14 +544,14 @@ resolveOrError (Just table) field = Nothing -> Left $ ColumnNotFound (tableName table) field Just typedField -> Right typedField -callPlan :: ProcDescription -> ApiRequest -> S.Set FieldName -> LBS.ByteString -> ReadPlanTree -> CallPlan +callPlan :: Routine -> ApiRequest -> S.Set FieldName -> LBS.ByteString -> ReadPlanTree -> CallPlan callPlan proc ApiRequest{iPreferences=Preferences{..}} paramKeys args readReq = FunctionCall { funCQi = QualifiedIdentifier (pdSchema proc) (pdName proc) , funCParams = callParams , funCArgs = Just args -, funCScalar = procReturnsScalar proc -, funCSetOfScalar = procReturnsSetOfScalar proc -, funCRetCompositeAlias = procReturnsCompositeAlias proc +, funCScalar = funcReturnsScalar proc +, funCSetOfScalar = funcReturnsSetOfScalar proc +, funCRetCompositeAlias = funcReturnsCompositeAlias proc , funCReturning = inferColsEmbedNeeds readReq [] } where @@ -616,11 +616,11 @@ addFilterToLogicForest flt lf = Stmnt flt : lf -- | If raw(binary) output is requested, check that MediaType is one of the -- admitted rawMediaTypes and that`?select=...` contains only one field other -- than `*` -binaryField :: AppConfig -> MediaType -> Maybe ProcDescription -> ReadPlanTree -> Either ApiRequestError (Maybe FieldName) +binaryField :: AppConfig -> MediaType -> Maybe Routine -> ReadPlanTree -> Either ApiRequestError (Maybe FieldName) binaryField AppConfig{configRawMediaTypes} acceptMediaType proc rpTree | isRawMediaType = - if (procReturnsScalar <$> proc) == Just True || - (procReturnsSetOfScalar <$> proc) == Just True + if (funcReturnsScalar <$> proc) == Just True || + (funcReturnsSetOfScalar <$> 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 f9635665d..76e3341f6 100644 --- a/src/PostgREST/Plan/CallPlan.hs +++ b/src/PostgREST/Plan/CallPlan.hs @@ -11,8 +11,8 @@ import qualified Data.ByteString.Lazy as LBS import qualified Data.HashMap.Strict as HM import PostgREST.SchemaCache.Identifiers (FieldName, QualifiedIdentifier) -import PostgREST.SchemaCache.Proc (ProcDescription (..), - ProcParam (..)) +import PostgREST.SchemaCache.Routine (Routine (..), + RoutineParam (..)) import Protolude @@ -27,11 +27,11 @@ data CallPlan = FunctionCall } data CallParams - = KeyParams [ProcParam] -- ^ Call with key params: func(a := val1, b:= val2) - | OnePosParam ProcParam -- ^ Call with positional params(only one supported): func(val) + = KeyParams [RoutineParam] -- ^ Call with key params: func(a := val1, b:= val2) + | OnePosParam RoutineParam -- ^ Call with positional params(only one supported): func(val) -- | Convert rpc params `/rpc/func?a=val1&b=val2` to json `{"a": "val1", "b": "val2"} -jsonRpcParams :: ProcDescription -> [(Text, Text)] -> LBS.ByteString +jsonRpcParams :: Routine -> [(Text, Text)] -> LBS.ByteString jsonRpcParams proc prms = if not $ pdHasVariadic proc then -- if proc has no variadic param, save steps and directly convert to json JSON.encode $ HM.fromList $ second JSON.toJSON <$> prms @@ -43,11 +43,11 @@ jsonRpcParams proc prms = mergeParams (Variadic a) (Variadic b) = Variadic $ b ++ a mergeParams v _ = v -- repeated params for non-variadic parameters are not merged -toRpcParamValue :: ProcDescription -> (Text, Text) -> (Text, RpcParamValue) +toRpcParamValue :: Routine -> (Text, Text) -> (Text, RpcParamValue) toRpcParamValue proc (k, v) | prmIsVariadic k = (k, Variadic [v]) | otherwise = (k, Fixed v) where - prmIsVariadic prm = isJust $ find (\ProcParam{ppName, ppVar} -> ppName == prm && ppVar) $ pdParams proc + prmIsVariadic prm = isJust $ find (\RoutineParam{ppName, ppVar} -> ppName == prm && ppVar) $ pdParams proc -- | RPC query param value `/rpc/func?v=`, used for VARIADIC functions on form-urlencoded POST and GETs -- | It can be fixed `?v=1` or repeated `?v=1&v=2&v=3. diff --git a/src/PostgREST/Plan/Types.hs b/src/PostgREST/Plan/Types.hs index bd8637386..ca53611a9 100644 --- a/src/PostgREST/Plan/Types.hs +++ b/src/PostgREST/Plan/Types.hs @@ -1,7 +1,6 @@ module PostgREST.Plan.Types ( TypedField(..) , resolveTableField - ) where import qualified Data.HashMap.Strict.InsOrd as HMI diff --git a/src/PostgREST/Query.hs b/src/PostgREST/Query.hs index 23bbf729a..65a4164b7 100644 --- a/src/PostgREST/Query.hs +++ b/src/PostgREST/Query.hs @@ -27,12 +27,12 @@ import qualified Hasql.Encoders as HE import qualified Hasql.Statement as SQL import qualified Hasql.Transaction as SQL -import qualified PostgREST.Error as Error -import qualified PostgREST.Query.QueryBuilder as QueryBuilder -import qualified PostgREST.Query.Statements as Statements -import qualified PostgREST.RangeQuery as RangeQuery -import qualified PostgREST.SchemaCache as SchemaCache -import qualified PostgREST.SchemaCache.Proc as Proc +import qualified PostgREST.Error as Error +import qualified PostgREST.Query.QueryBuilder as QueryBuilder +import qualified PostgREST.Query.Statements as Statements +import qualified PostgREST.RangeQuery as RangeQuery +import qualified PostgREST.SchemaCache as SchemaCache +import qualified PostgREST.SchemaCache.Routine as Routine import Data.Scientific (FPFormat (..), formatScientific, isInteger) @@ -59,8 +59,7 @@ import PostgREST.Query.Statements (ResultSet (..)) import PostgREST.SchemaCache (SchemaCache (..)) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..), Schema) -import PostgREST.SchemaCache.Proc (ProcDescription (..), - ProcsMap) +import PostgREST.SchemaCache.Routine (Routine (..), RoutineMap) import PostgREST.SchemaCache.Table (TablesMap) import Protolude hiding (Handler) @@ -152,14 +151,14 @@ deleteQuery mrPlan apiReq@ApiRequest{..} conf = do optionalRollback conf apiReq pure resultSet -invokeQuery :: ProcDescription -> CallReadPlan -> ApiRequest -> AppConfig -> PgVersion -> DbHandler ResultSet +invokeQuery :: Routine -> CallReadPlan -> ApiRequest -> AppConfig -> PgVersion -> DbHandler ResultSet invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequest{iPreferences=Preferences{..}, ..} conf@AppConfig{..} pgVer = do resultSet <- lift . SQL.statement mempty $ Statements.prepareCall - (Proc.procReturnsScalar proc) - (Proc.procReturnsSingleComposite proc) - (Proc.procReturnsSetOfScalar proc) + (Routine.funcReturnsScalar proc) + (Routine.funcReturnsSingleComposite proc) + (Routine.funcReturnsSetOfScalar proc) (QueryBuilder.callPlanToQuery crCallPlan pgVer) (QueryBuilder.readPlanToQuery crReadPlan) (QueryBuilder.readPlanToCountQuery crReadPlan) @@ -172,19 +171,19 @@ invokeQuery proc CallReadPlan{crReadPlan, crCallPlan, crBinField} apiReq@ApiRequ failNotSingular iAcceptMediaType resultSet pure resultSet -openApiQuery :: SchemaCache -> PgVersion -> AppConfig -> Schema -> DbHandler (Maybe (TablesMap, ProcsMap, Maybe Text)) +openApiQuery :: SchemaCache -> PgVersion -> AppConfig -> Schema -> DbHandler (Maybe (TablesMap, RoutineMap, Maybe Text)) openApiQuery sCache pgVer AppConfig{..} tSchema = lift $ case configOpenApiMode of OAFollowPriv -> do tableAccess <- SQL.statement [tSchema] (SchemaCache.accessibleTables pgVer configDbPreparedStatements) Just <$> ((,,) (HM.filterWithKey (\qi _ -> S.member qi tableAccess) $ SchemaCache.dbTables sCache) - <$> SQL.statement tSchema (SchemaCache.accessibleProcs pgVer configDbPreparedStatements) + <$> SQL.statement tSchema (SchemaCache.accessibleFuncs pgVer configDbPreparedStatements) <*> SQL.statement tSchema (SchemaCache.schemaDescription configDbPreparedStatements)) OAIgnorePriv -> Just <$> ((,,) (HM.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch == tSchema) $ SchemaCache.dbTables sCache) - (HM.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch == tSchema) $ SchemaCache.dbProcs sCache) + (HM.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch == tSchema) $ SchemaCache.dbRoutines sCache) <$> SQL.statement tSchema (SchemaCache.schemaDescription configDbPreparedStatements)) OADisabled -> pure Nothing diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index d8aa0b3c3..1ede09a4b 100644 --- a/src/PostgREST/Query/QueryBuilder.hs +++ b/src/PostgREST/Query/QueryBuilder.hs @@ -25,11 +25,11 @@ import PostgREST.ApiRequest.Preferences (PreferResolution (..)) import PostgREST.Config.PgVersion (PgVersion, pgVersion110, pgVersion130) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..)) -import PostgREST.SchemaCache.Proc (ProcParam (..)) import PostgREST.SchemaCache.Relationship (Cardinality (..), Junction (..), Relationship (..), relIsToOne) +import PostgREST.SchemaCache.Routine (RoutineParam (..)) import PostgREST.ApiRequest.Types import PostgREST.Plan.CallPlan @@ -180,7 +180,7 @@ callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar re callIt argument | pgVer < pgVersion130 && pgVer >= pgVersion110 && returnsCompositeAlias = "(SELECT (" <> SQL.sql (fromQi qi) <> "(" <> argument <> ")).*) pgrst_call" | otherwise = SQL.sql (fromQi qi) <> "(" <> argument <> ") pgrst_call" - fmtParams :: [ProcParam] -> SQL.Snippet + fmtParams :: [RoutineParam] -> SQL.Snippet fmtParams prms = SQL.sql $ BS.intercalate ", " ((\a -> (if ppVar a then "VARIADIC " else mempty) <> pgFmtIdent (ppName a) <> " := pgrst_body." <> pgFmtIdent (ppName a)) <$> prms) diff --git a/src/PostgREST/Response.hs b/src/PostgREST/Response.hs index 398259bf5..6d2b96f1c 100644 --- a/src/PostgREST/Response.hs +++ b/src/PostgREST/Response.hs @@ -50,13 +50,12 @@ import PostgREST.Response.GucHeader (GucHeader, unwrapGucHeader) import PostgREST.SchemaCache (SchemaCache (..)) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..), Schema) -import PostgREST.SchemaCache.Proc (ProcDescription (..), - ProcVolatility (..), - ProcsMap) +import PostgREST.SchemaCache.Routine (FuncVolatility (..), + Routine (..), RoutineMap) import PostgREST.SchemaCache.Table (Table (..), TablesMap) -import qualified PostgREST.ApiRequest.Types as ApiRequestTypes -import qualified PostgREST.SchemaCache.Proc as Proc +import qualified PostgREST.ApiRequest.Types as ApiRequestTypes +import qualified PostgREST.SchemaCache.Routine as Routine import Protolude hiding (Handler, toS) import Protolude.Conv (toS) @@ -189,7 +188,7 @@ infoIdentResponse identifier sCache = ["PATCH" | tableUpdatable table] ++ ["DELETE" | tableDeletable table] -infoProcResponse :: ProcDescription -> Wai.Response +infoProcResponse :: Routine -> Wai.Response infoProcResponse proc | pdVolatility proc == Volatile = respondInfo "OPTIONS,POST" | otherwise = respondInfo "OPTIONS,GET,HEAD,POST" @@ -201,7 +200,7 @@ respondInfo allowHeader = let allOrigins = ("Access-Control-Allow-Origin", "*") in Wai.responseLBS HTTP.status200 [allOrigins, (HTTP.hAllow, allowHeader)] mempty -invokeResponse :: InvokeMethod -> ProcDescription -> ApiRequest -> ResultSet -> Wai.Response +invokeResponse :: InvokeMethod -> Routine -> ApiRequest -> ResultSet -> Wai.Response invokeResponse invMethod proc ctxApiRequest@ApiRequest{..} resultSet = case resultSet of RSStandard {..} -> do let @@ -214,7 +213,7 @@ invokeResponse invMethod proc ctxApiRequest@ApiRequest{..} resultSet = case resu else LBS.fromStrict rsBody headers = [contentRange] - if Proc.procReturnsVoid proc then + if Routine.funcReturnsVoid proc then response HTTP.status204 headers mempty else response status @@ -224,7 +223,7 @@ invokeResponse invMethod proc ctxApiRequest@ApiRequest{..} resultSet = case resu RSPlan plan -> Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan -openApiResponse :: Bool -> Maybe (TablesMap, ProcsMap, Maybe Text) -> AppConfig -> SchemaCache -> Schema -> Bool -> Wai.Response +openApiResponse :: Bool -> Maybe (TablesMap, RoutineMap, Maybe Text) -> AppConfig -> SchemaCache -> Schema -> Bool -> Wai.Response openApiResponse headersOnly body conf sCache schema negotiatedByProfile = Wai.responseLBS HTTP.status200 (MediaType.toContentType MTOpenAPI : maybeToList (profileHeader schema negotiatedByProfile)) diff --git a/src/PostgREST/Response/OpenAPI.hs b/src/PostgREST/Response/OpenAPI.hs index 917a8feff..db855f9fd 100644 --- a/src/PostgREST/Response/OpenAPI.hs +++ b/src/PostgREST/Response/OpenAPI.hs @@ -28,11 +28,11 @@ import PostgREST.Config (AppConfig (..), Proxy (..), isMalformedProxyUri, toURI) import PostgREST.SchemaCache (SchemaCache (..)) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..)) -import PostgREST.SchemaCache.Proc (ProcDescription (..), - ProcParam (..)) import PostgREST.SchemaCache.Relationship (Cardinality (..), Relationship (..), RelationshipsMap) +import PostgREST.SchemaCache.Routine (Routine (..), + RoutineParam (..)) import PostgREST.SchemaCache.Table (Column (..), Table (..), TablesMap, tableColumnsList) @@ -42,7 +42,7 @@ import PostgREST.MediaType import Protolude hiding (Proxy, get) -encode :: AppConfig -> SchemaCache -> TablesMap -> HM.HashMap k [ProcDescription] -> Maybe Text -> LBS.ByteString +encode :: AppConfig -> SchemaCache -> TablesMap -> HM.HashMap k [Routine] -> Maybe Text -> LBS.ByteString encode conf sCache tables procs schemaDescription = JSON.encode $ postgrestSpec @@ -145,7 +145,7 @@ makeProperty tbl rels col = (colName col, Inline s) & type_ .~ toSwaggerType (colType col) & items .~ (SwaggerItemsObject <$> makePropertyItems (colType col)) -makeProcSchema :: ProcDescription -> Schema +makeProcSchema :: Routine -> Schema makeProcSchema pd = (mempty :: Schema) & description .~ pdDescription pd @@ -153,8 +153,8 @@ makeProcSchema pd = & properties .~ fromList (fmap makeProcProperty (pdParams pd)) & required .~ fmap ppName (filter ppReq (pdParams pd)) -makeProcProperty :: ProcParam -> (Text, Referenced Schema) -makeProcProperty (ProcParam n t _ _) = (n, Inline s) +makeProcProperty :: RoutineParam -> (Text, Referenced Schema) +makeProcProperty (RoutineParam n t _ _) = (n, Inline s) where s = (mempty :: Schema) & type_ .~ toSwaggerType t @@ -180,8 +180,8 @@ makePreferParam ts = "resolution" -> ["resolution=ignore-duplicates", "resolution=merge-duplicates"] _ -> [] -makeProcGetParam :: ProcParam -> Referenced Param -makeProcGetParam (ProcParam n t r v) = +makeProcGetParam :: RoutineParam -> Referenced Param +makeProcGetParam (RoutineParam n t r v) = Inline $ (mempty :: Param) & name .~ n & required ?~ r @@ -206,10 +206,10 @@ makeProcGetParam (ProcParam n t r v) = Nothing -> SwaggerString _ -> fromJust paramType -makeProcGetParams :: [ProcParam] -> [Referenced Param] +makeProcGetParams :: [RoutineParam] -> [Referenced Param] makeProcGetParams = fmap makeProcGetParam -makeProcPostParams :: ProcDescription -> [Referenced Param] +makeProcPostParams :: Routine -> [Referenced Param] makeProcPostParams pd = [ Inline $ (mempty :: Param) & name .~ "args" @@ -340,7 +340,7 @@ makePathItem t = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdatable t rs = [ T.intercalate "." ["rowFilter", tn, colName c ] | c <- tableColumnsList t ] ref = Ref . Reference -makeProcPathItem :: ProcDescription -> (FilePath, PathItem) +makeProcPathItem :: Routine -> (FilePath, PathItem) makeProcPathItem pd = ("/rpc/" ++ toS (pdName pd), pe) where -- Use first line of proc description as summary; rest as description (if present) @@ -372,7 +372,7 @@ makeRootPathItem = ("/", p) pr = (mempty :: PathItem) & get ?~ getOp p = pr -makePathItems :: [ProcDescription] -> [Table] -> InsOrdHashMap FilePath PathItem +makePathItems :: [Routine] -> [Table] -> InsOrdHashMap FilePath PathItem makePathItems pds ti = fromList $ makeRootPathItem : fmap makePathItem ti ++ fmap makeProcPathItem pds @@ -392,7 +392,7 @@ escapeHostName "*6" = "0.0.0.0" escapeHostName "!6" = "0.0.0.0" escapeHostName h = h -postgrestSpec :: RelationshipsMap -> [ProcDescription] -> [Table] -> (Text, Text, Integer, Text) -> Maybe Text -> Bool -> Swagger +postgrestSpec :: RelationshipsMap -> [Routine] -> [Table] -> (Text, Text, Integer, Text) -> Maybe Text -> Bool -> Swagger postgrestSpec rels pds ti (s, h, p, b) sd allowSecurityDef = (mempty :: Swagger) & basePath ?~ T.unpack b & schemes ?~ [s'] diff --git a/src/PostgREST/SchemaCache.hs b/src/PostgREST/SchemaCache.hs index 859d83954..cd61e0dca 100644 --- a/src/PostgREST/SchemaCache.hs +++ b/src/PostgREST/SchemaCache.hs @@ -22,7 +22,7 @@ module PostgREST.SchemaCache ( SchemaCache(..) , querySchemaCache , accessibleTables - , accessibleProcs + , accessibleFuncs , schemaDescription ) where @@ -44,15 +44,14 @@ import PostgREST.Config.PgVersion (PgVersion, pgVersion100, import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName, QualifiedIdentifier (..), Schema) -import PostgREST.SchemaCache.Proc (PgType (..), - ProcDescription (..), - ProcParam (..), - ProcVolatility (..), - ProcsMap, RetType (..)) import PostgREST.SchemaCache.Relationship (Cardinality (..), Junction (..), Relationship (..), RelationshipsMap) +import PostgREST.SchemaCache.Routine (FuncVolatility (..), + PgType (..), RetType (..), + Routine (..), RoutineMap, + RoutineParam (..)) import PostgREST.SchemaCache.Table (Column (..), ColumnMap, Table (..), TablesMap) @@ -62,7 +61,7 @@ import Protolude data SchemaCache = SchemaCache { dbTables :: TablesMap , dbRelationships :: RelationshipsMap - , dbProcs :: ProcsMap + , dbRoutines :: RoutineMap } deriving (Generic, JSON.ToJSON) @@ -111,7 +110,7 @@ querySchemaCache schemas extraSearchPath prepared = do tabs <- SQL.statement schemas $ allTables pgVer prepared keyDeps <- SQL.statement (schemas, extraSearchPath) $ allViewsKeyDependencies prepared m2oRels <- SQL.statement mempty $ allM2OandO2ORels pgVer prepared - procs <- SQL.statement schemas $ allProcs pgVer prepared + funcs <- SQL.statement schemas $ allFunctions pgVer prepared cRels <- SQL.statement mempty $ allComputedRels prepared let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps @@ -120,7 +119,7 @@ querySchemaCache schemas extraSearchPath prepared = do return $ removeInternal schemas $ SchemaCache { dbTables = tabsWViewsPks , dbRelationships = getOverrideRelationshipsMap rels cRels - , dbProcs = procs + , dbRoutines = funcs } -- | overrides detected relationships with the computed relationships and gets the RelationshipsMap @@ -150,7 +149,7 @@ removeInternal schemas dbStruct = dbTables = HM.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct , dbRelationships = filter (\r -> qiSchema (relForeignTable r) `elem` schemas && not (hasInternalJunction r)) <$> HM.filterWithKey (\(QualifiedIdentifier sch _, _) _ -> sch `elem` schemas ) (dbRelationships dbStruct) - , dbProcs = dbProcs dbStruct -- procs are only obtained from the exposed schemas, no need to filter them. + , dbRoutines = dbRoutines dbStruct -- procs are only obtained from the exposed schemas, no need to filter them. } where hasInternalJunction ComputedRelationship{} = False @@ -228,17 +227,17 @@ viewKeyDepFromRow (s1,t1,s2,v2,cons,consType,sCols) = ViewKeyDependency (Qualifi | consType == "f" = FKDep | otherwise = FKDepRef -- f_ref, we build this type in the query -decodeProcs :: HD.Result ProcsMap -decodeProcs = - -- Duplicate rows for a function means they're overloaded, order these by least args according to ProcDescription Ord instance - map sort . HM.fromListWith (++) . map ((\(x,y) -> (x, [y])) . addKey) <$> HD.rowList procRow +decodeFuncs :: HD.Result RoutineMap +decodeFuncs = + -- Duplicate rows for a function means they're overloaded, order these by least args according to Routine Ord instance + map sort . HM.fromListWith (++) . map ((\(x,y) -> (x, [y])) . addKey) <$> HD.rowList funcRow where - procRow = ProcDescription + funcRow = Function <$> column HD.text <*> column HD.text <*> nullableColumn HD.text <*> compositeArrayColumn - (ProcParam + (RoutineParam <$> compositeField HD.text <*> compositeField HD.text <*> compositeField HD.bool @@ -253,7 +252,7 @@ decodeProcs = <*> (parseVolatility <$> column HD.char) <*> column HD.bool - addKey :: ProcDescription -> (QualifiedIdentifier, ProcDescription) + addKey :: Routine -> (QualifiedIdentifier, Routine) addKey pd = (QualifiedIdentifier (pdSchema pd) (pdName pd), pd) parseRetType :: Text -> Text -> Bool -> Bool -> Bool -> Bool -> RetType @@ -267,23 +266,23 @@ decodeProcs = | isComposite = Composite qi isCompositeAlias | otherwise = Scalar False - parseVolatility :: Char -> ProcVolatility + parseVolatility :: Char -> FuncVolatility parseVolatility v | v == 'i' = Immutable | v == 's' = Stable | otherwise = Volatile -- only 'v' can happen here -allProcs :: PgVersion -> Bool -> SQL.Statement [Schema] ProcsMap -allProcs pgVer = SQL.Statement sql (arrayParam HE.text) decodeProcs +allFunctions :: PgVersion -> Bool -> SQL.Statement [Schema] RoutineMap +allFunctions pgVer = SQL.Statement sql (arrayParam HE.text) decodeFuncs where - sql = procsSqlQuery pgVer <> " AND pn.nspname = ANY($1)" + sql = funcsSqlQuery pgVer <> " AND pn.nspname = ANY($1)" -accessibleProcs :: PgVersion -> Bool -> SQL.Statement Schema ProcsMap -accessibleProcs pgVer = SQL.Statement sql (param HE.text) decodeProcs +accessibleFuncs :: PgVersion -> Bool -> SQL.Statement Schema RoutineMap +accessibleFuncs pgVer = SQL.Statement sql (param HE.text) decodeFuncs where - sql = procsSqlQuery pgVer <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" + sql = funcsSqlQuery pgVer <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" -procsSqlQuery :: PgVersion -> SqlQuery -procsSqlQuery pgVer = [q| +funcsSqlQuery :: PgVersion -> SqlQuery +funcsSqlQuery pgVer = [q| -- Recursively get the base types of domains WITH base_types AS ( diff --git a/src/PostgREST/SchemaCache/Proc.hs b/src/PostgREST/SchemaCache/Proc.hs deleted file mode 100644 index e1337d4da..000000000 --- a/src/PostgREST/SchemaCache/Proc.hs +++ /dev/null @@ -1,103 +0,0 @@ -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} - -module PostgREST.SchemaCache.Proc - ( PgType(..) - , ProcDescription(..) - , ProcParam(..) - , ProcVolatility(..) - , ProcsMap - , RetType(..) - , procReturnsScalar - , procReturnsSetOfScalar - , procReturnsSingleComposite - , procReturnsVoid - , procTableName - , procReturnsCompositeAlias - ) where - -import qualified Data.Aeson as JSON -import qualified Data.HashMap.Strict as HM - -import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..), - Schema, TableName) - -import Protolude - -data PgType - = Scalar Bool -- True if the type is void - | Composite QualifiedIdentifier Bool -- True if the composite is a domain alias(used to work around a bug in pg 11 and 12, see QueryBuilder.hs) - deriving (Eq, Ord, Generic, JSON.ToJSON) - -data RetType - = Single PgType - | SetOf PgType - deriving (Eq, Ord, Generic, JSON.ToJSON) - -data ProcVolatility - = Volatile - | Stable - | Immutable - deriving (Eq, Ord, Generic, JSON.ToJSON) - -data ProcDescription = ProcDescription - { pdSchema :: Schema - , pdName :: Text - , pdDescription :: Maybe Text - , pdParams :: [ProcParam] - , pdReturnType :: RetType - , pdVolatility :: ProcVolatility - , pdHasVariadic :: Bool - } - deriving (Eq, Generic, JSON.ToJSON) - -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 - ProcDescription schema1 name1 des1 prms1 rt1 vol1 hasVar1 `compare` ProcDescription schema2 name2 des2 prms2 rt2 vol2 hasVar2 - | schema1 == schema2 && name1 == name2 && length prms1 < length prms2 = LT - | schema2 == schema2 && name1 == name2 && length prms1 > length prms2 = GT - | 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). --- | It uses a HashMap for a faster lookup. -type ProcsMap = HM.HashMap QualifiedIdentifier [ProcDescription] - -procReturnsScalar :: ProcDescription -> Bool -procReturnsScalar proc = case proc of - ProcDescription{pdReturnType = Single (Scalar _)} -> True - _ -> False - -procReturnsSetOfScalar :: ProcDescription -> Bool -procReturnsSetOfScalar proc = case proc of - ProcDescription{pdReturnType = SetOf (Scalar _)} -> True - _ -> False - -procReturnsCompositeAlias :: ProcDescription -> Bool -procReturnsCompositeAlias proc = case proc of - ProcDescription{pdReturnType = Single (Composite _ True)} -> True - ProcDescription{pdReturnType = SetOf (Composite _ True)} -> 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 - -procTableName :: ProcDescription -> Maybe TableName -procTableName proc = case pdReturnType proc of - SetOf (Composite qi _) -> Just $ qiName qi - Single (Composite qi _) -> Just $ qiName qi - _ -> Nothing diff --git a/src/PostgREST/SchemaCache/Routine.hs b/src/PostgREST/SchemaCache/Routine.hs new file mode 100644 index 000000000..2a5c0e7fc --- /dev/null +++ b/src/PostgREST/SchemaCache/Routine.hs @@ -0,0 +1,103 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} + +module PostgREST.SchemaCache.Routine + ( PgType(..) + , Routine(..) + , RoutineParam(..) + , FuncVolatility(..) + , RoutineMap + , RetType(..) + , funcReturnsScalar + , funcReturnsSetOfScalar + , funcReturnsSingleComposite + , funcReturnsVoid + , funcTableName + , funcReturnsCompositeAlias + ) where + +import qualified Data.Aeson as JSON +import qualified Data.HashMap.Strict as HM + +import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..), + Schema, TableName) + +import Protolude + +data PgType + = Scalar Bool -- True if the type is void + | Composite QualifiedIdentifier Bool -- True if the composite is a domain alias(used to work around a bug in pg 11 and 12, see QueryBuilder.hs) + deriving (Eq, Ord, Generic, JSON.ToJSON) + +data RetType + = Single PgType + | SetOf PgType + deriving (Eq, Ord, Generic, JSON.ToJSON) + +data FuncVolatility + = Volatile + | Stable + | Immutable + deriving (Eq, Ord, Generic, JSON.ToJSON) + +data Routine = Function + { pdSchema :: Schema + , pdName :: Text + , pdDescription :: Maybe Text + , pdParams :: [RoutineParam] + , pdReturnType :: RetType + , pdVolatility :: FuncVolatility + , pdHasVariadic :: Bool + } + deriving (Eq, Generic, JSON.ToJSON) + +data RoutineParam = RoutineParam + { 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 Routine where + Function schema1 name1 des1 prms1 rt1 vol1 hasVar1 `compare` Function schema2 name2 des2 prms2 rt2 vol2 hasVar2 + | schema1 == schema2 && name1 == name2 && length prms1 < length prms2 = LT + | schema2 == schema2 && name1 == name2 && length prms1 > length prms2 = GT + | 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 Routine). +-- | It uses a HashMap for a faster lookup. +type RoutineMap = HM.HashMap QualifiedIdentifier [Routine] + +funcReturnsScalar :: Routine -> Bool +funcReturnsScalar proc = case proc of + Function{pdReturnType = Single (Scalar _)} -> True + _ -> False + +funcReturnsSetOfScalar :: Routine -> Bool +funcReturnsSetOfScalar proc = case proc of + Function{pdReturnType = SetOf (Scalar _)} -> True + _ -> False + +funcReturnsCompositeAlias :: Routine -> Bool +funcReturnsCompositeAlias proc = case proc of + Function{pdReturnType = Single (Composite _ True)} -> True + Function{pdReturnType = SetOf (Composite _ True)} -> True + _ -> False + +funcReturnsSingleComposite :: Routine -> Bool +funcReturnsSingleComposite proc = case proc of + Function{pdReturnType = Single (Composite _ _)} -> True + _ -> False + +funcReturnsVoid :: Routine -> Bool +funcReturnsVoid proc = case proc of + Function{pdReturnType = Single (Scalar True)} -> True + _ -> False + +funcTableName :: Routine -> Maybe TableName +funcTableName proc = case pdReturnType proc of + SetOf (Composite qi _) -> Just $ qiName qi + Single (Composite qi _) -> Just $ qiName qi + _ -> Nothing