2021-02-14 09:07:52 +03:00
|
|
|
module Hasura.Backends.Postgres.DDL.Source
|
2021-05-21 05:46:58 +03:00
|
|
|
( ToMetadataFetchQuery
|
|
|
|
, fetchPgScalars
|
|
|
|
, fetchTableMetadata
|
2021-05-27 18:06:13 +03:00
|
|
|
, fetchFunctionMetadata
|
2021-05-25 09:50:13 +03:00
|
|
|
, initCatalogForSource
|
2021-02-23 20:37:27 +03:00
|
|
|
, postDropSourceHook
|
|
|
|
, resolveDatabaseMetadata
|
2021-05-21 05:46:58 +03:00
|
|
|
, resolveSourceConfig
|
2021-02-23 20:37:27 +03:00
|
|
|
) where
|
2021-02-14 09:07:52 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
import Hasura.Prelude
|
|
|
|
|
2021-06-01 20:33:25 +03:00
|
|
|
import qualified Data.HashMap.Strict as Map
|
2021-06-23 21:00:19 +03:00
|
|
|
import qualified Data.List.NonEmpty as NE
|
2021-06-01 20:33:25 +03:00
|
|
|
import qualified Database.PG.Query as Q
|
|
|
|
import qualified Language.Haskell.TH.Lib as TH
|
|
|
|
import qualified Language.Haskell.TH.Syntax as TH
|
2021-05-31 16:54:08 +03:00
|
|
|
|
2021-06-01 20:33:25 +03:00
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
|
|
|
import Data.FileEmbed (makeRelativeToProject)
|
|
|
|
import Data.Time.Clock (UTCTime)
|
2021-02-14 09:07:52 +03:00
|
|
|
|
|
|
|
import Hasura.Backends.Postgres.Connection
|
2021-06-01 20:33:25 +03:00
|
|
|
import Hasura.Backends.Postgres.DDL.Source.Version
|
2021-02-14 09:07:52 +03:00
|
|
|
import Hasura.Backends.Postgres.SQL.Types
|
2021-05-11 18:18:31 +03:00
|
|
|
import Hasura.Base.Error
|
2021-04-22 00:44:37 +03:00
|
|
|
import Hasura.RQL.Types.Backend
|
2021-02-14 09:07:52 +03:00
|
|
|
import Hasura.RQL.Types.Common
|
2021-06-07 16:57:24 +03:00
|
|
|
import Hasura.RQL.Types.EventTrigger (RecreateEventTriggers (..))
|
2021-02-14 09:07:52 +03:00
|
|
|
import Hasura.RQL.Types.Function
|
|
|
|
import Hasura.RQL.Types.Source
|
|
|
|
import Hasura.RQL.Types.Table
|
|
|
|
import Hasura.SQL.Backend
|
2021-02-22 17:59:13 +03:00
|
|
|
import Hasura.Server.Migrate.Internal
|
2021-06-01 20:33:25 +03:00
|
|
|
import Hasura.Server.Types (MaintenanceMode (..))
|
2021-02-14 09:07:52 +03:00
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
|
2021-05-21 05:46:58 +03:00
|
|
|
-- | We differentiate the handling of metadata between Citus and Vanilla
|
|
|
|
-- Postgres because Citus imposes limitations on the types of joins that it
|
|
|
|
-- permits, which then limits the types of relations that we can track.
|
|
|
|
class ToMetadataFetchQuery (pgKind :: PostgresKind) where
|
|
|
|
tableMetadata :: Q.Query
|
|
|
|
|
|
|
|
instance ToMetadataFetchQuery 'Vanilla where
|
|
|
|
tableMetadata = $(makeRelativeToProject "src-rsr/pg_table_metadata.sql" >>= Q.sqlFromFile)
|
|
|
|
|
|
|
|
instance ToMetadataFetchQuery 'Citus where
|
|
|
|
tableMetadata = $(makeRelativeToProject "src-rsr/citus_table_metadata.sql" >>= Q.sqlFromFile)
|
|
|
|
|
2021-02-22 10:52:42 +03:00
|
|
|
resolveSourceConfig
|
|
|
|
:: (MonadIO m, MonadResolveSource m)
|
2021-04-22 00:44:37 +03:00
|
|
|
=> SourceName -> PostgresConnConfiguration -> m (Either QErr (SourceConfig ('Postgres pgKind)))
|
2021-02-22 10:52:42 +03:00
|
|
|
resolveSourceConfig name config = runExceptT do
|
2021-02-14 09:07:52 +03:00
|
|
|
sourceResolver <- getSourceResolver
|
2021-02-22 10:52:42 +03:00
|
|
|
liftEitherM $ liftIO $ sourceResolver name config
|
2021-02-14 09:07:52 +03:00
|
|
|
|
2021-02-22 10:52:42 +03:00
|
|
|
resolveDatabaseMetadata
|
2021-04-22 00:44:37 +03:00
|
|
|
:: forall pgKind m
|
2021-05-21 05:46:58 +03:00
|
|
|
. (Backend ('Postgres pgKind), ToMetadataFetchQuery pgKind, MonadIO m, MonadBaseControl IO m)
|
2021-05-25 09:50:13 +03:00
|
|
|
=> SourceConfig ('Postgres pgKind) -> m (Either QErr (ResolvedSource ('Postgres pgKind)))
|
|
|
|
resolveDatabaseMetadata sourceConfig = runExceptT do
|
|
|
|
(tablesMeta, functionsMeta, pgScalars) <- runLazyTx (_pscExecCtx sourceConfig) Q.ReadOnly $ do
|
2021-02-14 09:07:52 +03:00
|
|
|
tablesMeta <- fetchTableMetadata
|
|
|
|
functionsMeta <- fetchFunctionMetadata
|
|
|
|
pgScalars <- fetchPgScalars
|
|
|
|
pure (tablesMeta, functionsMeta, pgScalars)
|
|
|
|
pure $ ResolvedSource sourceConfig tablesMeta functionsMeta pgScalars
|
|
|
|
|
2021-05-25 09:50:13 +03:00
|
|
|
-- | Initialise catalog tables for a source, including those required by the event delivery subsystem.
|
2021-06-07 16:57:24 +03:00
|
|
|
initCatalogForSource
|
|
|
|
:: forall m . MonadTx m => MaintenanceMode -> UTCTime -> m RecreateEventTriggers
|
2021-05-31 16:54:08 +03:00
|
|
|
initCatalogForSource maintenanceMode migrationTime = do
|
2021-02-14 09:07:52 +03:00
|
|
|
hdbCatalogExist <- doesSchemaExist "hdb_catalog"
|
|
|
|
eventLogTableExist <- doesTableExist "hdb_catalog" "event_log"
|
|
|
|
sourceVersionTableExist <- doesTableExist "hdb_catalog" "hdb_source_catalog_version"
|
2021-04-21 13:55:18 +03:00
|
|
|
-- when maintenance mode is enabled, don't perform any migrations
|
2021-06-07 16:57:24 +03:00
|
|
|
if | maintenanceMode == MaintenanceModeEnabled -> pure RETDoNothing
|
2021-02-14 09:07:52 +03:00
|
|
|
-- Fresh database
|
2021-04-21 13:55:18 +03:00
|
|
|
| not hdbCatalogExist -> liftTx do
|
2021-02-22 17:59:13 +03:00
|
|
|
Q.unitQE defaultTxErrorHandler "CREATE SCHEMA hdb_catalog" () False
|
|
|
|
enablePgcryptoExtension
|
|
|
|
initPgSourceCatalog
|
2021-06-07 16:57:24 +03:00
|
|
|
return RETDoNothing
|
2021-02-14 09:07:52 +03:00
|
|
|
-- Only 'hdb_catalog' schema defined
|
2021-06-07 16:57:24 +03:00
|
|
|
| not sourceVersionTableExist && not eventLogTableExist -> do
|
2021-02-22 17:59:13 +03:00
|
|
|
liftTx initPgSourceCatalog
|
2021-06-07 16:57:24 +03:00
|
|
|
return RETDoNothing
|
2021-02-14 09:07:52 +03:00
|
|
|
-- Source is initialised by pre multisource support servers
|
2021-02-22 17:59:13 +03:00
|
|
|
| not sourceVersionTableExist && eventLogTableExist -> do
|
|
|
|
-- Update the Source Catalog to v43 to include the new migration
|
|
|
|
-- changes. Skipping this step will result in errors.
|
2021-06-01 20:33:25 +03:00
|
|
|
currMetadataCatalogVersion <- liftTx getCatalogVersion
|
2021-05-31 16:54:08 +03:00
|
|
|
-- we migrate to the 43 version, which is the migration where
|
|
|
|
-- metadata separation is introduced
|
2021-06-01 20:33:25 +03:00
|
|
|
migrateTo43MetadataCatalog currMetadataCatalogVersion
|
2021-02-22 17:59:13 +03:00
|
|
|
liftTx createVersionTable
|
2021-06-01 20:33:25 +03:00
|
|
|
-- Migrate the catalog from initial version i.e '1'
|
|
|
|
migrateSourceCatalogFrom "1"
|
2021-06-07 16:57:24 +03:00
|
|
|
return RETRecreate
|
|
|
|
| otherwise -> migrateSourceCatalog >> return RETRecreate
|
2021-02-14 09:07:52 +03:00
|
|
|
where
|
|
|
|
initPgSourceCatalog = do
|
2021-03-16 20:35:35 +03:00
|
|
|
() <- Q.multiQE defaultTxErrorHandler $(makeRelativeToProject "src-rsr/init_pg_source.sql" >>= Q.sqlFromFile)
|
2021-02-14 09:07:52 +03:00
|
|
|
setSourceCatalogVersion
|
|
|
|
|
|
|
|
createVersionTable = do
|
|
|
|
() <- Q.multiQE defaultTxErrorHandler
|
|
|
|
[Q.sql|
|
|
|
|
CREATE TABLE hdb_catalog.hdb_source_catalog_version(
|
|
|
|
version TEXT NOT NULL,
|
|
|
|
upgraded_on TIMESTAMPTZ NOT NULL
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE UNIQUE INDEX hdb_source_catalog_version_one_row
|
|
|
|
ON hdb_catalog.hdb_source_catalog_version((version IS NOT NULL));
|
|
|
|
|]
|
2021-06-01 20:33:25 +03:00
|
|
|
pure ()
|
2021-02-14 09:07:52 +03:00
|
|
|
|
2021-06-01 20:33:25 +03:00
|
|
|
migrateTo43MetadataCatalog prevVersion = do
|
2021-02-22 17:59:13 +03:00
|
|
|
let neededMigrations = dropWhile ((/= prevVersion) . fst) upMigrationsUntil43
|
2021-06-23 21:00:19 +03:00
|
|
|
case NE.nonEmpty neededMigrations of
|
|
|
|
Just nonEmptyNeededMigrations -> do
|
|
|
|
-- Migrations aren't empty. We need to update the catalog version after migrations
|
|
|
|
traverse_ snd nonEmptyNeededMigrations
|
|
|
|
setCatalogVersion "43" migrationTime
|
|
|
|
Nothing ->
|
|
|
|
-- No migrations exists, implies the database is migrated to latest metadata catalog version
|
|
|
|
pure ()
|
2021-02-22 17:59:13 +03:00
|
|
|
|
2021-06-01 20:33:25 +03:00
|
|
|
-- NOTE (rakesh):
|
|
|
|
-- Down migrations for postgres sources is not supported in this PR. We need an
|
|
|
|
-- exhaustive discussion to make a call as I think, as of now, it is not
|
|
|
|
-- trivial. For metadata catalog migrations, we have a separate downgrade
|
|
|
|
-- command in the graphql-engine exe.
|
|
|
|
--
|
|
|
|
-- I can think of two ways:
|
|
|
|
--
|
|
|
|
-- - Just like downgrade, we need to have a new command path for downgrading
|
|
|
|
-- pg sources (command design should support other backends too,
|
|
|
|
-- graphql-engine source-downgrade postgres --to-catalog-version 1 --
|
|
|
|
-- downgrade all available pg sources to 1)
|
|
|
|
-- - Have an online documentation with necessary SQLs to help users to
|
|
|
|
-- downgrade pg sources themselves. Improve error message by referring the URL
|
|
|
|
-- to the documentation.
|
|
|
|
|
|
|
|
migrateSourceCatalog :: MonadTx m => m ()
|
|
|
|
migrateSourceCatalog =
|
|
|
|
getSourceCatalogVersion >>= migrateSourceCatalogFrom
|
|
|
|
|
|
|
|
migrateSourceCatalogFrom :: (MonadTx m) => Text -> m ()
|
|
|
|
migrateSourceCatalogFrom prevVersion
|
|
|
|
| prevVersion == latestSourceCatalogVersionText = pure ()
|
|
|
|
| [] <- neededMigrations =
|
|
|
|
throw400 NotSupported $
|
|
|
|
"Expected source catalog version <= "
|
|
|
|
<> latestSourceCatalogVersionText
|
|
|
|
<> ", but the current version is " <> prevVersion
|
|
|
|
| otherwise = do
|
|
|
|
traverse_ snd neededMigrations
|
|
|
|
setSourceCatalogVersion
|
|
|
|
where
|
|
|
|
neededMigrations =
|
|
|
|
dropWhile ((/= prevVersion) . fst) sourceMigrations
|
|
|
|
|
|
|
|
sourceMigrations :: (MonadTx m) => [(Text, m ())]
|
|
|
|
sourceMigrations =
|
|
|
|
$(let migrationFromFile from =
|
|
|
|
let to = from + 1
|
|
|
|
path = "src-rsr/pg_source_migrations/" <> show from <> "_to_" <> show to <> ".sql"
|
|
|
|
in [| runTx $(makeRelativeToProject path >>= Q.sqlFromFile) |]
|
|
|
|
|
|
|
|
migrationsFromFile = map $ \(from :: Integer) ->
|
|
|
|
[| ($(TH.lift $ tshow from), $(migrationFromFile from)) |]
|
|
|
|
|
|
|
|
in TH.listE $ migrationsFromFile [1..(latestSourceCatalogVersion - 1)]
|
|
|
|
)
|
|
|
|
|
|
|
|
-- Upgrade the hdb_catalog schema to v43 (Metadata catalog)
|
2021-02-22 17:59:13 +03:00
|
|
|
upMigrationsUntil43 :: MonadTx m => [(Text, m ())]
|
|
|
|
upMigrationsUntil43 =
|
|
|
|
$(let migrationFromFile from to =
|
|
|
|
let path = "src-rsr/migrations/" <> from <> "_to_" <> to <> ".sql"
|
2021-03-16 20:35:35 +03:00
|
|
|
in [| runTx $(makeRelativeToProject path >>= Q.sqlFromFile) |]
|
2021-02-22 17:59:13 +03:00
|
|
|
|
|
|
|
migrationsFromFile = map $ \(to :: Integer) ->
|
|
|
|
let from = to - 1
|
|
|
|
in [| ( $(TH.lift $ tshow from)
|
|
|
|
, $(migrationFromFile (show from) (show to))
|
|
|
|
) |]
|
|
|
|
in TH.listE
|
|
|
|
-- version 0.8 is the only non-integral catalog version
|
|
|
|
$ [| ("0.8", $(migrationFromFile "08" "1")) |]
|
|
|
|
: migrationsFromFile [2..3]
|
|
|
|
++ [| ("3", from3To4) |]
|
|
|
|
: migrationsFromFile [5..43]
|
|
|
|
)
|
|
|
|
|
2021-02-14 09:07:52 +03:00
|
|
|
-- | Fetch Postgres metadata of all user tables
|
2021-04-22 00:44:37 +03:00
|
|
|
fetchTableMetadata
|
|
|
|
:: forall pgKind m
|
2021-05-21 05:46:58 +03:00
|
|
|
. (Backend ('Postgres pgKind), ToMetadataFetchQuery pgKind, MonadTx m)
|
2021-04-22 00:44:37 +03:00
|
|
|
=> m (DBTablesMetadata ('Postgres pgKind))
|
2021-02-14 09:07:52 +03:00
|
|
|
fetchTableMetadata = do
|
|
|
|
results <- liftTx $ Q.withQE defaultTxErrorHandler
|
2021-05-21 05:46:58 +03:00
|
|
|
(tableMetadata @pgKind) () True
|
2021-02-14 09:07:52 +03:00
|
|
|
pure $ Map.fromList $ flip map results $
|
|
|
|
\(schema, table, Q.AltJ info) -> (QualifiedObject schema table, info)
|
|
|
|
|
|
|
|
-- | Fetch Postgres metadata for all user functions
|
2021-04-22 00:44:37 +03:00
|
|
|
fetchFunctionMetadata :: (MonadTx m) => m (DBFunctionsMetadata ('Postgres pgKind))
|
2021-02-14 09:07:52 +03:00
|
|
|
fetchFunctionMetadata = do
|
|
|
|
results <- liftTx $ Q.withQE defaultTxErrorHandler
|
2021-03-16 20:35:35 +03:00
|
|
|
$(makeRelativeToProject "src-rsr/pg_function_metadata.sql" >>= Q.sqlFromFile) () True
|
2021-02-14 09:07:52 +03:00
|
|
|
pure $ Map.fromList $ flip map results $
|
|
|
|
\(schema, table, Q.AltJ infos) -> (QualifiedObject schema table, infos)
|
|
|
|
|
|
|
|
-- | Fetch all scalar types from Postgres
|
|
|
|
fetchPgScalars :: MonadTx m => m (HashSet PGScalarType)
|
|
|
|
fetchPgScalars =
|
|
|
|
liftTx $ Q.getAltJ . runIdentity . Q.getRow
|
|
|
|
<$> Q.withQE defaultTxErrorHandler
|
|
|
|
[Q.sql|
|
|
|
|
SELECT coalesce(json_agg(typname), '[]')
|
|
|
|
FROM pg_catalog.pg_type where typtype = 'b'
|
|
|
|
|] () True
|
2021-02-23 20:37:27 +03:00
|
|
|
|
|
|
|
-- | Clean source database after dropping in metadata
|
|
|
|
postDropSourceHook
|
|
|
|
:: (MonadIO m, MonadError QErr m, MonadBaseControl IO m)
|
|
|
|
=> PGSourceConfig -> m ()
|
|
|
|
postDropSourceHook sourceConfig = do
|
|
|
|
-- Clean traces of Hasura in source database
|
2021-05-25 09:50:13 +03:00
|
|
|
--
|
|
|
|
-- There are three type of database we have to consider here, which we
|
|
|
|
-- refer to as types 1, 2, and 3 below:
|
|
|
|
-- 1. default postgres source (no separate metadata database)
|
|
|
|
-- In this case, we want to drop nothing.
|
|
|
|
--
|
|
|
|
-- 2. dedicated metadata database
|
|
|
|
-- In this case, we want to only drop source-related tables ("event_log",
|
|
|
|
-- "hdb_source_catalog_version", etc), leaving the rest of the schema intact.
|
|
|
|
--
|
|
|
|
-- 3. non-default postgres source (necessarily without metadata tables)
|
|
|
|
-- In this case, we want to drop the entire "hdb_catalog" schema.
|
2021-02-23 20:37:27 +03:00
|
|
|
liftEitherM $ runPgSourceWriteTx sourceConfig $ do
|
|
|
|
hdbMetadataTableExist <- doesTableExist "hdb_catalog" "hdb_metadata"
|
|
|
|
eventLogTableExist <- doesTableExist "hdb_catalog" "event_log"
|
2021-05-25 09:50:13 +03:00
|
|
|
if
|
|
|
|
-- If "hdb_metadata" and "event_log" tables are found in the "hdb_catalog" schema,
|
|
|
|
-- then this implies the source is being used as the default postgres source, i.e.
|
|
|
|
-- this is a default postgres source (type 1 above).
|
|
|
|
-- In this case we don't drop anything in the catalog schema.
|
|
|
|
| hdbMetadataTableExist && eventLogTableExist -> pure ()
|
|
|
|
-- However, it is possible that the above condition is not met for a default
|
|
|
|
-- postgres source. This will happen if no event triggers have been defined,
|
|
|
|
-- because we initialise event catalog tables only when required (i.e. when
|
|
|
|
-- a trigger is defined).
|
|
|
|
--
|
|
|
|
-- This could lead to a possible problem where "hdb_metadata" exists, "event_log"
|
|
|
|
-- does not exist, but the _other_ source-related tables exist. In that case, we
|
|
|
|
-- would end up dropping them here, which would go against our requirements above.
|
|
|
|
-- However, observe that these tables are always all created or destroyed together,
|
|
|
|
-- in single transactions where we run setup/teardown SQL files, so this condition
|
|
|
|
-- is guaranteed to not take place.
|
|
|
|
--
|
|
|
|
-- So if only "hdb_metadata" exists, we have one of two possible cases:
|
|
|
|
-- * this is a metadata database (type 2) and we can drop all source-related tables
|
|
|
|
-- * this is a default database (type 1) which has no source-related tables (because
|
|
|
|
-- it has no "event_log" table, it cannot have the others, because of the previous
|
|
|
|
-- argument)
|
|
|
|
--
|
|
|
|
-- It should be clear that we can now safely issue DROP IF EXISTS statements for
|
|
|
|
-- all source-related tables now according to the spec above. The IF EXISTS lets us
|
|
|
|
-- handle both cases uniformly, doing nothing in the second case, and for metadata
|
|
|
|
-- databases, we drop only source-related tables from the database's "hdb_catalog" schema.
|
|
|
|
| hdbMetadataTableExist -> Q.multiQE
|
|
|
|
defaultTxErrorHandler $(makeRelativeToProject "src-rsr/drop_pg_source.sql" >>= Q.sqlFromFile)
|
|
|
|
-- Otherwise, we have a non-default postgres source, which has no metadata tables.
|
|
|
|
-- We drop the entire "hdb_catalog" schema as discussed above.
|
2021-02-23 20:37:27 +03:00
|
|
|
| otherwise -> dropHdbCatalogSchema
|
|
|
|
|
|
|
|
-- Destory postgres source connection
|
|
|
|
liftIO $ _pecDestroyConn $ _pscExecCtx sourceConfig
|
2021-05-26 10:40:34 +03:00
|
|
|
|
|
|
|
-- Run other drop hooks configured at source creation time
|
|
|
|
liftIO $ _pscPostDropHook sourceConfig
|