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
46 import Data.Ord (Down(..))
48 --import Data.Semigroup
50 import qualified Data.Set as S
51 import qualified Data.List as List
52 import Data.Maybe (fromMaybe)
53 -- import Data.Tuple.Extra (first)
54 import qualified Data.Map.Strict as Map
55 import Data.Map.Strict (Map)
56 import qualified Data.Set as Set
57 import Control.Category ((>>>))
58 import Control.Concurrent
59 import Control.Lens (makeLenses, makePrisms, Getter, Iso', iso, from, (.~), (?=), (#), to, folded, {-withIndex, ifolded,-} view, use, (^.), (^..), (^?), (+~), (%~), (%=), sumOf, at, _Just, Each(..), itraverse_, both, forOf_, (%%~))
60 import Control.Monad.Error.Class (MonadError)
61 import Control.Monad.Reader
62 import Control.Monad.State
63 import Data.Aeson hiding ((.=))
64 import Data.Aeson.TH (deriveJSON)
65 import Data.Either(Either(Left))
66 -- import Data.Map (lookup)
67 import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
68 import Data.Swagger hiding (version, patch)
69 import Data.Text (Text, isInfixOf, count)
71 import GHC.Generics (Generic)
72 import Gargantext.Core.Utils.Prefix (unPrefix)
73 -- import Gargantext.Database.Schema.Ngrams (NgramsTypeId, ngramsTypeId, NgramsTableData(..))
74 import Gargantext.Database.Config (userMaster)
75 import Gargantext.Database.Metrics.NgramsByNode (getOccByNgramsOnlySlow)
76 import Gargantext.Database.Schema.Ngrams (NgramsType)
77 import Gargantext.Database.Types.Node (NodeType(..))
78 import Gargantext.Database.Utils (fromField', HasConnection)
79 import Gargantext.Database.Node.Select
80 import Gargantext.Database.Ngrams
81 --import Gargantext.Database.Lists (listsWith)
82 import Gargantext.Database.Schema.Node (HasNodeError)
83 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
84 import qualified Gargantext.Database.Schema.Ngrams as Ngrams
85 -- import Gargantext.Database.Schema.NodeNgram hiding (Action)
86 import Gargantext.Prelude
87 -- import Gargantext.Core.Types (ListTypeId, listTypeId)
88 import Gargantext.Core.Types (ListType(..), NodeId, ListId, DocId, Limit, Offset, HasInvalidError, assertValid)
89 import Servant hiding (Patch)
90 import System.FileLock (FileLock)
91 import Test.QuickCheck (elements)
92 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
97 instance ToSchema TODO where
98 instance ToParamSchema TODO where
100 ------------------------------------------------------------------------
101 --data FacetFormat = Table | Chart
102 data TabType = Docs | Terms | Sources | Authors | Institutes | Trash
104 deriving (Generic, Enum, Bounded)
106 instance FromHttpApiData TabType
108 parseUrlPiece "Docs" = pure Docs
109 parseUrlPiece "Terms" = pure Terms
110 parseUrlPiece "Sources" = pure Sources
111 parseUrlPiece "Institutes" = pure Institutes
112 parseUrlPiece "Authors" = pure Authors
113 parseUrlPiece "Trash" = pure Trash
115 parseUrlPiece "Contacts" = pure Contacts
117 parseUrlPiece _ = Left "Unexpected value of TabType"
119 instance ToParamSchema TabType
120 instance ToJSON TabType
121 instance FromJSON TabType
122 instance ToSchema TabType
123 instance Arbitrary TabType
125 arbitrary = elements [minBound .. maxBound]
127 newtype MSet a = MSet (Map a ())
128 deriving (Eq, Ord, Show, Generic, Arbitrary, Semigroup, Monoid)
130 instance ToJSON a => ToJSON (MSet a) where
131 toJSON (MSet m) = toJSON (Map.keys m)
132 toEncoding (MSet m) = toEncoding (Map.keys m)
134 mSetFromSet :: Set a -> MSet a
135 mSetFromSet = MSet . Map.fromSet (const ())
137 mSetFromList :: Ord a => [a] -> MSet a
138 mSetFromList = MSet . Map.fromList . map (\x -> (x, ()))
140 -- mSetToSet :: Ord a => MSet a -> Set a
141 -- mSetToSet (MSet a) = Set.fromList ( Map.keys a)
142 mSetToSet :: Ord a => MSet a -> Set a
143 mSetToSet = Set.fromList . mSetToList
145 mSetToList :: MSet a -> [a]
146 mSetToList (MSet a) = Map.keys a
148 instance Foldable MSet where
149 foldMap f (MSet m) = Map.foldMapWithKey (\k _ -> f k) m
151 instance (Ord a, FromJSON a) => FromJSON (MSet a) where
152 parseJSON = fmap mSetFromList . parseJSON
154 instance (ToJSONKey a, ToSchema a) => ToSchema (MSet a) where
156 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
158 ------------------------------------------------------------------------
159 type NgramsTerm = Text
161 data RootParent = RootParent
162 { _rp_root :: NgramsTerm
163 , _rp_parent :: NgramsTerm
165 deriving (Ord, Eq, Show, Generic)
167 deriveJSON (unPrefix "_rp_") ''RootParent
168 makeLenses ''RootParent
170 data NgramsRepoElement = NgramsRepoElement
172 , _nre_list :: ListType
173 --, _nre_root_parent :: Maybe RootParent
174 , _nre_root :: Maybe NgramsTerm
175 , _nre_parent :: Maybe NgramsTerm
176 , _nre_children :: MSet NgramsTerm
178 deriving (Ord, Eq, Show, Generic)
180 deriveJSON (unPrefix "_nre_") ''NgramsRepoElement
181 makeLenses ''NgramsRepoElement
184 NgramsElement { _ne_ngrams :: NgramsTerm
186 , _ne_list :: ListType
187 , _ne_occurrences :: Int
188 , _ne_root :: Maybe NgramsTerm
189 , _ne_parent :: Maybe NgramsTerm
190 , _ne_children :: MSet NgramsTerm
192 deriving (Ord, Eq, Show, Generic)
194 deriveJSON (unPrefix "_ne_") ''NgramsElement
195 makeLenses ''NgramsElement
197 mkNgramsElement :: NgramsTerm -> ListType -> Maybe RootParent -> MSet NgramsTerm -> NgramsElement
198 mkNgramsElement ngrams list rp children =
199 NgramsElement ngrams size list 1 (_rp_root <$> rp) (_rp_parent <$> rp) children
202 size = 1 + count " " ngrams
204 newNgramsElement :: Maybe ListType -> NgramsTerm -> NgramsElement
205 newNgramsElement mayList ngrams = mkNgramsElement ngrams (fromMaybe GraphTerm mayList) Nothing mempty
207 instance ToSchema NgramsElement
208 instance Arbitrary NgramsElement where
209 arbitrary = elements [newNgramsElement Nothing "sport"]
211 ngramsElementToRepo :: NgramsElement -> NgramsRepoElement
213 (NgramsElement { _ne_size = s
227 ngramsElementFromRepo :: NgramsTerm -> NgramsRepoElement -> NgramsElement
228 ngramsElementFromRepo
237 NgramsElement { _ne_size = s
242 , _ne_ngrams = ngrams
243 , _ne_occurrences = panic $ "API.Ngrams._ne_occurrences"
245 -- Here we could use 0 if we want to avoid any `panic`.
246 -- It will not happen using getTableNgrams if
247 -- getOccByNgramsOnly provides a count of occurrences for
248 -- all the ngrams given.
252 ------------------------------------------------------------------------
253 newtype NgramsTable = NgramsTable [NgramsElement]
254 deriving (Ord, Eq, Generic, ToJSON, FromJSON, Show)
256 type ListNgrams = NgramsTable
258 makePrisms ''NgramsTable
260 -- | Question: why these repetition of Type in this instance
261 -- may you document it please ?
262 instance Each NgramsTable NgramsTable NgramsElement NgramsElement where
263 each = _NgramsTable . each
266 -- | TODO Check N and Weight
268 toNgramsElement :: [NgramsTableData] -> [NgramsElement]
269 toNgramsElement ns = map toNgramsElement' ns
271 toNgramsElement' (NgramsTableData _ p t _ lt w) = NgramsElement t lt' (round w) p' c'
275 Just x -> lookup x mapParent
276 c' = maybe mempty identity $ lookup t mapChildren
277 lt' = maybe (panic "API.Ngrams: listypeId") identity lt
279 mapParent :: Map Int Text
280 mapParent = Map.fromListWith (<>) $ map (\(NgramsTableData i _ t _ _ _) -> (i,t)) ns
282 mapChildren :: Map Text (Set Text)
283 mapChildren = Map.mapKeys (\i -> (maybe (panic "API.Ngrams.mapChildren: ParentId with no Terms: Impossible") identity $ lookup i mapParent))
284 $ Map.fromListWith (<>)
285 $ map (first fromJust)
286 $ filter (isJust . fst)
287 $ map (\(NgramsTableData _ p t _ _ _) -> (p, Set.singleton t)) ns
290 mockTable :: NgramsTable
291 mockTable = NgramsTable
292 [ mkNgramsElement "animal" GraphTerm Nothing (mSetFromList ["dog", "cat"])
293 , mkNgramsElement "cat" GraphTerm (rp "animal") mempty
294 , mkNgramsElement "cats" StopTerm Nothing mempty
295 , mkNgramsElement "dog" GraphTerm (rp "animal") (mSetFromList ["dogs"])
296 , mkNgramsElement "dogs" StopTerm (rp "dog") mempty
297 , mkNgramsElement "fox" GraphTerm Nothing mempty
298 , mkNgramsElement "object" CandidateTerm Nothing mempty
299 , mkNgramsElement "nothing" StopTerm Nothing mempty
300 , mkNgramsElement "organic" GraphTerm Nothing (mSetFromList ["flower"])
301 , mkNgramsElement "flower" GraphTerm (rp "organic") mempty
302 , mkNgramsElement "moon" CandidateTerm Nothing mempty
303 , mkNgramsElement "sky" StopTerm Nothing mempty
306 rp n = Just $ RootParent n n
308 instance Arbitrary NgramsTable where
309 arbitrary = pure mockTable
311 instance ToSchema NgramsTable
313 ------------------------------------------------------------------------
314 type NgramsTableMap = Map NgramsTerm NgramsRepoElement
316 ------------------------------------------------------------------------
317 -- On the Client side:
318 --data Action = InGroup NgramsId NgramsId
319 -- | OutGroup NgramsId NgramsId
320 -- | SetListType NgramsId ListType
322 data PatchSet a = PatchSet
326 deriving (Eq, Ord, Show, Generic)
328 makeLenses ''PatchSet
329 makePrisms ''PatchSet
331 instance ToJSON a => ToJSON (PatchSet a) where
332 toJSON = genericToJSON $ unPrefix "_"
333 toEncoding = genericToEncoding $ unPrefix "_"
335 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
336 parseJSON = genericParseJSON $ unPrefix "_"
339 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
340 arbitrary = PatchSet <$> arbitrary <*> arbitrary
342 type instance Patched (PatchSet a) = Set a
344 type ConflictResolutionPatchSet a = SimpleConflictResolution' (Set a)
345 type instance ConflictResolution (PatchSet a) = ConflictResolutionPatchSet a
347 instance Ord a => Semigroup (PatchSet a) where
348 p <> q = PatchSet { _rem = (q ^. rem) `Set.difference` (p ^. add) <> p ^. rem
349 , _add = (q ^. add) `Set.difference` (p ^. rem) <> p ^. add
352 instance Ord a => Monoid (PatchSet a) where
353 mempty = PatchSet mempty mempty
355 instance Ord a => Group (PatchSet a) where
356 invert (PatchSet r a) = PatchSet a r
358 instance Ord a => Composable (PatchSet a) where
359 composable _ _ = undefined
361 instance Ord a => Action (PatchSet a) (Set a) where
362 act p source = (source `Set.difference` (p ^. rem)) <> p ^. add
364 instance Applicable (PatchSet a) (Set a) where
365 applicable _ _ = mempty
367 instance Ord a => Validity (PatchSet a) where
368 validate p = check (Set.disjoint (p ^. rem) (p ^. add)) "_rem and _add should be dijoint"
370 instance Ord a => Transformable (PatchSet a) where
371 transformable = undefined
373 conflicts _p _q = undefined
375 transformWith conflict p q = undefined conflict p q
377 instance ToSchema a => ToSchema (PatchSet a)
380 type AddRem = Replace (Maybe ())
382 remPatch, addPatch :: AddRem
383 remPatch = replace (Just ()) Nothing
384 addPatch = replace Nothing (Just ())
386 isRem :: Replace (Maybe ()) -> Bool
387 isRem = (== remPatch)
389 type PatchMap = PM.PatchMap
391 newtype PatchMSet a = PatchMSet (PatchMap a AddRem)
392 deriving (Eq, Show, Generic, Validity, Semigroup, Monoid,
393 Transformable, Composable)
395 type ConflictResolutionPatchMSet a = a -> ConflictResolutionReplace (Maybe ())
396 type instance ConflictResolution (PatchMSet a) = ConflictResolutionPatchMSet a
398 -- TODO this breaks module abstraction
399 makePrisms ''PM.PatchMap
401 makePrisms ''PatchMSet
403 _PatchMSetIso :: Ord a => Iso' (PatchMSet a) (PatchSet a)
404 _PatchMSetIso = _PatchMSet . _PatchMap . iso f g . from _PatchSet
406 f :: Ord a => Map a (Replace (Maybe ())) -> (Set a, Set a)
407 f = Map.partition isRem >>> both %~ Map.keysSet
409 g :: Ord a => (Set a, Set a) -> Map a (Replace (Maybe ()))
410 g (rems, adds) = Map.fromSet (const remPatch) rems
411 <> Map.fromSet (const addPatch) adds
413 instance Ord a => Action (PatchMSet a) (MSet a) where
414 act (PatchMSet p) (MSet m) = MSet $ act p m
416 instance Ord a => Applicable (PatchMSet a) (MSet a) where
417 applicable (PatchMSet p) (MSet m) = applicable p m
419 instance (Ord a, ToJSON a) => ToJSON (PatchMSet a) where
420 toJSON = toJSON . view _PatchMSetIso
421 toEncoding = toEncoding . view _PatchMSetIso
423 instance (Ord a, FromJSON a) => FromJSON (PatchMSet a) where
424 parseJSON = fmap (_PatchMSetIso #) . parseJSON
426 instance (Ord a, Arbitrary a) => Arbitrary (PatchMSet a) where
427 arbitrary = (PatchMSet . PM.fromMap) <$> arbitrary
429 instance ToSchema a => ToSchema (PatchMSet a) where
431 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
433 type instance Patched (PatchMSet a) = MSet a
435 instance (Eq a, Arbitrary a) => Arbitrary (Replace a) where
436 arbitrary = uncurry replace <$> arbitrary
437 -- If they happen to be equal then the patch is Keep.
439 instance ToSchema a => ToSchema (Replace a) where
440 declareNamedSchema (_ :: proxy (Replace a)) = do
441 -- TODO Keep constructor is not supported here.
442 aSchema <- declareSchemaRef (Proxy :: Proxy a)
443 return $ NamedSchema (Just "Replace") $ mempty
444 & type_ .~ SwaggerObject
446 InsOrdHashMap.fromList
450 & required .~ [ "old", "new" ]
453 NgramsPatch { _patch_children :: PatchMSet NgramsTerm
454 , _patch_list :: Replace ListType -- TODO Map UserId ListType
456 deriving (Eq, Show, Generic)
458 deriveJSON (unPrefix "_") ''NgramsPatch
459 makeLenses ''NgramsPatch
461 instance ToSchema NgramsPatch
463 instance Arbitrary NgramsPatch where
464 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
466 type NgramsPatchIso = PairPatch (PatchMSet NgramsTerm) (Replace ListType)
468 _NgramsPatch :: Iso' NgramsPatch NgramsPatchIso
469 _NgramsPatch = iso (\(NgramsPatch c l) -> c :*: l) (\(c :*: l) -> NgramsPatch c l)
471 instance Semigroup NgramsPatch where
472 p <> q = _NgramsPatch # (p ^. _NgramsPatch <> q ^. _NgramsPatch)
474 instance Monoid NgramsPatch where
475 mempty = _NgramsPatch # mempty
477 instance Validity NgramsPatch where
478 validate p = p ^. _NgramsPatch . to validate
480 instance Transformable NgramsPatch where
481 transformable p q = transformable (p ^. _NgramsPatch) (q ^. _NgramsPatch)
483 conflicts p q = conflicts (p ^. _NgramsPatch) (q ^. _NgramsPatch)
485 transformWith conflict p q = (_NgramsPatch # p', _NgramsPatch # q')
487 (p', q') = transformWith conflict (p ^. _NgramsPatch) (q ^. _NgramsPatch)
489 type ConflictResolutionNgramsPatch =
490 ( ConflictResolutionPatchMSet NgramsTerm
491 , ConflictResolutionReplace ListType
493 type instance ConflictResolution NgramsPatch =
494 ConflictResolutionNgramsPatch
496 type PatchedNgramsPatch = (Set NgramsTerm, ListType)
497 -- ~ Patched NgramsPatchIso
498 type instance Patched NgramsPatch = PatchedNgramsPatch
500 instance Applicable NgramsPatch (Maybe NgramsRepoElement) where
501 applicable p Nothing = check (p == mempty) "NgramsPatch should be empty here"
502 applicable p (Just nre) =
503 applicable (p ^. patch_children) (nre ^. nre_children) <>
504 applicable (p ^. patch_list) (nre ^. nre_list)
506 instance Action NgramsPatch NgramsRepoElement where
507 act p = (nre_children %~ act (p ^. patch_children))
508 . (nre_list %~ act (p ^. patch_list))
510 instance Action NgramsPatch (Maybe NgramsRepoElement) where
513 newtype NgramsTablePatch = NgramsTablePatch (PatchMap NgramsTerm NgramsPatch)
514 deriving (Eq, Show, Generic, ToJSON, FromJSON, Semigroup, Monoid, Validity, Transformable)
516 instance FromField NgramsTablePatch
518 fromField = fromField'
520 instance FromField (PatchMap NgramsType (PatchMap NodeId NgramsTablePatch))
522 fromField = fromField'
524 --instance (Ord k, Action pv (Maybe v)) => Action (PatchMap k pv) (Map k v) where
526 type instance ConflictResolution NgramsTablePatch =
527 NgramsTerm -> ConflictResolutionNgramsPatch
529 type PatchedNgramsTablePatch = Map NgramsTerm PatchedNgramsPatch
530 -- ~ Patched (PatchMap NgramsTerm NgramsPatch)
531 type instance Patched NgramsTablePatch = PatchedNgramsTablePatch
533 makePrisms ''NgramsTablePatch
534 instance ToSchema (PatchMap NgramsTerm NgramsPatch)
535 instance ToSchema NgramsTablePatch
537 instance Applicable NgramsTablePatch (Maybe NgramsTableMap) where
538 applicable p = applicable (p ^. _NgramsTablePatch)
540 instance Action NgramsTablePatch (Maybe NgramsTableMap) where
542 fmap (execState (reParentNgramsTablePatch p)) .
543 act (p ^. _NgramsTablePatch)
545 instance Arbitrary NgramsTablePatch where
546 arbitrary = NgramsTablePatch <$> PM.fromMap <$> arbitrary
548 -- Should it be less than an Lens' to preserve PatchMap's abstraction.
549 -- ntp_ngrams_patches :: Lens' NgramsTablePatch (Map NgramsTerm NgramsPatch)
550 -- ntp_ngrams_patches = _NgramsTablePatch . undefined
552 type ReParent a = forall m. MonadState NgramsTableMap m => a -> m ()
554 reRootChildren :: NgramsTerm -> ReParent NgramsTerm
555 reRootChildren root ngram = do
556 nre <- use $ at ngram
557 forOf_ (_Just . nre_children . folded) nre $ \child -> do
558 at child . _Just . nre_root ?= root
559 reRootChildren root child
561 reParent :: Maybe RootParent -> ReParent NgramsTerm
562 reParent rp child = do
563 at child . _Just %= ( (nre_parent .~ (_rp_parent <$> rp))
564 . (nre_root .~ (_rp_root <$> rp))
566 reRootChildren (fromMaybe child (rp ^? _Just . rp_root)) child
568 reParentAddRem :: RootParent -> NgramsTerm -> ReParent AddRem
569 reParentAddRem rp child p =
570 reParent (if isRem p then Nothing else Just rp) child
572 reParentNgramsPatch :: NgramsTerm -> ReParent NgramsPatch
573 reParentNgramsPatch parent ngramsPatch = do
574 root_of_parent <- use (at parent . _Just . nre_root)
576 root = fromMaybe parent root_of_parent
577 rp = RootParent { _rp_root = root, _rp_parent = parent }
578 itraverse_ (reParentAddRem rp) (ngramsPatch ^. patch_children . _PatchMSet . _PatchMap)
579 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
581 reParentNgramsTablePatch :: ReParent NgramsTablePatch
582 reParentNgramsTablePatch p = itraverse_ reParentNgramsPatch (p ^. _NgramsTablePatch. _PatchMap)
583 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
585 ------------------------------------------------------------------------
586 ------------------------------------------------------------------------
589 data Versioned a = Versioned
590 { _v_version :: Version
593 deriving (Generic, Show)
594 deriveJSON (unPrefix "_v_") ''Versioned
595 makeLenses ''Versioned
596 instance ToSchema a => ToSchema (Versioned a)
597 instance Arbitrary a => Arbitrary (Versioned a) where
598 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
601 -- TODO sequencs of modifications (Patchs)
602 type NgramsIdPatch = Patch NgramsId NgramsPatch
604 ngramsPatch :: Int -> NgramsPatch
605 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopTerm)]) (Set.fromList [n]) Set.empty
607 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
608 toEdit n p = Edit n p
609 ngramsIdPatch :: Patch NgramsId NgramsPatch
610 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
611 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
612 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
615 -- applyPatchBack :: Patch -> IO Patch
616 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
618 ------------------------------------------------------------------------
619 ------------------------------------------------------------------------
620 ------------------------------------------------------------------------
623 -- TODO: Replace.old is ignored which means that if the current list
624 -- `GraphTerm` and that the patch is `Replace CandidateTerm StopTerm` then
625 -- the list is going to be `StopTerm` while it should keep `GraphTerm`.
626 -- However this should not happen in non conflicting situations.
627 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
628 mkListsUpdate nt patches =
629 [ (ngramsTypeId nt, ng, listTypeId lt)
630 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
631 , lt <- patch ^.. patch_list . new
634 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
637 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
638 mkChildrenGroups addOrRem nt patches =
639 [ (ngramsTypeId nt, parent, child)
640 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
641 , child <- patch ^.. patch_children . to addOrRem . folded
645 ngramsTypeFromTabType :: TabType -> NgramsType
646 ngramsTypeFromTabType tabType =
647 let lieu = "Garg.API.Ngrams: " :: Text in
649 Sources -> Ngrams.Sources
650 Authors -> Ngrams.Authors
651 Institutes -> Ngrams.Institutes
652 Terms -> Ngrams.NgramsTerms
653 _ -> panic $ lieu <> "No Ngrams for this tab"
654 -- TODO: This `panic` would disapear with custom NgramsType.
656 ------------------------------------------------------------------------
658 { _r_version :: Version
661 -- first patch in the list is the most recent
665 instance (FromJSON s, FromJSON p) => FromJSON (Repo s p) where
666 parseJSON = genericParseJSON $ unPrefix "_r_"
668 instance (ToJSON s, ToJSON p) => ToJSON (Repo s p) where
669 toJSON = genericToJSON $ unPrefix "_r_"
670 toEncoding = genericToEncoding $ unPrefix "_r_"
674 initRepo :: Monoid s => Repo s p
675 initRepo = Repo 1 mempty []
677 type NgramsRepo = Repo NgramsState NgramsStatePatch
678 type NgramsState = Map NgramsType (Map NodeId NgramsTableMap)
679 type NgramsStatePatch = PatchMap NgramsType (PatchMap NodeId NgramsTablePatch)
681 initMockRepo :: NgramsRepo
682 initMockRepo = Repo 1 s []
684 s = Map.singleton Ngrams.NgramsTerms
685 $ Map.singleton 47254
687 [ (n ^. ne_ngrams, ngramsElementToRepo n) | n <- mockTable ^. _NgramsTable ]
689 data RepoEnv = RepoEnv
690 { _renv_var :: !(MVar NgramsRepo)
691 , _renv_saver :: !(IO ())
692 , _renv_lock :: !FileLock
698 class HasRepoVar env where
699 repoVar :: Getter env (MVar NgramsRepo)
701 instance HasRepoVar (MVar NgramsRepo) where
704 class HasRepoSaver env where
705 repoSaver :: Getter env (IO ())
707 class (HasRepoVar env, HasRepoSaver env) => HasRepo env where
708 repoEnv :: Getter env RepoEnv
710 instance HasRepo RepoEnv where
713 instance HasRepoVar RepoEnv where
716 instance HasRepoSaver RepoEnv where
717 repoSaver = renv_saver
719 type RepoCmdM env err m =
725 ------------------------------------------------------------------------
727 saveRepo :: ( MonadReader env m, MonadIO m, HasRepoSaver env )
729 saveRepo = liftIO =<< view repoSaver
731 listTypeConflictResolution :: ListType -> ListType -> ListType
732 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
734 ngramsStatePatchConflictResolution
735 :: NgramsType -> NodeId -> NgramsTerm
736 -> ConflictResolutionNgramsPatch
737 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
739 -- undefined {- TODO think this through -}, listTypeConflictResolution)
742 -- Insertions are not considered as patches,
743 -- they do not extend history,
744 -- they do not bump version.
745 insertNewOnly :: a -> Maybe b -> a
746 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
747 -- TODO error handling
749 something :: Monoid a => Maybe a -> a
750 something Nothing = mempty
751 something (Just a) = a
754 -- TODO refactor with putListNgrams
755 copyListNgrams :: RepoCmdM env err m
756 => NodeId -> NodeId -> NgramsType
758 copyListNgrams srcListId dstListId ngramsType = do
760 liftIO $ modifyMVar_ var $
761 pure . (r_state . at ngramsType %~ (Just . f . something))
764 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
765 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
767 -- TODO refactor with putListNgrams
768 -- The list must be non-empty!
769 -- The added ngrams must be non-existent!
770 addListNgrams :: RepoCmdM env err m
771 => NodeId -> NgramsType
772 -> [NgramsElement] -> m ()
773 addListNgrams listId ngramsType nes = do
775 liftIO $ modifyMVar_ var $
776 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
779 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
782 -- If the given list of ngrams elements contains ngrams already in
783 -- the repo, they will be ignored.
784 putListNgrams :: RepoCmdM env err m
785 => NodeId -> NgramsType
786 -> [NgramsElement] -> m ()
787 putListNgrams _ _ [] = pure ()
788 putListNgrams listId ngramsType nes = do
789 -- printDebug "putListNgrams" (length nes)
791 liftIO $ modifyMVar_ var $
792 pure . (r_state . at ngramsType %~ (Just . (at listId %~ (Just . (<> m) . something)) . something))
795 m = Map.fromList $ (\n -> (n ^. ne_ngrams, ngramsElementToRepo n)) <$> nes
797 tableNgramsPost :: RepoCmdM env err m => TabType -> NodeId -> Maybe ListType -> [NgramsTerm] -> m ()
798 tableNgramsPost tabType listId mayList =
799 putListNgrams listId (ngramsTypeFromTabType tabType) . fmap (newNgramsElement mayList)
801 -- Apply the given patch to the DB and returns the patch to be applied on the
803 tableNgramsPut :: (HasInvalidError err, RepoCmdM env err m)
805 -> Versioned NgramsTablePatch
806 -> m (Versioned NgramsTablePatch)
807 tableNgramsPut tabType listId (Versioned p_version p_table)
808 | p_table == mempty = do
809 let ngramsType = ngramsTypeFromTabType tabType
812 r <- liftIO $ readMVar var
815 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
816 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
818 pure (Versioned (r ^. r_version) q_table)
821 let ngramsType = ngramsTypeFromTabType tabType
822 (p0, p0_validity) = PM.singleton listId p_table
823 (p, p_validity) = PM.singleton ngramsType p0
825 assertValid p0_validity
826 assertValid p_validity
829 vq' <- liftIO $ modifyMVar var $ \r -> do
831 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
832 (p', q') = transformWith ngramsStatePatchConflictResolution p q
833 r' = r & r_version +~ 1
835 & r_history %~ (p' :)
836 q'_table = q' ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
838 -- Ideally we would like to check these properties. However:
839 -- * They should be checked only to debug the code. The client data
840 -- should be able to trigger these.
841 -- * What kind of error should they throw (we are in IO here)?
842 -- * Should we keep modifyMVar?
843 -- * Should we throw the validation in an Exception, catch it around
844 -- modifyMVar and throw it back as an Error?
845 assertValid $ transformable p q
846 assertValid $ applicable p' (r ^. r_state)
848 pure (r', Versioned (r' ^. r_version) q'_table)
853 mergeNgramsElement :: NgramsRepoElement -> NgramsRepoElement -> NgramsRepoElement
854 mergeNgramsElement _neOld neNew = neNew
856 { _ne_list :: ListType
857 If we merge the parents/children we can potentially create cycles!
858 , _ne_parent :: Maybe NgramsTerm
859 , _ne_children :: MSet NgramsTerm
863 getNgramsTableMap :: RepoCmdM env err m
864 => NodeId -> NgramsType -> m (Versioned NgramsTableMap)
865 getNgramsTableMap nodeId ngramsType = do
867 repo <- liftIO $ readMVar v
868 pure $ Versioned (repo ^. r_version)
869 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
874 -- | TODO Errors management
875 -- TODO: polymorphic for Annuaire or Corpus or ...
876 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
877 -- TODO: should take only one ListId
882 getTableNgrams :: forall env err m.
883 (RepoCmdM env err m, HasNodeError err, HasConnection env)
884 => NodeType -> NodeId -> TabType
885 -> ListId -> Limit -> Maybe Offset
887 -> Maybe MinSize -> Maybe MaxSize
889 -> (NgramsTerm -> Bool)
890 -> m (Versioned NgramsTable)
891 getTableNgrams nType nId tabType listId limit_ offset
892 listType minSize maxSize orderBy searchQuery = do
894 lIds <- selectNodesWithUsername NodeList userMaster
896 ngramsType = ngramsTypeFromTabType tabType
897 offset' = maybe 0 identity offset
898 listType' = maybe (const True) (==) listType
899 minSize' = maybe (const True) (<=) minSize
900 maxSize' = maybe (const True) (>=) maxSize
902 selected_node n = minSize' s
904 && searchQuery (n ^. ne_ngrams)
905 && listType' (n ^. ne_list)
909 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
911 ---------------------------------------
912 sortOnOrder Nothing = identity
913 sortOnOrder (Just TermAsc) = List.sortOn $ view ne_ngrams
914 sortOnOrder (Just TermDesc) = List.sortOn $ Down . view ne_ngrams
915 sortOnOrder (Just ScoreAsc) = List.sortOn $ view ne_occurrences
916 sortOnOrder (Just ScoreDesc) = List.sortOn $ Down . view ne_occurrences
918 ---------------------------------------
919 selectAndPaginate :: Map NgramsTerm NgramsElement -> [NgramsElement]
920 selectAndPaginate tableMap = roots <> inners
922 list = tableMap ^.. each
923 rootOf ne = maybe ne (\r -> fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r))
925 selected_nodes = list & take limit_
927 . filter selected_node
928 . sortOnOrder orderBy
929 roots = rootOf <$> selected_nodes
930 rootsSet = Set.fromList (_ne_ngrams <$> roots)
931 inners = list & filter (selected_inner rootsSet)
933 ---------------------------------------
934 setScores :: forall t. Each t t NgramsElement NgramsElement => Bool -> t -> m t
935 setScores False table = pure table
936 setScores True table = do
937 let ngrams_terms = (table ^.. each . ne_ngrams)
938 occurrences <- getOccByNgramsOnlySlow nType nId
944 setOcc ne = ne & ne_occurrences .~ sumOf (at (ne ^. ne_ngrams) . _Just) occurrences
946 pure $ table & each %~ setOcc
947 ---------------------------------------
949 -- lists <- catMaybes <$> listsWith userMaster
950 -- trace (show lists) $
951 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
953 let nSco = needsScores orderBy
954 tableMap1 <- getNgramsTableMap listId ngramsType
955 tableMap2 <- tableMap1 & v_data %%~ setScores nSco
956 . Map.mapWithKey ngramsElementFromRepo
957 tableMap2 & v_data %%~ fmap NgramsTable
958 . setScores (not nSco)
963 -- TODO: find a better place for the code above, All APIs stay here
964 type QueryParamR = QueryParam' '[Required, Strict]
967 data OrderBy = TermAsc | TermDesc | ScoreAsc | ScoreDesc
968 deriving (Generic, Enum, Bounded, Read, Show)
970 instance FromHttpApiData OrderBy
972 parseUrlPiece "TermAsc" = pure TermAsc
973 parseUrlPiece "TermDesc" = pure TermDesc
974 parseUrlPiece "ScoreAsc" = pure ScoreAsc
975 parseUrlPiece "ScoreDesc" = pure ScoreDesc
976 parseUrlPiece _ = Left "Unexpected value of OrderBy"
978 instance ToParamSchema OrderBy
979 instance FromJSON OrderBy
980 instance ToJSON OrderBy
981 instance ToSchema OrderBy
982 instance Arbitrary OrderBy
984 arbitrary = elements [minBound..maxBound]
986 needsScores :: Maybe OrderBy -> Bool
987 needsScores (Just ScoreAsc) = True
988 needsScores (Just ScoreDesc) = True
989 needsScores _ = False
991 type TableNgramsApiGet = Summary " Table Ngrams API Get"
992 :> QueryParamR "ngramsType" TabType
993 :> QueryParamR "list" ListId
994 :> QueryParamR "limit" Limit
995 :> QueryParam "offset" Offset
996 :> QueryParam "listType" ListType
997 :> QueryParam "minTermSize" MinSize
998 :> QueryParam "maxTermSize" MaxSize
999 :> QueryParam "orderBy" OrderBy
1000 :> QueryParam "search" Text
1001 :> Get '[JSON] (Versioned NgramsTable)
1003 type TableNgramsApiPut = Summary " Table Ngrams API Change"
1004 :> QueryParamR "ngramsType" TabType
1005 :> QueryParamR "list" ListId
1006 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
1007 :> Put '[JSON] (Versioned NgramsTablePatch)
1009 type TableNgramsApiPost = Summary " Table Ngrams API Adds new ngrams"
1010 :> QueryParamR "ngramsType" TabType
1011 :> QueryParamR "list" ListId
1012 :> QueryParam "listType" ListType
1013 :> ReqBody '[JSON] [NgramsTerm]
1016 type TableNgramsApi = TableNgramsApiGet
1017 :<|> TableNgramsApiPut
1018 :<|> TableNgramsApiPost
1020 getTableNgramsCorpus :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
1021 => NodeId -> TabType
1022 -> ListId -> Limit -> Maybe Offset
1024 -> Maybe MinSize -> Maybe MaxSize
1026 -> Maybe Text -- full text search
1027 -> m (Versioned NgramsTable)
1028 getTableNgramsCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy mt =
1029 getTableNgrams NodeCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1031 searchQuery = maybe (const True) isInfixOf mt
1033 -- | Text search is deactivated for now for ngrams by doc only
1034 getTableNgramsDoc :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
1036 -> ListId -> Limit -> Maybe Offset
1038 -> Maybe MinSize -> Maybe MaxSize
1040 -> Maybe Text -- full text search
1041 -> m (Versioned NgramsTable)
1042 getTableNgramsDoc dId tabType listId limit_ offset listType minSize maxSize orderBy _mt = do
1043 ns <- selectNodesWithUsername NodeList userMaster
1044 let ngramsType = ngramsTypeFromTabType tabType
1045 ngs <- selectNgramsByDoc (ns <> [listId]) dId ngramsType
1046 let searchQuery = flip S.member (S.fromList ngs)
1047 getTableNgrams NodeDocument dId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1053 apiNgramsTableCorpus :: ( RepoCmdM env err m
1055 , HasInvalidError err
1058 => NodeId -> ServerT TableNgramsApi m
1059 apiNgramsTableCorpus cId = getTableNgramsCorpus cId
1061 :<|> tableNgramsPost
1064 apiNgramsTableDoc :: ( RepoCmdM env err m
1066 , HasInvalidError err
1069 => DocId -> ServerT TableNgramsApi m
1070 apiNgramsTableDoc dId = getTableNgramsDoc dId
1072 :<|> tableNgramsPost
1073 -- > add new ngrams in database (TODO AD)
1074 -- > index all the corpus accordingly (TODO AD)