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 DataKinds #-}
21 {-# LANGUAGE DeriveGeneric #-}
22 {-# LANGUAGE NoImplicitPrelude #-}
23 {-# LANGUAGE OverloadedStrings #-}
24 {-# LANGUAGE ScopedTypeVariables #-}
25 {-# LANGUAGE TemplateHaskell #-}
26 {-# LANGUAGE TypeOperators #-}
27 {-# LANGUAGE FlexibleContexts #-}
28 {-# LANGUAGE FlexibleInstances #-}
29 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
30 {-# LANGUAGE MultiParamTypeClasses #-}
31 {-# LANGUAGE RankNTypes #-}
32 {-# LANGUAGE TypeFamilies #-}
33 {-# OPTIONS -fno-warn-orphans #-}
35 module Gargantext.API.Ngrams
47 , apiNgramsTableCorpus
69 , NgramsRepoElement(..)
78 , ngramsTypeFromTabType
95 , listNgramsChangedSince
99 -- import Debug.Trace (trace)
100 import Prelude (Enum, Bounded, Semigroup(..), minBound, maxBound {-, round-}, error)
101 -- import Gargantext.Database.Schema.User (UserId)
102 import Data.Patch.Class (Replace, replace, Action(act), Applicable(..),
103 Composable(..), Transformable(..),
104 PairPatch(..), Patched, ConflictResolution,
105 ConflictResolutionReplace, ours)
106 import qualified Data.Map.Strict.Patch as PM
108 import Data.Ord (Down(..))
110 --import Data.Semigroup
111 import Data.Set (Set)
112 import qualified Data.Set as S
113 import qualified Data.List as List
114 import Data.Maybe (fromMaybe)
115 -- import Data.Tuple.Extra (first)
116 import qualified Data.Map.Strict as Map
117 import Data.Map.Strict (Map)
118 import qualified Data.Set as Set
119 import Control.Category ((>>>))
120 import Control.Concurrent
121 import Control.Lens (makeLenses, makePrisms, Getter, Iso', iso, from, (.~), (?=), (#), to, folded, {-withIndex, ifolded,-} view, use, (^.), (^..), (^?), (+~), (%~), (.~), (%=), sumOf, at, _Just, Each(..), itraverse_, both, forOf_, (%%~), (?~), mapped)
122 import Control.Monad.Base (MonadBase, liftBase)
123 import Control.Monad.Error.Class (MonadError)
124 import Control.Monad.Reader
125 import Control.Monad.State
126 import Control.Monad.Trans.Control (MonadBaseControl)
127 import Data.Aeson hiding ((.=))
128 import Data.Aeson.TH (deriveJSON)
129 import Data.Either(Either(Left))
130 import Data.Either.Extra (maybeToEither)
131 -- import Data.Map (lookup)
132 import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
133 import Data.Swagger hiding (version, patch)
134 import Data.Text (Text, isInfixOf, count)
136 import Formatting (hprint, int, (%))
137 import Formatting.Clock (timeSpecs)
138 import GHC.Generics (Generic)
139 import Gargantext.Core.Utils.Prefix (unPrefix, unPrefixSwagger)
140 -- import Gargantext.Database.Schema.Ngrams (NgramsTypeId, ngramsTypeId, NgramsTableData(..))
141 import Gargantext.Database.Config (userMaster)
142 import Gargantext.Database.Metrics.NgramsByNode (getOccByNgramsOnlyFast')
143 import Gargantext.Database.Schema.Ngrams (NgramsType)
144 import Gargantext.Database.Types.Node (NodeType(..))
145 import Gargantext.Database.Utils (fromField', HasConnectionPool)
146 import Gargantext.Database.Node.Select
147 import Gargantext.Database.Ngrams
148 --import Gargantext.Database.Lists (listsWith)
149 import Gargantext.Database.Types.Errors (HasNodeError)
150 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
151 import qualified Gargantext.Database.Schema.Ngrams as Ngrams
152 -- import Gargantext.Database.Schema.NodeNgram hiding (Action)
153 import Gargantext.Prelude
154 import Gargantext.Core.Types (TODO)
155 import Gargantext.Core.Types (ListType(..), NodeId, ListId, DocId, Limit, Offset, HasInvalidError, assertValid)
156 import Servant hiding (Patch)
157 import System.Clock (getTime, TimeSpec, Clock(..))
158 import System.FileLock (FileLock)
159 import System.IO (stderr)
160 import Test.QuickCheck (elements)
161 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
163 ------------------------------------------------------------------------
164 --data FacetFormat = Table | Chart
165 data TabType = Docs | Trash | MoreFav | MoreTrash
166 | Terms | Sources | Authors | Institutes
168 deriving (Generic, Enum, Bounded, Show)
170 instance FromHttpApiData TabType
172 parseUrlPiece "Docs" = pure Docs
173 parseUrlPiece "Trash" = pure Trash
174 parseUrlPiece "MoreFav" = pure MoreFav
175 parseUrlPiece "MoreTrash" = pure MoreTrash
177 parseUrlPiece "Terms" = pure Terms
178 parseUrlPiece "Sources" = pure Sources
179 parseUrlPiece "Institutes" = pure Institutes
180 parseUrlPiece "Authors" = pure Authors
182 parseUrlPiece "Contacts" = pure Contacts
184 parseUrlPiece _ = Left "Unexpected value of TabType"
186 instance ToParamSchema TabType
187 instance ToJSON TabType
188 instance FromJSON TabType
189 instance ToSchema TabType
190 instance Arbitrary TabType
192 arbitrary = elements [minBound .. maxBound]
194 newtype MSet a = MSet (Map a ())
195 deriving (Eq, Ord, Show, Generic, Arbitrary, Semigroup, Monoid)
197 instance ToJSON a => ToJSON (MSet a) where
198 toJSON (MSet m) = toJSON (Map.keys m)
199 toEncoding (MSet m) = toEncoding (Map.keys m)
201 mSetFromSet :: Set a -> MSet a
202 mSetFromSet = MSet . Map.fromSet (const ())
204 mSetFromList :: Ord a => [a] -> MSet a
205 mSetFromList = MSet . Map.fromList . map (\x -> (x, ()))
207 -- mSetToSet :: Ord a => MSet a -> Set a
208 -- mSetToSet (MSet a) = Set.fromList ( Map.keys a)
209 mSetToSet :: Ord a => MSet a -> Set a
210 mSetToSet = Set.fromList . mSetToList
212 mSetToList :: MSet a -> [a]
213 mSetToList (MSet a) = Map.keys a
215 instance Foldable MSet where
216 foldMap f (MSet m) = Map.foldMapWithKey (\k _ -> f k) m
218 instance (Ord a, FromJSON a) => FromJSON (MSet a) where
219 parseJSON = fmap mSetFromList . parseJSON
221 instance (ToJSONKey a, ToSchema a) => ToSchema (MSet a) where
223 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
225 ------------------------------------------------------------------------
226 type NgramsTerm = Text
228 data RootParent = RootParent
229 { _rp_root :: NgramsTerm
230 , _rp_parent :: NgramsTerm
232 deriving (Ord, Eq, Show, Generic)
234 deriveJSON (unPrefix "_rp_") ''RootParent
235 makeLenses ''RootParent
237 data NgramsRepoElement = NgramsRepoElement
239 , _nre_list :: ListType
240 --, _nre_root_parent :: Maybe RootParent
241 , _nre_root :: Maybe NgramsTerm
242 , _nre_parent :: Maybe NgramsTerm
243 , _nre_children :: MSet NgramsTerm
245 deriving (Ord, Eq, Show, Generic)
247 deriveJSON (unPrefix "_nre_") ''NgramsRepoElement
248 makeLenses ''NgramsRepoElement
250 instance ToSchema NgramsRepoElement where
251 declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_nre_")
255 NgramsElement { _ne_ngrams :: NgramsTerm
257 , _ne_list :: ListType
258 , _ne_occurrences :: Int
259 , _ne_root :: Maybe NgramsTerm
260 , _ne_parent :: Maybe NgramsTerm
261 , _ne_children :: MSet NgramsTerm
263 deriving (Ord, Eq, Show, Generic)
265 deriveJSON (unPrefix "_ne_") ''NgramsElement
266 makeLenses ''NgramsElement
268 mkNgramsElement :: NgramsTerm
273 mkNgramsElement ngrams list rp children =
274 NgramsElement ngrams size list 1 (_rp_root <$> rp) (_rp_parent <$> rp) children
277 size = 1 + count " " ngrams
279 newNgramsElement :: Maybe ListType -> NgramsTerm -> NgramsElement
280 newNgramsElement mayList ngrams =
281 mkNgramsElement ngrams (fromMaybe GraphTerm mayList) Nothing mempty
283 instance ToSchema NgramsElement where
284 declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_ne_")
285 instance Arbitrary NgramsElement where
286 arbitrary = elements [newNgramsElement Nothing "sport"]
288 ngramsElementToRepo :: NgramsElement -> NgramsRepoElement
290 (NgramsElement { _ne_size = s
304 ngramsElementFromRepo :: NgramsTerm -> NgramsRepoElement -> NgramsElement
305 ngramsElementFromRepo
314 NgramsElement { _ne_size = s
319 , _ne_ngrams = ngrams
320 , _ne_occurrences = panic $ "API.Ngrams._ne_occurrences"
322 -- Here we could use 0 if we want to avoid any `panic`.
323 -- It will not happen using getTableNgrams if
324 -- getOccByNgramsOnly provides a count of occurrences for
325 -- all the ngrams given.
329 ------------------------------------------------------------------------
330 newtype NgramsTable = NgramsTable [NgramsElement]
331 deriving (Ord, Eq, Generic, ToJSON, FromJSON, Show)
333 type NgramsList = NgramsTable
335 makePrisms ''NgramsTable
337 -- | Question: why these repetition of Type in this instance
338 -- may you document it please ?
339 instance Each NgramsTable NgramsTable NgramsElement NgramsElement where
340 each = _NgramsTable . each
343 -- | TODO Check N and Weight
345 toNgramsElement :: [NgramsTableData] -> [NgramsElement]
346 toNgramsElement ns = map toNgramsElement' ns
348 toNgramsElement' (NgramsTableData _ p t _ lt w) = NgramsElement t lt' (round w) p' c'
352 Just x -> lookup x mapParent
353 c' = maybe mempty identity $ lookup t mapChildren
354 lt' = maybe (panic "API.Ngrams: listypeId") identity lt
356 mapParent :: Map Int Text
357 mapParent = Map.fromListWith (<>) $ map (\(NgramsTableData i _ t _ _ _) -> (i,t)) ns
359 mapChildren :: Map Text (Set Text)
360 mapChildren = Map.mapKeys (\i -> (maybe (panic "API.Ngrams.mapChildren: ParentId with no Terms: Impossible") identity $ lookup i mapParent))
361 $ Map.fromListWith (<>)
362 $ map (first fromJust)
363 $ filter (isJust . fst)
364 $ map (\(NgramsTableData _ p t _ _ _) -> (p, Set.singleton t)) ns
367 mockTable :: NgramsTable
368 mockTable = NgramsTable
369 [ mkNgramsElement "animal" GraphTerm Nothing (mSetFromList ["dog", "cat"])
370 , mkNgramsElement "cat" GraphTerm (rp "animal") mempty
371 , mkNgramsElement "cats" StopTerm Nothing mempty
372 , mkNgramsElement "dog" GraphTerm (rp "animal") (mSetFromList ["dogs"])
373 , mkNgramsElement "dogs" StopTerm (rp "dog") mempty
374 , mkNgramsElement "fox" GraphTerm Nothing mempty
375 , mkNgramsElement "object" CandidateTerm Nothing mempty
376 , mkNgramsElement "nothing" StopTerm Nothing mempty
377 , mkNgramsElement "organic" GraphTerm Nothing (mSetFromList ["flower"])
378 , mkNgramsElement "flower" GraphTerm (rp "organic") mempty
379 , mkNgramsElement "moon" CandidateTerm Nothing mempty
380 , mkNgramsElement "sky" StopTerm Nothing mempty
383 rp n = Just $ RootParent n n
385 instance Arbitrary NgramsTable where
386 arbitrary = pure mockTable
388 instance ToSchema NgramsTable
390 ------------------------------------------------------------------------
391 type NgramsTableMap = Map NgramsTerm NgramsRepoElement
392 ------------------------------------------------------------------------
393 -- On the Client side:
394 --data Action = InGroup NgramsId NgramsId
395 -- | OutGroup NgramsId NgramsId
396 -- | SetListType NgramsId ListType
398 data PatchSet a = PatchSet
402 deriving (Eq, Ord, Show, Generic)
404 makeLenses ''PatchSet
405 makePrisms ''PatchSet
407 instance ToJSON a => ToJSON (PatchSet a) where
408 toJSON = genericToJSON $ unPrefix "_"
409 toEncoding = genericToEncoding $ unPrefix "_"
411 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
412 parseJSON = genericParseJSON $ unPrefix "_"
415 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
416 arbitrary = PatchSet <$> arbitrary <*> arbitrary
418 type instance Patched (PatchSet a) = Set a
420 type ConflictResolutionPatchSet a = SimpleConflictResolution' (Set a)
421 type instance ConflictResolution (PatchSet a) = ConflictResolutionPatchSet a
423 instance Ord a => Semigroup (PatchSet a) where
424 p <> q = PatchSet { _rem = (q ^. rem) `Set.difference` (p ^. add) <> p ^. rem
425 , _add = (q ^. add) `Set.difference` (p ^. rem) <> p ^. add
428 instance Ord a => Monoid (PatchSet a) where
429 mempty = PatchSet mempty mempty
431 instance Ord a => Group (PatchSet a) where
432 invert (PatchSet r a) = PatchSet a r
434 instance Ord a => Composable (PatchSet a) where
435 composable _ _ = undefined
437 instance Ord a => Action (PatchSet a) (Set a) where
438 act p source = (source `Set.difference` (p ^. rem)) <> p ^. add
440 instance Applicable (PatchSet a) (Set a) where
441 applicable _ _ = mempty
443 instance Ord a => Validity (PatchSet a) where
444 validate p = check (Set.disjoint (p ^. rem) (p ^. add)) "_rem and _add should be dijoint"
446 instance Ord a => Transformable (PatchSet a) where
447 transformable = undefined
449 conflicts _p _q = undefined
451 transformWith conflict p q = undefined conflict p q
453 instance ToSchema a => ToSchema (PatchSet a)
456 type AddRem = Replace (Maybe ())
458 remPatch, addPatch :: AddRem
459 remPatch = replace (Just ()) Nothing
460 addPatch = replace Nothing (Just ())
462 isRem :: Replace (Maybe ()) -> Bool
463 isRem = (== remPatch)
465 type PatchMap = PM.PatchMap
467 newtype PatchMSet a = PatchMSet (PatchMap a AddRem)
468 deriving (Eq, Show, Generic, Validity, Semigroup, Monoid,
469 Transformable, Composable)
471 type ConflictResolutionPatchMSet a = a -> ConflictResolutionReplace (Maybe ())
472 type instance ConflictResolution (PatchMSet a) = ConflictResolutionPatchMSet a
474 -- TODO this breaks module abstraction
475 makePrisms ''PM.PatchMap
477 makePrisms ''PatchMSet
479 _PatchMSetIso :: Ord a => Iso' (PatchMSet a) (PatchSet a)
480 _PatchMSetIso = _PatchMSet . _PatchMap . iso f g . from _PatchSet
482 f :: Ord a => Map a (Replace (Maybe ())) -> (Set a, Set a)
483 f = Map.partition isRem >>> both %~ Map.keysSet
485 g :: Ord a => (Set a, Set a) -> Map a (Replace (Maybe ()))
486 g (rems, adds) = Map.fromSet (const remPatch) rems
487 <> Map.fromSet (const addPatch) adds
489 instance Ord a => Action (PatchMSet a) (MSet a) where
490 act (PatchMSet p) (MSet m) = MSet $ act p m
492 instance Ord a => Applicable (PatchMSet a) (MSet a) where
493 applicable (PatchMSet p) (MSet m) = applicable p m
495 instance (Ord a, ToJSON a) => ToJSON (PatchMSet a) where
496 toJSON = toJSON . view _PatchMSetIso
497 toEncoding = toEncoding . view _PatchMSetIso
499 instance (Ord a, FromJSON a) => FromJSON (PatchMSet a) where
500 parseJSON = fmap (_PatchMSetIso #) . parseJSON
502 instance (Ord a, Arbitrary a) => Arbitrary (PatchMSet a) where
503 arbitrary = (PatchMSet . PM.fromMap) <$> arbitrary
505 instance ToSchema a => ToSchema (PatchMSet a) where
507 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
509 type instance Patched (PatchMSet a) = MSet a
511 instance (Eq a, Arbitrary a) => Arbitrary (Replace a) where
512 arbitrary = uncurry replace <$> arbitrary
513 -- If they happen to be equal then the patch is Keep.
515 instance ToSchema a => ToSchema (Replace a) where
516 declareNamedSchema (_ :: Proxy (Replace a)) = do
517 -- TODO Keep constructor is not supported here.
518 aSchema <- declareSchemaRef (Proxy :: Proxy a)
519 return $ NamedSchema (Just "Replace") $ mempty
520 & type_ ?~ SwaggerObject
522 InsOrdHashMap.fromList
526 & required .~ [ "old", "new" ]
529 NgramsPatch { _patch_children :: PatchMSet NgramsTerm
530 , _patch_list :: Replace ListType -- TODO Map UserId ListType
532 deriving (Eq, Show, Generic)
534 deriveJSON (unPrefix "_") ''NgramsPatch
535 makeLenses ''NgramsPatch
537 instance ToSchema NgramsPatch where
538 declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_")
540 instance Arbitrary NgramsPatch where
541 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
543 type NgramsPatchIso = PairPatch (PatchMSet NgramsTerm) (Replace ListType)
545 _NgramsPatch :: Iso' NgramsPatch NgramsPatchIso
546 _NgramsPatch = iso (\(NgramsPatch c l) -> c :*: l) (\(c :*: l) -> NgramsPatch c l)
548 instance Semigroup NgramsPatch where
549 p <> q = _NgramsPatch # (p ^. _NgramsPatch <> q ^. _NgramsPatch)
551 instance Monoid NgramsPatch where
552 mempty = _NgramsPatch # mempty
554 instance Validity NgramsPatch where
555 validate p = p ^. _NgramsPatch . to validate
557 instance Transformable NgramsPatch where
558 transformable p q = transformable (p ^. _NgramsPatch) (q ^. _NgramsPatch)
560 conflicts p q = conflicts (p ^. _NgramsPatch) (q ^. _NgramsPatch)
562 transformWith conflict p q = (_NgramsPatch # p', _NgramsPatch # q')
564 (p', q') = transformWith conflict (p ^. _NgramsPatch) (q ^. _NgramsPatch)
566 type ConflictResolutionNgramsPatch =
567 ( ConflictResolutionPatchMSet NgramsTerm
568 , ConflictResolutionReplace ListType
570 type instance ConflictResolution NgramsPatch =
571 ConflictResolutionNgramsPatch
573 type PatchedNgramsPatch = (Set NgramsTerm, ListType)
574 -- ~ Patched NgramsPatchIso
575 type instance Patched NgramsPatch = PatchedNgramsPatch
577 instance Applicable NgramsPatch (Maybe NgramsRepoElement) where
578 applicable p Nothing = check (p == mempty) "NgramsPatch should be empty here"
579 applicable p (Just nre) =
580 applicable (p ^. patch_children) (nre ^. nre_children) <>
581 applicable (p ^. patch_list) (nre ^. nre_list)
583 instance Action NgramsPatch NgramsRepoElement where
584 act p = (nre_children %~ act (p ^. patch_children))
585 . (nre_list %~ act (p ^. patch_list))
587 instance Action NgramsPatch (Maybe NgramsRepoElement) where
590 newtype NgramsTablePatch = NgramsTablePatch (PatchMap NgramsTerm NgramsPatch)
591 deriving (Eq, Show, Generic, ToJSON, FromJSON, Semigroup, Monoid, Validity, Transformable)
593 instance FromField NgramsTablePatch
595 fromField = fromField'
597 instance FromField (PatchMap NgramsType (PatchMap NodeId NgramsTablePatch))
599 fromField = fromField'
601 --instance (Ord k, Action pv (Maybe v)) => Action (PatchMap k pv) (Map k v) where
603 type instance ConflictResolution NgramsTablePatch =
604 NgramsTerm -> ConflictResolutionNgramsPatch
606 type PatchedNgramsTablePatch = Map NgramsTerm PatchedNgramsPatch
607 -- ~ Patched (PatchMap NgramsTerm NgramsPatch)
608 type instance Patched NgramsTablePatch = PatchedNgramsTablePatch
610 makePrisms ''NgramsTablePatch
611 instance ToSchema (PatchMap NgramsTerm NgramsPatch)
612 instance ToSchema NgramsTablePatch
614 instance Applicable NgramsTablePatch (Maybe NgramsTableMap) where
615 applicable p = applicable (p ^. _NgramsTablePatch)
617 instance Action NgramsTablePatch (Maybe NgramsTableMap) where
619 fmap (execState (reParentNgramsTablePatch p)) .
620 act (p ^. _NgramsTablePatch)
622 instance Arbitrary NgramsTablePatch where
623 arbitrary = NgramsTablePatch <$> PM.fromMap <$> arbitrary
625 -- Should it be less than an Lens' to preserve PatchMap's abstraction.
626 -- ntp_ngrams_patches :: Lens' NgramsTablePatch (Map NgramsTerm NgramsPatch)
627 -- ntp_ngrams_patches = _NgramsTablePatch . undefined
629 type ReParent a = forall m. MonadState NgramsTableMap m => a -> m ()
631 reRootChildren :: NgramsTerm -> ReParent NgramsTerm
632 reRootChildren root ngram = do
633 nre <- use $ at ngram
634 forOf_ (_Just . nre_children . folded) nre $ \child -> do
635 at child . _Just . nre_root ?= root
636 reRootChildren root child
638 reParent :: Maybe RootParent -> ReParent NgramsTerm
639 reParent rp child = do
640 at child . _Just %= ( (nre_parent .~ (_rp_parent <$> rp))
641 . (nre_root .~ (_rp_root <$> rp))
643 reRootChildren (fromMaybe child (rp ^? _Just . rp_root)) child
645 reParentAddRem :: RootParent -> NgramsTerm -> ReParent AddRem
646 reParentAddRem rp child p =
647 reParent (if isRem p then Nothing else Just rp) child
649 reParentNgramsPatch :: NgramsTerm -> ReParent NgramsPatch
650 reParentNgramsPatch parent ngramsPatch = do
651 root_of_parent <- use (at parent . _Just . nre_root)
653 root = fromMaybe parent root_of_parent
654 rp = RootParent { _rp_root = root, _rp_parent = parent }
655 itraverse_ (reParentAddRem rp) (ngramsPatch ^. patch_children . _PatchMSet . _PatchMap)
656 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
658 reParentNgramsTablePatch :: ReParent NgramsTablePatch
659 reParentNgramsTablePatch p = itraverse_ reParentNgramsPatch (p ^. _NgramsTablePatch. _PatchMap)
660 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
662 ------------------------------------------------------------------------
663 ------------------------------------------------------------------------
666 data Versioned a = Versioned
667 { _v_version :: Version
670 deriving (Generic, Show, Eq)
671 deriveJSON (unPrefix "_v_") ''Versioned
672 makeLenses ''Versioned
673 instance ToSchema a => ToSchema (Versioned a) where
674 declareNamedSchema = genericDeclareNamedSchema (unPrefixSwagger "_v_")
675 instance Arbitrary a => Arbitrary (Versioned a) where
676 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
680 -- TODO sequences of modifications (Patchs)
681 type NgramsIdPatch = Patch NgramsId NgramsPatch
683 ngramsPatch :: Int -> NgramsPatch
684 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopTerm)]) (Set.fromList [n]) Set.empty
686 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
687 toEdit n p = Edit n p
688 ngramsIdPatch :: Patch NgramsId NgramsPatch
689 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
690 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
691 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
694 -- applyPatchBack :: Patch -> IO Patch
695 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
697 ------------------------------------------------------------------------
698 ------------------------------------------------------------------------
699 ------------------------------------------------------------------------
702 -- TODO: Replace.old is ignored which means that if the current list
703 -- `GraphTerm` and that the patch is `Replace CandidateTerm StopTerm` then
704 -- the list is going to be `StopTerm` while it should keep `GraphTerm`.
705 -- However this should not happen in non conflicting situations.
706 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
707 mkListsUpdate nt patches =
708 [ (ngramsTypeId nt, ng, listTypeId lt)
709 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
710 , lt <- patch ^.. patch_list . new
713 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
716 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
717 mkChildrenGroups addOrRem nt patches =
718 [ (ngramsTypeId nt, parent, child)
719 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
720 , child <- patch ^.. patch_children . to addOrRem . folded
724 ngramsTypeFromTabType :: TabType -> NgramsType
725 ngramsTypeFromTabType tabType =
726 let lieu = "Garg.API.Ngrams: " :: Text in
728 Sources -> Ngrams.Sources
729 Authors -> Ngrams.Authors
730 Institutes -> Ngrams.Institutes
731 Terms -> Ngrams.NgramsTerms
732 _ -> panic $ lieu <> "No Ngrams for this tab"
733 -- TODO: This `panic` would disapear with custom NgramsType.
735 ------------------------------------------------------------------------
737 { _r_version :: Version
740 -- first patch in the list is the most recent
744 instance (FromJSON s, FromJSON p) => FromJSON (Repo s p) where
745 parseJSON = genericParseJSON $ unPrefix "_r_"
747 instance (ToJSON s, ToJSON p) => ToJSON (Repo s p) where
748 toJSON = genericToJSON $ unPrefix "_r_"
749 toEncoding = genericToEncoding $ unPrefix "_r_"
753 initRepo :: Monoid s => Repo s p
754 initRepo = Repo 1 mempty []
756 type NgramsRepo = Repo NgramsState NgramsStatePatch
757 type NgramsState = Map NgramsType (Map NodeId NgramsTableMap)
758 type NgramsStatePatch = PatchMap NgramsType (PatchMap NodeId NgramsTablePatch)
760 initMockRepo :: NgramsRepo
761 initMockRepo = Repo 1 s []
763 s = Map.singleton Ngrams.NgramsTerms
764 $ Map.singleton 47254
766 [ (n ^. ne_ngrams, ngramsElementToRepo n) | n <- mockTable ^. _NgramsTable ]
768 data RepoEnv = RepoEnv
769 { _renv_var :: !(MVar NgramsRepo)
770 , _renv_saver :: !(IO ())
771 , _renv_lock :: !FileLock
777 class HasRepoVar env where
778 repoVar :: Getter env (MVar NgramsRepo)
780 instance HasRepoVar (MVar NgramsRepo) where
783 class HasRepoSaver env where
784 repoSaver :: Getter env (IO ())
786 class (HasRepoVar env, HasRepoSaver env) => HasRepo env where
787 repoEnv :: Getter env RepoEnv
789 instance HasRepo RepoEnv where
792 instance HasRepoVar RepoEnv where
795 instance HasRepoSaver RepoEnv where
796 repoSaver = renv_saver
798 type RepoCmdM env err m =
801 , MonadBaseControl IO m
804 ------------------------------------------------------------------------
806 saveRepo :: ( MonadReader env m, MonadBase IO m, HasRepoSaver env )
808 saveRepo = liftBase =<< view repoSaver
810 listTypeConflictResolution :: ListType -> ListType -> ListType
811 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
813 ngramsStatePatchConflictResolution
814 :: NgramsType -> NodeId -> NgramsTerm
815 -> ConflictResolutionNgramsPatch
816 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
818 -- undefined {- TODO think this through -}, listTypeConflictResolution)
821 -- Insertions are not considered as patches,
822 -- they do not extend history,
823 -- they do not bump version.
824 insertNewOnly :: a -> Maybe b -> a
825 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
826 -- TODO error handling
828 something :: Monoid a => Maybe a -> a
829 something Nothing = mempty
830 something (Just a) = a
833 -- TODO refactor with putListNgrams
834 copyListNgrams :: RepoCmdM env err m
835 => NodeId -> NodeId -> NgramsType
837 copyListNgrams srcListId dstListId ngramsType = do
839 liftBase $ modifyMVar_ var $
840 pure . (r_state . at ngramsType %~ (Just . f . something))
843 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
844 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
846 -- TODO refactor with putListNgrams
847 -- The list must be non-empty!
848 -- The added ngrams must be non-existent!
849 addListNgrams :: RepoCmdM env err m
850 => NodeId -> NgramsType
851 -> [NgramsElement] -> m ()
852 addListNgrams listId ngramsType nes = do
854 liftBase $ modifyMVar_ var $
855 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
858 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
861 rmListNgrams :: RepoCmdM env err m
865 rmListNgrams l nt = setListNgrams l nt mempty
867 -- | TODO: incr the Version number
868 -- && should use patch
869 setListNgrams :: RepoCmdM env err m
872 -> Map NgramsTerm NgramsRepoElement
874 setListNgrams listId ngramsType ns = do
876 liftBase $ modifyMVar_ var $
880 (at listId .~ ( Just ns))
887 -- If the given list of ngrams elements contains ngrams already in
888 -- the repo, they will be ignored.
889 putListNgrams :: RepoCmdM env err m
890 => NodeId -> NgramsType
891 -> [NgramsElement] -> m ()
892 putListNgrams _ _ [] = pure ()
893 putListNgrams listId ngramsType nes = putListNgrams' listId ngramsType m
895 m = Map.fromList $ map (\n -> (n ^. ne_ngrams, ngramsElementToRepo n)) nes
897 putListNgrams' :: RepoCmdM env err m
898 => ListId -> NgramsType
899 -> Map NgramsTerm NgramsRepoElement
901 putListNgrams' listId ngramsType ns = do
902 -- printDebug "putListNgrams" (length nes)
904 liftBase $ modifyMVar_ var $
921 tableNgramsPost :: RepoCmdM env err m
925 -> [NgramsTerm] -> m ()
926 tableNgramsPost tabType listId mayList =
927 putListNgrams listId (ngramsTypeFromTabType tabType) . fmap (newNgramsElement mayList)
929 currentVersion :: RepoCmdM env err m
933 r <- liftBase $ readMVar var
934 pure $ r ^. r_version
936 tableNgramsPull :: RepoCmdM env err m
937 => ListId -> NgramsType
939 -> m (Versioned NgramsTablePatch)
940 tableNgramsPull listId ngramsType p_version = do
942 r <- liftBase $ readMVar var
945 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
946 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
948 pure (Versioned (r ^. r_version) q_table)
950 -- Apply the given patch to the DB and returns the patch to be applied on the
953 tableNgramsPut :: (HasInvalidError err, RepoCmdM env err m)
955 -> Versioned NgramsTablePatch
956 -> m (Versioned NgramsTablePatch)
957 tableNgramsPut tabType listId (Versioned p_version p_table)
958 | p_table == mempty = do
959 let ngramsType = ngramsTypeFromTabType tabType
960 tableNgramsPull listId ngramsType p_version
963 let ngramsType = ngramsTypeFromTabType tabType
964 (p0, p0_validity) = PM.singleton listId p_table
965 (p, p_validity) = PM.singleton ngramsType p0
967 assertValid p0_validity
968 assertValid p_validity
971 vq' <- liftBase $ modifyMVar var $ \r -> do
973 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
974 (p', q') = transformWith ngramsStatePatchConflictResolution p q
975 r' = r & r_version +~ 1
977 & r_history %~ (p' :)
978 q'_table = q' ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
980 -- Ideally we would like to check these properties. However:
981 -- * They should be checked only to debug the code. The client data
982 -- should be able to trigger these.
983 -- * What kind of error should they throw (we are in IO here)?
984 -- * Should we keep modifyMVar?
985 -- * Should we throw the validation in an Exception, catch it around
986 -- modifyMVar and throw it back as an Error?
987 assertValid $ transformable p q
988 assertValid $ applicable p' (r ^. r_state)
990 pure (r', Versioned (r' ^. r_version) q'_table)
995 mergeNgramsElement :: NgramsRepoElement -> NgramsRepoElement -> NgramsRepoElement
996 mergeNgramsElement _neOld neNew = neNew
998 { _ne_list :: ListType
999 If we merge the parents/children we can potentially create cycles!
1000 , _ne_parent :: Maybe NgramsTerm
1001 , _ne_children :: MSet NgramsTerm
1005 getNgramsTableMap :: RepoCmdM env err m
1008 -> m (Versioned NgramsTableMap)
1009 getNgramsTableMap nodeId ngramsType = do
1011 repo <- liftBase $ readMVar v
1012 pure $ Versioned (repo ^. r_version)
1013 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
1018 -- | TODO Errors management
1019 -- TODO: polymorphic for Annuaire or Corpus or ...
1020 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
1021 -- TODO: should take only one ListId
1023 getTime' :: MonadBase IO m => m TimeSpec
1024 getTime' = liftBase $ getTime ProcessCPUTime
1027 getTableNgrams :: forall env err m.
1028 (RepoCmdM env err m, HasNodeError err, HasConnectionPool env)
1029 => NodeType -> NodeId -> TabType
1030 -> ListId -> Limit -> Maybe Offset
1032 -> Maybe MinSize -> Maybe MaxSize
1034 -> (NgramsTerm -> Bool)
1035 -> m (Versioned NgramsTable)
1036 getTableNgrams _nType nId tabType listId limit_ offset
1037 listType minSize maxSize orderBy searchQuery = do
1040 -- lIds <- selectNodesWithUsername NodeList userMaster
1042 ngramsType = ngramsTypeFromTabType tabType
1043 offset' = maybe 0 identity offset
1044 listType' = maybe (const True) (==) listType
1045 minSize' = maybe (const True) (<=) minSize
1046 maxSize' = maybe (const True) (>=) maxSize
1048 selected_node n = minSize' s
1050 && searchQuery (n ^. ne_ngrams)
1051 && listType' (n ^. ne_list)
1055 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
1057 ---------------------------------------
1058 sortOnOrder Nothing = identity
1059 sortOnOrder (Just TermAsc) = List.sortOn $ view ne_ngrams
1060 sortOnOrder (Just TermDesc) = List.sortOn $ Down . view ne_ngrams
1061 sortOnOrder (Just ScoreAsc) = List.sortOn $ view ne_occurrences
1062 sortOnOrder (Just ScoreDesc) = List.sortOn $ Down . view ne_occurrences
1064 ---------------------------------------
1065 selectAndPaginate :: Map NgramsTerm NgramsElement -> [NgramsElement]
1066 selectAndPaginate tableMap = roots <> inners
1068 list = tableMap ^.. each
1069 rootOf ne = maybe ne (\r -> fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r))
1071 selected_nodes = list & take limit_
1073 . filter selected_node
1074 . sortOnOrder orderBy
1075 roots = rootOf <$> selected_nodes
1076 rootsSet = Set.fromList (_ne_ngrams <$> roots)
1077 inners = list & filter (selected_inner rootsSet)
1079 ---------------------------------------
1080 setScores :: forall t. Each t t NgramsElement NgramsElement => Bool -> t -> m t
1081 setScores False table = pure table
1082 setScores True table = do
1083 let ngrams_terms = (table ^.. each . ne_ngrams)
1085 occurrences <- getOccByNgramsOnlyFast' nId
1090 liftBase $ hprint stderr
1091 ("getTableNgrams/setScores #ngrams=" % int % " time=" % timeSpecs % "\n")
1092 (length ngrams_terms) t1 t2
1094 occurrences <- getOccByNgramsOnlySlow nType nId
1100 setOcc ne = ne & ne_occurrences .~ sumOf (at (ne ^. ne_ngrams) . _Just) occurrences
1102 pure $ table & each %~ setOcc
1103 ---------------------------------------
1105 -- lists <- catMaybes <$> listsWith userMaster
1106 -- trace (show lists) $
1107 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
1109 let scoresNeeded = needsScores orderBy
1110 tableMap1 <- getNgramsTableMap listId ngramsType
1112 tableMap2 <- tableMap1 & v_data %%~ setScores scoresNeeded
1113 . Map.mapWithKey ngramsElementFromRepo
1115 tableMap3 <- tableMap2 & v_data %%~ fmap NgramsTable
1116 . setScores (not scoresNeeded)
1119 liftBase $ hprint stderr
1120 ("getTableNgrams total=" % timeSpecs
1121 % " map1=" % timeSpecs
1122 % " map2=" % timeSpecs
1123 % " map3=" % timeSpecs
1124 % " sql=" % (if scoresNeeded then "map2" else "map3")
1126 ) t0 t3 t0 t1 t1 t2 t2 t3
1132 -- TODO: find a better place for the code above, All APIs stay here
1133 type QueryParamR = QueryParam' '[Required, Strict]
1135 data OrderBy = TermAsc | TermDesc | ScoreAsc | ScoreDesc
1136 deriving (Generic, Enum, Bounded, Read, Show)
1138 instance FromHttpApiData OrderBy
1140 parseUrlPiece "TermAsc" = pure TermAsc
1141 parseUrlPiece "TermDesc" = pure TermDesc
1142 parseUrlPiece "ScoreAsc" = pure ScoreAsc
1143 parseUrlPiece "ScoreDesc" = pure ScoreDesc
1144 parseUrlPiece _ = Left "Unexpected value of OrderBy"
1147 instance ToParamSchema OrderBy
1148 instance FromJSON OrderBy
1149 instance ToJSON OrderBy
1150 instance ToSchema OrderBy
1151 instance Arbitrary OrderBy
1153 arbitrary = elements [minBound..maxBound]
1155 needsScores :: Maybe OrderBy -> Bool
1156 needsScores (Just ScoreAsc) = True
1157 needsScores (Just ScoreDesc) = True
1158 needsScores _ = False
1160 type TableNgramsApiGet = Summary " Table Ngrams API Get"
1161 :> QueryParamR "ngramsType" TabType
1162 :> QueryParamR "list" ListId
1163 :> QueryParamR "limit" Limit
1164 :> QueryParam "offset" Offset
1165 :> QueryParam "listType" ListType
1166 :> QueryParam "minTermSize" MinSize
1167 :> QueryParam "maxTermSize" MaxSize
1168 :> QueryParam "orderBy" OrderBy
1169 :> QueryParam "search" Text
1170 :> Get '[JSON] (Versioned NgramsTable)
1172 type TableNgramsApiPut = Summary " Table Ngrams API Change"
1173 :> QueryParamR "ngramsType" TabType
1174 :> QueryParamR "list" ListId
1175 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
1176 :> Put '[JSON] (Versioned NgramsTablePatch)
1178 type TableNgramsApiPost = Summary " Table Ngrams API Adds new ngrams"
1179 :> QueryParamR "ngramsType" TabType
1180 :> QueryParamR "list" ListId
1181 :> QueryParam "listType" ListType
1182 :> ReqBody '[JSON] [NgramsTerm]
1185 type TableNgramsApi = TableNgramsApiGet
1186 :<|> TableNgramsApiPut
1187 :<|> TableNgramsApiPost
1189 getTableNgramsCorpus :: (RepoCmdM env err m, HasNodeError err, HasConnectionPool env)
1190 => NodeId -> TabType
1191 -> ListId -> Limit -> Maybe Offset
1193 -> Maybe MinSize -> Maybe MaxSize
1195 -> Maybe Text -- full text search
1196 -> m (Versioned NgramsTable)
1197 getTableNgramsCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy mt =
1198 getTableNgrams NodeCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1200 searchQuery = maybe (const True) isInfixOf mt
1202 -- | Text search is deactivated for now for ngrams by doc only
1203 getTableNgramsDoc :: (RepoCmdM env err m, HasNodeError err, HasConnectionPool env)
1205 -> ListId -> Limit -> Maybe Offset
1207 -> Maybe MinSize -> Maybe MaxSize
1209 -> Maybe Text -- full text search
1210 -> m (Versioned NgramsTable)
1211 getTableNgramsDoc dId tabType listId limit_ offset listType minSize maxSize orderBy _mt = do
1212 ns <- selectNodesWithUsername NodeList userMaster
1213 let ngramsType = ngramsTypeFromTabType tabType
1214 ngs <- selectNgramsByDoc (ns <> [listId]) dId ngramsType
1215 let searchQuery = flip S.member (S.fromList ngs)
1216 getTableNgrams NodeDocument dId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1220 apiNgramsTableCorpus :: ( RepoCmdM env err m
1222 , HasInvalidError err
1223 , HasConnectionPool env
1225 => NodeId -> ServerT TableNgramsApi m
1226 apiNgramsTableCorpus cId = getTableNgramsCorpus cId
1228 :<|> tableNgramsPost
1231 apiNgramsTableDoc :: ( RepoCmdM env err m
1233 , HasInvalidError err
1234 , HasConnectionPool env
1236 => DocId -> ServerT TableNgramsApi m
1237 apiNgramsTableDoc dId = getTableNgramsDoc dId
1239 :<|> tableNgramsPost
1240 -- > add new ngrams in database (TODO AD)
1241 -- > index all the corpus accordingly (TODO AD)
1243 listNgramsChangedSince :: RepoCmdM env err m
1244 => ListId -> NgramsType -> Version -> m (Versioned Bool)
1245 listNgramsChangedSince listId ngramsType version
1247 Versioned <$> currentVersion <*> pure True
1249 tableNgramsPull listId ngramsType version & mapped . v_data %~ (== mempty)
1252 instance Arbitrary NgramsRepoElement where
1253 arbitrary = elements $ map ngramsElementToRepo ns
1255 NgramsTable ns = mockTable
1258 instance FromHttpApiData (Map NgramsType (Versioned NgramsTableMap))
1260 parseUrlPiece x = maybeToEither x (decode $ cs x)