2 Module : Gargantext.API.Ngrams
3 Description : Server API
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 get ngrams filtered by NgramsType
18 {-# LANGUAGE ConstraintKinds #-}
19 {-# LANGUAGE DataKinds #-}
20 {-# LANGUAGE DeriveGeneric #-}
21 {-# LANGUAGE NoImplicitPrelude #-}
22 {-# LANGUAGE OverloadedStrings #-}
23 {-# LANGUAGE ScopedTypeVariables #-}
24 {-# LANGUAGE TemplateHaskell #-}
25 {-# LANGUAGE TypeOperators #-}
26 {-# LANGUAGE FlexibleContexts #-}
27 {-# LANGUAGE FlexibleInstances #-}
28 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
29 {-# LANGUAGE MultiParamTypeClasses #-}
30 {-# LANGUAGE RankNTypes #-}
31 {-# LANGUAGE TypeFamilies #-}
32 {-# OPTIONS -fno-warn-orphans #-}
34 module Gargantext.API.Ngrams
37 -- import Debug.Trace (trace)
38 import Prelude (Enum, Bounded, Semigroup(..), minBound, maxBound {-, round-}, error)
39 -- import Gargantext.Database.Schema.User (UserId)
40 import Data.Patch.Class (Replace, replace, Action(act), Applicable(..),
41 Composable(..), Transformable(..),
42 PairPatch(..), Patched, ConflictResolution,
43 ConflictResolutionReplace, ours)
44 import qualified Data.Map.Strict.Patch as PM
47 --import Data.Semigroup
49 import qualified Data.Set as S
50 -- import qualified Data.List as List
51 import Data.Maybe (fromMaybe)
52 -- import Data.Tuple.Extra (first)
53 import qualified Data.Map.Strict as Map
54 import Data.Map.Strict (Map)
55 import qualified Data.Set as Set
56 import Control.Category ((>>>))
57 import Control.Concurrent
58 import Control.Lens (makeLenses, makePrisms, Getter, Iso', iso, from, (.~), (?=), (#), to, folded, {-withIndex, ifolded,-} view, use, (^.), (^..), (^?), (+~), (%~), (%=), sumOf, at, _Just, Each(..), itraverse_, both, mapped, forOf_)
59 import Control.Monad.Error.Class (MonadError)
60 import Control.Monad.Reader
61 import Control.Monad.State
62 import Data.Aeson hiding ((.=))
63 import Data.Aeson.TH (deriveJSON)
64 import Data.Either(Either(Left))
65 -- import Data.Map (lookup)
66 import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
67 import Data.Swagger hiding (version, patch)
68 import Data.Text (Text, isInfixOf, count)
70 import GHC.Generics (Generic)
71 import Gargantext.Core.Utils.Prefix (unPrefix)
72 -- import Gargantext.Database.Schema.Ngrams (NgramsTypeId, ngramsTypeId, NgramsTableData(..))
73 import Gargantext.Database.Config (userMaster)
74 import Gargantext.Database.Metrics.NgramsByNode (getOccByNgramsOnlySafe)
75 import Gargantext.Database.Schema.Ngrams (NgramsType)
76 import Gargantext.Database.Types.Node (NodeType(..))
77 import Gargantext.Database.Utils (fromField', HasConnection)
78 import Gargantext.Database.Node.Select
79 import Gargantext.Database.Ngrams
80 --import Gargantext.Database.Lists (listsWith)
81 import Gargantext.Database.Schema.Node (HasNodeError)
82 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
83 import qualified Gargantext.Database.Schema.Ngrams as Ngrams
84 -- import Gargantext.Database.Schema.NodeNgram hiding (Action)
85 import Gargantext.Prelude
86 -- import Gargantext.Core.Types (ListTypeId, listTypeId)
87 import Gargantext.Core.Types (ListType(..), NodeId, ListId, CorpusId, DocId, Limit, Offset, HasInvalidError, assertValid)
88 import Servant hiding (Patch)
89 import System.FileLock (FileLock)
90 import Test.QuickCheck (elements)
91 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
96 instance ToSchema TODO where
98 ------------------------------------------------------------------------
99 --data FacetFormat = Table | Chart
100 data TabType = Docs | Terms | Sources | Authors | Institutes | Trash
102 deriving (Generic, Enum, Bounded)
104 instance FromHttpApiData TabType
106 parseUrlPiece "Docs" = pure Docs
107 parseUrlPiece "Terms" = pure Terms
108 parseUrlPiece "Sources" = pure Sources
109 parseUrlPiece "Institutes" = pure Institutes
110 parseUrlPiece "Authors" = pure Authors
111 parseUrlPiece "Trash" = pure Trash
113 parseUrlPiece "Contacts" = pure Contacts
115 parseUrlPiece _ = Left "Unexpected value of TabType"
117 instance ToParamSchema TabType
118 instance ToJSON TabType
119 instance FromJSON TabType
120 instance ToSchema TabType
121 instance Arbitrary TabType
123 arbitrary = elements [minBound .. maxBound]
125 newtype MSet a = MSet (Map a ())
126 deriving (Eq, Ord, Show, Generic, Arbitrary, Semigroup, Monoid)
128 instance ToJSON a => ToJSON (MSet a) where
129 toJSON (MSet m) = toJSON (Map.keys m)
130 toEncoding (MSet m) = toEncoding (Map.keys m)
132 mSetFromSet :: Set a -> MSet a
133 mSetFromSet = MSet . Map.fromSet (const ())
135 mSetFromList :: Ord a => [a] -> MSet a
136 mSetFromList = MSet . Map.fromList . map (\x -> (x, ()))
138 -- mSetToSet :: Ord a => MSet a -> Set a
139 -- mSetToSet (MSet a) = Set.fromList ( Map.keys a)
140 mSetToSet :: Ord a => MSet a -> Set a
141 mSetToSet = Set.fromList . mSetToList
143 mSetToList :: MSet a -> [a]
144 mSetToList (MSet a) = Map.keys a
146 instance Foldable MSet where
147 foldMap f (MSet m) = Map.foldMapWithKey (\k _ -> f k) m
149 instance (Ord a, FromJSON a) => FromJSON (MSet a) where
150 parseJSON = fmap mSetFromList . parseJSON
152 instance (ToJSONKey a, ToSchema a) => ToSchema (MSet a) where
154 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
156 ------------------------------------------------------------------------
157 type NgramsTerm = Text
159 data RootParent = RootParent
160 { _rp_root :: NgramsTerm
161 , _rp_parent :: NgramsTerm
163 deriving (Ord, Eq, Show, Generic)
165 deriveJSON (unPrefix "_rp_") ''RootParent
166 makeLenses ''RootParent
168 data NgramsRepoElement = NgramsRepoElement
170 , _nre_list :: ListType
171 --, _nre_root_parent :: Maybe RootParent
172 , _nre_root :: Maybe NgramsTerm
173 , _nre_parent :: Maybe NgramsTerm
174 , _nre_children :: MSet NgramsTerm
176 deriving (Ord, Eq, Show, Generic)
178 deriveJSON (unPrefix "_nre_") ''NgramsRepoElement
179 makeLenses ''NgramsRepoElement
182 NgramsElement { _ne_ngrams :: NgramsTerm
184 , _ne_list :: ListType
185 , _ne_occurrences :: Int
186 , _ne_root :: Maybe NgramsTerm
187 , _ne_parent :: Maybe NgramsTerm
188 , _ne_children :: MSet NgramsTerm
190 deriving (Ord, Eq, Show, Generic)
192 deriveJSON (unPrefix "_ne_") ''NgramsElement
193 makeLenses ''NgramsElement
195 mkNgramsElement :: NgramsTerm -> ListType -> Maybe RootParent -> MSet NgramsTerm -> NgramsElement
196 mkNgramsElement ngrams list rp children =
197 NgramsElement ngrams size list 1 (_rp_root <$> rp) (_rp_parent <$> rp) children
200 size = 1 + count " " ngrams
202 instance ToSchema NgramsElement
203 instance Arbitrary NgramsElement where
204 arbitrary = elements [mkNgramsElement "sport" GraphTerm Nothing mempty]
206 ngramsElementToRepo :: NgramsElement -> NgramsRepoElement
208 (NgramsElement { _ne_size = s
222 ngramsElementFromRepo :: (NgramsTerm, NgramsRepoElement) -> NgramsElement
223 ngramsElementFromRepo
232 NgramsElement { _ne_size = s
237 , _ne_ngrams = ngrams
238 , _ne_occurrences = panic "API.Ngrams._ne_occurrences"
239 -- ^ Here we could use 0 if we want to avoid any `panic`.
240 -- It will not happen using getTableNgrams if
241 -- getOccByNgramsOnly provides a count of occurrences for
242 -- all the ngrams given.
245 ------------------------------------------------------------------------
246 newtype NgramsTable = NgramsTable [NgramsElement]
247 deriving (Ord, Eq, Generic, ToJSON, FromJSON, Show)
249 type ListNgrams = NgramsTable
251 makePrisms ''NgramsTable
253 -- | Question: why these repetition of Type in this instance
254 -- may you document it please ?
255 instance Each NgramsTable NgramsTable NgramsElement NgramsElement where
256 each = _NgramsTable . each
259 -- | TODO Check N and Weight
261 toNgramsElement :: [NgramsTableData] -> [NgramsElement]
262 toNgramsElement ns = map toNgramsElement' ns
264 toNgramsElement' (NgramsTableData _ p t _ lt w) = NgramsElement t lt' (round w) p' c'
268 Just x -> lookup x mapParent
269 c' = maybe mempty identity $ lookup t mapChildren
270 lt' = maybe (panic "API.Ngrams: listypeId") identity lt
272 mapParent :: Map Int Text
273 mapParent = Map.fromListWith (<>) $ map (\(NgramsTableData i _ t _ _ _) -> (i,t)) ns
275 mapChildren :: Map Text (Set Text)
276 mapChildren = Map.mapKeys (\i -> (maybe (panic "API.Ngrams.mapChildren: ParentId with no Terms: Impossible") identity $ lookup i mapParent))
277 $ Map.fromListWith (<>)
278 $ map (first fromJust)
279 $ filter (isJust . fst)
280 $ map (\(NgramsTableData _ p t _ _ _) -> (p, Set.singleton t)) ns
283 mockTable :: NgramsTable
284 mockTable = NgramsTable
285 [ mkNgramsElement "animal" GraphTerm Nothing (mSetFromList ["dog", "cat"])
286 , mkNgramsElement "cat" GraphTerm (rp "animal") mempty
287 , mkNgramsElement "cats" StopTerm Nothing mempty
288 , mkNgramsElement "dog" GraphTerm (rp "animal") (mSetFromList ["dogs"])
289 , mkNgramsElement "dogs" StopTerm (rp "dog") mempty
290 , mkNgramsElement "fox" GraphTerm Nothing mempty
291 , mkNgramsElement "object" CandidateTerm Nothing mempty
292 , mkNgramsElement "nothing" StopTerm Nothing mempty
293 , mkNgramsElement "organic" GraphTerm Nothing (mSetFromList ["flower"])
294 , mkNgramsElement "flower" GraphTerm (rp "organic") mempty
295 , mkNgramsElement "moon" CandidateTerm Nothing mempty
296 , mkNgramsElement "sky" StopTerm Nothing mempty
299 rp n = Just $ RootParent n n
301 instance Arbitrary NgramsTable where
302 arbitrary = pure mockTable
304 instance ToSchema NgramsTable
306 ------------------------------------------------------------------------
307 type NgramsTableMap = Map NgramsTerm NgramsRepoElement
309 ------------------------------------------------------------------------
310 -- On the Client side:
311 --data Action = InGroup NgramsId NgramsId
312 -- | OutGroup NgramsId NgramsId
313 -- | SetListType NgramsId ListType
315 data PatchSet a = PatchSet
319 deriving (Eq, Ord, Show, Generic)
321 makeLenses ''PatchSet
322 makePrisms ''PatchSet
324 instance ToJSON a => ToJSON (PatchSet a) where
325 toJSON = genericToJSON $ unPrefix "_"
326 toEncoding = genericToEncoding $ unPrefix "_"
328 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
329 parseJSON = genericParseJSON $ unPrefix "_"
332 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
333 arbitrary = PatchSet <$> arbitrary <*> arbitrary
335 type instance Patched (PatchSet a) = Set a
337 type ConflictResolutionPatchSet a = SimpleConflictResolution' (Set a)
338 type instance ConflictResolution (PatchSet a) = ConflictResolutionPatchSet a
340 instance Ord a => Semigroup (PatchSet a) where
341 p <> q = PatchSet { _rem = (q ^. rem) `Set.difference` (p ^. add) <> p ^. rem
342 , _add = (q ^. add) `Set.difference` (p ^. rem) <> p ^. add
345 instance Ord a => Monoid (PatchSet a) where
346 mempty = PatchSet mempty mempty
348 instance Ord a => Group (PatchSet a) where
349 invert (PatchSet r a) = PatchSet a r
351 instance Ord a => Composable (PatchSet a) where
352 composable _ _ = undefined
354 instance Ord a => Action (PatchSet a) (Set a) where
355 act p source = (source `Set.difference` (p ^. rem)) <> p ^. add
357 instance Applicable (PatchSet a) (Set a) where
358 applicable _ _ = mempty
360 instance Ord a => Validity (PatchSet a) where
361 validate p = check (Set.disjoint (p ^. rem) (p ^. add)) "_rem and _add should be dijoint"
363 instance Ord a => Transformable (PatchSet a) where
364 transformable = undefined
366 conflicts _p _q = undefined
368 transformWith conflict p q = undefined conflict p q
370 instance ToSchema a => ToSchema (PatchSet a)
373 type AddRem = Replace (Maybe ())
375 remPatch, addPatch :: AddRem
376 remPatch = replace (Just ()) Nothing
377 addPatch = replace Nothing (Just ())
379 isRem :: Replace (Maybe ()) -> Bool
380 isRem = (== remPatch)
382 type PatchMap = PM.PatchMap
384 newtype PatchMSet a = PatchMSet (PatchMap a AddRem)
385 deriving (Eq, Show, Generic, Validity, Semigroup, Monoid,
386 Transformable, Composable)
388 type ConflictResolutionPatchMSet a = a -> ConflictResolutionReplace (Maybe ())
389 type instance ConflictResolution (PatchMSet a) = ConflictResolutionPatchMSet a
391 -- TODO this breaks module abstraction
392 makePrisms ''PM.PatchMap
394 makePrisms ''PatchMSet
396 _PatchMSetIso :: Ord a => Iso' (PatchMSet a) (PatchSet a)
397 _PatchMSetIso = _PatchMSet . _PatchMap . iso f g . from _PatchSet
399 f :: Ord a => Map a (Replace (Maybe ())) -> (Set a, Set a)
400 f = Map.partition isRem >>> both %~ Map.keysSet
402 g :: Ord a => (Set a, Set a) -> Map a (Replace (Maybe ()))
403 g (rems, adds) = Map.fromSet (const remPatch) rems
404 <> Map.fromSet (const addPatch) adds
406 instance Ord a => Action (PatchMSet a) (MSet a) where
407 act (PatchMSet p) (MSet m) = MSet $ act p m
409 instance Ord a => Applicable (PatchMSet a) (MSet a) where
410 applicable (PatchMSet p) (MSet m) = applicable p m
412 instance (Ord a, ToJSON a) => ToJSON (PatchMSet a) where
413 toJSON = toJSON . view _PatchMSetIso
414 toEncoding = toEncoding . view _PatchMSetIso
416 instance (Ord a, FromJSON a) => FromJSON (PatchMSet a) where
417 parseJSON = fmap (_PatchMSetIso #) . parseJSON
419 instance (Ord a, Arbitrary a) => Arbitrary (PatchMSet a) where
420 arbitrary = (PatchMSet . PM.fromMap) <$> arbitrary
422 instance ToSchema a => ToSchema (PatchMSet a) where
424 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
426 type instance Patched (PatchMSet a) = MSet a
428 instance (Eq a, Arbitrary a) => Arbitrary (Replace a) where
429 arbitrary = uncurry replace <$> arbitrary
430 -- If they happen to be equal then the patch is Keep.
432 instance ToSchema a => ToSchema (Replace a) where
433 declareNamedSchema (_ :: proxy (Replace a)) = do
434 -- TODO Keep constructor is not supported here.
435 aSchema <- declareSchemaRef (Proxy :: Proxy a)
436 return $ NamedSchema (Just "Replace") $ mempty
437 & type_ .~ SwaggerObject
439 InsOrdHashMap.fromList
443 & required .~ [ "old", "new" ]
446 NgramsPatch { _patch_children :: PatchMSet NgramsTerm
447 , _patch_list :: Replace ListType -- TODO Map UserId ListType
449 deriving (Eq, Show, Generic)
451 deriveJSON (unPrefix "_") ''NgramsPatch
452 makeLenses ''NgramsPatch
454 instance ToSchema NgramsPatch
456 instance Arbitrary NgramsPatch where
457 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
459 type NgramsPatchIso = PairPatch (PatchMSet NgramsTerm) (Replace ListType)
461 _NgramsPatch :: Iso' NgramsPatch NgramsPatchIso
462 _NgramsPatch = iso (\(NgramsPatch c l) -> c :*: l) (\(c :*: l) -> NgramsPatch c l)
464 instance Semigroup NgramsPatch where
465 p <> q = _NgramsPatch # (p ^. _NgramsPatch <> q ^. _NgramsPatch)
467 instance Monoid NgramsPatch where
468 mempty = _NgramsPatch # mempty
470 instance Validity NgramsPatch where
471 validate p = p ^. _NgramsPatch . to validate
473 instance Transformable NgramsPatch where
474 transformable p q = transformable (p ^. _NgramsPatch) (q ^. _NgramsPatch)
476 conflicts p q = conflicts (p ^. _NgramsPatch) (q ^. _NgramsPatch)
478 transformWith conflict p q = (_NgramsPatch # p', _NgramsPatch # q')
480 (p', q') = transformWith conflict (p ^. _NgramsPatch) (q ^. _NgramsPatch)
482 type ConflictResolutionNgramsPatch =
483 ( ConflictResolutionPatchMSet NgramsTerm
484 , ConflictResolutionReplace ListType
486 type instance ConflictResolution NgramsPatch =
487 ConflictResolutionNgramsPatch
489 type PatchedNgramsPatch = (Set NgramsTerm, ListType)
490 -- ~ Patched NgramsPatchIso
491 type instance Patched NgramsPatch = PatchedNgramsPatch
493 instance Applicable NgramsPatch (Maybe NgramsRepoElement) where
494 applicable p Nothing = check (p == mempty) "NgramsPatch should be empty here"
495 applicable p (Just nre) =
496 applicable (p ^. patch_children) (nre ^. nre_children) <>
497 applicable (p ^. patch_list) (nre ^. nre_list)
499 instance Action NgramsPatch NgramsRepoElement where
500 act p = (nre_children %~ act (p ^. patch_children))
501 . (nre_list %~ act (p ^. patch_list))
503 instance Action NgramsPatch (Maybe NgramsRepoElement) where
506 newtype NgramsTablePatch = NgramsTablePatch (PatchMap NgramsTerm NgramsPatch)
507 deriving (Eq, Show, Generic, ToJSON, FromJSON, Semigroup, Monoid, Validity, Transformable)
509 instance FromField NgramsTablePatch
511 fromField = fromField'
513 instance FromField (PatchMap NgramsType (PatchMap NodeId NgramsTablePatch))
515 fromField = fromField'
517 --instance (Ord k, Action pv (Maybe v)) => Action (PatchMap k pv) (Map k v) where
519 type instance ConflictResolution NgramsTablePatch =
520 NgramsTerm -> ConflictResolutionNgramsPatch
522 type PatchedNgramsTablePatch = Map NgramsTerm PatchedNgramsPatch
523 -- ~ Patched (PatchMap NgramsTerm NgramsPatch)
524 type instance Patched NgramsTablePatch = PatchedNgramsTablePatch
526 makePrisms ''NgramsTablePatch
527 instance ToSchema (PatchMap NgramsTerm NgramsPatch)
528 instance ToSchema NgramsTablePatch
530 instance Applicable NgramsTablePatch (Maybe NgramsTableMap) where
531 applicable p = applicable (p ^. _NgramsTablePatch)
533 instance Action NgramsTablePatch (Maybe NgramsTableMap) where
535 fmap (execState (reParentNgramsTablePatch p)) .
536 act (p ^. _NgramsTablePatch)
538 instance Arbitrary NgramsTablePatch where
539 arbitrary = NgramsTablePatch <$> PM.fromMap <$> arbitrary
541 -- Should it be less than an Lens' to preserve PatchMap's abstraction.
542 -- ntp_ngrams_patches :: Lens' NgramsTablePatch (Map NgramsTerm NgramsPatch)
543 -- ntp_ngrams_patches = _NgramsTablePatch . undefined
545 type ReParent a = forall m. MonadState NgramsTableMap m => a -> m ()
547 reRootChildren :: NgramsTerm -> ReParent NgramsTerm
548 reRootChildren root ngram = do
549 nre <- use $ at ngram
550 forOf_ (_Just . nre_children . folded) nre $ \child -> do
551 at child . _Just . nre_root ?= root
552 reRootChildren root child
554 reParent :: Maybe RootParent -> ReParent NgramsTerm
555 reParent rp child = do
556 at child . _Just %= ( (nre_parent .~ (_rp_parent <$> rp))
557 . (nre_root .~ (_rp_root <$> rp))
559 reRootChildren (fromMaybe child (rp ^? _Just . rp_root)) child
561 reParentAddRem :: RootParent -> NgramsTerm -> ReParent AddRem
562 reParentAddRem rp child p =
563 reParent (if isRem p then Nothing else Just rp) child
565 reParentNgramsPatch :: NgramsTerm -> ReParent NgramsPatch
566 reParentNgramsPatch parent ngramsPatch = do
567 root_of_parent <- use (at parent . _Just . nre_root)
569 root = fromMaybe parent root_of_parent
570 rp = RootParent { _rp_root = root, _rp_parent = parent }
571 itraverse_ (reParentAddRem rp) (ngramsPatch ^. patch_children . _PatchMSet . _PatchMap)
572 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
574 reParentNgramsTablePatch :: ReParent NgramsTablePatch
575 reParentNgramsTablePatch p = itraverse_ reParentNgramsPatch (p ^. _NgramsTablePatch. _PatchMap)
576 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
578 ------------------------------------------------------------------------
579 ------------------------------------------------------------------------
582 data Versioned a = Versioned
583 { _v_version :: Version
586 deriving (Generic, Show)
587 deriveJSON (unPrefix "_v_") ''Versioned
588 makeLenses ''Versioned
589 instance ToSchema a => ToSchema (Versioned a)
590 instance Arbitrary a => Arbitrary (Versioned a) where
591 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
594 -- TODO sequencs of modifications (Patchs)
595 type NgramsIdPatch = Patch NgramsId NgramsPatch
597 ngramsPatch :: Int -> NgramsPatch
598 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopTerm)]) (Set.fromList [n]) Set.empty
600 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
601 toEdit n p = Edit n p
602 ngramsIdPatch :: Patch NgramsId NgramsPatch
603 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
604 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
605 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
608 -- applyPatchBack :: Patch -> IO Patch
609 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
611 ------------------------------------------------------------------------
612 ------------------------------------------------------------------------
613 ------------------------------------------------------------------------
616 -- TODO: Replace.old is ignored which means that if the current list
617 -- `GraphTerm` and that the patch is `Replace CandidateTerm StopTerm` then
618 -- the list is going to be `StopTerm` while it should keep `GraphTerm`.
619 -- However this should not happen in non conflicting situations.
620 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
621 mkListsUpdate nt patches =
622 [ (ngramsTypeId nt, ng, listTypeId lt)
623 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
624 , lt <- patch ^.. patch_list . new
627 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
630 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
631 mkChildrenGroups addOrRem nt patches =
632 [ (ngramsTypeId nt, parent, child)
633 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
634 , child <- patch ^.. patch_children . to addOrRem . folded
638 ngramsTypeFromTabType :: TabType -> NgramsType
639 ngramsTypeFromTabType tabType =
640 let lieu = "Garg.API.Ngrams: " :: Text in
642 Sources -> Ngrams.Sources
643 Authors -> Ngrams.Authors
644 Institutes -> Ngrams.Institutes
645 Terms -> Ngrams.NgramsTerms
646 _ -> panic $ lieu <> "No Ngrams for this tab"
647 -- ^ TODO: This `panic` would disapear with custom NgramsType.
649 ------------------------------------------------------------------------
651 { _r_version :: Version
654 -- ^ first patch in the list is the most recent
658 instance (FromJSON s, FromJSON p) => FromJSON (Repo s p) where
659 parseJSON = genericParseJSON $ unPrefix "_r_"
661 instance (ToJSON s, ToJSON p) => ToJSON (Repo s p) where
662 toJSON = genericToJSON $ unPrefix "_r_"
663 toEncoding = genericToEncoding $ unPrefix "_r_"
667 initRepo :: Monoid s => Repo s p
668 initRepo = Repo 1 mempty []
670 type NgramsRepo = Repo NgramsState NgramsStatePatch
671 type NgramsState = Map NgramsType (Map NodeId NgramsTableMap)
672 type NgramsStatePatch = PatchMap NgramsType (PatchMap NodeId NgramsTablePatch)
674 initMockRepo :: NgramsRepo
675 initMockRepo = Repo 1 s []
677 s = Map.singleton Ngrams.NgramsTerms
678 $ Map.singleton 47254
680 [ (n ^. ne_ngrams, ngramsElementToRepo n) | n <- mockTable ^. _NgramsTable ]
682 data RepoEnv = RepoEnv
683 { _renv_var :: !(MVar NgramsRepo)
684 , _renv_saver :: !(IO ())
685 , _renv_lock :: !FileLock
691 class HasRepoVar env where
692 repoVar :: Getter env (MVar NgramsRepo)
694 instance HasRepoVar (MVar NgramsRepo) where
697 class HasRepoSaver env where
698 repoSaver :: Getter env (IO ())
700 class (HasRepoVar env, HasRepoSaver env) => HasRepo env where
701 repoEnv :: Getter env RepoEnv
703 instance HasRepo RepoEnv where
706 instance HasRepoVar RepoEnv where
709 instance HasRepoSaver RepoEnv where
710 repoSaver = renv_saver
712 type RepoCmdM env err m =
718 ------------------------------------------------------------------------
720 saveRepo :: ( MonadReader env m, MonadIO m, HasRepoSaver env )
722 saveRepo = liftIO =<< view repoSaver
724 listTypeConflictResolution :: ListType -> ListType -> ListType
725 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
727 ngramsStatePatchConflictResolution
728 :: NgramsType -> NodeId -> NgramsTerm
729 -> ConflictResolutionNgramsPatch
730 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
732 -- undefined {- TODO think this through -}, listTypeConflictResolution)
735 -- Insertions are not considered as patches,
736 -- they do not extend history,
737 -- they do not bump version.
738 insertNewOnly :: a -> Maybe b -> a
739 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
740 -- TODO error handling
742 something :: Monoid a => Maybe a -> a
743 something Nothing = mempty
744 something (Just a) = a
747 -- TODO refactor with putListNgrams
748 copyListNgrams :: RepoCmdM env err m
749 => NodeId -> NodeId -> NgramsType
751 copyListNgrams srcListId dstListId ngramsType = do
753 liftIO $ modifyMVar_ var $
754 pure . (r_state . at ngramsType %~ (Just . f . something))
757 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
758 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
760 -- TODO refactor with putListNgrams
761 -- The list must be non-empty!
762 -- The added ngrams must be non-existent!
763 addListNgrams :: RepoCmdM env err m
764 => NodeId -> NgramsType
765 -> [NgramsElement] -> m ()
766 addListNgrams listId ngramsType nes = do
768 liftIO $ modifyMVar_ var $
769 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
772 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
775 putListNgrams :: RepoCmdM env err m
776 => NodeId -> NgramsType
777 -> [NgramsElement] -> m ()
778 putListNgrams _ _ [] = pure ()
779 putListNgrams listId ngramsType nes = do
780 -- printDebug "putListNgrams" (length nes)
782 liftIO $ modifyMVar_ var $
783 pure . (r_state . at ngramsType %~ (Just . (at listId %~ (Just . (m <>) . something)) . something))
786 m = Map.fromList $ (\n -> (n ^. ne_ngrams, ngramsElementToRepo n)) <$> nes
788 -- Apply the given patch to the DB and returns the patch to be applied on the
790 tableNgramsPut :: (HasInvalidError err, RepoCmdM env err m)
791 => CorpusId -> TabType -> ListId
792 -> Versioned NgramsTablePatch
793 -> m (Versioned NgramsTablePatch)
794 tableNgramsPut _corpusId tabType listId (Versioned p_version p_table)
795 | p_table == mempty = do
796 let ngramsType = ngramsTypeFromTabType tabType
799 r <- liftIO $ readMVar var
802 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
803 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
805 pure (Versioned (r ^. r_version) q_table)
808 let ngramsType = ngramsTypeFromTabType tabType
809 (p0, p0_validity) = PM.singleton listId p_table
810 (p, p_validity) = PM.singleton ngramsType p0
812 assertValid p0_validity
813 assertValid p_validity
816 vq' <- liftIO $ modifyMVar var $ \r -> do
818 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
819 (p', q') = transformWith ngramsStatePatchConflictResolution p q
820 r' = r & r_version +~ 1
822 & r_history %~ (p' :)
823 q'_table = q' ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
825 -- Ideally we would like to check these properties. However:
826 -- * They should be checked only to debug the code. The client data
827 -- should be able to trigger these.
828 -- * What kind of error should they throw (we are in IO here)?
829 -- * Should we keep modifyMVar?
830 -- * Should we throw the validation in an Exception, catch it around
831 -- modifyMVar and throw it back as an Error?
832 assertValid $ transformable p q
833 assertValid $ applicable p' (r ^. r_state)
835 pure (r', Versioned (r' ^. r_version) q'_table)
840 mergeNgramsElement :: NgramsRepoElement -> NgramsRepoElement -> NgramsRepoElement
841 mergeNgramsElement _neOld neNew = neNew
843 { _ne_list :: ListType
844 If we merge the parents/children we can potentially create cycles!
845 , _ne_parent :: Maybe NgramsTerm
846 , _ne_children :: MSet NgramsTerm
850 getNgramsTableMap :: RepoCmdM env err m
851 => NodeId -> NgramsType -> m (Versioned NgramsTableMap)
852 getNgramsTableMap nodeId ngramsType = do
854 repo <- liftIO $ readMVar v
855 pure $ Versioned (repo ^. r_version)
856 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
861 -- | TODO Errors management
862 -- TODO: polymorphic for Annuaire or Corpus or ...
863 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
864 -- TODO: should take only one ListId
869 getTableNgrams :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
871 -> ListId -> Limit -> Maybe Offset
873 -> Maybe MinSize -> Maybe MaxSize
874 -> (NgramsTerm -> Bool)
875 -> m (Versioned NgramsTable)
876 getTableNgrams nId tabType listId limit_ offset
877 listType minSize maxSize searchQuery = do
880 ngramsType = ngramsTypeFromTabType tabType
881 offset' = maybe 0 identity offset
882 listType' = maybe (const True) (==) listType
883 minSize' = maybe (const True) (<=) minSize
884 maxSize' = maybe (const True) (>=) maxSize
886 selected_node n = minSize' s
888 && searchQuery (n ^. ne_ngrams)
889 && listType' (n ^. ne_list)
893 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
895 finalize tableMap = NgramsTable $ roots <> inners
897 rootOf ne = maybe ne (\r -> ngramsElementFromRepo (r, fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r)))
899 list = ngramsElementFromRepo <$> Map.toList tableMap
900 selected_nodes = list & take limit_ . drop offset' . filter selected_node
901 roots = rootOf <$> selected_nodes
902 rootsSet = Set.fromList (_ne_ngrams <$> roots)
903 inners = list & filter (selected_inner rootsSet)
905 -- lists <- catMaybes <$> listsWith userMaster
906 -- trace (show lists) $
907 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
909 table <- getNgramsTableMap listId ngramsType & mapped . v_data %~ finalize
911 lIds <- selectNodesWithUsername NodeList userMaster
912 occurrences <- getOccByNgramsOnlySafe nId (lIds <> [listId]) ngramsType (table ^.. v_data . _NgramsTable . each . ne_ngrams)
915 setOcc ne = ne & ne_occurrences .~ sumOf (at (ne ^. ne_ngrams) . _Just) occurrences
917 pure $ table & v_data . _NgramsTable . each %~ setOcc
922 -- TODO: find a better place for the code above, All APIs stay here
923 type QueryParamR = QueryParam' '[Required, Strict]
925 type TableNgramsApiGet = Summary " Table Ngrams API Get"
926 :> QueryParamR "ngramsType" TabType
927 :> QueryParamR "list" ListId
928 :> QueryParamR "limit" Limit
929 :> QueryParam "offset" Offset
930 :> QueryParam "listType" ListType
931 :> QueryParam "minTermSize" Int
932 :> QueryParam "maxTermSize" Int
933 :> QueryParam "search" Text
934 :> Get '[JSON] (Versioned NgramsTable)
936 type TableNgramsApiPut = Summary " Table Ngrams API Change"
937 :> QueryParamR "ngramsType" TabType
938 :> QueryParamR "list" ListId
939 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
940 :> Put '[JSON] (Versioned NgramsTablePatch)
943 getTableNgramsCorpus :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
945 -> ListId -> Limit -> Maybe Offset
947 -> Maybe MinSize -> Maybe MaxSize
948 -> Maybe Text -- full text search
949 -> m (Versioned NgramsTable)
950 getTableNgramsCorpus nId tabType listId limit_ offset listType minSize maxSize mt =
951 getTableNgrams nId tabType listId limit_ offset listType minSize maxSize searchQuery
953 searchQuery = maybe (const True) isInfixOf mt
955 -- | Text search is deactivated for now for ngrams by doc only
956 getTableNgramsDoc :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
958 -> ListId -> Limit -> Maybe Offset
960 -> Maybe MinSize -> Maybe MaxSize
961 -> Maybe Text -- full text search
962 -> m (Versioned NgramsTable)
963 getTableNgramsDoc dId tabType listId limit_ offset listType minSize maxSize _mt = do
964 ns <- selectNodesWithUsername NodeList userMaster
965 let ngramsType = ngramsTypeFromTabType tabType
966 ngs <- selectNgramsByDoc (ns <> [listId]) dId ngramsType
967 let searchQuery = flip S.member (S.fromList ngs)
968 getTableNgrams dId tabType listId limit_ offset listType minSize maxSize searchQuery
974 -- TODO Doc Table Ngrams API
975 type ApiNgramsTableDoc = TableNgramsApiGet
976 -- :<|> TableNgramsApiPut
977 -- :<|> TableNgramsApiPost
979 apiNgramsTableDoc :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
981 -> ListId -> Limit -> Maybe Offset
983 -> Maybe MinSize -> Maybe MaxSize
984 -> Maybe Text -- full text search
985 -> m (Versioned NgramsTable)
987 --apiDocNgramsTable :: ApiDocNgramsTable
988 --apiDocNgramsTable :: ApiDocNgramsTable
989 --apiDocNgramsTable = getTableNgramsDoc
992 -- > add new ngrams to the repo (TODO NP)
993 -- > add new ngrams in database (TODO AD)
994 -- > index all the corpus accordingly (TODO AD)
996 apiNgramsTableDoc = getTableNgramsDoc