2019-07-11 10:41:20 +03:00
|
|
|
{- |
|
|
|
|
Description: Create/delete SQL functions to/from Hasura metadata.
|
|
|
|
-}
|
|
|
|
|
2019-01-25 06:31:54 +03:00
|
|
|
module Hasura.RQL.DDL.Schema.Function where
|
|
|
|
|
2019-05-08 10:36:43 +03:00
|
|
|
import Hasura.EncJSON
|
2019-09-19 07:47:36 +03:00
|
|
|
import Hasura.GraphQL.Utils (showNames)
|
2019-01-25 06:31:54 +03:00
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.Types
|
|
|
|
import Hasura.SQL.Types
|
|
|
|
|
|
|
|
import Data.Aeson
|
|
|
|
import Data.Aeson.Casing
|
|
|
|
import Data.Aeson.TH
|
|
|
|
import Language.Haskell.TH.Syntax (Lift)
|
|
|
|
|
|
|
|
import qualified Hasura.GraphQL.Schema as GS
|
|
|
|
import qualified Language.GraphQL.Draft.Syntax as G
|
|
|
|
|
|
|
|
import qualified Data.HashMap.Strict as M
|
|
|
|
import qualified Data.Sequence as Seq
|
|
|
|
import qualified Data.Text as T
|
|
|
|
import qualified Database.PG.Query as Q
|
|
|
|
|
|
|
|
|
|
|
|
data PGTypType
|
|
|
|
= PTBASE
|
|
|
|
| PTCOMPOSITE
|
|
|
|
| PTDOMAIN
|
|
|
|
| PTENUM
|
|
|
|
| PTRANGE
|
2019-09-17 04:51:11 +03:00
|
|
|
| PTPSEUDO
|
2019-01-25 06:31:54 +03:00
|
|
|
deriving (Show, Eq)
|
|
|
|
$(deriveJSON defaultOptions{constructorTagModifier = drop 2} ''PGTypType)
|
|
|
|
|
|
|
|
data RawFuncInfo
|
|
|
|
= RawFuncInfo
|
|
|
|
{ rfiHasVariadic :: !Bool
|
|
|
|
, rfiFunctionType :: !FunctionType
|
|
|
|
, rfiReturnTypeSchema :: !SchemaName
|
|
|
|
, rfiReturnTypeName :: !T.Text
|
|
|
|
, rfiReturnTypeType :: !PGTypType
|
|
|
|
, rfiReturnsSet :: !Bool
|
2019-08-06 18:27:35 +03:00
|
|
|
, rfiInputArgTypes :: ![PGScalarType]
|
2019-01-25 06:31:54 +03:00
|
|
|
, rfiInputArgNames :: ![T.Text]
|
2019-08-28 22:27:15 +03:00
|
|
|
, rfiDefaultArgs :: !Int
|
2019-01-25 06:31:54 +03:00
|
|
|
, rfiReturnsTable :: !Bool
|
2019-09-17 04:51:11 +03:00
|
|
|
, rfiDescription :: !(Maybe PGDescription)
|
2019-01-25 06:31:54 +03:00
|
|
|
} deriving (Show, Eq)
|
2019-05-08 10:36:43 +03:00
|
|
|
$(deriveJSON (aesonDrop 3 snakeCase) ''RawFuncInfo)
|
2019-01-25 06:31:54 +03:00
|
|
|
|
2019-08-28 22:27:15 +03:00
|
|
|
mkFunctionArgs :: Int -> [PGScalarType] -> [T.Text] -> [FunctionArg]
|
|
|
|
mkFunctionArgs defArgsNo tys argNames =
|
2019-01-25 06:31:54 +03:00
|
|
|
bool withNames withNoNames $ null argNames
|
|
|
|
where
|
2019-08-28 22:27:15 +03:00
|
|
|
hasDefaultBoolSeq = replicate (length argNames - defArgsNo) False
|
|
|
|
-- only last arguments can have default expression
|
|
|
|
<> replicate defArgsNo True
|
2019-01-25 06:31:54 +03:00
|
|
|
|
2019-08-28 22:27:15 +03:00
|
|
|
tysWithHasDefault = zip tys hasDefaultBoolSeq
|
|
|
|
|
|
|
|
withNoNames = flip map tysWithHasDefault $
|
|
|
|
\(ty, hasDef) -> FunctionArg Nothing ty hasDef
|
|
|
|
withNames = zipWith mkArg argNames tysWithHasDefault
|
|
|
|
|
|
|
|
mkArg "" (ty, hasDef) = FunctionArg Nothing ty hasDef
|
|
|
|
mkArg n (ty, hasDef) = FunctionArg (Just $ FunctionArgName n) ty hasDef
|
2019-01-25 06:31:54 +03:00
|
|
|
|
|
|
|
validateFuncArgs :: MonadError QErr m => [FunctionArg] -> m ()
|
|
|
|
validateFuncArgs args =
|
|
|
|
unless (null invalidArgs) $ throw400 NotSupported $
|
|
|
|
"arguments: " <> showNames invalidArgs
|
|
|
|
<> " are not in compliance with GraphQL spec"
|
|
|
|
where
|
|
|
|
funcArgsText = mapMaybe (fmap getFuncArgNameTxt . faName) args
|
2019-09-19 07:47:36 +03:00
|
|
|
invalidArgs = filter (not . G.isValidName) $ map G.Name funcArgsText
|
2019-01-25 06:31:54 +03:00
|
|
|
|
2019-05-08 10:36:43 +03:00
|
|
|
mkFunctionInfo
|
2019-10-11 08:13:57 +03:00
|
|
|
:: (QErrM m, HasSystemDefined m) => QualifiedFunction -> RawFuncInfo -> m FunctionInfo
|
2019-01-25 06:31:54 +03:00
|
|
|
mkFunctionInfo qf rawFuncInfo = do
|
|
|
|
-- throw error if function has variadic arguments
|
|
|
|
when hasVariadic $ throw400 NotSupported "function with \"VARIADIC\" parameters are not supported"
|
|
|
|
-- throw error if return type is not composite type
|
|
|
|
when (retTyTyp /= PTCOMPOSITE) $ throw400 NotSupported "function does not return a \"COMPOSITE\" type"
|
|
|
|
-- throw error if function do not returns SETOF
|
|
|
|
unless retSet $ throw400 NotSupported "function does not return a SETOF"
|
|
|
|
-- throw error if return type is not a valid table
|
|
|
|
unless returnsTab $ throw400 NotSupported "function does not return a SETOF table"
|
|
|
|
-- throw error if function type is VOLATILE
|
|
|
|
when (funTy == FTVOLATILE) $ throw400 NotSupported "function of type \"VOLATILE\" is not supported now"
|
|
|
|
|
2019-08-28 22:27:15 +03:00
|
|
|
let funcArgs = mkFunctionArgs defArgsNo inpArgTyps inpArgNames
|
2019-01-25 06:31:54 +03:00
|
|
|
validateFuncArgs funcArgs
|
|
|
|
|
2019-10-11 08:13:57 +03:00
|
|
|
systemDefined <- askSystemDefined
|
2019-01-25 06:31:54 +03:00
|
|
|
let funcArgsSeq = Seq.fromList funcArgs
|
2019-08-17 00:35:22 +03:00
|
|
|
dep = SchemaDependency (SOTable retTable) DRTable
|
2019-01-25 06:31:54 +03:00
|
|
|
retTable = QualifiedObject retSn (TableName retN)
|
2019-10-11 08:13:57 +03:00
|
|
|
return $ FunctionInfo qf systemDefined funTy funcArgsSeq retTable [dep] descM
|
2019-01-25 06:31:54 +03:00
|
|
|
where
|
2019-09-17 04:51:11 +03:00
|
|
|
RawFuncInfo hasVariadic funTy retSn retN retTyTyp retSet
|
|
|
|
inpArgTyps inpArgNames defArgsNo returnsTab descM
|
2019-01-25 06:31:54 +03:00
|
|
|
= rawFuncInfo
|
|
|
|
|
2019-10-11 08:13:57 +03:00
|
|
|
saveFunctionToCatalog :: QualifiedFunction -> SystemDefined -> Q.TxE QErr ()
|
|
|
|
saveFunctionToCatalog (QualifiedObject sn fn) systemDefined =
|
2019-01-25 06:31:54 +03:00
|
|
|
Q.unitQE defaultTxErrorHandler [Q.sql|
|
|
|
|
INSERT INTO "hdb_catalog"."hdb_function" VALUES ($1, $2, $3)
|
2019-10-11 08:13:57 +03:00
|
|
|
|] (sn, fn, systemDefined) False
|
2019-01-25 06:31:54 +03:00
|
|
|
|
|
|
|
delFunctionFromCatalog :: QualifiedFunction -> Q.TxE QErr ()
|
|
|
|
delFunctionFromCatalog (QualifiedObject sn fn) =
|
|
|
|
Q.unitQE defaultTxErrorHandler [Q.sql|
|
|
|
|
DELETE FROM hdb_catalog.hdb_function
|
|
|
|
WHERE function_schema = $1
|
|
|
|
AND function_name = $2
|
|
|
|
|] (sn, fn) False
|
|
|
|
|
|
|
|
newtype TrackFunction
|
|
|
|
= TrackFunction
|
|
|
|
{ tfName :: QualifiedFunction}
|
|
|
|
deriving (Show, Eq, FromJSON, ToJSON, Lift)
|
|
|
|
|
2019-07-11 10:41:20 +03:00
|
|
|
-- | Track function, Phase 1:
|
|
|
|
-- Validate function tracking operation. Fails if function is already being
|
|
|
|
-- tracked, or if a table with the same name is being tracked.
|
2019-01-25 06:31:54 +03:00
|
|
|
trackFunctionP1
|
|
|
|
:: (CacheRM m, UserInfoM m, QErrM m) => TrackFunction -> m ()
|
|
|
|
trackFunctionP1 (TrackFunction qf) = do
|
|
|
|
adminOnly
|
|
|
|
rawSchemaCache <- askSchemaCache
|
|
|
|
when (M.member qf $ scFunctions rawSchemaCache) $
|
|
|
|
throw400 AlreadyTracked $ "function already tracked : " <>> qf
|
2019-07-11 10:41:20 +03:00
|
|
|
let qt = fmap (TableName . getFunctionTxt) qf
|
|
|
|
when (M.member qt $ scTables rawSchemaCache) $
|
|
|
|
throw400 NotSupported $ "table with name " <> qf <<> " already exists"
|
2019-01-25 06:31:54 +03:00
|
|
|
|
2019-10-11 08:13:57 +03:00
|
|
|
trackFunctionP2Setup :: (QErrM m, CacheRWM m, HasSystemDefined m, MonadTx m)
|
2019-05-08 10:36:43 +03:00
|
|
|
=> QualifiedFunction -> RawFuncInfo -> m ()
|
|
|
|
trackFunctionP2Setup qf rawfi = do
|
|
|
|
fi <- mkFunctionInfo qf rawfi
|
2019-01-25 06:31:54 +03:00
|
|
|
let retTable = fiReturnType fi
|
|
|
|
err = err400 NotExists $ "table " <> retTable <<> " is not tracked"
|
|
|
|
sc <- askSchemaCache
|
|
|
|
void $ liftMaybe err $ M.lookup retTable $ scTables sc
|
|
|
|
addFunctionToCache fi
|
|
|
|
|
2019-10-11 08:13:57 +03:00
|
|
|
trackFunctionP2 :: (QErrM m, CacheRWM m, HasSystemDefined m, MonadTx m)
|
2019-03-18 19:22:21 +03:00
|
|
|
=> QualifiedFunction -> m EncJSON
|
2019-01-25 06:31:54 +03:00
|
|
|
trackFunctionP2 qf = do
|
|
|
|
sc <- askSchemaCache
|
|
|
|
let defGCtx = scDefaultRemoteGCtx sc
|
|
|
|
funcNameGQL = GS.qualObjectToName qf
|
|
|
|
-- check function name is in compliance with GraphQL spec
|
2019-09-19 07:47:36 +03:00
|
|
|
unless (G.isValidName funcNameGQL) $ throw400 NotSupported $
|
2019-01-25 06:31:54 +03:00
|
|
|
"function name " <> qf <<> " is not in compliance with GraphQL spec"
|
|
|
|
-- check for conflicts in remote schema
|
|
|
|
GS.checkConflictingNode defGCtx funcNameGQL
|
2019-05-08 10:36:43 +03:00
|
|
|
|
|
|
|
-- fetch function info
|
|
|
|
functionInfos <- liftTx fetchFuncDets
|
|
|
|
rawfi <- case functionInfos of
|
|
|
|
[] ->
|
|
|
|
throw400 NotExists $ "no such function exists in postgres : " <>> qf
|
|
|
|
[rawfi] -> return rawfi
|
|
|
|
_ ->
|
|
|
|
throw400 NotSupported $
|
|
|
|
"function " <> qf <<> " is overloaded. Overloaded functions are not supported"
|
|
|
|
trackFunctionP2Setup qf rawfi
|
2019-10-11 08:13:57 +03:00
|
|
|
systemDefined <- askSystemDefined
|
|
|
|
liftTx $ saveFunctionToCatalog qf systemDefined
|
2019-01-25 06:31:54 +03:00
|
|
|
return successMsg
|
2019-05-08 10:36:43 +03:00
|
|
|
where
|
|
|
|
QualifiedObject sn fn = qf
|
|
|
|
fetchFuncDets = map (Q.getAltJ . runIdentity) <$>
|
|
|
|
Q.listQE defaultTxErrorHandler [Q.sql|
|
|
|
|
SELECT function_info
|
|
|
|
FROM hdb_catalog.hdb_function_info_agg
|
|
|
|
WHERE function_schema = $1
|
|
|
|
AND function_name = $2
|
|
|
|
|] (sn, fn) True
|
2019-01-25 06:31:54 +03:00
|
|
|
|
|
|
|
runTrackFunc
|
2019-10-11 08:13:57 +03:00
|
|
|
:: ( QErrM m, CacheRWM m, HasSystemDefined m
|
|
|
|
, MonadTx m, UserInfoM m
|
2019-01-25 06:31:54 +03:00
|
|
|
)
|
2019-03-18 19:22:21 +03:00
|
|
|
=> TrackFunction -> m EncJSON
|
2019-01-25 06:31:54 +03:00
|
|
|
runTrackFunc q = do
|
|
|
|
trackFunctionP1 q
|
|
|
|
trackFunctionP2 $ tfName q
|
|
|
|
|
|
|
|
newtype UnTrackFunction
|
|
|
|
= UnTrackFunction
|
|
|
|
{ utfName :: QualifiedFunction }
|
|
|
|
deriving (Show, Eq, FromJSON, ToJSON, Lift)
|
|
|
|
|
|
|
|
runUntrackFunc
|
|
|
|
:: ( QErrM m, CacheRWM m, MonadTx m
|
|
|
|
, UserInfoM m
|
|
|
|
)
|
2019-03-18 19:22:21 +03:00
|
|
|
=> UnTrackFunction -> m EncJSON
|
2019-01-25 06:31:54 +03:00
|
|
|
runUntrackFunc (UnTrackFunction qf) = do
|
|
|
|
adminOnly
|
|
|
|
void $ askFunctionInfo qf
|
|
|
|
liftTx $ delFunctionFromCatalog qf
|
|
|
|
delFunctionFromCache qf
|
|
|
|
return successMsg
|