{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TupleSections #-} module Stackage.Database.Query ( -- * Snapshot newestSnapshot , newestLTS , newestLTSMajor , newestNightly , getSnapshots , countSnapshots , ltsMajorVersions , snapshotBefore , lookupSnapshot , snapshotTitle , lastXLts5Nightly , snapshotsJSON , getLatestLtsByGhc , getSnapshotModules , getSnapshotPackageModules -- * Package , getAllPackages , getPackagesForSnapshot , getPackageVersionForSnapshot , getLatests , getHackageLatestVersion , getSnapshotPackageInfo , getSnapshotPackageLatestVersion , getSnapshotPackagePageInfo , getPackageInfo , getSnapshotsForPackage -- ** Dependencies , getForwardDeps , getReverseDeps , getDepsCount -- ** Deprecations , getDeprecated -- * Needed for Cron Job -- ** Re-exports from Pantry , loadBlobById , getTreeForKey , treeCabal -- ** Stackage server , addSnapshotPackage , getHackageCabalByRev0 , getHackageCabalByKey , snapshotMarkUpdated , insertSnapshotName , addDeprecated , markModuleHasDocs , insertSnapshotPackageModules , insertDeps -- ** For Hoogle db creation , lastLtsNightly , getSnapshotPackageCabalBlob ) where import qualified Data.Aeson as A import Data.Bifunctor (bimap) import qualified Data.List as L import Database.Esqueleto import Database.Esqueleto.Internal.Language (FromPreprocess) import Database.Esqueleto.Internal.Sql import qualified Database.Persist as P import Pantry.Internal.Stackage (EntityField(..), PackageName, Unique(..), Version, getBlobKey, getPackageNameById, getPackageNameId, getTreeForKey, getVersionId, loadBlobById, mkSafeFilePath, treeCabal) import RIO hiding (on, (^.)) import qualified RIO.Map as Map import qualified RIO.Set as Set import qualified RIO.Text as T import RIO.Time (Day, UTCTime) import Stackage.Database.PackageInfo import Stackage.Database.Schema import Stackage.Database.Types -- | Construct a pretty title for the snapshot snapshotTitle :: Snapshot -> Text snapshotTitle s = snapshotPrettyName (snapshotName s) (snapshotCompiler s) -- | Get the snapshot from the database. lookupSnapshot :: GetStackageDatabase env m => SnapName -> m (Maybe (Entity Snapshot)) lookupSnapshot name = run $ getBy $ UniqueSnapshot name -- | A way to lookup a name of the newest snapshot per type: 'lts', 'lts-x' and 'nightly'. This is -- used for resolving a snapshot newestSnapshot :: GetStackageDatabase env m => SnapshotBranch -> m (Maybe SnapName) newestSnapshot LtsBranch = fmap (uncurry SNLts) <$> newestLTS newestSnapshot NightlyBranch = fmap SNNightly <$> newestNightly newestSnapshot (LtsMajorBranch x) = fmap (SNLts x) <$> newestLTSMajor x -- | Get the latest known LTS snapshot newestLTS :: GetStackageDatabase env m => m (Maybe (Int, Int)) newestLTS = run $ liftM (fmap go) $ selectFirst [] [P.Desc LtsMajor, P.Desc LtsMinor] where go (Entity _ lts) = (ltsMajor lts, ltsMinor lts) -- | Get the minor version 'y' of latest known LTS snapshot for the major version 'x' in 'lts-x.y' newestLTSMajor :: GetStackageDatabase env m => Int -> m (Maybe Int) newestLTSMajor x = run $ liftM (fmap $ ltsMinor . entityVal) $ P.selectFirst [LtsMajor P.==. x] [P.Desc LtsMinor] ltsMajorVersions :: GetStackageDatabase env m => m [(Int, Int)] ltsMajorVersions = run $ liftM (dropOldMinors . map (toPair . entityVal)) $ P.selectList [] [P.Desc LtsMajor, P.Desc LtsMinor] where toPair (Lts _ x y) = (x, y) dropOldMinors [] = [] dropOldMinors (l@(x, _):rest) = l : dropOldMinors (dropWhile sameMinor rest) where sameMinor (y, _) = x == y -- | Look up the date 'in the newest nightly snapshot. newestNightly :: GetStackageDatabase env m => m (Maybe Day) newestNightly = run $ liftM (fmap $ nightlyDay . entityVal) $ selectFirst [] [P.Desc NightlyDay] -- | Get the snapshot which precedes the given one with respect to it's branch (nightly/lts) snapshotBefore :: GetStackageDatabase env m => SnapName -> m (Maybe (SnapshotId, SnapName)) snapshotBefore (SNLts x y) = ltsBefore x y snapshotBefore (SNNightly day) = nightlyBefore day nightlyBefore :: GetStackageDatabase env m => Day -> m (Maybe (SnapshotId, SnapName)) nightlyBefore day = do run $ liftM (fmap go) $ P.selectFirst [NightlyDay P.<. day] [P.Desc NightlyDay] where go (Entity _ nightly) = (nightlySnap nightly, SNNightly $ nightlyDay nightly) ltsBefore :: GetStackageDatabase env m => Int -> Int -> m (Maybe (SnapshotId, SnapName)) ltsBefore x y = do run $ liftM (fmap go) $ selectFirst ( [LtsMajor P.<=. x, LtsMinor P.<. y] P.||. [LtsMajor P.<. x] ) [P.Desc LtsMajor, P.Desc LtsMinor] where go (Entity _ lts) = (ltsSnap lts, SNLts (ltsMajor lts) (ltsMinor lts)) lastXLts5Nightly :: GetStackageDatabase env m => Int -> m [SnapName] lastXLts5Nightly ltsCount = run $ do ls <- P.selectList [] [P.Desc LtsMajor, P.Desc LtsMinor, P.LimitTo ltsCount] ns <- P.selectList [] [P.Desc NightlyDay, P.LimitTo 5] return $ map l ls <> map n ns where l (Entity _ x) = SNLts (ltsMajor x) (ltsMinor x) n (Entity _ x) = SNNightly (nightlyDay x) lastLtsNightly :: GetStackageDatabase env m => Int -> Int -> m (Map SnapshotId SnapName) lastLtsNightly ltsCount nightlyCount = run $ do ls <- P.selectList [] [P.Desc LtsMajor, P.Desc LtsMinor, P.LimitTo ltsCount] ns <- P.selectList [] [P.Desc NightlyDay, P.LimitTo nightlyCount] Map.map snapshotName <$> P.getMany (map (ltsSnap . P.entityVal) ls <> map (nightlySnap . P.entityVal) ns) snapshotsJSON :: GetStackageDatabase env m => m A.Value snapshotsJSON = do mlatestNightly <- newestNightly ltses <- ltsMajorVersions let lts = case ltses of [] -> [] majorVersions@(latest:_) -> ("lts" A..= printLts latest) : map toObj majorVersions nightly = case mlatestNightly of Nothing -> id Just n -> (("nightly" A..= printNightly n) :) return $ A.object $ nightly lts where toObj lts@(major, _) = T.pack ("lts-" <> show major) A..= printLts lts printLts (major, minor) = "lts-" <> show major <> "." <> show minor printNightly day = "nightly-" <> T.pack (show day) getLatestLtsByGhc :: GetStackageDatabase env m => m [(Int, Int, Text, Day)] getLatestLtsByGhc = run $ fmap (dedupe . map toTuple) $ do select $ from $ \(lts `InnerJoin` snapshot) -> do on $ lts ^. LtsSnap ==. snapshot ^. SnapshotId orderBy [desc (lts ^. LtsMajor), desc (lts ^. LtsMinor)] groupBy ( snapshot ^. SnapshotCompiler , lts ^. LtsId , lts ^. LtsMajor , lts ^. LtsMinor , snapshot ^. SnapshotId) return (lts, snapshot) where toTuple (Entity _ lts, Entity _ snapshot) = ( ltsMajor lts , ltsMinor lts , textDisplay (snapshotCompiler snapshot) , snapshotCreated snapshot) dedupe [] = [] dedupe (x:xs) = x : dedupe (dropWhile (\y -> thd x == thd y) xs) thd (_, _, x, _) = x -- | Count snapshots that belong to a specific SnapshotBranch countSnapshots :: (GetStackageDatabase env m) => Maybe SnapshotBranch -> m Int countSnapshots Nothing = run $ P.count ([] :: [P.Filter Snapshot]) countSnapshots (Just NightlyBranch) = run $ P.count ([] :: [P.Filter Nightly]) countSnapshots (Just LtsBranch) = run $ P.count ([] :: [P.Filter Lts]) countSnapshots (Just (LtsMajorBranch x)) = run $ P.count [LtsMajor P.==. x] -- | Get snapshots that belong to a specific SnapshotBranch getSnapshots :: (GetStackageDatabase env m) => Maybe SnapshotBranch -> Int -- ^ limit -> Int -- ^ offset -> m [Entity Snapshot] getSnapshots mBranch l o = run $ case mBranch of Nothing -> P.selectList [] [P.LimitTo l, P.OffsetBy o, P.Desc SnapshotCreated] Just NightlyBranch -> select $ from $ \(nightly `InnerJoin` snapshot) -> do on $ nightly ^. NightlySnap ==. snapshot ^. SnapshotId orderBy [desc (nightly ^. NightlyDay)] limit $ fromIntegral l offset $ fromIntegral o pure snapshot Just LtsBranch -> do select $ from $ \(lts `InnerJoin` snapshot) -> do on $ lts ^. LtsSnap ==. snapshot ^. SnapshotId orderBy [desc (lts ^. LtsMajor), desc (lts ^. LtsMinor)] limit $ fromIntegral l offset $ fromIntegral o pure snapshot Just (LtsMajorBranch v) -> do select $ from $ \(lts `InnerJoin` snapshot) -> do on $ lts ^. LtsSnap ==. snapshot ^. SnapshotId orderBy [desc (lts ^. LtsMinor)] where_ ((lts ^. LtsMajor) ==. (val v)) limit $ fromIntegral l offset $ fromIntegral o pure snapshot getSnapshotModules :: GetStackageDatabase env m => SnapshotId -> m [ModuleListingInfo] getSnapshotModules sid = run $ do map toModuleListingInfo <$> select (from $ \(spm `InnerJoin` m `InnerJoin` sp `InnerJoin` pn `InnerJoin` v) -> do on $ sp ^. SnapshotPackageVersion ==. v ^. VersionId on $ sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId on $ spm ^. SnapshotPackageModuleSnapshotPackage ==. sp ^. SnapshotPackageId on $ spm ^. SnapshotPackageModuleModule ==. m ^. ModuleNameId where_ $ (sp ^. SnapshotPackageSnapshot ==. val sid) &&. (spm ^. SnapshotPackageModuleHasDocs ==. val True) orderBy [asc (m ^. ModuleNameName), asc (pn ^. PackageNameName)] pure (m ^. ModuleNameName, pn ^. PackageNameName, v ^. VersionVersion)) where toModuleListingInfo (Value moduleName, Value packageName, Value version) = ModuleListingInfo { mliModuleName = moduleName , mliPackageIdentifier = PackageIdentifierP packageName version } getSnapshotPackageModules :: SnapshotPackageId -> Bool -> ReaderT SqlBackend (RIO env) [ModuleNameP] getSnapshotPackageModules snapshotPackageId hasDocs = map unValue <$> select (from $ \(spm `InnerJoin` m) -> do on $ spm ^. SnapshotPackageModuleModule ==. m ^. ModuleNameId where_ $ (spm ^. SnapshotPackageModuleSnapshotPackage ==. val snapshotPackageId) &&. (spm ^. SnapshotPackageModuleHasDocs ==. val hasDocs) orderBy [asc (m ^. ModuleNameName)] pure (m ^. ModuleNameName)) getAllPackages :: GetStackageDatabase env m => m [(SnapName, PackageListingInfo)] getAllPackages = run (map toPackageListingInfo <$> select (from $ \(sp `InnerJoin` snap `InnerJoin` pn `InnerJoin` v) -> distinctOn [don (pn ^. PackageNameName)] $ do on (sp ^. SnapshotPackageVersion ==. v ^. VersionId) on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) on (sp ^. SnapshotPackageSnapshot ==. snap ^. SnapshotId) orderBy [ asc (pn ^. PackageNameName) , desc (versionArray v) , desc (sp ^. SnapshotPackageRevision) , desc (snap ^. SnapshotCreated) ] pure ( snap ^. SnapshotName , pn ^. PackageNameName , v ^. VersionVersion , sp ^. SnapshotPackageSynopsis , sp ^. SnapshotPackageOrigin))) where toPackageListingInfo (Value snapName, name, version, synopsis, origin) = ( snapName , PackageListingInfo { pliName = unValue name , pliVersion = unValue version , pliSynopsis = unValue synopsis , pliOrigin = unValue origin }) getPackagesForSnapshot :: GetStackageDatabase env m => SnapshotId -> m [PackageListingInfo] getPackagesForSnapshot snapshotId = run (map toPackageListingInfo <$> select (from $ \(sp `InnerJoin` pn `InnerJoin` v) -> do on (sp ^. SnapshotPackageVersion ==. v ^. VersionId) on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) where_ (sp ^. SnapshotPackageSnapshot ==. val snapshotId) orderBy [asc (pn ^. PackageNameName)] pure ( pn ^. PackageNameName , v ^. VersionVersion , sp ^. SnapshotPackageSynopsis , sp ^. SnapshotPackageOrigin))) where toPackageListingInfo (Value pliName, Value pliVersion, Value pliSynopsis, Value pliOrigin) = PackageListingInfo {pliName, pliVersion, pliSynopsis, pliOrigin} getPackageVersionForSnapshot :: GetStackageDatabase env m => SnapshotId -> PackageNameP -> m (Maybe VersionP) getPackageVersionForSnapshot snapshotId pname = run $ selectApplyMaybe unValue (from $ \(sp `InnerJoin` pn `InnerJoin` v) -> do on (sp ^. SnapshotPackageVersion ==. v ^. VersionId) on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) where_ ((sp ^. SnapshotPackageSnapshot ==. val snapshotId) &&. (pn ^. PackageNameName ==. val pname)) pure (v ^. VersionVersion)) getLatest :: FromPreprocess SqlQuery SqlExpr SqlBackend t => PackageNameP -> (t -> SqlExpr (Value SnapshotId)) -> (t -> SqlQuery ()) -> ReaderT SqlBackend (RIO env) (Maybe LatestInfo) getLatest pname onWhich orderWhich = selectApplyMaybe toLatestInfo (from $ \(which `InnerJoin` snap `InnerJoin` sp `InnerJoin` pn `InnerJoin` v) -> do on (sp ^. SnapshotPackageVersion ==. v ^. VersionId) on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) on (sp ^. SnapshotPackageSnapshot ==. snap ^. SnapshotId) on (snap ^. SnapshotId ==. onWhich which) where_ (pn ^. PackageNameName ==. val pname) orderWhich which limit 1 pure (snap ^. SnapshotName, v ^. VersionVersion, sp ^. SnapshotPackageRevision)) where toLatestInfo (snapName, ver, mrev) = LatestInfo (unValue snapName) $ toVersionMRev (unValue ver) (unValue mrev) getLatests :: PackageNameP -> ReaderT SqlBackend (RIO env) [LatestInfo] getLatests pname = do mLts <- getLatest pname (^. LtsSnap) (\lts -> orderBy [desc (lts ^. LtsMajor), desc (lts ^. LtsMinor)]) mNightly <- getLatest pname (^. NightlySnap) (\nightly -> orderBy [desc (nightly ^. NightlyDay)]) pure $ catMaybes [mLts, mNightly] -- | Looks up in pantry the latest information about the package on Hackage. getHackageLatestVersion :: PackageNameP -> ReaderT SqlBackend (RIO env) (Maybe HackageCabalInfo) getHackageLatestVersion pname = selectApplyMaybe toHackageCabalInfo $ from (\(hc `InnerJoin` pn `InnerJoin` v) -> do on (hc ^. HackageCabalVersion ==. v ^. VersionId) on (hc ^. HackageCabalName ==. pn ^. PackageNameId) where_ (pn ^. PackageNameName ==. val pname) orderBy [desc (versionArray v), desc (hc ^. HackageCabalRevision)] limit 1 pure ( hc ^. HackageCabalId , hc ^. HackageCabalCabal , v ^. VersionVersion , hc ^. HackageCabalRevision)) where toHackageCabalInfo (cid, cbid, v, rev) = HackageCabalInfo { hciCabalId = unValue cid , hciCabalBlobId = unValue cbid , hciPackageName = pname , hciVersionRev = toVersionRev (unValue v) (unValue rev) } getSnapshotPackageInfo :: GetStackageDatabase env m => SnapName -> PackageNameP -> m (Maybe SnapshotPackageInfo) getSnapshotPackageInfo snapName pname = fmap snd . listToMaybe <$> run (snapshotPackageInfoQuery $ \_sp s pn _v spiQ -> do where_ ((s ^. SnapshotName ==. val snapName) &&. (pn ^. PackageNameName ==. val pname)) pure ((), spiQ)) getSnapshotPackagePageInfo :: GetStackageDatabase env m => SnapshotPackageInfo -> Int -> m SnapshotPackagePageInfo getSnapshotPackagePageInfo spi maxDisplayedDeps = run $ do mhciLatest <- getHackageLatestVersion $ spiPackageName spi -- TODO: check for `spiOrigin spi` once other than `Hackage` are implemented forwardDepsCount <- getForwardDepsCount spi reverseDepsCount <- getReverseDepsCount spi forwardDeps <- if forwardDepsCount > 0 then getForwardDeps spi (Just maxDisplayedDeps) else pure [] reverseDeps <- if reverseDepsCount > 0 then getReverseDeps spi (Just maxDisplayedDeps) else pure [] latestInfo <- getLatests (spiPackageName spi) moduleNames <- getModuleNames (spiSnapshotPackageId spi) mcabalBlobKey <- traverse getBlobKey $ spiCabalBlobId spi pure SnapshotPackagePageInfo { sppiSnapshotPackageInfo = spi , sppiLatestHackageCabalInfo = mhciLatest , sppiForwardDeps = map (first dropVersionRev) forwardDeps , sppiForwardDepsCount = forwardDepsCount , sppiReverseDeps = map (first dropVersionRev) reverseDeps , sppiReverseDepsCount = reverseDepsCount , sppiLatestInfo = latestInfo , sppiModuleNames = moduleNames , sppiPantryCabal = mcabalBlobKey RIO.<&> \cabalBlobKey -> PantryCabal { pcPackageName = spiPackageName spi , pcVersion = spiVersion spi , pcCabalKey = cabalBlobKey } , sppiVersion = listToMaybe [ spiVersionRev spi | VersionRev ver mrev <- maybe [] (pure . hciVersionRev) mhciLatest ++ map liVersionRev latestInfo , ver > curVer || (ver == curVer && fromMaybe (Revision 0) mrev > fromMaybe (Revision 0) mcurRev) ] } where VersionRev curVer mcurRev = spiVersionRev spi type SqlExprSPI = ( SqlExpr (Value SnapshotPackageId) , SqlExpr (Value SnapshotId) , SqlExpr (Value SnapName) , SqlExpr (Value PackageNameP) , SqlExpr (Value (Maybe BlobId)) , SqlExpr (Value VersionP) , SqlExpr (Value (Maybe Revision)) , SqlExpr (Value Origin) , SqlExpr (Value (Maybe TreeEntryId)) , SqlExpr (Value (Maybe TreeEntryId)) ) snapshotPackageInfoQuery :: (SqlSelect a b) => ( SqlExpr (Entity SnapshotPackage) -> SqlExpr (Entity Snapshot) -> SqlExpr (Entity PackageName) -> SqlExpr (Entity Version) -> SqlExprSPI -> SqlQuery (a, SqlExprSPI) ) -> ReaderT SqlBackend (RIO env) [(b, SnapshotPackageInfo)] snapshotPackageInfoQuery customize = fmap (\(extraValue, spiValues) -> (extraValue, toSnapshotPackageInfo spiValues)) <$> select (from $ \(sp `InnerJoin` s `InnerJoin` pn `InnerJoin` v) -> do on (sp ^. SnapshotPackageVersion ==. v ^. VersionId) on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) on (sp ^. SnapshotPackageSnapshot ==. s ^. SnapshotId) customize sp s pn v $ ( sp ^. SnapshotPackageId , s ^. SnapshotId , s ^. SnapshotName , pn ^. PackageNameName , sp ^. SnapshotPackageCabal , v ^. VersionVersion , sp ^. SnapshotPackageRevision , sp ^. SnapshotPackageOrigin , sp ^. SnapshotPackageReadme , sp ^. SnapshotPackageChangelog)) where toSnapshotPackageInfo (spid, sid, sn, pn, spc, v, spr, spo, rm, cl) = SnapshotPackageInfo { spiSnapshotPackageId = unValue spid , spiSnapshotId = unValue sid , spiCabalBlobId = unValue spc , spiSnapName = unValue sn , spiPackageName = unValue pn , spiVersion = unValue v , spiRevision = unValue spr , spiOrigin = unValue spo , spiReadme = unValue rm , spiChangelog = unValue cl } getSnapshotPackageLatestVersion :: GetStackageDatabase env m => PackageNameP -> m (Maybe SnapshotPackageInfo) getSnapshotPackageLatestVersion pname = fmap snd . listToMaybe <$> run (snapshotPackageInfoQuery $ \_sp s pn v spiQ -> do where_ (pn ^. PackageNameName ==. val pname) orderBy [ desc (versionArray v) , desc (s ^. SnapshotCreated) ] limit 1 pure ((), spiQ)) -- | A helper function that expects at most one element to be returned by a `select` and applies a -- function to the returned result selectApplyMaybe :: (SqlSelect a b, MonadIO m) => (b -> r) -> SqlQuery a -> ReaderT SqlBackend m (Maybe r) selectApplyMaybe f = fmap (fmap f . listToMaybe) . select -- | Convert a string representation of a version to an array so it can be used for sorting. versionArray :: SqlExpr (Entity Version) -> SqlExpr (Value [Int64]) versionArray v = stringsToInts (stringToArray (v ^. VersionVersion) (val ("." :: String))) stringsToInts :: SqlExpr (Value [String]) -> SqlExpr (Value [Int64]) stringsToInts = unsafeSqlCastAs "INTEGER[]" -- | Define postgresql native function in Haskell with Esqueleto stringToArray :: (SqlString s1, SqlString s2) => SqlExpr (Value s1) -> SqlExpr (Value s2) -> SqlExpr (Value [String]) stringToArray s1 s2 = unsafeSqlFunction "string_to_array" (s1, s2) getSnapshotsForPackage :: GetStackageDatabase env m => PackageNameP -> Maybe Int -> m [(CompilerP, SnapshotPackageInfo)] getSnapshotsForPackage pname mlimit = fmap (first unValue) <$> run (snapshotPackageInfoQuery $ \_sp s pn _v spiQ -> do where_ (pn ^. PackageNameName ==. val pname) orderBy [desc (s ^. SnapshotCreated)] forM_ mlimit (limit . fromIntegral) pure (s ^. SnapshotCompiler, spiQ)) getPackageInfo :: GetStackageDatabase env m => Either HackageCabalInfo SnapshotPackageInfo -> m PackageInfo getPackageInfo (Left hci) = run $ do cabalBlob <- loadBlobById (hciCabalBlobId hci) pure $ toPackageInfo (parseCabalBlob cabalBlob) Nothing Nothing getPackageInfo (Right spi) = run $ case spiCabalBlobId spi of Just cabalBlobId -> do gpd <- parseCabalBlob <$> loadBlobById cabalBlobId mreadme <- maybe (pure Nothing) getFileByTreeEntryId (spiReadme spi) mchangelog <- maybe (pure Nothing) getFileByTreeEntryId (spiChangelog spi) pure $ toPackageInfo gpd (toContentFile Readme <$> mreadme) (toContentFile Changelog <$> mchangelog) Nothing -> error "FIXME: handle a case when cabal file isn't available but package.yaml is" where toContentFile :: (ByteString -> Bool -> a) -> (SafeFilePath, ByteString) -> a toContentFile con (path, bs) = con bs (isMarkdownFilePath path) getFileByTreeEntryId :: TreeEntryId -> ReaderT SqlBackend (RIO env) (Maybe (SafeFilePath, ByteString)) getFileByTreeEntryId teid = selectApplyMaybe (bimap unValue unValue) $ from $ \(te `InnerJoin` fp `InnerJoin` b) -> do on $ te ^. TreeEntryBlob ==. b ^. BlobId on $ te ^. TreeEntryPath ==. fp ^. FilePathId where_ $ te ^. TreeEntryId ==. val teid pure (fp ^. FilePathPath, b ^. BlobContents) getModuleNames :: SnapshotPackageId -> ReaderT SqlBackend (RIO env) [ModuleNameP] getModuleNames spid = map unValue <$> select (from $ \(spm `InnerJoin` pm) -> do on (spm ^. SnapshotPackageModuleModule ==. pm ^. ModuleNameId) where_ (spm ^. SnapshotPackageModuleSnapshotPackage ==. val spid) orderBy [desc (pm ^. ModuleNameName)] pure (pm ^. ModuleNameName)) ------ Dependencies getForwardDeps :: SnapshotPackageInfo -> Maybe Int -> ReaderT SqlBackend (RIO env) [(PackageVersionRev, VersionRangeP)] getForwardDeps spi mlimit = fmap toDepRange <$> select (from $ \(user `InnerJoin` uses `InnerJoin` pn `InnerJoin` v) -> do on (uses ^. SnapshotPackageVersion ==. v ^. VersionId) on (uses ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) on (user ^. DepUses ==. uses ^. SnapshotPackagePackageName) where_ $ (user ^. DepUser ==. val (spiSnapshotPackageId spi)) &&. (uses ^. SnapshotPackageSnapshot ==. val (spiSnapshotId spi)) orderBy [desc (pn ^. PackageNameName)] maybe (pure ()) (limit . fromIntegral) mlimit pure ( pn ^. PackageNameName , v ^. VersionVersion , uses ^. SnapshotPackageRevision , user ^. DepRange)) where toDepRange (pn, v, rev, range) = (PackageVersionRev (unValue pn) (toVersionMRev (unValue v) (unValue rev)), unValue range) getForwardDepsCount :: SnapshotPackageInfo -> ReaderT SqlBackend (RIO env) Int getForwardDepsCount spi = P.count [DepUser P.==. spiSnapshotPackageId spi] getReverseDepsCount :: SnapshotPackageInfo -> ReaderT SqlBackend (RIO env) Int getReverseDepsCount spi = fromMaybe 0 <$> selectApplyMaybe unValue (from $ \(sp `InnerJoin` dep `InnerJoin` curPn) -> do on (dep ^. DepUses ==. curPn ^. PackageNameId) on (sp ^. SnapshotPackageId ==. dep ^. DepUser) where_ $ (curPn ^. PackageNameName ==. val (spiPackageName spi)) &&. (sp ^. SnapshotPackageSnapshot ==. val (spiSnapshotId spi)) pure countRows) getDepsCount :: GetStackageDatabase env m => SnapshotPackageInfo -> m (Int, Int) getDepsCount spi = run $ (,) <$> getForwardDepsCount spi <*> getReverseDepsCount spi getReverseDeps :: SnapshotPackageInfo -> Maybe Int -- ^ Optionally limit number of dependencies -> ReaderT SqlBackend (RIO env) [(PackageVersionRev, VersionRangeP)] getReverseDeps spi mlimit = fmap toDepRange <$> select (from $ \(sp `InnerJoin` dep `InnerJoin` pn `InnerJoin` v `InnerJoin` curPn) -> do on (dep ^. DepUses ==. curPn ^. PackageNameId) on (sp ^. SnapshotPackageVersion ==. v ^. VersionId) on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) on (sp ^. SnapshotPackageId ==. dep ^. DepUser) where_ $ (curPn ^. PackageNameName ==. val (spiPackageName spi)) &&. (sp ^. SnapshotPackageSnapshot ==. val (spiSnapshotId spi)) orderBy [desc (pn ^. PackageNameName)] maybe (pure ()) (limit . fromIntegral) mlimit pure ( pn ^. PackageNameName , v ^. VersionVersion , sp ^. SnapshotPackageRevision , dep ^. DepRange)) where toDepRange (pn, v, rev, range) = (PackageVersionRev (unValue pn) (toVersionMRev (unValue v) (unValue rev)), unValue range) ----- Deprecated -- | See if a package is deprecated on hackage and in favour of which packages. getDeprecated :: GetStackageDatabase env m => PackageNameP -> m (Bool, [PackageNameP]) getDeprecated pname = run $ lookupPackageNameId pname >>= \case Just pnid -> P.getBy (UniqueDeprecated pnid) >>= \case Just (Entity _ (Deprecated _ inFavourOfIds)) -> do names <- mapM lookupPackageNameById inFavourOfIds return (True, catMaybes names) Nothing -> return defRes Nothing -> return defRes where defRes = (False, []) -------------------------- -- Cron related queries -- -------------------------- snapshotMarkUpdated :: GetStackageDatabase env m => SnapshotId -> UTCTime -> m () snapshotMarkUpdated snapKey updatedOn = run $ P.update snapKey [SnapshotUpdatedOn P.=. Just updatedOn] insertSnapshotName :: GetStackageDatabase env m => SnapshotId -> SnapName -> m () insertSnapshotName snapKey snapName = run $ case snapName of SNLts major minor -> void $ insertUnique $ Lts snapKey major minor SNNightly day -> void $ insertUnique $ Nightly snapKey day -- | Add a map of all dependencies for the package together with version bounds. Returns a set of -- all dependencies that could not be found in pantry insertDeps :: HasLogFunc env => PackageIdentifierP -- ^ For error reporting only. -> SnapshotPackageId -> Map PackageNameP VersionRangeP -> ReaderT SqlBackend (RIO env) (Set PackageNameP) insertDeps pid snapshotPackageId dependencies = Map.keysSet <$> Map.traverseMaybeWithKey insertDep dependencies where insertDep dep range = lookupPackageNameId dep >>= \case Just packageNameId -> do void $ insertBy (Dep snapshotPackageId packageNameId range) return Nothing Nothing -> do lift $ logWarn $ "Couldn't find a dependency of " <> display pid <> " in Pantry with name: " <> display dep return $ Just dep -- TODO: Optimize, whenever package is already in one snapshot only create the modules and new -- SnapshotPackage addSnapshotPackage :: HasLogFunc env => SnapshotId -> CompilerP -> Origin -> Maybe (Entity Tree) -> Maybe HackageCabalId -> Bool -> Map FlagNameP Bool -> PackageIdentifierP -> GenericPackageDescription -> ReaderT SqlBackend (RIO env) () addSnapshotPackage snapshotId compiler origin mTree mHackageCabalId isHidden flags pid gpd = do let PackageIdentifierP pname pver = pid keyInsertBy = fmap (either entityKey id) . P.insertBy mTreeId = entityKey <$> mTree packageNameId <- maybe (getPackageNameId (unPackageNameP pname)) (pure . treeName . entityVal) mTree versionId <- maybe (getVersionId (unVersionP pver)) (pure . treeVersion . entityVal) mTree mrevision <- maybe (pure Nothing) getHackageRevision mHackageCabalId mreadme <- fromMaybe (pure Nothing) $ getContentTreeEntryId <$> mTreeId <*> mreadmeQuery mchangelog <- fromMaybe (pure Nothing) $ getContentTreeEntryId <$> mTreeId <*> mchangelogQuery let snapshotPackage = SnapshotPackage { snapshotPackageSnapshot = snapshotId , snapshotPackagePackageName = packageNameId , snapshotPackageVersion = versionId , snapshotPackageRevision = mrevision , snapshotPackageCabal = treeCabal =<< entityVal <$> mTree , snapshotPackageTreeBlob = treeKey . entityVal <$> mTree , snapshotPackageOrigin = origin , snapshotPackageOriginUrl = "" -- TODO: add , snapshotPackageSynopsis = getSynopsis gpd , snapshotPackageReadme = mreadme , snapshotPackageChangelog = mchangelog , snapshotPackageIsHidden = isHidden , snapshotPackageFlags = flags } snapshotPackageId <- keyInsertBy snapshotPackage -- TODO: collect all missing dependencies and make a report _ <- insertDeps pid snapshotPackageId (extractDependencies compiler flags gpd) insertSnapshotPackageModules snapshotPackageId (extractModuleNames gpd) getContentTreeEntryId :: TreeId -> (SqlExpr (Value SafeFilePath) -> SqlExpr (Value Bool)) -> ReaderT SqlBackend (RIO env) (Maybe TreeEntryId) getContentTreeEntryId treeId filePathQuery = do (mteid, _isMarkdown) <- foldl' preferMarkdown (Nothing, False) <$> select (from $ \(te `InnerJoin` p) -> do on $ te ^. TreeEntryPath ==. p ^. FilePathId where_ $ (te ^. TreeEntryTree ==. val treeId) &&. filePathQuery (p ^. FilePathPath) pure (p ^. FilePathPath, te ^. TreeEntryId)) pure mteid where preferMarkdown (_, False) (Value path, Value teid) = (Just teid, isMarkdownFilePath path) preferMarkdown pref@(_, True) _ = pref mchangelogQuery :: Maybe (SqlExpr (Value SafeFilePath) -> SqlExpr (Value Bool)) mchangelogQuery = do changelog <- mkSafeFilePath "changelog." changes <- mkSafeFilePath "changes." pure (\ path -> (path `ilike` val changelog ++. (%)) ||. (path `ilike` val changes ++. (%))) mreadmeQuery :: Maybe (SqlExpr (Value SafeFilePath) -> SqlExpr (Value Bool)) mreadmeQuery = do readme <- mkSafeFilePath "readme." pure (\ path -> path `ilike` val readme ++. (%)) getHackageRevision :: MonadIO m => HackageCabalId -> ReaderT SqlBackend m (Maybe Revision) getHackageRevision hcid = selectApplyMaybe unValue $ from $ \hc -> do where_ (hc ^. HackageCabalId ==. val hcid) pure (hc ^. HackageCabalRevision) lookupPackageNameId :: PackageNameP -> ReaderT SqlBackend (RIO env) (Maybe PackageNameId) lookupPackageNameId pname = fmap entityKey <$> getBy (UniquePackageName pname) lookupPackageNameById :: PackageNameId -> ReaderT SqlBackend (RIO env) (Maybe PackageNameP) lookupPackageNameById pnid = fmap PackageNameP <$> getPackageNameById pnid addDeprecated :: HasLogFunc env => Deprecation -> ReaderT SqlBackend (RIO env) () addDeprecated (Deprecation pname inFavourOfNameSet) = do mPackageNameId <- lookupPackageNameId pname case mPackageNameId of Just packageNameId -> do let inFavourOfNames = Set.toList inFavourOfNameSet inFavourOfAllIds <- mapM lookupPackageNameId inFavourOfNames let (badNames, inFavourOfIds) = partitionEithers $ L.zipWith (\name mid -> maybe (Left name) Right mid) inFavourOfNames inFavourOfAllIds void $ upsertBy (UniqueDeprecated packageNameId) (Deprecated packageNameId inFavourOfIds) [DeprecatedInFavourOf P.=. inFavourOfIds] unless (null badNames) $ lift $ logError $ mconcat ("Couldn't find in Pantry names of packages in deprecation list: " : L.intersperse ", " (map display badNames)) Nothing -> lift $ logError $ "Package name: " <> display pname <> " from deprecation list was not found in Pantry." getHackageCabalByRev0 :: PackageIdentifierP -> ReaderT SqlBackend (RIO env) (Maybe (HackageCabalId, BlobId, Maybe TreeId)) getHackageCabalByRev0 pid = getHackageCabalByRev pid Nothing getHackageCabalByRev :: PackageIdentifierP -> Maybe Revision -> ReaderT SqlBackend (RIO env) (Maybe (HackageCabalId, BlobId, Maybe TreeId)) getHackageCabalByRev (PackageIdentifierP pname ver) mrev = selectApplyMaybe (\(Value hcid, Value bid, Value mtid) -> (hcid, bid, mtid)) $ from $ \(hc `InnerJoin` pn `InnerJoin` v) -> do on (hc ^. HackageCabalVersion ==. v ^. VersionId) on (hc ^. HackageCabalName ==. pn ^. PackageNameId) where_ ((pn ^. PackageNameName ==. val pname) &&. (v ^. VersionVersion ==. val ver) &&. (hc ^. HackageCabalRevision ==. val (fromMaybe (Revision 0) mrev))) return (hc ^. HackageCabalId, hc ^. HackageCabalCabal, hc ^. HackageCabalTree) -- | This query will return `Nothing` if the tarball for the hackage cabal file hasn't been loaded -- yet. getHackageCabalByKey :: PackageIdentifierP -> BlobKey -> ReaderT SqlBackend (RIO env) (Maybe (HackageCabalId, Maybe TreeId)) getHackageCabalByKey (PackageIdentifierP pname ver) (BlobKey sha size) = selectApplyMaybe (\(Value hcid, Value mtid) -> (hcid, mtid)) $ from $ \(hc `InnerJoin` pn `InnerJoin` v `InnerJoin` b) -> do on (hc ^. HackageCabalCabal ==. b ^. BlobId) on (hc ^. HackageCabalVersion ==. v ^. VersionId) on (hc ^. HackageCabalName ==. pn ^. PackageNameId) where_ ((pn ^. PackageNameName ==. val pname) &&. (v ^. VersionVersion ==. val ver) &&. (b ^. BlobSha ==. val sha) &&. (b ^. BlobSize ==. val size)) return (hc ^. HackageCabalId, hc ^. HackageCabalTree) getSnapshotPackageId :: SnapshotId -> PackageIdentifierP -> ReaderT SqlBackend (RIO env) (Maybe SnapshotPackageId) getSnapshotPackageId snapshotId (PackageIdentifierP pname ver) = selectApplyMaybe unValue $ from $ \(sp `InnerJoin` pn `InnerJoin` v) -> do on (sp ^. SnapshotPackageVersion ==. v ^. VersionId) on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) where_ ((sp ^. SnapshotPackageSnapshot ==. val snapshotId) &&. (pn ^. PackageNameName ==. val pname) &&. (v ^. VersionVersion ==. val ver)) return (sp ^. SnapshotPackageId) getSnapshotPackageCabalBlob :: GetStackageDatabase env m => SnapshotId -> PackageNameP -> m (Maybe ByteString) getSnapshotPackageCabalBlob snapshotId pname = run $ selectApplyMaybe unValue $ from $ \(blob `InnerJoin` sp `InnerJoin` pn) -> do on (sp ^. SnapshotPackagePackageName ==. pn ^. PackageNameId) on (just (blob ^. BlobId) ==. sp ^. SnapshotPackageCabal) where_ ((sp ^. SnapshotPackageSnapshot ==. val snapshotId) &&. (pn ^. PackageNameName ==. val pname)) return (blob ^. BlobContents) -- | Add all modules available for the package in a particular snapshot. Initially they are marked -- as without available documentation. insertSnapshotPackageModules :: SnapshotPackageId -> [ModuleNameP] -> ReaderT SqlBackend (RIO env) () insertSnapshotPackageModules snapshotPackageId = mapM_ $ \modName -> do moduleId <- insertModuleSafe modName void $ P.insertBy (SnapshotPackageModule snapshotPackageId moduleId False) -- | Idempotent and thread safe way of adding a new module. insertModuleSafe :: ModuleNameP -> ReaderT SqlBackend (RIO env) ModuleNameId insertModuleSafe modName = do rawExecute "INSERT INTO module_name(name) VALUES (?) ON CONFLICT DO NOTHING" [toPersistValue modName] mModId <- select $ from $ \m -> do where_ (m ^. ModuleNameName ==. val modName) return (m ^. ModuleNameId) case mModId of [Value modId] -> return modId _ -> error $ "Module name: " ++ show modName ++ " should have been inserted by now" markModuleHasDocs :: SnapshotId -> PackageIdentifierP -> Maybe SnapshotPackageId -- ^ If we know ahead of time the SnapshotPackageId it will speed up a great deal if don't have -- to look it up in the database. -> ModuleNameP -> ReaderT SqlBackend (RIO env) (Maybe SnapshotPackageId) markModuleHasDocs snapshotId pid mSnapshotPackageId modName = maybe (getSnapshotPackageId snapshotId pid) (pure . Just) mSnapshotPackageId >>= \case Just snapshotPackageId -> do rawExecute "UPDATE snapshot_package_module \ \SET has_docs = true \ \FROM module_name \ \WHERE module_name.id = snapshot_package_module.module \ \AND module_name.name = ? \ \AND snapshot_package_module.snapshot_package = ?" [toPersistValue modName, toPersistValue snapshotPackageId] return $ Just snapshotPackageId Nothing -> return Nothing