1 {-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
3 Module : Gargantext.API.Ngrams
4 Description : Server API
5 Copyright : (c) CNRS, 2017-Present
6 License : AGPL + CECILL v3
7 Maintainer : team@gargantext.org
8 Stability : experimental
14 get ngrams filtered by NgramsType
19 {-# LANGUAGE ConstraintKinds #-}
20 {-# LANGUAGE ScopedTypeVariables #-}
21 {-# LANGUAGE TemplateHaskell #-}
22 {-# LANGUAGE TypeOperators #-}
23 {-# LANGUAGE TypeFamilies #-}
25 module Gargantext.API.Ngrams
32 --, rmListNgrams TODO fix before exporting
35 , apiNgramsTableCorpus
58 , NgramsRepoElement(..)
67 , ngramsTypeFromTabType
85 , listNgramsChangedSince
89 import Control.Concurrent
90 import Control.Lens ((.~), view, (^.), (^..), (+~), (%~), (.~), sumOf, at, _Just, Each(..), (%%~), mapped)
91 import Control.Monad.Reader
92 import Data.Aeson hiding ((.=))
93 import qualified Data.Aeson.Text as DAT
94 import Data.Either (Either(..))
96 import qualified Data.List as List
97 import Data.Map.Strict (Map)
98 import qualified Data.Map.Strict as Map
99 import qualified Data.Map.Strict.Patch as PM
100 import Data.Maybe (fromMaybe)
102 import Data.Ord (Down(..))
103 import Data.Patch.Class (Action(act), Transformable(..), ours)
104 import qualified Data.Set as S
105 import qualified Data.Set as Set
106 import Data.Swagger hiding (version, patch)
107 import Data.Text (Text, isInfixOf, unpack)
108 import Data.Text.Lazy.IO as DTL
109 import Formatting (hprint, int, (%))
110 import Formatting.Clock (timeSpecs)
111 import GHC.Generics (Generic)
112 import Servant hiding (Patch)
113 import System.Clock (getTime, TimeSpec, Clock(..))
114 import System.IO (stderr)
115 import Test.QuickCheck (elements)
116 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
118 import Prelude (error)
119 import Gargantext.Prelude
121 import Gargantext.API.Admin.Types (HasSettings)
122 import Gargantext.API.Ngrams.Types
123 import Gargantext.Core.Types (ListType(..), NodeId, ListId, DocId, Limit, Offset, HasInvalidError, assertValid)
124 import Gargantext.Core.Types (TODO)
125 import Gargantext.Core.Viz.Graph.API (recomputeGraph)
126 import Gargantext.Core.Viz.Graph.Distances (Distance(Conditional))
127 import Gargantext.Database.Action.Metrics.NgramsByNode (getOccByNgramsOnlyFast')
128 import Gargantext.Database.Query.Table.Node.Select
129 import Gargantext.Database.Query.Table.Ngrams hiding (NgramsType(..), ngrams, ngramsType, ngrams_terms)
130 import Gargantext.Database.Admin.Config (userMaster)
131 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
132 import Gargantext.Database.Admin.Types.Node (NodeType(..))
133 import Gargantext.Database.Prelude (HasConnectionPool, HasConfig)
134 import qualified Gargantext.Database.Query.Table.Ngrams as TableNgrams
135 import Gargantext.Database.Query.Table.Node (getNode)
136 import Gargantext.Database.Schema.Node (NodePoly(..))
139 -- TODO sequences of modifications (Patchs)
140 type NgramsIdPatch = Patch NgramsId NgramsPatch
142 ngramsPatch :: Int -> NgramsPatch
143 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopTerm)]) (Set.fromList [n]) Set.empty
145 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
146 toEdit n p = Edit n p
147 ngramsIdPatch :: Patch NgramsId NgramsPatch
148 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
149 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
150 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
153 -- applyPatchBack :: Patch -> IO Patch
154 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
156 ------------------------------------------------------------------------
157 ------------------------------------------------------------------------
158 ------------------------------------------------------------------------
161 -- TODO: Replace.old is ignored which means that if the current list
162 -- `MapTerm` and that the patch is `Replace CandidateTerm StopTerm` then
163 -- the list is going to be `StopTerm` while it should keep `MapTerm`.
164 -- However this should not happen in non conflicting situations.
165 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
166 mkListsUpdate nt patches =
167 [ (ngramsTypeId nt, ng, listTypeId lt)
168 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
169 , lt <- patch ^.. patch_list . new
172 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
175 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
176 mkChildrenGroups addOrRem nt patches =
177 [ (ngramsTypeId nt, parent, child)
178 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
179 , child <- patch ^.. patch_children . to addOrRem . folded
183 ngramsTypeFromTabType :: TabType -> TableNgrams.NgramsType
184 ngramsTypeFromTabType tabType =
185 let lieu = "Garg.API.Ngrams: " :: Text in
187 Sources -> TableNgrams.Sources
188 Authors -> TableNgrams.Authors
189 Institutes -> TableNgrams.Institutes
190 Terms -> TableNgrams.NgramsTerms
191 _ -> panic $ lieu <> "No Ngrams for this tab"
192 -- TODO: This `panic` would disapear with custom NgramsType.
194 ------------------------------------------------------------------------
196 saveRepo :: ( MonadReader env m, MonadBase IO m, HasRepoSaver env )
198 saveRepo = liftBase =<< view repoSaver
200 listTypeConflictResolution :: ListType -> ListType -> ListType
201 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
203 ngramsStatePatchConflictResolution
204 :: TableNgrams.NgramsType
207 -> ConflictResolutionNgramsPatch
208 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
209 = (ours, (const ours, ours), (False, False))
210 -- (False, False) mean here that Mod has always priority.
211 -- (True, False) <- would mean priority to the left (same as ours).
213 -- undefined {- TODO think this through -}, listTypeConflictResolution)
216 -- Insertions are not considered as patches,
217 -- they do not extend history,
218 -- they do not bump version.
219 insertNewOnly :: a -> Maybe b -> a
220 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
221 -- TODO error handling
223 something :: Monoid a => Maybe a -> a
224 something Nothing = mempty
225 something (Just a) = a
228 -- TODO refactor with putListNgrams
229 copyListNgrams :: RepoCmdM env err m
230 => NodeId -> NodeId -> NgramsType
232 copyListNgrams srcListId dstListId ngramsType = do
234 liftBase $ modifyMVar_ var $
235 pure . (r_state . at ngramsType %~ (Just . f . something))
238 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
239 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
241 -- TODO refactor with putListNgrams
242 -- The list must be non-empty!
243 -- The added ngrams must be non-existent!
244 addListNgrams :: RepoCmdM env err m
245 => NodeId -> NgramsType
246 -> [NgramsElement] -> m ()
247 addListNgrams listId ngramsType nes = do
249 liftBase $ modifyMVar_ var $
250 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
253 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
257 rmListNgrams :: RepoCmdM env err m
259 -> TableNgrams.NgramsType
261 rmListNgrams l nt = setListNgrams l nt mempty
263 -- | TODO: incr the Version number
264 -- && should use patch
266 setListNgrams :: RepoCmdM env err m
268 -> TableNgrams.NgramsType
269 -> Map NgramsTerm NgramsRepoElement
271 setListNgrams listId ngramsType ns = do
273 liftBase $ modifyMVar_ var $
277 (at listId .~ ( Just ns))
284 -- This is no longer part of the API.
285 -- This function is maintained for its usage in Database.Action.Flow.List.
286 -- If the given list of ngrams elements contains ngrams already in
287 -- the repo, they will be ignored.
288 putListNgrams :: (HasInvalidError err, RepoCmdM env err m)
290 -> TableNgrams.NgramsType
293 putListNgrams _ _ [] = pure ()
294 putListNgrams nodeId ngramsType nes = putListNgrams' nodeId ngramsType m
296 m = Map.fromList $ map (\n -> (n ^. ne_ngrams, ngramsElementToRepo n)) nes
298 putListNgrams' :: (HasInvalidError err, RepoCmdM env err m)
300 -> TableNgrams.NgramsType
301 -> Map NgramsTerm NgramsRepoElement
303 putListNgrams' nodeId ngramsType ns = do
304 -- printDebug "[putListNgrams'] nodeId" nodeId
305 -- printDebug "[putListNgrams'] ngramsType" ngramsType
306 -- printDebug "[putListNgrams'] ns" ns
308 let p1 = NgramsTablePatch . PM.fromMap $ NgramsReplace Nothing . Just <$> ns
309 (p0, p0_validity) = PM.singleton nodeId p1
310 (p, p_validity) = PM.singleton ngramsType p0
311 assertValid p0_validity
312 assertValid p_validity
316 q <- commitStatePatch (Versioned v p)
318 -- What if another commit comes in between?
319 -- Shall we have a blindCommitStatePatch? It would not ask for a version but just a patch.
320 -- The modifyMVar_ would test the patch with applicable first.
321 -- If valid the rest would be atomic and no merge is required.
324 liftBase $ modifyMVar_ var $ \r -> do
325 pure $ r & r_version +~ 1
327 & r_state . at ngramsType %~
340 currentVersion :: RepoCmdM env err m
344 r <- liftBase $ readMVar var
345 pure $ r ^. r_version
348 -- tableNgramsPut :: (HasInvalidError err, RepoCmdM env err m)
349 commitStatePatch :: RepoCmdM env err m => Versioned NgramsStatePatch -> m (Versioned NgramsStatePatch)
350 commitStatePatch (Versioned p_version p) = do
352 vq' <- liftBase $ modifyMVar var $ \r -> do
354 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
355 (p', q') = transformWith ngramsStatePatchConflictResolution p q
356 r' = r & r_version +~ 1
358 & r_history %~ (p' :)
360 -- Ideally we would like to check these properties. However:
361 -- * They should be checked only to debug the code. The client data
362 -- should be able to trigger these.
363 -- * What kind of error should they throw (we are in IO here)?
364 -- * Should we keep modifyMVar?
365 -- * Should we throw the validation in an Exception, catch it around
366 -- modifyMVar and throw it back as an Error?
367 assertValid $ transformable p q
368 assertValid $ applicable p' (r ^. r_state)
370 pure (r', Versioned (r' ^. r_version) q')
375 -- This is a special case of tableNgramsPut where the input patch is empty.
376 tableNgramsPull :: RepoCmdM env err m
378 -> TableNgrams.NgramsType
380 -> m (Versioned NgramsTablePatch)
381 tableNgramsPull listId ngramsType p_version = do
383 r <- liftBase $ readMVar var
386 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
387 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
389 pure (Versioned (r ^. r_version) q_table)
391 -- Apply the given patch to the DB and returns the patch to be applied on the
394 tableNgramsPut :: (HasNodeError err,
397 HasConnectionPool env,
402 -> Versioned NgramsTablePatch
403 -> m (Versioned NgramsTablePatch)
404 tableNgramsPut tabType listId (Versioned p_version p_table)
405 | p_table == mempty = do
406 let ngramsType = ngramsTypeFromTabType tabType
407 tableNgramsPull listId ngramsType p_version
410 let ngramsType = ngramsTypeFromTabType tabType
411 (p0, p0_validity) = PM.singleton listId p_table
412 (p, p_validity) = PM.singleton ngramsType p0
414 assertValid p0_validity
415 assertValid p_validity
417 ret <- commitStatePatch (Versioned p_version p)
418 <&> v_data %~ (view (_PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just))
420 node <- getNode listId
421 let nId = _node_id node
422 uId = _node_userId node
423 _ <- recomputeGraph uId nId Conditional
428 { _ne_list :: ListType
429 If we merge the parents/children we can potentially create cycles!
430 , _ne_parent :: Maybe NgramsTerm
431 , _ne_children :: MSet NgramsTerm
435 getNgramsTableMap :: RepoCmdM env err m
437 -> TableNgrams.NgramsType
438 -> m (Versioned NgramsTableMap)
439 getNgramsTableMap nodeId ngramsType = do
441 repo <- liftBase $ readMVar v
442 pure $ Versioned (repo ^. r_version)
443 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
445 dumpJsonTableMap :: RepoCmdM env err m
448 -> TableNgrams.NgramsType
450 dumpJsonTableMap fpath nodeId ngramsType = do
451 m <- getNgramsTableMap nodeId ngramsType
452 liftBase $ DTL.writeFile (unpack fpath) (DAT.encodeToLazyText m)
458 -- | TODO Errors management
459 -- TODO: polymorphic for Annuaire or Corpus or ...
460 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
461 -- TODO: should take only one ListId
463 getTime' :: MonadBase IO m => m TimeSpec
464 getTime' = liftBase $ getTime ProcessCPUTime
467 getTableNgrams :: forall env err m.
468 (RepoCmdM env err m, HasNodeError err, HasConnectionPool env, HasConfig env)
469 => NodeType -> NodeId -> TabType
470 -> ListId -> Limit -> Maybe Offset
472 -> Maybe MinSize -> Maybe MaxSize
474 -> (NgramsTerm -> Bool)
475 -> m (Versioned NgramsTable)
476 getTableNgrams _nType nId tabType listId limit_ offset
477 listType minSize maxSize orderBy searchQuery = do
480 -- lIds <- selectNodesWithUsername NodeList userMaster
482 ngramsType = ngramsTypeFromTabType tabType
483 offset' = maybe 0 identity offset
484 listType' = maybe (const True) (==) listType
485 minSize' = maybe (const True) (<=) minSize
486 maxSize' = maybe (const True) (>=) maxSize
488 selected_node n = minSize' s
490 && searchQuery (n ^. ne_ngrams)
491 && listType' (n ^. ne_list)
495 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
497 ---------------------------------------
498 sortOnOrder Nothing = identity
499 sortOnOrder (Just TermAsc) = List.sortOn $ view ne_ngrams
500 sortOnOrder (Just TermDesc) = List.sortOn $ Down . view ne_ngrams
501 sortOnOrder (Just ScoreAsc) = List.sortOn $ view ne_occurrences
502 sortOnOrder (Just ScoreDesc) = List.sortOn $ Down . view ne_occurrences
504 ---------------------------------------
505 selectAndPaginate :: Map NgramsTerm NgramsElement -> [NgramsElement]
506 selectAndPaginate tableMap = roots <> inners
508 list = tableMap ^.. each
509 rootOf ne = maybe ne (\r -> fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r))
511 selected_nodes = list & take limit_
513 . filter selected_node
514 . sortOnOrder orderBy
515 roots = rootOf <$> selected_nodes
516 rootsSet = Set.fromList (_ne_ngrams <$> roots)
517 inners = list & filter (selected_inner rootsSet)
519 ---------------------------------------
520 setScores :: forall t. Each t t NgramsElement NgramsElement => Bool -> t -> m t
521 setScores False table = pure table
522 setScores True table = do
523 let ngrams_terms = unNgramsTerm <$> (table ^.. each . ne_ngrams)
525 occurrences <- getOccByNgramsOnlyFast' nId
530 liftBase $ hprint stderr
531 ("getTableNgrams/setScores #ngrams=" % int % " time=" % timeSpecs % "\n")
532 (length ngrams_terms) t1 t2
534 occurrences <- getOccByNgramsOnlySlow nType nId
540 setOcc ne = ne & ne_occurrences .~ sumOf (at (unNgramsTerm (ne ^. ne_ngrams)) . _Just) occurrences
542 pure $ table & each %~ setOcc
543 ---------------------------------------
545 -- lists <- catMaybes <$> listsWith userMaster
546 -- trace (show lists) $
547 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
549 let scoresNeeded = needsScores orderBy
550 tableMap1 <- getNgramsTableMap listId ngramsType
552 tableMap2 <- tableMap1 & v_data %%~ setScores scoresNeeded
553 . Map.mapWithKey ngramsElementFromRepo
555 tableMap3 <- tableMap2 & v_data %%~ fmap NgramsTable
556 . setScores (not scoresNeeded)
559 liftBase $ hprint stderr
560 ("getTableNgrams total=" % timeSpecs
561 % " map1=" % timeSpecs
562 % " map2=" % timeSpecs
563 % " map3=" % timeSpecs
564 % " sql=" % (if scoresNeeded then "map2" else "map3")
566 ) t0 t3 t0 t1 t1 t2 t2 t3
570 scoresRecomputeTableNgrams :: forall env err m. (RepoCmdM env err m, HasNodeError err, HasConnectionPool env, HasConfig env) => NodeId -> TabType -> ListId -> m Int
571 scoresRecomputeTableNgrams nId tabType listId = do
572 tableMap <- getNgramsTableMap listId ngramsType
573 _ <- tableMap & v_data %%~ setScores
574 . Map.mapWithKey ngramsElementFromRepo
578 ngramsType = ngramsTypeFromTabType tabType
580 setScores :: forall t. Each t t NgramsElement NgramsElement => t -> m t
582 let ngrams_terms = unNgramsTerm <$> (table ^.. each . ne_ngrams)
583 occurrences <- getOccByNgramsOnlyFast' nId
588 setOcc ne = ne & ne_occurrences .~ sumOf (at (unNgramsTerm (ne ^. ne_ngrams)) . _Just) occurrences
590 pure $ table & each %~ setOcc
596 -- TODO: find a better place for the code above, All APIs stay here
598 data OrderBy = TermAsc | TermDesc | ScoreAsc | ScoreDesc
599 deriving (Generic, Enum, Bounded, Read, Show)
601 instance FromHttpApiData OrderBy
603 parseUrlPiece "TermAsc" = pure TermAsc
604 parseUrlPiece "TermDesc" = pure TermDesc
605 parseUrlPiece "ScoreAsc" = pure ScoreAsc
606 parseUrlPiece "ScoreDesc" = pure ScoreDesc
607 parseUrlPiece _ = Left "Unexpected value of OrderBy"
610 instance ToParamSchema OrderBy
611 instance FromJSON OrderBy
612 instance ToJSON OrderBy
613 instance ToSchema OrderBy
614 instance Arbitrary OrderBy
616 arbitrary = elements [minBound..maxBound]
618 needsScores :: Maybe OrderBy -> Bool
619 needsScores (Just ScoreAsc) = True
620 needsScores (Just ScoreDesc) = True
621 needsScores _ = False
623 type TableNgramsApiGet = Summary " Table Ngrams API Get"
624 :> QueryParamR "ngramsType" TabType
625 :> QueryParamR "list" ListId
626 :> QueryParamR "limit" Limit
627 :> QueryParam "offset" Offset
628 :> QueryParam "listType" ListType
629 :> QueryParam "minTermSize" MinSize
630 :> QueryParam "maxTermSize" MaxSize
631 :> QueryParam "orderBy" OrderBy
632 :> QueryParam "search" Text
633 :> Get '[JSON] (Versioned NgramsTable)
635 type TableNgramsApiPut = Summary " Table Ngrams API Change"
636 :> QueryParamR "ngramsType" TabType
637 :> QueryParamR "list" ListId
638 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
639 :> Put '[JSON] (Versioned NgramsTablePatch)
641 type RecomputeScoresNgramsApiGet = Summary " Recompute scores for ngrams table"
642 :> QueryParamR "ngramsType" TabType
643 :> QueryParamR "list" ListId
644 :> "recompute" :> Post '[JSON] Int
646 type TableNgramsApiGetVersion = Summary " Table Ngrams API Get Version"
647 :> QueryParamR "ngramsType" TabType
648 :> QueryParamR "list" ListId
649 :> Get '[JSON] Version
651 type TableNgramsApi = TableNgramsApiGet
652 :<|> TableNgramsApiPut
653 :<|> RecomputeScoresNgramsApiGet
654 :<|> "version" :> TableNgramsApiGetVersion
656 getTableNgramsCorpus :: (RepoCmdM env err m, HasNodeError err, HasConnectionPool env, HasConfig env)
663 -> Maybe MinSize -> Maybe MaxSize
665 -> Maybe Text -- full text search
666 -> m (Versioned NgramsTable)
667 getTableNgramsCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy mt =
668 getTableNgrams NodeCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
670 searchQuery (NgramsTerm nt) = maybe (const True) isInfixOf mt nt
672 getTableNgramsVersion :: (RepoCmdM env err m, HasNodeError err, HasConnectionPool env, HasConfig env)
677 getTableNgramsVersion _nId _tabType _listId = currentVersion
679 -- Versioned { _v_version = v } <- getTableNgramsCorpus nId tabType listId 100000 Nothing Nothing Nothing Nothing Nothing Nothing
680 -- This line above looks like a waste of computation to finally get only the version.
681 -- See the comment about listNgramsChangedSince.
684 -- | Text search is deactivated for now for ngrams by doc only
685 getTableNgramsDoc :: (RepoCmdM env err m, HasNodeError err, HasConnectionPool env, HasConfig env)
687 -> ListId -> Limit -> Maybe Offset
689 -> Maybe MinSize -> Maybe MaxSize
691 -> Maybe Text -- full text search
692 -> m (Versioned NgramsTable)
693 getTableNgramsDoc dId tabType listId limit_ offset listType minSize maxSize orderBy _mt = do
694 ns <- selectNodesWithUsername NodeList userMaster
695 let ngramsType = ngramsTypeFromTabType tabType
696 ngs <- selectNgramsByDoc (ns <> [listId]) dId ngramsType
697 let searchQuery (NgramsTerm nt) = flip S.member (S.fromList ngs) nt
698 getTableNgrams NodeDocument dId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
702 apiNgramsTableCorpus :: ( RepoCmdM env err m
704 , HasInvalidError err
705 , HasConnectionPool env
709 => NodeId -> ServerT TableNgramsApi m
710 apiNgramsTableCorpus cId = getTableNgramsCorpus cId
712 :<|> scoresRecomputeTableNgrams cId
713 :<|> getTableNgramsVersion cId
715 apiNgramsTableDoc :: ( RepoCmdM env err m
717 , HasInvalidError err
718 , HasConnectionPool env
722 => DocId -> ServerT TableNgramsApi m
723 apiNgramsTableDoc dId = getTableNgramsDoc dId
725 :<|> scoresRecomputeTableNgrams dId
726 :<|> getTableNgramsVersion dId
727 -- > index all the corpus accordingly (TODO AD)
729 -- Did the given list of ngrams changed since the given version?
730 -- The returned value is versioned boolean value, meaning that one always retrieve the
732 -- If the given version is negative then one simply receive the latest version and True.
733 -- Using this function is more precise than simply comparing the latest version number
734 -- with the local version number. Indeed there might be no change to this particular list
735 -- and still the version number has changed because of other lists.
737 -- Here the added value is to make a compromise between precision, computation, and bandwidth:
738 -- * currentVersion: good computation, good bandwidth, bad precision.
739 -- * listNgramsChangedSince: good precision, good bandwidth, bad computation.
740 -- * tableNgramsPull: good precision, good bandwidth (if you use the received data!), bad computation.
741 listNgramsChangedSince :: RepoCmdM env err m
742 => ListId -> TableNgrams.NgramsType -> Version -> m (Versioned Bool)
743 listNgramsChangedSince listId ngramsType version
745 Versioned <$> currentVersion <*> pure True
747 tableNgramsPull listId ngramsType version & mapped . v_data %~ (== mempty)