diff --git a/.circleci/config.yml b/.circleci/config.yml index 43dffa39..2d5c1728 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -157,7 +157,7 @@ jobs: # build the server binary, and package into docker image build_server: docker: - - image: hasura/graphql-engine-server-builder:20190415-1 + - image: hasura/graphql-engine-server-builder:20190507-1 working_directory: ~/graphql-engine steps: - attach_workspace: @@ -223,7 +223,7 @@ jobs: environment: PG_VERSION: "11_1" docker: - - image: hasura/graphql-engine-server-builder:20190415-1 + - image: hasura/graphql-engine-server-builder:20190507-1 # TODO: change this to circleci postgis when they have one for pg 11 - image: mdillon/postgis:11-alpine <<: *test_pg_env @@ -233,7 +233,7 @@ jobs: environment: PG_VERSION: "10_6" docker: - - image: hasura/graphql-engine-server-builder:20190415-1 + - image: hasura/graphql-engine-server-builder:20190507-1 - image: circleci/postgres:10.6-alpine-postgis <<: *test_pg_env @@ -242,7 +242,7 @@ jobs: environment: PG_VERSION: "9_6" docker: - - image: hasura/graphql-engine-server-builder:20190415-1 + - image: hasura/graphql-engine-server-builder:20190507-1 - image: circleci/postgres:9.6-alpine-postgis <<: *test_pg_env @@ -251,7 +251,7 @@ jobs: environment: PG_VERSION: "9_5" docker: - - image: hasura/graphql-engine-server-builder:20190415-1 + - image: hasura/graphql-engine-server-builder:20190507-1 - image: circleci/postgres:9.5-alpine-postgis <<: *test_pg_env diff --git a/.circleci/server-builder.dockerfile b/.circleci/server-builder.dockerfile index e5ed894f..55b3b23c 100644 --- a/.circleci/server-builder.dockerfile +++ b/.circleci/server-builder.dockerfile @@ -3,7 +3,7 @@ FROM debian:stretch-20190228-slim ARG docker_ver="17.09.0-ce" -ARG resolver="lts-13.12" +ARG resolver="lts-13.20" ARG stack_ver="1.9.3" ARG postgres_ver="11" diff --git a/server/graphql-engine.cabal b/server/graphql-engine.cabal index 061f539a..141f610c 100644 --- a/server/graphql-engine.cabal +++ b/server/graphql-engine.cabal @@ -171,6 +171,7 @@ library , Hasura.RQL.Types.SchemaCache , Hasura.RQL.Types.SchemaCacheTypes , Hasura.RQL.Types.Common + , Hasura.RQL.Types.Catalog , Hasura.RQL.Types.BoolExp , Hasura.RQL.Types.Permission , Hasura.RQL.Types.Error diff --git a/server/src-exec/Main.hs b/server/src-exec/Main.hs index 56325a46..efe057e0 100644 --- a/server/src-exec/Main.hs +++ b/server/src-exec/Main.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE TypeApplications #-} module Main where import Migrate (migrateCatalog) @@ -16,6 +17,7 @@ import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BLC import qualified Data.Text as T +import qualified Data.Time.Clock as Clock import qualified Data.Yaml as Y import qualified Network.HTTP.Client as HTTP import qualified Network.HTTP.Client.TLS as HTTP @@ -122,6 +124,8 @@ main = do mJwtSecret mUnAuthRole corsCfg enableConsole enableTelemetry strfyNum enabledAPIs lqOpts) -> do let sqlGenCtx = SQLGenCtx strfyNum + + initTime <- Clock.getCurrentTime -- log serve options unLogger logger $ serveOptsToLog so hloggerCtx <- mkLoggerCtx $ defaultLoggerSettings False @@ -138,10 +142,7 @@ main = do pool <- Q.initPGPool ci cp pgLogger -- safe init catalog - initRes <- initialise sqlGenCtx logger ci httpManager - - -- prepare event triggers data - prepareEvents logger ci + initRes <- initialise pool sqlGenCtx logger httpManager (app, cacheRef, cacheInitTime) <- mkWaiApp isoL loggerCtx sqlGenCtx pool ci httpManager am @@ -161,8 +162,9 @@ main = do evFetchMilliSec <- getFromEnv defaultFetchIntervalMilliSec "HASURA_GRAPHQL_EVENTS_FETCH_INTERVAL" logEnvHeaders <- getFromEnv False "LOG_HEADERS_FROM_ENV" + -- prepare event triggers data + prepareEvents pool logger eventEngineCtx <- atomically $ initEventEngineCtx maxEvThrds evFetchMilliSec - let scRef = _scrCache cacheRef unLogger logger $ mkGenericStrLog "event_triggers" "starting workers" @@ -177,36 +179,42 @@ main = do unLogger logger $ mkGenericStrLog "telemetry" telemetryNotice void $ C.forkIO $ runTelemetry logger httpManager scRef initRes + finishTime <- Clock.getCurrentTime + let apiInitTime = realToFrac $ Clock.diffUTCTime finishTime initTime unLogger logger $ - mkGenericStrLog "server" "starting API server" + mkGenericStrLog "server" $ + "starting API server, took " <> show @Double apiInitTime <> "s" Warp.runSettings warpSettings app HCExport -> do ci <- procConnInfo rci - res <- runTx pgLogger ci fetchMetadata + res <- runTx' pgLogger ci fetchMetadata either printErrJExit printJSON res HCClean -> do ci <- procConnInfo rci - res <- runTx pgLogger ci cleanCatalog + res <- runTx' pgLogger ci cleanCatalog either printErrJExit (const cleanSuccess) res HCExecute -> do queryBs <- BL.getContents ci <- procConnInfo rci let sqlGenCtx = SQLGenCtx False - res <- runAsAdmin sqlGenCtx pgLogger ci httpManager $ execQuery queryBs + pool <- getMinimalPool pgLogger ci + res <- runAsAdmin pool sqlGenCtx httpManager $ execQuery queryBs either printErrJExit BLC.putStrLn res HCVersion -> putStrLn $ "Hasura GraphQL Engine: " ++ T.unpack currentVersion where - runTx pgLogger ci tx = do + runTx pool tx = + runExceptT $ Q.runTx pool (Q.Serializable, Nothing) tx + + runTx' pgLogger ci tx = do pool <- getMinimalPool pgLogger ci runExceptT $ Q.runTx pool (Q.Serializable, Nothing) tx - runAsAdmin sqlGenCtx pgLogger ci httpManager m = do - pool <- getMinimalPool pgLogger ci + runAsAdmin pool sqlGenCtx httpManager m = do res <- runExceptT $ peelRun emptySchemaCache adminUserInfo httpManager sqlGenCtx (PGExecCtx pool Q.Serializable) m return $ fmap fst res @@ -219,28 +227,28 @@ main = do let connParams = Q.defaultConnParams { Q.cpConns = 1 } Q.initPGPool ci connParams pgLogger - initialise sqlGenCtx (Logger logger) ci httpMgr = do + initialise pool sqlGenCtx (Logger logger) httpMgr = do currentTime <- getCurrentTime - let pgLogger = mkPGLogger $ Logger logger -- initialise the catalog - initRes <- runAsAdmin sqlGenCtx pgLogger ci httpMgr $ initCatalogSafe currentTime + initRes <- runAsAdmin pool sqlGenCtx httpMgr $ + initCatalogSafe currentTime either printErrJExit (logger . mkGenericStrLog "db_init") initRes -- migrate catalog if necessary - migRes <- runAsAdmin sqlGenCtx pgLogger ci httpMgr $ migrateCatalog currentTime + migRes <- runAsAdmin pool sqlGenCtx httpMgr $ + migrateCatalog currentTime either printErrJExit (logger . mkGenericStrLog "db_migrate") migRes -- generate and retrieve uuids - getUniqIds pgLogger ci + getUniqIds pool - prepareEvents (Logger logger) ci = do - let pgLogger = mkPGLogger $ Logger logger + prepareEvents pool (Logger logger) = do logger $ mkGenericStrLog "event_triggers" "preparing data" - res <- runTx pgLogger ci unlockAllEvents + res <- runTx pool unlockAllEvents either printErrJExit return res - getUniqIds pgLogger ci = do - eDbId <- runTx pgLogger ci getDbId + getUniqIds pool = do + eDbId <- runTx pool getDbId dbId <- either printErrJExit return eDbId fp <- liftIO generateFingerprint return (dbId, fp) diff --git a/server/src-exec/Migrate.hs b/server/src-exec/Migrate.hs index 07ac7545..5a99b370 100644 --- a/server/src-exec/Migrate.hs +++ b/server/src-exec/Migrate.hs @@ -19,7 +19,7 @@ import qualified Data.Yaml.TH as Y import qualified Database.PG.Query as Q curCatalogVer :: T.Text -curCatalogVer = "13" +curCatalogVer = "14" migrateMetadata :: ( MonadTx m @@ -272,6 +272,13 @@ from12To13 = liftTx $ do $(Q.sqlFromFile "src-rsr/migrate_from_12_to_13.sql") return () +from13To14 :: MonadTx m => m () +from13To14 = liftTx $ do + -- Migrate database + Q.Discard () <- Q.multiQE defaultTxErrorHandler + $(Q.sqlFromFile "src-rsr/migrate_from_13_to_14.sql") + return () + migrateCatalog :: ( MonadTx m , CacheRWM m @@ -295,13 +302,16 @@ migrateCatalog migrationTime = do | preVer == "7" -> from7ToCurrent | preVer == "8" -> from8ToCurrent | preVer == "9" -> from9ToCurrent - | preVer == "10" -> from10ToCurrent - | preVer == "11" -> from11ToCurrent - | preVer == "12" -> from12ToCurrent + | preVer == "10" -> from10ToCurrent + | preVer == "11" -> from11ToCurrent + | preVer == "12" -> from12ToCurrent + | preVer == "13" -> from13ToCurrent | otherwise -> throw400 NotSupported $ "unsupported version : " <> preVer where - from12ToCurrent = from12To13 >> postMigrate + from13ToCurrent = from13To14 >> postMigrate + + from12ToCurrent = from12To13 >> from13ToCurrent from11ToCurrent = from11To12 >> from12ToCurrent diff --git a/server/src-lib/Hasura/RQL/DDL/Relationship.hs b/server/src-lib/Hasura/RQL/DDL/Relationship.hs index b25cbe14..0c17b745 100644 --- a/server/src-lib/Hasura/RQL/DDL/Relationship.hs +++ b/server/src-lib/Hasura/RQL/DDL/Relationship.hs @@ -23,10 +23,12 @@ import Hasura.RQL.DDL.Deps import Hasura.RQL.DDL.Permission (purgePerm) import Hasura.RQL.DDL.Relationship.Types import Hasura.RQL.Types +import Hasura.RQL.Types.Catalog import Hasura.SQL.Types import Data.Aeson.Types import qualified Data.HashMap.Strict as HM +import qualified Data.HashSet as HS import qualified Data.Map.Strict as M import qualified Data.Text as T import Data.Tuple (swap) @@ -67,12 +69,12 @@ persistRel (QualifiedObject sn tn) rn relType relDef comment = VALUES ($1, $2, $3, $4, $5 :: jsonb, $6) |] (sn, tn, rn, relTypeToTxt relType, Q.AltJ relDef, comment) True -checkForColConfilct +checkForFldConfilct :: (MonadError QErr m) => TableInfo -> FieldName -> m () -checkForColConfilct tabInfo f = +checkForFldConfilct tabInfo f = case HM.lookup f (tiFieldInfoMap tabInfo) of Just _ -> throw400 AlreadyExists $ mconcat [ "column/relationship " <>> f @@ -88,7 +90,7 @@ validateObjRel -> m () validateObjRel qt (RelDef rn ru _) = do tabInfo <- askTabInfo qt - checkForColConfilct tabInfo (fromRel rn) + checkForFldConfilct tabInfo (fromRel rn) let fim = tiFieldInfoMap tabInfo case ru of RUFKeyOn cn -> assertPGCol fim "" cn @@ -103,9 +105,9 @@ createObjRelP1 (WithTable qt rd) = do validateObjRel qt rd objRelP2Setup - :: (QErrM m, CacheRWM m, MonadTx m) - => QualifiedTable -> RelDef ObjRelUsing -> m () -objRelP2Setup qt (RelDef rn ru _) = do + :: (QErrM m, CacheRWM m) + => QualifiedTable -> HS.HashSet CatalogFKey -> RelDef ObjRelUsing -> m () +objRelP2Setup qt fkeys (RelDef rn ru _) = do (relInfo, deps) <- case ru of RUManual (ObjRelManualConfig rm) -> do let refqt = rmTable rm @@ -115,38 +117,20 @@ objRelP2Setup qt (RelDef rn ru _) = do return (RelInfo rn ObjRel (zip lCols rCols) refqt True, deps) RUFKeyOn cn -> do -- TODO: validation should account for this too - res <- liftTx $ Q.catchE defaultTxErrorHandler $ fetchFKeyDetail cn - case mapMaybe processRes res of - [] -> throw400 ConstraintError - "no foreign constraint exists on the given column" - [(consName, refsn, reftn, colMapping)] -> do - let deps = [ SchemaDependency (SOTableObj qt $ TOCons consName) "fkey" - , SchemaDependency (SOTableObj qt $ TOCol cn) "using_col" - -- this needs to be added explicitly to handle the remote table - -- being untracked. In this case, neither the using_col nor - -- the constraint name will help. - , SchemaDependency (SOTable refqt) "remote_table" - ] - refqt = QualifiedObject refsn reftn - void $ askTabInfo refqt - return (RelInfo rn ObjRel colMapping refqt False, deps) - _ -> throw400 ConstraintError - "more than one foreign key constraint exists on the given column" + CatalogFKey _ refqt consName colMap <- + getRequiredFkey cn fkeys $ \fk -> _cfkTable fk == qt + + let deps = [ SchemaDependency (SOTableObj qt $ TOCons consName) "fkey" + , SchemaDependency (SOTableObj qt $ TOCol cn) "using_col" + -- this needs to be added explicitly to handle the remote table + -- being untracked. In this case, neither the using_col nor + -- the constraint name will help. + , SchemaDependency (SOTable refqt) "remote_table" + ] + colMapping = HM.toList colMap + void $ askTabInfo refqt + return (RelInfo rn ObjRel colMapping refqt False, deps) addRelToCache rn relInfo deps qt - where - QualifiedObject sn tn = qt - fetchFKeyDetail cn = - Q.listQ [Q.sql| - SELECT constraint_name, ref_table_table_schema, ref_table, column_mapping - FROM hdb_catalog.hdb_foreign_key_constraint - WHERE table_schema = $1 - AND table_name = $2 - AND (column_mapping ->> $3) IS NOT NULL - |] (sn, tn, cn) False - processRes (consn, refsn, reftn, mapping) = - case M.toList (Q.getAltJ mapping) of - m@[_] -> Just (consn, refsn, reftn, m) - _ -> Nothing objRelP2 :: ( QErrM m @@ -157,7 +141,8 @@ objRelP2 -> ObjRelDef -> m () objRelP2 qt rd@(RelDef rn ru comment) = do - objRelP2Setup qt rd + fkeys <- liftTx $ fetchTableFkeys qt + objRelP2Setup qt fkeys rd liftTx $ persistRel qt rn ObjRel (toJSON ru) comment createObjRelP2 @@ -183,7 +168,7 @@ validateArrRel => QualifiedTable -> ArrRelDef -> m () validateArrRel qt (RelDef rn ru _) = do tabInfo <- askTabInfo qt - checkForColConfilct tabInfo (fromRel rn) + checkForFldConfilct tabInfo (fromRel rn) let fim = tiFieldInfoMap tabInfo case ru of RUFKeyOn (ArrRelUsingFKeyOn remoteQt rcn) -> do @@ -195,9 +180,9 @@ validateArrRel qt (RelDef rn ru _) = do validateManualConfig fim rm arrRelP2Setup - :: (QErrM m, CacheRWM m, MonadTx m) - => QualifiedTable -> ArrRelDef -> m () -arrRelP2Setup qt (RelDef rn ru _) = do + :: (QErrM m, CacheRWM m) + => QualifiedTable -> HS.HashSet CatalogFKey -> ArrRelDef -> m () +arrRelP2Setup qt fkeys (RelDef rn ru _) = do (relInfo, deps) <- case ru of RUManual (ArrRelManualConfig rm) -> do let refqt = rmTable rm @@ -206,46 +191,26 @@ arrRelP2Setup qt (RelDef rn ru _) = do <> map (\c -> SchemaDependency (SOTableObj refqt $ TOCol c) "rcol") rCols return (RelInfo rn ArrRel (zip lCols rCols) refqt True, deps) RUFKeyOn (ArrRelUsingFKeyOn refqt refCol) -> do - let QualifiedObject refSn refTn = refqt -- TODO: validation should account for this too - res <- liftTx $ Q.catchE defaultTxErrorHandler $ - fetchFKeyDetail refSn refTn refCol - case mapMaybe processRes res of - [] -> throw400 ConstraintError - "no foreign constraint exists on the given column" - [(consName, mapping)] -> do - let deps = [ SchemaDependency (SOTableObj refqt $ TOCons consName) "remote_fkey" - , SchemaDependency (SOTableObj refqt $ TOCol refCol) "using_col" - -- we don't need to necessarily track the remote table like we did in - -- case of obj relationships as the remote table is indirectly - -- tracked by tracking the constraint name and 'using_col' - , SchemaDependency (SOTable refqt) "remote_table" - ] - return (RelInfo rn ArrRel (map swap mapping) refqt False, deps) - _ -> throw400 ConstraintError - "more than one foreign key constraint exists on the given column" + CatalogFKey _ _ consName colMap <- getRequiredFkey refCol fkeys $ + \fk -> _cfkTable fk == refqt && _cfkRefTable fk == qt + let deps = [ SchemaDependency (SOTableObj refqt $ TOCons consName) "remote_fkey" + , SchemaDependency (SOTableObj refqt $ TOCol refCol) "using_col" + -- we don't need to necessarily track the remote table like we did in + -- case of obj relationships as the remote table is indirectly + -- tracked by tracking the constraint name and 'using_col' + , SchemaDependency (SOTable refqt) "remote_table" + ] + mapping = HM.toList colMap + return (RelInfo rn ArrRel (map swap mapping) refqt False, deps) addRelToCache rn relInfo deps qt - where - QualifiedObject sn tn = qt - fetchFKeyDetail refsn reftn refcn = Q.listQ [Q.sql| - SELECT constraint_name, column_mapping - FROM hdb_catalog.hdb_foreign_key_constraint - WHERE table_schema = $1 - AND table_name = $2 - AND (column_mapping -> $3) IS NOT NULL - AND ref_table_table_schema = $4 - AND ref_table = $5 - |] (refsn, reftn, refcn, sn, tn) False - processRes (consn, mapping) = - case M.toList (Q.getAltJ mapping) of - m@[_] -> Just (consn, m) - _ -> Nothing arrRelP2 :: (QErrM m, CacheRWM m, MonadTx m) => QualifiedTable -> ArrRelDef -> m () arrRelP2 qt rd@(RelDef rn u comment) = do - arrRelP2Setup qt rd + fkeys <- liftTx $ fetchFkeysAsRemoteTable qt + arrRelP2Setup qt fkeys rd liftTx $ persistRel qt rn ArrRel (toJSON u) comment createArrRelP2 @@ -343,3 +308,49 @@ setRelComment (SetRelComment (QualifiedObject sn tn) rn comment) = AND table_name = $3 AND rel_name = $4 |] (comment, sn, tn, rn) True + +getRequiredFkey + :: (QErrM m) + => PGCol + -> HS.HashSet CatalogFKey + -> (CatalogFKey -> Bool) + -> m CatalogFKey +getRequiredFkey col fkeySet preCondition = + case filterFkeys of + [] -> throw400 ConstraintError + "no foreign constraint exists on the given column" + [k] -> return k + _ -> throw400 ConstraintError + "more than one foreign key constraint exists on the given column" + where + filterFkeys = HS.toList $ HS.filter filterFn fkeySet + filterFn k = + preCondition k && isJust (HM.lookup col $ _cfkColumnMapping k) + +fetchTableFkeys :: QualifiedTable -> Q.TxE QErr (HS.HashSet CatalogFKey) +fetchTableFkeys qt@(QualifiedObject sn tn) = do + r <- Q.listQE defaultTxErrorHandler [Q.sql| + SELECT f.constraint_name, + f.ref_table_table_schema, + f.ref_table, + f.column_mapping + FROM hdb_catalog.hdb_foreign_key_constraint f + WHERE f.table_schema = $1 AND f.table_name = $2 + |] (sn, tn) True + fmap HS.fromList $ + forM r $ \(constr, refsn, reftn, Q.AltJ colMapping) -> + return $ CatalogFKey qt (QualifiedObject refsn reftn) constr colMapping + +fetchFkeysAsRemoteTable :: QualifiedTable -> Q.TxE QErr (HS.HashSet CatalogFKey) +fetchFkeysAsRemoteTable rqt@(QualifiedObject rsn rtn) = do + r <- Q.listQE defaultTxErrorHandler [Q.sql| + SELECT f.table_schema, + f.table_name, + f.constraint_name, + f.column_mapping + FROM hdb_catalog.hdb_foreign_key_constraint f + WHERE f.ref_table_table_schema = $1 AND f.ref_table = $2 + |] (rsn, rtn) True + fmap HS.fromList $ + forM r $ \(sn, tn, constr, Q.AltJ colMapping) -> + return $ CatalogFKey (QualifiedObject sn tn) rqt constr colMapping diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Function.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Function.hs index 4fac4e26..66184670 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Function.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Function.hs @@ -1,8 +1,8 @@ module Hasura.RQL.DDL.Schema.Function where +import Hasura.EncJSON import Hasura.GraphQL.Utils (isValidName, showNames) import Hasura.Prelude -import Hasura.EncJSON import Hasura.RQL.Types import Hasura.SQL.Types @@ -42,7 +42,7 @@ data RawFuncInfo , rfiInputArgNames :: ![T.Text] , rfiReturnsTable :: !Bool } deriving (Show, Eq) -$(deriveFromJSON (aesonDrop 3 snakeCase) ''RawFuncInfo) +$(deriveJSON (aesonDrop 3 snakeCase) ''RawFuncInfo) mkFunctionArgs :: [PGColType] -> [T.Text] -> [FunctionArg] mkFunctionArgs tys argNames = @@ -63,7 +63,8 @@ validateFuncArgs args = funcArgsText = mapMaybe (fmap getFuncArgNameTxt . faName) args invalidArgs = filter (not . isValidName) $ map G.Name funcArgsText -mkFunctionInfo :: QualifiedFunction -> RawFuncInfo -> Q.TxE QErr FunctionInfo +mkFunctionInfo + :: QErrM m => QualifiedFunction -> RawFuncInfo -> m FunctionInfo mkFunctionInfo qf rawFuncInfo = do -- throw error if function has variadic arguments when hasVariadic $ throw400 NotSupported "function with \"VARIADIC\" parameters are not supported" @@ -88,21 +89,6 @@ mkFunctionInfo qf rawFuncInfo = do retSet inpArgTyps inpArgNames returnsTab = rawFuncInfo --- Build function info -getFunctionInfo :: QualifiedFunction -> Q.TxE QErr FunctionInfo -getFunctionInfo qf@(QualifiedObject sn fn) = do - -- fetch function details - funcData <- Q.catchE defaultTxErrorHandler $ - Q.listQ $(Q.sqlFromFile "src-rsr/function_info.sql") (sn, fn) True - - case funcData of - [] -> - throw400 NotExists $ "no such function exists in postgres : " <>> qf - [Identity (Q.AltJ rawFuncInfo)] -> mkFunctionInfo qf rawFuncInfo - _ -> - throw400 NotSupported $ - "function " <> qf <<> " is overloaded. Overloaded functions are not supported" - saveFunctionToCatalog :: QualifiedFunction -> Bool -> Q.TxE QErr () saveFunctionToCatalog (QualifiedObject sn fn) isSystemDefined = Q.unitQE defaultTxErrorHandler [Q.sql| @@ -131,9 +117,9 @@ trackFunctionP1 (TrackFunction qf) = do throw400 AlreadyTracked $ "function already tracked : " <>> qf trackFunctionP2Setup :: (QErrM m, CacheRWM m, MonadTx m) - => QualifiedFunction -> m () -trackFunctionP2Setup qf = do - fi <- withPathK "name" $ liftTx $ getFunctionInfo qf + => QualifiedFunction -> RawFuncInfo -> m () +trackFunctionP2Setup qf rawfi = do + fi <- mkFunctionInfo qf rawfi let retTable = fiReturnType fi err = err400 NotExists $ "table " <> retTable <<> " is not tracked" sc <- askSchemaCache @@ -151,9 +137,28 @@ trackFunctionP2 qf = do "function name " <> qf <<> " is not in compliance with GraphQL spec" -- check for conflicts in remote schema GS.checkConflictingNode defGCtx funcNameGQL - trackFunctionP2Setup qf + + -- 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 liftTx $ saveFunctionToCatalog qf False return successMsg + 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 runTrackFunc :: ( QErrM m, CacheRWM m, MonadTx m diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs index ccbb72e3..1df5a11a 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Table.hs @@ -17,6 +17,7 @@ import Hasura.RQL.DDL.Schema.Function import Hasura.RQL.DDL.Schema.Rename import Hasura.RQL.DDL.Utils import Hasura.RQL.Types +import Hasura.RQL.Types.Catalog import Hasura.Server.Utils (matchRegex) import Hasura.SQL.Types @@ -31,6 +32,7 @@ import Language.Haskell.TH.Syntax (Lift) import Network.URI.Extended () import qualified Data.HashMap.Strict as M +import qualified Data.HashSet as HS import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Database.PostgreSQL.LibPQ as PQ @@ -48,17 +50,6 @@ saveTableToCatalog (QualifiedObject sn tn) = INSERT INTO "hdb_catalog"."hdb_table" VALUES ($1, $2) |] (sn, tn) False --- Build the TableInfo with all its columns -getTableInfo :: QualifiedTable -> Bool -> Q.TxE QErr TableInfo -getTableInfo qt@(QualifiedObject sn tn) isSystemDefined = do - tableData <- Q.catchE defaultTxErrorHandler $ - Q.listQ $(Q.sqlFromFile "src-rsr/table_info.sql")(sn, tn) True - case tableData of - [] -> throw400 NotExists $ "no such table/view exists in postgres : " <>> qt - [(Q.AltJ cols, Q.AltJ pkeyCols, Q.AltJ cons, Q.AltJ viewInfoM)] -> - return $ mkTableInfo qt isSystemDefined cons cols pkeyCols viewInfoM - _ -> throw500 $ "more than one row found for: " <>> qt - newtype TrackTable = TrackTable { tName :: QualifiedTable } @@ -72,30 +63,39 @@ trackExistingTableOrViewP1 (TrackTable vn) = do when (M.member vn $ scTables rawSchemaCache) $ throw400 AlreadyTracked $ "view/table already tracked : " <>> vn -trackExistingTableOrViewP2Setup - :: (QErrM m, CacheRWM m, MonadTx m) - => QualifiedTable -> Bool -> m () -trackExistingTableOrViewP2Setup tn isSystemDefined = do - ti <- liftTx $ getTableInfo tn isSystemDefined - addTableToCache ti - trackExistingTableOrViewP2 :: (QErrM m, CacheRWM m, MonadTx m, MonadIO m, HasHttpManager m) => QualifiedTable -> Bool -> m EncJSON trackExistingTableOrViewP2 vn isSystemDefined = do sc <- askSchemaCache let defGCtx = scDefaultRemoteGCtx sc - tn = GS.qualObjectToName vn - GS.checkConflictingNode defGCtx tn + GS.checkConflictingNode defGCtx $ GS.qualObjectToName vn - trackExistingTableOrViewP2Setup vn isSystemDefined - liftTx $ Q.catchE defaultTxErrorHandler $ - saveTableToCatalog vn + tables <- liftTx fetchTableCatalog + case tables of + [] -> throw400 NotExists $ "no such table/view exists in postgres : " <>> vn + [ti] -> addTableToCache ti + _ -> throw500 $ "more than one row found for: " <>> vn + liftTx $ Q.catchE defaultTxErrorHandler $ saveTableToCatalog vn -- refresh the gCtx in schema cache refreshGCtxMapInSchema return successMsg + where + QualifiedObject sn tn = vn + mkTableInfo (cols, pCols, constraints, viewInfoM) = + let colMap = M.fromList $ flip map (Q.getAltJ cols) $ + \c -> (fromPGCol $ pgiName c, FIColumn c) + in TableInfo vn isSystemDefined colMap mempty (Q.getAltJ constraints) + (Q.getAltJ pCols) (Q.getAltJ viewInfoM) mempty + fetchTableCatalog = map mkTableInfo <$> + Q.listQE defaultTxErrorHandler [Q.sql| + SELECT columns, primary_key_columns, + constraints, view_info + FROM hdb_catalog.hdb_table_info_agg + WHERE table_schema = $1 AND table_name = $2 + |] (sn, tn) True runTrackTableQ :: ( QErrM m, CacheRWM m, MonadTx m @@ -309,7 +309,7 @@ checkNewInconsistentMeta => SchemaCache -- old schema cache -> SchemaCache -- new schema cache -> m () -checkNewInconsistentMeta oldSC newSC = do +checkNewInconsistentMeta oldSC newSC = unless (null newInconsMetaObjects) $ do let err = err500 Unexpected "cannot continue due to newly found inconsistent metadata" @@ -350,56 +350,60 @@ buildSchemaCacheG withSetup = do writeSchemaCache emptySchemaCache hMgr <- askHttpManager sqlGenCtx <- askSQLGenCtx - tables <- liftTx $ Q.catchE defaultTxErrorHandler fetchTables - forM_ tables $ \(sn, tn, isSystemDefined) -> do - let qt = QualifiedObject sn tn + + -- fetch all catalog metadata + CatalogMetadata tables relationships permissions qTemplates + eventTriggers remoteSchemas functions fkeys' <- liftTx fetchCatalogData + + let fkeys = HS.fromList fkeys' + + -- tables + forM_ tables $ \ct -> do + let qt = _ctTable ct + isSysDef = _ctSystemDefined ct + tableInfoM = _ctInfo ct mkInconsObj = InconsistentMetadataObj (MOTable qt) MOTTable $ toJSON $ TrackTable qt - modifyErr (\e -> "table " <> tn <<> "; " <> e) $ - handleInconsistentObj mkInconsObj $ - trackExistingTableOrViewP2Setup qt isSystemDefined + modifyErr (\e -> "table " <> qt <<> "; " <> e) $ + handleInconsistentObj mkInconsObj $ do + ti <- onNothing tableInfoM $ throw400 NotExists $ + "no such table/view exists in postgres : " <>> qt + addTableToCache $ ti{tiSystemDefined = isSysDef} - -- Fetch all the relationships - relationships <- liftTx $ Q.catchE defaultTxErrorHandler fetchRelationships - - forM_ relationships $ \(sn, tn, rn, rt, Q.AltJ rDef, cmnt) -> do - let qt = QualifiedObject sn tn - objId = MOTableObj qt $ MTORel rn rt + -- relationships + forM_ relationships $ \(CatalogRelation qt rn rt rDef cmnt) -> do + let objId = MOTableObj qt $ MTORel rn rt def = toJSON $ WithTable qt $ RelDef rn rDef cmnt mkInconsObj = InconsistentMetadataObj objId (MOTRel rt) def - modifyErr (\e -> "table " <> tn <<> "; rel " <> rn <<> "; " <> e) $ + modifyErr (\e -> "table " <> qt <<> "; rel " <> rn <<> "; " <> e) $ handleInconsistentObj mkInconsObj $ case rt of ObjRel -> do using <- decodeValue rDef let relDef = RelDef rn using Nothing validateObjRel qt relDef - objRelP2Setup qt relDef + objRelP2Setup qt fkeys relDef ArrRel -> do using <- decodeValue rDef let relDef = RelDef rn using Nothing validateArrRel qt relDef - arrRelP2Setup qt relDef + arrRelP2Setup qt fkeys relDef - -- Fetch all the permissions - permissions <- liftTx $ Q.catchE defaultTxErrorHandler fetchPermissions - - forM_ permissions $ \(sn, tn, rn, pt, Q.AltJ pDef, cmnt) -> do - let qt = QualifiedObject sn tn - objId = MOTableObj qt $ MTOPerm rn pt + -- permissions + forM_ permissions $ \(CatalogPermission qt rn pt pDef cmnt) -> do + let objId = MOTableObj qt $ MTOPerm rn pt def = toJSON $ WithTable qt $ PermDef rn pDef cmnt mkInconsObj = InconsistentMetadataObj objId (MOTPerm pt) def - modifyErr (\e -> "table " <> tn <<> "; role " <> rn <<> "; " <> e) $ + modifyErr (\e -> "table " <> qt <<> "; role " <> rn <<> "; " <> e) $ handleInconsistentObj mkInconsObj $ case pt of - PTInsert -> permHelper withSetup sqlGenCtx sn tn rn pDef PAInsert - PTSelect -> permHelper withSetup sqlGenCtx sn tn rn pDef PASelect - PTUpdate -> permHelper withSetup sqlGenCtx sn tn rn pDef PAUpdate - PTDelete -> permHelper withSetup sqlGenCtx sn tn rn pDef PADelete + PTInsert -> permHelper withSetup sqlGenCtx qt rn pDef PAInsert + PTSelect -> permHelper withSetup sqlGenCtx qt rn pDef PASelect + PTUpdate -> permHelper withSetup sqlGenCtx qt rn pDef PAUpdate + PTDelete -> permHelper withSetup sqlGenCtx qt rn pDef PADelete - -- Fetch all the query templates - qtemplates <- liftTx $ Q.catchE defaultTxErrorHandler fetchQTemplates - forM_ qtemplates $ \(qtn, Q.AltJ qtDefVal) -> do + -- query templates + forM_ qTemplates $ \(CatalogQueryTemplate qtn qtDefVal) -> do let def = object ["name" .= qtn, "template" .= qtDefVal] mkInconsObj = InconsistentMetadataObj (MOQTemplate qtn) MOTQTemplate def @@ -410,10 +414,9 @@ buildSchemaCacheG withSetup = do CreateQueryTemplate qtn qtDef Nothing addQTemplateToCache qti deps - eventTriggers <- liftTx $ Q.catchE defaultTxErrorHandler fetchEventTriggers - forM_ eventTriggers $ \(sn, tn, trn, Q.AltJ configuration) -> do - let qt = QualifiedObject sn tn - objId = MOTableObj qt $ MTOTrigger trn + -- event triggers + forM_ eventTriggers $ \(CatalogEventTrigger qt trn configuration) -> do + let objId = MOTableObj qt $ MTOTrigger trn def = object ["table" .= qt, "configuration" .= configuration] mkInconsObj = InconsistentMetadataObj objId MOTEventTrigger def handleInconsistentObj mkInconsObj $ do @@ -423,30 +426,29 @@ buildSchemaCacheG withSetup = do when withSetup $ liftTx $ mkTriggerQ trn qt allCols (stringifyNum sqlGenCtx) (etcDefinition etc) - functions <- liftTx $ Q.catchE defaultTxErrorHandler fetchFunctions - forM_ functions $ \(sn, fn) -> do - let qf = QualifiedObject sn fn - def = toJSON $ TrackFunction qf + -- sql functions + forM_ functions $ \(CatalogFunction qf rawfiM) -> do + let def = toJSON $ TrackFunction qf mkInconsObj = InconsistentMetadataObj (MOFunction qf) MOTFunction def - modifyErr (\e -> "function " <> fn <<> "; " <> e) $ - handleInconsistentObj mkInconsObj $ - trackFunctionP2Setup qf + modifyErr (\e -> "function " <> qf <<> "; " <> e) $ + handleInconsistentObj mkInconsObj $ do + rawfi <- onNothing rawfiM $ + throw400 NotExists $ "no such function exists in postgres : " <>> qf + trackFunctionP2Setup qf rawfi -- build GraphQL context postGCtxSc <- askSchemaCache >>= GS.updateSCWithGCtx writeSchemaCache postGCtxSc -- remote schemas - remoteSchemas <- liftTx fetchRemoteSchemas forM_ remoteSchemas $ resolveSingleRemoteSchema hMgr where - permHelper setup sqlGenCtx sn tn rn pDef pa = do + permHelper setup sqlGenCtx qt rn pDef pa = do qCtx <- mkAdminQCtx sqlGenCtx <$> askSchemaCache perm <- decodeValue pDef - let qt = QualifiedObject sn tn - permDef = PermDef rn perm Nothing + let permDef = PermDef rn perm Nothing createPerm = WithTable qt permDef (permInfo, deps) <- liftP1WithQCtx qCtx $ createPermP1 createPerm when setup $ addPermP2Setup qt permDef permInfo @@ -470,39 +472,10 @@ buildSchemaCacheG withSetup = do , scDefaultRemoteGCtx = mergedDefGCtx } - fetchTables = - Q.listQ [Q.sql| - SELECT table_schema, table_name, is_system_defined - FROM hdb_catalog.hdb_table - |] () False - - fetchRelationships = - Q.listQ [Q.sql| - SELECT table_schema, table_name, rel_name, rel_type, rel_def::json, comment - FROM hdb_catalog.hdb_relationship - |] () False - - fetchPermissions = - Q.listQ [Q.sql| - SELECT table_schema, table_name, role_name, perm_type, perm_def::json, comment - FROM hdb_catalog.hdb_permission - |] () False - - fetchQTemplates = - Q.listQ [Q.sql| - SELECT template_name, template_defn :: json FROM hdb_catalog.hdb_query_template - |] () False - - fetchEventTriggers = - Q.listQ [Q.sql| - SELECT e.schema_name, e.table_name, e.name, e.configuration::json - FROM hdb_catalog.event_triggers e - |] () False - fetchFunctions = - Q.listQ [Q.sql| - SELECT function_schema, function_name - FROM hdb_catalog.hdb_function - |] () False +fetchCatalogData :: Q.TxE QErr CatalogMetadata +fetchCatalogData = + (Q.getAltJ . runIdentity . Q.getRow) <$> Q.withQE defaultTxErrorHandler + $(Q.sqlFromFile "src-rsr/catalog_metadata.sql") () True data RunSQL = RunSQL diff --git a/server/src-lib/Hasura/RQL/Types/Catalog.hs b/server/src-lib/Hasura/RQL/Types/Catalog.hs new file mode 100644 index 00000000..7de89c3a --- /dev/null +++ b/server/src-lib/Hasura/RQL/Types/Catalog.hs @@ -0,0 +1,93 @@ +module Hasura.RQL.Types.Catalog where + +import Hasura.Prelude + +import Hasura.RQL.DDL.Schema.Function +import Hasura.RQL.Types.Common +import Hasura.RQL.Types.EventTrigger +import Hasura.RQL.Types.Permission +import Hasura.RQL.Types.RemoteSchema +import Hasura.RQL.Types.SchemaCache +import Hasura.SQL.Types + +import Data.Aeson +import Data.Aeson.Casing +import Data.Aeson.TH + +import qualified Data.HashMap.Strict as HM + +data CatalogTable + = CatalogTable + { _ctTable :: !QualifiedTable + , _ctSystemDefined :: !Bool + , _ctInfo :: !(Maybe TableInfo) + } deriving (Show, Eq) +$(deriveJSON (aesonDrop 3 snakeCase) ''CatalogTable) + +data CatalogRelation + = CatalogRelation + { _crTable :: !QualifiedTable + , _crRelName :: !RelName + , _crRelType :: !RelType + , _crDef :: !Value + , _crComment :: !(Maybe Text) + } deriving (Show, Eq) +$(deriveJSON (aesonDrop 3 snakeCase) ''CatalogRelation) + +data CatalogPermission + = CatalogPermission + { _cpTable :: !QualifiedTable + , _cpRole :: !RoleName + , _cpPermType :: !PermType + , _cpDef :: !Value + , _cpComment :: !(Maybe Text) + } deriving (Show, Eq) +$(deriveJSON (aesonDrop 3 snakeCase) ''CatalogPermission) + +data CatalogQueryTemplate + = CatalogQueryTemplate + { _cqtName :: !TQueryName + , _cqtDef :: !Value + } deriving (Show, Eq) +$(deriveJSON (aesonDrop 4 snakeCase) ''CatalogQueryTemplate) + +data CatalogEventTrigger + = CatalogEventTrigger + { _cetTable :: !QualifiedTable + , _cetName :: !TriggerName + , _cetDef :: !Value + } deriving (Show, Eq) +$(deriveJSON (aesonDrop 4 snakeCase) ''CatalogEventTrigger) + +data CatalogFunction + = CatalogFunction + { _cfFunction :: !QualifiedFunction + , _cfInfo :: !(Maybe RawFuncInfo) + } deriving (Show, Eq) +$(deriveJSON (aesonDrop 3 snakeCase) ''CatalogFunction) + +type ColMapping = HM.HashMap PGCol PGCol + +data CatalogFKey + = CatalogFKey + { _cfkTable :: !QualifiedTable + , _cfkRefTable :: !QualifiedTable + , _cfkConstraint :: !ConstraintName + , _cfkColumnMapping :: !ColMapping + } deriving (Show, Eq, Generic) +$(deriveJSON (aesonDrop 4 snakeCase) ''CatalogFKey) + +instance Hashable CatalogFKey + +data CatalogMetadata + = CatalogMetadata + { _cmTables :: ![CatalogTable] + , _cmRelations :: ![CatalogRelation] + , _cmPermissions :: ![CatalogPermission] + , _cmQueryTemplates :: ![CatalogQueryTemplate] + , _cmEventTriggers :: ![CatalogEventTrigger] + , _cmRemoteSchemas :: ![AddRemoteSchemaQuery] + , _cmFunctions :: ![CatalogFunction] + , _cmForeignKeys :: ![CatalogFKey] + } deriving (Show, Eq) +$(deriveJSON (aesonDrop 3 snakeCase) ''CatalogMetadata) diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs index cab27511..0f03f67c 100644 --- a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs +++ b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs @@ -20,7 +20,6 @@ module Hasura.RQL.Types.SchemaCache , onlyComparableCols , isUniqueOrPrimary , isForeignKey - , mkTableInfo , addTableToCache , modTableInCache , delTableFromCache @@ -353,19 +352,18 @@ data TableInfo $(deriveToJSON (aesonDrop 2 snakeCase) ''TableInfo) -mkTableInfo - :: QualifiedTable - -> Bool - -> [ConstraintName] - -> [PGColInfo] - -> [PGCol] - -> Maybe ViewInfo -> TableInfo -mkTableInfo tn isSystemDefined uniqCons cols pCols mVI = - TableInfo tn isSystemDefined colMap (M.fromList []) - uniqCons pCols mVI (M.fromList []) - where - colMap = M.fromList $ map f cols - f colInfo = (fromPGCol $ pgiName colInfo, FIColumn colInfo) +instance FromJSON TableInfo where + parseJSON = withObject "TableInfo" $ \o -> do + name <- o .: "name" + columns <- o .: "columns" + pkeyCols <- o .: "primary_key_columns" + constraints <- o .: "constraints" + viewInfoM <- o .:? "view_info" + isSystemDefined <- o .:? "is_system_defined" .!= False + let colMap = M.fromList $ flip map columns $ + \c -> (fromPGCol $ pgiName c, FIColumn c) + return $ TableInfo name isSystemDefined colMap mempty + constraints pkeyCols viewInfoM mempty data FunctionType = FTVOLATILE diff --git a/server/src-rsr/catalog_metadata.sql b/server/src-rsr/catalog_metadata.sql new file mode 100644 index 00000000..bb2c80b0 --- /dev/null +++ b/server/src-rsr/catalog_metadata.sql @@ -0,0 +1,191 @@ +select + json_build_object( + 'tables', tables.items, + 'relations', relations.items, + 'permissions', permissions.items, + 'query_templates', query_templates.items, + 'event_triggers', event_triggers.items, + 'remote_schemas', remote_schemas.items, + 'functions', functions.items, + 'foreign_keys', foreign_keys.items + ) +from + ( + select + coalesce(json_agg( + json_build_object( + 'table', + json_build_object( + 'name', ht.table_name, + 'schema', ht.table_schema + ), + 'system_defined', ht.is_system_defined, + 'info', tables.info + ) + ), '[]') as items + from + hdb_catalog.hdb_table as ht + left outer join ( + select + table_schema, + table_name, + json_build_object( + 'name', + json_build_object( + 'schema', table_schema, + 'name', table_name + ), + 'columns', columns, + 'primary_key_columns', primary_key_columns, + 'constraints', constraints, + 'view_info', view_info + ) as info + from + hdb_catalog.hdb_table_info_agg + ) as tables on ( + tables.table_schema = ht.table_schema + and tables.table_name = ht.table_name + ) + ) as tables, + ( + select + coalesce( + json_agg( + json_build_object( + 'table', + json_build_object( + 'schema', table_schema, + 'name', table_name + ), + 'rel_name', rel_name, + 'rel_type', rel_type, + 'def', rel_def :: json, + 'comment', comment + ) + ), + '[]' + ) as items + from + hdb_catalog.hdb_relationship + ) as relations, + ( + select + coalesce( + json_agg( + json_build_object( + 'table', + json_build_object( + 'schema', table_schema, + 'name', table_name + ), + 'role', role_name, + 'perm_type', perm_type, + 'def', perm_def :: json, + 'comment', comment + ) + ), + '[]' + ) as items + from + hdb_catalog.hdb_permission + ) as permissions, + ( + select + coalesce( + json_agg( + json_build_object( + 'name', template_name, + 'def', template_defn :: json + ) + ), + '[]' + ) as items + from + hdb_catalog.hdb_query_template + ) as query_templates, + ( + select + coalesce( + json_agg( + json_build_object( + 'table', + json_build_object( + 'schema', schema_name, + 'name', table_name + ), + 'name', name, + 'def', configuration :: json + ) + ), + '[]' + ) as items + from + hdb_catalog.event_triggers + ) as event_triggers, + ( + select + coalesce( + json_agg( + json_build_object( + 'name', + name, + 'definition', definition :: json, + 'comment', comment + ) + ), + '[]' + ) as items + from + hdb_catalog.remote_schemas + ) as remote_schemas, + ( + select + coalesce(json_agg(q.info), '[]') as items + from + ( + select + json_build_object( + 'function', + json_build_object( + 'schema', hf.function_schema, + 'name', hf.function_name + ), + 'info', function_info + ) as info + from + hdb_catalog.hdb_function hf + left outer join + hdb_catalog.hdb_function_info_agg hf_agg on + ( hf_agg.function_name = hf.function_name + and hf_agg.function_schema = hf.function_schema + ) + ) as q + ) as functions, + ( + select + coalesce(json_agg(foreign_key.info), '[]') as items + from + ( + select + json_build_object( + 'table', + json_build_object( + 'schema', f.table_schema, + 'name', f.table_name + ), + 'ref_table', + json_build_object( + 'schema', f.ref_table_table_schema, + 'name', f.ref_table + ), + 'constraint', f.constraint_name, + 'column_mapping', f.column_mapping + ) as info + from + hdb_catalog.hdb_foreign_key_constraint f + left outer join hdb_catalog.hdb_table ht + on ( ht.table_schema = f.table_schema + and ht.table_name = f.table_name + ) + ) as foreign_key + ) as foreign_keys diff --git a/server/src-rsr/function_info.sql b/server/src-rsr/function_info.sql deleted file mode 100644 index c3a97452..00000000 --- a/server/src-rsr/function_info.sql +++ /dev/null @@ -1,33 +0,0 @@ -SELECT - row_to_json ( - ( - SELECT - e - FROM - ( - SELECT - has_variadic, - function_type, - return_type_schema, - return_type_name, - return_type_type, - returns_set, - input_arg_types, - input_arg_names, - exists( - SELECT - 1 - FROM - information_schema.tables - WHERE - table_schema = return_type_schema - AND table_name = return_type_name - ) AS returns_table - ) AS e - ) - ) AS "raw_function_info" - FROM - hdb_catalog.hdb_function_agg - WHERE - function_schema = $1 - AND function_name = $2 diff --git a/server/src-rsr/initialise.sql b/server/src-rsr/initialise.sql index 20130be5..90043d8c 100644 --- a/server/src-rsr/initialise.sql +++ b/server/src-rsr/initialise.sql @@ -436,4 +436,113 @@ LANGUAGE plpgsql; CREATE TRIGGER hdb_schema_update_event_notifier AFTER INSERT ON hdb_catalog.hdb_schema_update_event FOR EACH ROW EXECUTE PROCEDURE hdb_catalog.hdb_schema_update_event_notifier(); +CREATE VIEW hdb_catalog.hdb_table_info_agg AS ( +select + tables.table_name as table_name, + tables.table_schema as table_schema, + coalesce(columns.columns, '[]') as columns, + coalesce(pk.columns, '[]') as primary_key_columns, + coalesce(constraints.constraints, '[]') as constraints, + coalesce(views.view_info, 'null') as view_info +from + information_schema.tables as tables + left outer join ( + select + c.table_name, + c.table_schema, + json_agg( + json_build_object( + 'name', + column_name, + 'type', + udt_name, + 'is_nullable', + is_nullable :: boolean + ) + ) as columns + from + information_schema.columns c + group by + c.table_schema, + c.table_name + ) columns on ( + tables.table_schema = columns.table_schema + AND tables.table_name = columns.table_name + ) + left outer join ( + select * from hdb_catalog.hdb_primary_key + ) pk on ( + tables.table_schema = pk.table_schema + AND tables.table_name = pk.table_name + ) + left outer join ( + select + c.table_schema, + c.table_name, + json_agg(constraint_name) as constraints + from + information_schema.table_constraints c + where + c.constraint_type = 'UNIQUE' + or c.constraint_type = 'PRIMARY KEY' + group by + c.table_schema, + c.table_name + ) constraints on ( + tables.table_schema = constraints.table_schema + AND tables.table_name = constraints.table_name + ) + left outer join ( + select + table_schema, + table_name, + json_build_object( + 'is_updatable', + (is_updatable::boolean OR is_trigger_updatable::boolean), + 'is_deletable', + (is_updatable::boolean OR is_trigger_deletable::boolean), + 'is_insertable', + (is_insertable_into::boolean OR is_trigger_insertable_into::boolean) + ) as view_info + from + information_schema.views v + ) views on ( + tables.table_schema = views.table_schema + AND tables.table_name = views.table_name + ) +); +CREATE VIEW hdb_catalog.hdb_function_info_agg AS ( + SELECT + function_name, + function_schema, + row_to_json ( + ( + SELECT + e + FROM + ( + SELECT + has_variadic, + function_type, + return_type_schema, + return_type_name, + return_type_type, + returns_set, + input_arg_types, + input_arg_names, + exists( + SELECT + 1 + FROM + information_schema.tables + WHERE + table_schema = return_type_schema + AND table_name = return_type_name + ) AS returns_table + ) AS e + ) + ) AS "function_info" + FROM + hdb_catalog.hdb_function_agg +); diff --git a/server/src-rsr/table_info.sql b/server/src-rsr/migrate_from_13_to_14.sql similarity index 64% rename from server/src-rsr/table_info.sql rename to server/src-rsr/migrate_from_13_to_14.sql index cf53f85c..f8bba7c7 100644 --- a/server/src-rsr/table_info.sql +++ b/server/src-rsr/migrate_from_13_to_14.sql @@ -1,4 +1,7 @@ +CREATE OR REPLACE VIEW hdb_catalog.hdb_table_info_agg AS ( select + tables.table_name as table_name, + tables.table_schema as table_schema, coalesce(columns.columns, '[]') as columns, coalesce(pk.columns, '[]') as primary_key_columns, coalesce(constraints.constraints, '[]') as constraints, @@ -7,8 +10,8 @@ from information_schema.tables as tables left outer join ( select - c.table_schema, c.table_name, + c.table_schema, json_agg( json_build_object( 'name', @@ -69,6 +72,39 @@ from tables.table_schema = views.table_schema AND tables.table_name = views.table_name ) -where - tables.table_schema = $1 AND - tables.table_name = $2 +); + +CREATE OR REPLACE VIEW hdb_catalog.hdb_function_info_agg AS ( + SELECT + function_name, + function_schema, + row_to_json ( + ( + SELECT + e + FROM + ( + SELECT + has_variadic, + function_type, + return_type_schema, + return_type_name, + return_type_type, + returns_set, + input_arg_types, + input_arg_names, + exists( + SELECT + 1 + FROM + information_schema.tables + WHERE + table_schema = return_type_schema + AND table_name = return_type_name + ) AS returns_table + ) AS e + ) + ) AS "function_info" + FROM + hdb_catalog.hdb_function_agg +); diff --git a/server/stack.yaml b/server/stack.yaml index 86e071db..b40f3286 100644 --- a/server/stack.yaml +++ b/server/stack.yaml @@ -2,7 +2,7 @@ # Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) # resolver: lts-10.8 -resolver: lts-13.16 +resolver: lts-13.20 # Local packages, usually specified by relative directory name packages: - '.' @@ -23,7 +23,7 @@ extra-deps: - primitive-extras-0.7.1 - stm-hamt-1.2.0.2 -- stm-containers-1.1.0.2 +- stm-containers-1.1.0.4 - reroute-0.5.0.0 - Spock-core-0.13.0.0