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 ------------------------------------------------------------------------
615 -- TODO: find a better place for this Gargantext.API.{Common|Prelude|Core} ?
616 type QueryParamR = QueryParam' '[Required, Strict]
618 type TableNgramsApiGet = Summary " Table Ngrams API Get"
619 :> QueryParamR "ngramsType" TabType
620 :> QueryParamR "list" ListId
621 :> QueryParamR "limit" Limit
622 :> QueryParam "offset" Offset
623 :> QueryParam "listType" ListType
624 :> QueryParam "minTermSize" Int
625 :> QueryParam "maxTermSize" Int
626 :> QueryParam "search" Text
627 :> Get '[JSON] (Versioned NgramsTable)
629 type TableNgramsApi = Summary " Table Ngrams API Change"
630 :> QueryParamR "ngramsType" TabType
631 :> QueryParamR "list" ListId
632 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
633 :> Put '[JSON] (Versioned NgramsTablePatch)
639 -- TODO: Replace.old is ignored which means that if the current list
640 -- `GraphTerm` and that the patch is `Replace CandidateTerm StopTerm` then
641 -- the list is going to be `StopTerm` while it should keep `GraphTerm`.
642 -- However this should not happen in non conflicting situations.
643 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
644 mkListsUpdate nt patches =
645 [ (ngramsTypeId nt, ng, listTypeId lt)
646 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
647 , lt <- patch ^.. patch_list . new
650 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
653 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
654 mkChildrenGroups addOrRem nt patches =
655 [ (ngramsTypeId nt, parent, child)
656 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
657 , child <- patch ^.. patch_children . to addOrRem . folded
661 ngramsTypeFromTabType :: TabType -> NgramsType
662 ngramsTypeFromTabType tabType =
663 let lieu = "Garg.API.Ngrams: " :: Text in
665 Sources -> Ngrams.Sources
666 Authors -> Ngrams.Authors
667 Institutes -> Ngrams.Institutes
668 Terms -> Ngrams.NgramsTerms
669 _ -> panic $ lieu <> "No Ngrams for this tab"
670 -- ^ TODO: This `panic` would disapear with custom NgramsType.
672 ------------------------------------------------------------------------
674 { _r_version :: Version
677 -- ^ first patch in the list is the most recent
681 instance (FromJSON s, FromJSON p) => FromJSON (Repo s p) where
682 parseJSON = genericParseJSON $ unPrefix "_r_"
684 instance (ToJSON s, ToJSON p) => ToJSON (Repo s p) where
685 toJSON = genericToJSON $ unPrefix "_r_"
686 toEncoding = genericToEncoding $ unPrefix "_r_"
690 initRepo :: Monoid s => Repo s p
691 initRepo = Repo 1 mempty []
693 type NgramsRepo = Repo NgramsState NgramsStatePatch
694 type NgramsState = Map NgramsType (Map NodeId NgramsTableMap)
695 type NgramsStatePatch = PatchMap NgramsType (PatchMap NodeId NgramsTablePatch)
697 initMockRepo :: NgramsRepo
698 initMockRepo = Repo 1 s []
700 s = Map.singleton Ngrams.NgramsTerms
701 $ Map.singleton 47254
703 [ (n ^. ne_ngrams, ngramsElementToRepo n) | n <- mockTable ^. _NgramsTable ]
705 data RepoEnv = RepoEnv
706 { _renv_var :: !(MVar NgramsRepo)
707 , _renv_saver :: !(IO ())
708 , _renv_lock :: !FileLock
714 class HasRepoVar env where
715 repoVar :: Getter env (MVar NgramsRepo)
717 instance HasRepoVar (MVar NgramsRepo) where
720 class HasRepoSaver env where
721 repoSaver :: Getter env (IO ())
723 class (HasRepoVar env, HasRepoSaver env) => HasRepo env where
724 repoEnv :: Getter env RepoEnv
726 instance HasRepo RepoEnv where
729 instance HasRepoVar RepoEnv where
732 instance HasRepoSaver RepoEnv where
733 repoSaver = renv_saver
735 type RepoCmdM env err m =
741 ------------------------------------------------------------------------
743 saveRepo :: ( MonadReader env m, MonadIO m, HasRepoSaver env )
745 saveRepo = liftIO =<< view repoSaver
747 listTypeConflictResolution :: ListType -> ListType -> ListType
748 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
750 ngramsStatePatchConflictResolution
751 :: NgramsType -> NodeId -> NgramsTerm
752 -> ConflictResolutionNgramsPatch
753 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
755 -- undefined {- TODO think this through -}, listTypeConflictResolution)
758 -- Insertions are not considered as patches,
759 -- they do not extend history,
760 -- they do not bump version.
761 insertNewOnly :: a -> Maybe b -> a
762 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
763 -- TODO error handling
765 something :: Monoid a => Maybe a -> a
766 something Nothing = mempty
767 something (Just a) = a
770 -- TODO refactor with putListNgrams
771 copyListNgrams :: RepoCmdM env err m
772 => NodeId -> NodeId -> NgramsType
774 copyListNgrams srcListId dstListId ngramsType = do
776 liftIO $ modifyMVar_ var $
777 pure . (r_state . at ngramsType %~ (Just . f . something))
780 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
781 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
783 -- TODO refactor with putListNgrams
784 -- The list must be non-empty!
785 -- The added ngrams must be non-existent!
786 addListNgrams :: RepoCmdM env err m
787 => NodeId -> NgramsType
788 -> [NgramsElement] -> m ()
789 addListNgrams listId ngramsType nes = do
791 liftIO $ modifyMVar_ var $
792 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
795 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
798 putListNgrams :: RepoCmdM env err m
799 => NodeId -> NgramsType
800 -> [NgramsElement] -> m ()
801 putListNgrams _ _ [] = pure ()
802 putListNgrams listId ngramsType nes = do
803 -- printDebug "putListNgrams" (length nes)
805 liftIO $ modifyMVar_ var $
806 pure . (r_state . at ngramsType %~ (Just . (at listId %~ (Just . (m <>) . something)) . something))
809 m = Map.fromList $ (\n -> (n ^. ne_ngrams, ngramsElementToRepo n)) <$> nes
811 -- Apply the given patch to the DB and returns the patch to be applied on the
813 tableNgramsPatch :: (HasInvalidError err, RepoCmdM env err m)
814 => CorpusId -> TabType -> ListId
815 -> Versioned NgramsTablePatch
816 -> m (Versioned NgramsTablePatch)
817 tableNgramsPatch _corpusId tabType listId (Versioned p_version p_table)
818 | p_table == mempty = do
819 let ngramsType = ngramsTypeFromTabType tabType
822 r <- liftIO $ readMVar var
825 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
826 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
828 pure (Versioned (r ^. r_version) q_table)
831 let ngramsType = ngramsTypeFromTabType tabType
832 (p0, p0_validity) = PM.singleton listId p_table
833 (p, p_validity) = PM.singleton ngramsType p0
835 assertValid p0_validity
836 assertValid p_validity
839 vq' <- liftIO $ modifyMVar var $ \r -> do
841 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
842 (p', q') = transformWith ngramsStatePatchConflictResolution p q
843 r' = r & r_version +~ 1
845 & r_history %~ (p' :)
846 q'_table = q' ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
848 -- Ideally we would like to check these properties. However:
849 -- * They should be checked only to debug the code. The client data
850 -- should be able to trigger these.
851 -- * What kind of error should they throw (we are in IO here)?
852 -- * Should we keep modifyMVar?
853 -- * Should we throw the validation in an Exception, catch it around
854 -- modifyMVar and throw it back as an Error?
855 assertValid $ transformable p q
856 assertValid $ applicable p' (r ^. r_state)
858 pure (r', Versioned (r' ^. r_version) q'_table)
863 mergeNgramsElement :: NgramsRepoElement -> NgramsRepoElement -> NgramsRepoElement
864 mergeNgramsElement _neOld neNew = neNew
866 { _ne_list :: ListType
867 If we merge the parents/children we can potentially create cycles!
868 , _ne_parent :: Maybe NgramsTerm
869 , _ne_children :: MSet NgramsTerm
873 getNgramsTableMap :: RepoCmdM env err m
874 => NodeId -> NgramsType -> m (Versioned NgramsTableMap)
875 getNgramsTableMap nodeId ngramsType = do
877 repo <- liftIO $ readMVar v
878 pure $ Versioned (repo ^. r_version)
879 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
884 -- | TODO Errors management
885 -- TODO: polymorphic for Annuaire or Corpus or ...
886 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
887 -- TODO: should take only one ListId
892 getTableNgramsCorpus :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
894 -> ListId -> Limit -> Maybe Offset
896 -> Maybe MinSize -> Maybe MaxSize
897 -> Maybe Text -- full text search
898 -> m (Versioned NgramsTable)
899 getTableNgramsCorpus nId tabType listId limit_ offset listType minSize maxSize mt =
900 getTableNgrams nId tabType listId limit_ offset listType minSize maxSize searchQuery
902 searchQuery = maybe (const True) isInfixOf mt
904 -- | Text search is deactivated for now for ngrams by doc only
905 getTableNgramsDoc :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
906 => CorpusId -> DocId -> TabType
907 -> ListId -> Limit -> Maybe Offset
909 -> Maybe MinSize -> Maybe MaxSize
910 -> Maybe Text -- full text search
911 -> m (Versioned NgramsTable)
912 getTableNgramsDoc cId dId tabType listId limit_ offset listType minSize maxSize _mt = do
913 ns <- selectNodesWithUsername NodeList userMaster
914 let ngramsType = ngramsTypeFromTabType tabType
915 ngs <- selectNgramsByDoc (ns <> [cId]) dId ngramsType
916 let searchQuery = flip S.member (S.fromList ngs)
917 getTableNgrams cId tabType listId limit_ offset listType minSize maxSize searchQuery
920 getTableNgrams :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
922 -> ListId -> Limit -> Maybe Offset
924 -> Maybe MinSize -> Maybe MaxSize
925 -> (NgramsTerm -> Bool)
926 -> m (Versioned NgramsTable)
927 getTableNgrams nId tabType listId limit_ offset
928 listType minSize maxSize searchQuery = do
931 ngramsType = ngramsTypeFromTabType tabType
932 offset' = maybe 0 identity offset
933 listType' = maybe (const True) (==) listType
934 minSize' = maybe (const True) (<=) minSize
935 maxSize' = maybe (const True) (>=) maxSize
937 selected_node n = minSize' s
939 && searchQuery (n ^. ne_ngrams)
940 && listType' (n ^. ne_list)
944 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
946 finalize tableMap = NgramsTable $ roots <> inners
948 rootOf ne = maybe ne (\r -> ngramsElementFromRepo (r, fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r)))
950 list = ngramsElementFromRepo <$> Map.toList tableMap
951 selected_nodes = list & take limit_ . drop offset' . filter selected_node
952 roots = rootOf <$> selected_nodes
953 rootsSet = Set.fromList (_ne_ngrams <$> roots)
954 inners = list & filter (selected_inner rootsSet)
956 -- lists <- catMaybes <$> listsWith userMaster
957 -- trace (show lists) $
958 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
960 table <- getNgramsTableMap listId ngramsType & mapped . v_data %~ finalize
962 lIds <- selectNodesWithUsername NodeList userMaster
963 occurrences <- getOccByNgramsOnlySafe nId (lIds <> [listId]) ngramsType (table ^.. v_data . _NgramsTable . each . ne_ngrams)
966 setOcc ne = ne & ne_occurrences .~ sumOf (at (ne ^. ne_ngrams) . _Just) occurrences
968 pure $ table & v_data . _NgramsTable . each %~ setOcc