diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs index d5d4d794ffc6c..560db3558bcfb 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs @@ -17,10 +17,12 @@ module Hasura.RQL.DDL.Schema.Diff , fetchFunctionMeta , FunctionDiff(..) , getFuncDiff + , getOverloadedFuncs ) where import Hasura.Prelude import Hasura.RQL.Types +import Hasura.Server.Utils (duplicates) import Hasura.SQL.Types import qualified Database.PG.Query as Q @@ -257,6 +259,7 @@ fetchFunctionMeta = do ) WHERE f.function_schema <> 'hdb_catalog' + GROUP BY p.oid, f.function_schema, f.function_name, f.function_type |] () False data FunctionDiff @@ -275,3 +278,11 @@ getFuncDiff oldMeta newMeta = let isTypeAltered = fmType oldfm /= fmType newfm alteredFunc = (funcFromMeta oldfm, fmType newfm) in bool Nothing (Just alteredFunc) isTypeAltered + +getOverloadedFuncs + :: [QualifiedFunction] -> [FunctionMeta] -> [QualifiedFunction] +getOverloadedFuncs trackedFuncs newFuncMeta = + duplicates $ map funcFromMeta trackedMeta + where + trackedMeta = flip filter newFuncMeta $ \fm -> + funcFromMeta fm `elem` trackedFuncs diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs index 85b66c5adc46f..8535d0188a47a 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs @@ -442,6 +442,12 @@ execWithMDCheck (RunSQL t cascade _) = do existingFuncs = M.keys $ scFunctions sc oldFuncMeta = flip filter oldFuncMetaU $ \fm -> funcFromMeta fm `elem` existingFuncs FunctionDiff droppedFuncs alteredFuncs = getFuncDiff oldFuncMeta newFuncMeta + overloadedFuncs = getOverloadedFuncs existingFuncs newFuncMeta + + -- Do not allow overloading functions + unless (null overloadedFuncs) $ + throw400 NotSupported $ "the following tracked function(s) cannot be overloaded: " + <> reportFuncs overloadedFuncs indirectDeps <- getSchemaChangeDeps schemaDiff @@ -490,12 +496,14 @@ execWithMDCheck (RunSQL t cascade _) = do refreshGCtxMapInSchema return res + where + reportFuncs = T.intercalate ", " . map dquoteTxt isAltrDropReplace :: QErrM m => T.Text -> m Bool isAltrDropReplace = either throwErr return . matchRegex regex False where throwErr s = throw500 $ "compiling regex failed: " <> T.pack s - regex = "alter|drop|replace" + regex = "alter|drop|replace|create function" runRunSQL :: (QErrM m, UserInfoM m, CacheRWM m, MonadTx m, MonadIO m, HasHttpManager m) diff --git a/server/src-lib/Hasura/SQL/Types.hs b/server/src-lib/Hasura/SQL/Types.hs index e171cb5afdf47..0d1fc30a44ec5 100644 --- a/server/src-lib/Hasura/SQL/Types.hs +++ b/server/src-lib/Hasura/SQL/Types.hs @@ -143,7 +143,7 @@ instance ToSQL ConstraintName where newtype FunctionName = FunctionName { getFunctionTxt :: T.Text } - deriving (Show, Eq, FromJSON, ToJSON, Q.ToPrepArg, Q.FromCol, Hashable, Lift) + deriving (Show, Eq, Ord, FromJSON, ToJSON, Q.ToPrepArg, Q.FromCol, Hashable, Lift) instance IsIden FunctionName where toIden (FunctionName t) = Iden t @@ -159,7 +159,7 @@ instance ToTxt FunctionName where newtype SchemaName = SchemaName { getSchemaTxt :: T.Text } - deriving (Show, Eq, FromJSON, ToJSON, Hashable, Q.ToPrepArg, Q.FromCol, Lift) + deriving (Show, Eq, Ord, FromJSON, ToJSON, Hashable, Q.ToPrepArg, Q.FromCol, Lift) publicSchema :: SchemaName publicSchema = SchemaName "public" @@ -174,7 +174,7 @@ data QualifiedObject a = QualifiedObject { qSchema :: !SchemaName , qName :: !a - } deriving (Show, Eq, Generic, Lift) + } deriving (Show, Eq, Ord, Generic, Lift) instance (FromJSON a) => FromJSON (QualifiedObject a) where parseJSON v@(String _) = diff --git a/server/tests-py/queries/graphql_query/functions/overloading_function_error.yaml b/server/tests-py/queries/graphql_query/functions/overloading_function_error.yaml new file mode 100644 index 0000000000000..bacb39c67dc15 --- /dev/null +++ b/server/tests-py/queries/graphql_query/functions/overloading_function_error.yaml @@ -0,0 +1,20 @@ +description: Create a new SQL function with same name (error) +url: /v1/query +status: 400 +response: + path: "$.args" + error: 'the following tracked function(s) cannot be overloaded: search_posts' + code: not-supported +query: + type: run_sql + args: + sql: | + create function search_posts(search text, id integer) + returns setof post as $$ + select * + from post + where + title ilike ('%' || search || '%') or + content ilike ('%' || search || '%') or + id = id + $$ language sql stable; diff --git a/server/tests-py/test_graphql_queries.py b/server/tests-py/test_graphql_queries.py index 5b1188c4c7142..768300cd50d58 100644 --- a/server/tests-py/test_graphql_queries.py +++ b/server/tests-py/test_graphql_queries.py @@ -342,6 +342,9 @@ def test_search_posts_aggregate(self, hge_ctx): def test_alter_function_error(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/alter_function_error.yaml') + def test_overloading_function_error(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/overloading_function_error.yaml') + @classmethod def dir(cls): return 'queries/graphql_query/functions'