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
99 ------------------------------------------------------------------------
100 --data FacetFormat = Table | Chart
101 data TabType = Docs | Terms | Sources | Authors | Institutes | Trash
103 deriving (Generic, Enum, Bounded)
105 instance FromHttpApiData TabType
107 parseUrlPiece "Docs" = pure Docs
108 parseUrlPiece "Terms" = pure Terms
109 parseUrlPiece "Sources" = pure Sources
110 parseUrlPiece "Institutes" = pure Institutes
111 parseUrlPiece "Authors" = pure Authors
112 parseUrlPiece "Trash" = pure Trash
114 parseUrlPiece "Contacts" = pure Contacts
116 parseUrlPiece _ = Left "Unexpected value of TabType"
118 instance ToParamSchema TabType
119 instance ToJSON TabType
120 instance FromJSON TabType
121 instance ToSchema TabType
122 instance Arbitrary TabType
124 arbitrary = elements [minBound .. maxBound]
126 newtype MSet a = MSet (Map a ())
127 deriving (Eq, Ord, Show, Generic, Arbitrary, Semigroup, Monoid)
129 instance ToJSON a => ToJSON (MSet a) where
130 toJSON (MSet m) = toJSON (Map.keys m)
131 toEncoding (MSet m) = toEncoding (Map.keys m)
133 mSetFromSet :: Set a -> MSet a
134 mSetFromSet = MSet . Map.fromSet (const ())
136 mSetFromList :: Ord a => [a] -> MSet a
137 mSetFromList = MSet . Map.fromList . map (\x -> (x, ()))
139 -- mSetToSet :: Ord a => MSet a -> Set a
140 -- mSetToSet (MSet a) = Set.fromList ( Map.keys a)
141 mSetToSet :: Ord a => MSet a -> Set a
142 mSetToSet = Set.fromList . mSetToList
144 mSetToList :: MSet a -> [a]
145 mSetToList (MSet a) = Map.keys a
147 instance Foldable MSet where
148 foldMap f (MSet m) = Map.foldMapWithKey (\k _ -> f k) m
150 instance (Ord a, FromJSON a) => FromJSON (MSet a) where
151 parseJSON = fmap mSetFromList . parseJSON
153 instance (ToJSONKey a, ToSchema a) => ToSchema (MSet a) where
155 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
157 ------------------------------------------------------------------------
158 type NgramsTerm = Text
160 data RootParent = RootParent
161 { _rp_root :: NgramsTerm
162 , _rp_parent :: NgramsTerm
164 deriving (Ord, Eq, Show, Generic)
166 deriveJSON (unPrefix "_rp_") ''RootParent
167 makeLenses ''RootParent
169 data NgramsRepoElement = NgramsRepoElement
171 , _nre_list :: ListType
172 --, _nre_root_parent :: Maybe RootParent
173 , _nre_root :: Maybe NgramsTerm
174 , _nre_parent :: Maybe NgramsTerm
175 , _nre_children :: MSet NgramsTerm
177 deriving (Ord, Eq, Show, Generic)
179 deriveJSON (unPrefix "_nre_") ''NgramsRepoElement
180 makeLenses ''NgramsRepoElement
183 NgramsElement { _ne_ngrams :: NgramsTerm
185 , _ne_list :: ListType
186 , _ne_occurrences :: Int
187 , _ne_root :: Maybe NgramsTerm
188 , _ne_parent :: Maybe NgramsTerm
189 , _ne_children :: MSet NgramsTerm
191 deriving (Ord, Eq, Show, Generic)
193 deriveJSON (unPrefix "_ne_") ''NgramsElement
194 makeLenses ''NgramsElement
196 mkNgramsElement :: NgramsTerm -> ListType -> Maybe RootParent -> MSet NgramsTerm -> NgramsElement
197 mkNgramsElement ngrams list rp children =
198 NgramsElement ngrams size list 1 (_rp_root <$> rp) (_rp_parent <$> rp) children
201 size = 1 + count " " ngrams
203 newNgramsElement :: NgramsTerm -> NgramsElement
204 newNgramsElement ngrams = mkNgramsElement ngrams GraphTerm Nothing mempty
206 instance ToSchema NgramsElement
207 instance Arbitrary NgramsElement where
208 arbitrary = elements [newNgramsElement "sport"]
210 ngramsElementToRepo :: NgramsElement -> NgramsRepoElement
212 (NgramsElement { _ne_size = s
226 ngramsElementFromRepo :: NgramsTerm -> NgramsRepoElement -> NgramsElement
227 ngramsElementFromRepo
236 NgramsElement { _ne_size = s
241 , _ne_ngrams = ngrams
242 , _ne_occurrences = panic $ "API.Ngrams._ne_occurrences"
244 -- Here we could use 0 if we want to avoid any `panic`.
245 -- It will not happen using getTableNgrams if
246 -- getOccByNgramsOnly provides a count of occurrences for
247 -- all the ngrams given.
251 ------------------------------------------------------------------------
252 newtype NgramsTable = NgramsTable [NgramsElement]
253 deriving (Ord, Eq, Generic, ToJSON, FromJSON, Show)
255 type ListNgrams = NgramsTable
257 makePrisms ''NgramsTable
259 -- | Question: why these repetition of Type in this instance
260 -- may you document it please ?
261 instance Each NgramsTable NgramsTable NgramsElement NgramsElement where
262 each = _NgramsTable . each
265 -- | TODO Check N and Weight
267 toNgramsElement :: [NgramsTableData] -> [NgramsElement]
268 toNgramsElement ns = map toNgramsElement' ns
270 toNgramsElement' (NgramsTableData _ p t _ lt w) = NgramsElement t lt' (round w) p' c'
274 Just x -> lookup x mapParent
275 c' = maybe mempty identity $ lookup t mapChildren
276 lt' = maybe (panic "API.Ngrams: listypeId") identity lt
278 mapParent :: Map Int Text
279 mapParent = Map.fromListWith (<>) $ map (\(NgramsTableData i _ t _ _ _) -> (i,t)) ns
281 mapChildren :: Map Text (Set Text)
282 mapChildren = Map.mapKeys (\i -> (maybe (panic "API.Ngrams.mapChildren: ParentId with no Terms: Impossible") identity $ lookup i mapParent))
283 $ Map.fromListWith (<>)
284 $ map (first fromJust)
285 $ filter (isJust . fst)
286 $ map (\(NgramsTableData _ p t _ _ _) -> (p, Set.singleton t)) ns
289 mockTable :: NgramsTable
290 mockTable = NgramsTable
291 [ mkNgramsElement "animal" GraphTerm Nothing (mSetFromList ["dog", "cat"])
292 , mkNgramsElement "cat" GraphTerm (rp "animal") mempty
293 , mkNgramsElement "cats" StopTerm Nothing mempty
294 , mkNgramsElement "dog" GraphTerm (rp "animal") (mSetFromList ["dogs"])
295 , mkNgramsElement "dogs" StopTerm (rp "dog") mempty
296 , mkNgramsElement "fox" GraphTerm Nothing mempty
297 , mkNgramsElement "object" CandidateTerm Nothing mempty
298 , mkNgramsElement "nothing" StopTerm Nothing mempty
299 , mkNgramsElement "organic" GraphTerm Nothing (mSetFromList ["flower"])
300 , mkNgramsElement "flower" GraphTerm (rp "organic") mempty
301 , mkNgramsElement "moon" CandidateTerm Nothing mempty
302 , mkNgramsElement "sky" StopTerm Nothing mempty
305 rp n = Just $ RootParent n n
307 instance Arbitrary NgramsTable where
308 arbitrary = pure mockTable
310 instance ToSchema NgramsTable
312 ------------------------------------------------------------------------
313 type NgramsTableMap = Map NgramsTerm NgramsRepoElement
315 ------------------------------------------------------------------------
316 -- On the Client side:
317 --data Action = InGroup NgramsId NgramsId
318 -- | OutGroup NgramsId NgramsId
319 -- | SetListType NgramsId ListType
321 data PatchSet a = PatchSet
325 deriving (Eq, Ord, Show, Generic)
327 makeLenses ''PatchSet
328 makePrisms ''PatchSet
330 instance ToJSON a => ToJSON (PatchSet a) where
331 toJSON = genericToJSON $ unPrefix "_"
332 toEncoding = genericToEncoding $ unPrefix "_"
334 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
335 parseJSON = genericParseJSON $ unPrefix "_"
338 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
339 arbitrary = PatchSet <$> arbitrary <*> arbitrary
341 type instance Patched (PatchSet a) = Set a
343 type ConflictResolutionPatchSet a = SimpleConflictResolution' (Set a)
344 type instance ConflictResolution (PatchSet a) = ConflictResolutionPatchSet a
346 instance Ord a => Semigroup (PatchSet a) where
347 p <> q = PatchSet { _rem = (q ^. rem) `Set.difference` (p ^. add) <> p ^. rem
348 , _add = (q ^. add) `Set.difference` (p ^. rem) <> p ^. add
351 instance Ord a => Monoid (PatchSet a) where
352 mempty = PatchSet mempty mempty
354 instance Ord a => Group (PatchSet a) where
355 invert (PatchSet r a) = PatchSet a r
357 instance Ord a => Composable (PatchSet a) where
358 composable _ _ = undefined
360 instance Ord a => Action (PatchSet a) (Set a) where
361 act p source = (source `Set.difference` (p ^. rem)) <> p ^. add
363 instance Applicable (PatchSet a) (Set a) where
364 applicable _ _ = mempty
366 instance Ord a => Validity (PatchSet a) where
367 validate p = check (Set.disjoint (p ^. rem) (p ^. add)) "_rem and _add should be dijoint"
369 instance Ord a => Transformable (PatchSet a) where
370 transformable = undefined
372 conflicts _p _q = undefined
374 transformWith conflict p q = undefined conflict p q
376 instance ToSchema a => ToSchema (PatchSet a)
379 type AddRem = Replace (Maybe ())
381 remPatch, addPatch :: AddRem
382 remPatch = replace (Just ()) Nothing
383 addPatch = replace Nothing (Just ())
385 isRem :: Replace (Maybe ()) -> Bool
386 isRem = (== remPatch)
388 type PatchMap = PM.PatchMap
390 newtype PatchMSet a = PatchMSet (PatchMap a AddRem)
391 deriving (Eq, Show, Generic, Validity, Semigroup, Monoid,
392 Transformable, Composable)
394 type ConflictResolutionPatchMSet a = a -> ConflictResolutionReplace (Maybe ())
395 type instance ConflictResolution (PatchMSet a) = ConflictResolutionPatchMSet a
397 -- TODO this breaks module abstraction
398 makePrisms ''PM.PatchMap
400 makePrisms ''PatchMSet
402 _PatchMSetIso :: Ord a => Iso' (PatchMSet a) (PatchSet a)
403 _PatchMSetIso = _PatchMSet . _PatchMap . iso f g . from _PatchSet
405 f :: Ord a => Map a (Replace (Maybe ())) -> (Set a, Set a)
406 f = Map.partition isRem >>> both %~ Map.keysSet
408 g :: Ord a => (Set a, Set a) -> Map a (Replace (Maybe ()))
409 g (rems, adds) = Map.fromSet (const remPatch) rems
410 <> Map.fromSet (const addPatch) adds
412 instance Ord a => Action (PatchMSet a) (MSet a) where
413 act (PatchMSet p) (MSet m) = MSet $ act p m
415 instance Ord a => Applicable (PatchMSet a) (MSet a) where
416 applicable (PatchMSet p) (MSet m) = applicable p m
418 instance (Ord a, ToJSON a) => ToJSON (PatchMSet a) where
419 toJSON = toJSON . view _PatchMSetIso
420 toEncoding = toEncoding . view _PatchMSetIso
422 instance (Ord a, FromJSON a) => FromJSON (PatchMSet a) where
423 parseJSON = fmap (_PatchMSetIso #) . parseJSON
425 instance (Ord a, Arbitrary a) => Arbitrary (PatchMSet a) where
426 arbitrary = (PatchMSet . PM.fromMap) <$> arbitrary
428 instance ToSchema a => ToSchema (PatchMSet a) where
430 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
432 type instance Patched (PatchMSet a) = MSet a
434 instance (Eq a, Arbitrary a) => Arbitrary (Replace a) where
435 arbitrary = uncurry replace <$> arbitrary
436 -- If they happen to be equal then the patch is Keep.
438 instance ToSchema a => ToSchema (Replace a) where
439 declareNamedSchema (_ :: proxy (Replace a)) = do
440 -- TODO Keep constructor is not supported here.
441 aSchema <- declareSchemaRef (Proxy :: Proxy a)
442 return $ NamedSchema (Just "Replace") $ mempty
443 & type_ .~ SwaggerObject
445 InsOrdHashMap.fromList
449 & required .~ [ "old", "new" ]
452 NgramsPatch { _patch_children :: PatchMSet NgramsTerm
453 , _patch_list :: Replace ListType -- TODO Map UserId ListType
455 deriving (Eq, Show, Generic)
457 deriveJSON (unPrefix "_") ''NgramsPatch
458 makeLenses ''NgramsPatch
460 instance ToSchema NgramsPatch
462 instance Arbitrary NgramsPatch where
463 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
465 type NgramsPatchIso = PairPatch (PatchMSet NgramsTerm) (Replace ListType)
467 _NgramsPatch :: Iso' NgramsPatch NgramsPatchIso
468 _NgramsPatch = iso (\(NgramsPatch c l) -> c :*: l) (\(c :*: l) -> NgramsPatch c l)
470 instance Semigroup NgramsPatch where
471 p <> q = _NgramsPatch # (p ^. _NgramsPatch <> q ^. _NgramsPatch)
473 instance Monoid NgramsPatch where
474 mempty = _NgramsPatch # mempty
476 instance Validity NgramsPatch where
477 validate p = p ^. _NgramsPatch . to validate
479 instance Transformable NgramsPatch where
480 transformable p q = transformable (p ^. _NgramsPatch) (q ^. _NgramsPatch)
482 conflicts p q = conflicts (p ^. _NgramsPatch) (q ^. _NgramsPatch)
484 transformWith conflict p q = (_NgramsPatch # p', _NgramsPatch # q')
486 (p', q') = transformWith conflict (p ^. _NgramsPatch) (q ^. _NgramsPatch)
488 type ConflictResolutionNgramsPatch =
489 ( ConflictResolutionPatchMSet NgramsTerm
490 , ConflictResolutionReplace ListType
492 type instance ConflictResolution NgramsPatch =
493 ConflictResolutionNgramsPatch
495 type PatchedNgramsPatch = (Set NgramsTerm, ListType)
496 -- ~ Patched NgramsPatchIso
497 type instance Patched NgramsPatch = PatchedNgramsPatch
499 instance Applicable NgramsPatch (Maybe NgramsRepoElement) where
500 applicable p Nothing = check (p == mempty) "NgramsPatch should be empty here"
501 applicable p (Just nre) =
502 applicable (p ^. patch_children) (nre ^. nre_children) <>
503 applicable (p ^. patch_list) (nre ^. nre_list)
505 instance Action NgramsPatch NgramsRepoElement where
506 act p = (nre_children %~ act (p ^. patch_children))
507 . (nre_list %~ act (p ^. patch_list))
509 instance Action NgramsPatch (Maybe NgramsRepoElement) where
512 newtype NgramsTablePatch = NgramsTablePatch (PatchMap NgramsTerm NgramsPatch)
513 deriving (Eq, Show, Generic, ToJSON, FromJSON, Semigroup, Monoid, Validity, Transformable)
515 instance FromField NgramsTablePatch
517 fromField = fromField'
519 instance FromField (PatchMap NgramsType (PatchMap NodeId NgramsTablePatch))
521 fromField = fromField'
523 --instance (Ord k, Action pv (Maybe v)) => Action (PatchMap k pv) (Map k v) where
525 type instance ConflictResolution NgramsTablePatch =
526 NgramsTerm -> ConflictResolutionNgramsPatch
528 type PatchedNgramsTablePatch = Map NgramsTerm PatchedNgramsPatch
529 -- ~ Patched (PatchMap NgramsTerm NgramsPatch)
530 type instance Patched NgramsTablePatch = PatchedNgramsTablePatch
532 makePrisms ''NgramsTablePatch
533 instance ToSchema (PatchMap NgramsTerm NgramsPatch)
534 instance ToSchema NgramsTablePatch
536 instance Applicable NgramsTablePatch (Maybe NgramsTableMap) where
537 applicable p = applicable (p ^. _NgramsTablePatch)
539 instance Action NgramsTablePatch (Maybe NgramsTableMap) where
541 fmap (execState (reParentNgramsTablePatch p)) .
542 act (p ^. _NgramsTablePatch)
544 instance Arbitrary NgramsTablePatch where
545 arbitrary = NgramsTablePatch <$> PM.fromMap <$> arbitrary
547 -- Should it be less than an Lens' to preserve PatchMap's abstraction.
548 -- ntp_ngrams_patches :: Lens' NgramsTablePatch (Map NgramsTerm NgramsPatch)
549 -- ntp_ngrams_patches = _NgramsTablePatch . undefined
551 type ReParent a = forall m. MonadState NgramsTableMap m => a -> m ()
553 reRootChildren :: NgramsTerm -> ReParent NgramsTerm
554 reRootChildren root ngram = do
555 nre <- use $ at ngram
556 forOf_ (_Just . nre_children . folded) nre $ \child -> do
557 at child . _Just . nre_root ?= root
558 reRootChildren root child
560 reParent :: Maybe RootParent -> ReParent NgramsTerm
561 reParent rp child = do
562 at child . _Just %= ( (nre_parent .~ (_rp_parent <$> rp))
563 . (nre_root .~ (_rp_root <$> rp))
565 reRootChildren (fromMaybe child (rp ^? _Just . rp_root)) child
567 reParentAddRem :: RootParent -> NgramsTerm -> ReParent AddRem
568 reParentAddRem rp child p =
569 reParent (if isRem p then Nothing else Just rp) child
571 reParentNgramsPatch :: NgramsTerm -> ReParent NgramsPatch
572 reParentNgramsPatch parent ngramsPatch = do
573 root_of_parent <- use (at parent . _Just . nre_root)
575 root = fromMaybe parent root_of_parent
576 rp = RootParent { _rp_root = root, _rp_parent = parent }
577 itraverse_ (reParentAddRem rp) (ngramsPatch ^. patch_children . _PatchMSet . _PatchMap)
578 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
580 reParentNgramsTablePatch :: ReParent NgramsTablePatch
581 reParentNgramsTablePatch p = itraverse_ reParentNgramsPatch (p ^. _NgramsTablePatch. _PatchMap)
582 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
584 ------------------------------------------------------------------------
585 ------------------------------------------------------------------------
588 data Versioned a = Versioned
589 { _v_version :: Version
592 deriving (Generic, Show)
593 deriveJSON (unPrefix "_v_") ''Versioned
594 makeLenses ''Versioned
595 instance ToSchema a => ToSchema (Versioned a)
596 instance Arbitrary a => Arbitrary (Versioned a) where
597 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
600 -- TODO sequencs of modifications (Patchs)
601 type NgramsIdPatch = Patch NgramsId NgramsPatch
603 ngramsPatch :: Int -> NgramsPatch
604 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopTerm)]) (Set.fromList [n]) Set.empty
606 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
607 toEdit n p = Edit n p
608 ngramsIdPatch :: Patch NgramsId NgramsPatch
609 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
610 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
611 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
614 -- applyPatchBack :: Patch -> IO Patch
615 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
617 ------------------------------------------------------------------------
618 ------------------------------------------------------------------------
619 ------------------------------------------------------------------------
622 -- TODO: Replace.old is ignored which means that if the current list
623 -- `GraphTerm` and that the patch is `Replace CandidateTerm StopTerm` then
624 -- the list is going to be `StopTerm` while it should keep `GraphTerm`.
625 -- However this should not happen in non conflicting situations.
626 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
627 mkListsUpdate nt patches =
628 [ (ngramsTypeId nt, ng, listTypeId lt)
629 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
630 , lt <- patch ^.. patch_list . new
633 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
636 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
637 mkChildrenGroups addOrRem nt patches =
638 [ (ngramsTypeId nt, parent, child)
639 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
640 , child <- patch ^.. patch_children . to addOrRem . folded
644 ngramsTypeFromTabType :: TabType -> NgramsType
645 ngramsTypeFromTabType tabType =
646 let lieu = "Garg.API.Ngrams: " :: Text in
648 Sources -> Ngrams.Sources
649 Authors -> Ngrams.Authors
650 Institutes -> Ngrams.Institutes
651 Terms -> Ngrams.NgramsTerms
652 _ -> panic $ lieu <> "No Ngrams for this tab"
653 -- TODO: This `panic` would disapear with custom NgramsType.
655 ------------------------------------------------------------------------
657 { _r_version :: Version
660 -- first patch in the list is the most recent
664 instance (FromJSON s, FromJSON p) => FromJSON (Repo s p) where
665 parseJSON = genericParseJSON $ unPrefix "_r_"
667 instance (ToJSON s, ToJSON p) => ToJSON (Repo s p) where
668 toJSON = genericToJSON $ unPrefix "_r_"
669 toEncoding = genericToEncoding $ unPrefix "_r_"
673 initRepo :: Monoid s => Repo s p
674 initRepo = Repo 1 mempty []
676 type NgramsRepo = Repo NgramsState NgramsStatePatch
677 type NgramsState = Map NgramsType (Map NodeId NgramsTableMap)
678 type NgramsStatePatch = PatchMap NgramsType (PatchMap NodeId NgramsTablePatch)
680 initMockRepo :: NgramsRepo
681 initMockRepo = Repo 1 s []
683 s = Map.singleton Ngrams.NgramsTerms
684 $ Map.singleton 47254
686 [ (n ^. ne_ngrams, ngramsElementToRepo n) | n <- mockTable ^. _NgramsTable ]
688 data RepoEnv = RepoEnv
689 { _renv_var :: !(MVar NgramsRepo)
690 , _renv_saver :: !(IO ())
691 , _renv_lock :: !FileLock
697 class HasRepoVar env where
698 repoVar :: Getter env (MVar NgramsRepo)
700 instance HasRepoVar (MVar NgramsRepo) where
703 class HasRepoSaver env where
704 repoSaver :: Getter env (IO ())
706 class (HasRepoVar env, HasRepoSaver env) => HasRepo env where
707 repoEnv :: Getter env RepoEnv
709 instance HasRepo RepoEnv where
712 instance HasRepoVar RepoEnv where
715 instance HasRepoSaver RepoEnv where
716 repoSaver = renv_saver
718 type RepoCmdM env err m =
724 ------------------------------------------------------------------------
726 saveRepo :: ( MonadReader env m, MonadIO m, HasRepoSaver env )
728 saveRepo = liftIO =<< view repoSaver
730 listTypeConflictResolution :: ListType -> ListType -> ListType
731 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
733 ngramsStatePatchConflictResolution
734 :: NgramsType -> NodeId -> NgramsTerm
735 -> ConflictResolutionNgramsPatch
736 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
738 -- undefined {- TODO think this through -}, listTypeConflictResolution)
741 -- Insertions are not considered as patches,
742 -- they do not extend history,
743 -- they do not bump version.
744 insertNewOnly :: a -> Maybe b -> a
745 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
746 -- TODO error handling
748 something :: Monoid a => Maybe a -> a
749 something Nothing = mempty
750 something (Just a) = a
753 -- TODO refactor with putListNgrams
754 copyListNgrams :: RepoCmdM env err m
755 => NodeId -> NodeId -> NgramsType
757 copyListNgrams srcListId dstListId ngramsType = do
759 liftIO $ modifyMVar_ var $
760 pure . (r_state . at ngramsType %~ (Just . f . something))
763 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
764 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
766 -- TODO refactor with putListNgrams
767 -- The list must be non-empty!
768 -- The added ngrams must be non-existent!
769 addListNgrams :: RepoCmdM env err m
770 => NodeId -> NgramsType
771 -> [NgramsElement] -> m ()
772 addListNgrams listId ngramsType nes = do
774 liftIO $ modifyMVar_ var $
775 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
778 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
781 -- If the given list of ngrams elements contains ngrams already in
782 -- the repo, they will be ignored.
783 putListNgrams :: RepoCmdM env err m
784 => NodeId -> NgramsType
785 -> [NgramsElement] -> m ()
786 putListNgrams _ _ [] = pure ()
787 putListNgrams listId ngramsType nes = do
788 -- printDebug "putListNgrams" (length nes)
790 liftIO $ modifyMVar_ var $
791 pure . (r_state . at ngramsType %~ (Just . (at listId %~ (Just . (<> m) . something)) . something))
794 m = Map.fromList $ (\n -> (n ^. ne_ngrams, ngramsElementToRepo n)) <$> nes
796 tableNgramsPost :: RepoCmdM env err m => TabType -> NodeId -> [NgramsTerm] -> m ()
797 tableNgramsPost tabType listId =
798 putListNgrams listId (ngramsTypeFromTabType tabType) . fmap newNgramsElement
800 -- Apply the given patch to the DB and returns the patch to be applied on the
802 tableNgramsPut :: (HasInvalidError err, RepoCmdM env err m)
804 -> Versioned NgramsTablePatch
805 -> m (Versioned NgramsTablePatch)
806 tableNgramsPut tabType listId (Versioned p_version p_table)
807 | p_table == mempty = do
808 let ngramsType = ngramsTypeFromTabType tabType
811 r <- liftIO $ readMVar var
814 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
815 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
817 pure (Versioned (r ^. r_version) q_table)
820 let ngramsType = ngramsTypeFromTabType tabType
821 (p0, p0_validity) = PM.singleton listId p_table
822 (p, p_validity) = PM.singleton ngramsType p0
824 assertValid p0_validity
825 assertValid p_validity
828 vq' <- liftIO $ modifyMVar var $ \r -> do
830 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
831 (p', q') = transformWith ngramsStatePatchConflictResolution p q
832 r' = r & r_version +~ 1
834 & r_history %~ (p' :)
835 q'_table = q' ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
837 -- Ideally we would like to check these properties. However:
838 -- * They should be checked only to debug the code. The client data
839 -- should be able to trigger these.
840 -- * What kind of error should they throw (we are in IO here)?
841 -- * Should we keep modifyMVar?
842 -- * Should we throw the validation in an Exception, catch it around
843 -- modifyMVar and throw it back as an Error?
844 assertValid $ transformable p q
845 assertValid $ applicable p' (r ^. r_state)
847 pure (r', Versioned (r' ^. r_version) q'_table)
852 mergeNgramsElement :: NgramsRepoElement -> NgramsRepoElement -> NgramsRepoElement
853 mergeNgramsElement _neOld neNew = neNew
855 { _ne_list :: ListType
856 If we merge the parents/children we can potentially create cycles!
857 , _ne_parent :: Maybe NgramsTerm
858 , _ne_children :: MSet NgramsTerm
862 getNgramsTableMap :: RepoCmdM env err m
863 => NodeId -> NgramsType -> m (Versioned NgramsTableMap)
864 getNgramsTableMap nodeId ngramsType = do
866 repo <- liftIO $ readMVar v
867 pure $ Versioned (repo ^. r_version)
868 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
873 -- | TODO Errors management
874 -- TODO: polymorphic for Annuaire or Corpus or ...
875 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
876 -- TODO: should take only one ListId
881 getTableNgrams :: forall env err m.
882 (RepoCmdM env err m, HasNodeError err, HasConnection env)
883 => NodeType -> NodeId -> TabType
884 -> ListId -> Limit -> Maybe Offset
886 -> Maybe MinSize -> Maybe MaxSize
888 -> (NgramsTerm -> Bool)
889 -> m (Versioned NgramsTable)
890 getTableNgrams nType nId tabType listId limit_ offset
891 listType minSize maxSize orderBy searchQuery = do
893 lIds <- selectNodesWithUsername NodeList userMaster
895 ngramsType = ngramsTypeFromTabType tabType
896 offset' = maybe 0 identity offset
897 listType' = maybe (const True) (==) listType
898 minSize' = maybe (const True) (<=) minSize
899 maxSize' = maybe (const True) (>=) maxSize
901 selected_node n = minSize' s
903 && searchQuery (n ^. ne_ngrams)
904 && listType' (n ^. ne_list)
908 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
910 ---------------------------------------
911 sortOnOrder Nothing = identity
912 sortOnOrder (Just TermAsc) = List.sortOn $ view ne_ngrams
913 sortOnOrder (Just TermDesc) = List.sortOn $ Down . view ne_ngrams
914 sortOnOrder (Just ScoreAsc) = List.sortOn $ view ne_occurrences
915 sortOnOrder (Just ScoreDesc) = List.sortOn $ Down . view ne_occurrences
917 ---------------------------------------
918 selectAndPaginate :: Map NgramsTerm NgramsElement -> [NgramsElement]
919 selectAndPaginate tableMap = roots <> inners
921 list = tableMap ^.. each
922 rootOf ne = maybe ne (\r -> fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r))
924 selected_nodes = list & take limit_
926 . filter selected_node
927 . sortOnOrder orderBy
928 roots = rootOf <$> selected_nodes
929 rootsSet = Set.fromList (_ne_ngrams <$> roots)
930 inners = list & filter (selected_inner rootsSet)
932 ---------------------------------------
933 setScores :: forall t. Each t t NgramsElement NgramsElement => Bool -> t -> m t
934 setScores False table = pure table
935 setScores True table = do
936 let ngrams_terms = (table ^.. each . ne_ngrams)
937 occurrences <- getOccByNgramsOnlySlow nType nId
943 setOcc ne = ne & ne_occurrences .~ sumOf (at (ne ^. ne_ngrams) . _Just) occurrences
945 pure $ table & each %~ setOcc
946 ---------------------------------------
948 -- lists <- catMaybes <$> listsWith userMaster
949 -- trace (show lists) $
950 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
952 let nSco = needsScores orderBy
953 tableMap1 <- getNgramsTableMap listId ngramsType
954 tableMap2 <- tableMap1 & v_data %%~ setScores nSco
955 . Map.mapWithKey ngramsElementFromRepo
956 tableMap2 & v_data %%~ fmap NgramsTable
957 . setScores (not nSco)
962 -- TODO: find a better place for the code above, All APIs stay here
963 type QueryParamR = QueryParam' '[Required, Strict]
966 data OrderBy = TermAsc | TermDesc | ScoreAsc | ScoreDesc
967 deriving (Generic, Enum, Bounded, Read, Show)
969 instance FromHttpApiData OrderBy
971 parseUrlPiece "TermAsc" = pure TermAsc
972 parseUrlPiece "TermDesc" = pure TermDesc
973 parseUrlPiece "ScoreAsc" = pure ScoreAsc
974 parseUrlPiece "ScoreDesc" = pure ScoreDesc
975 parseUrlPiece _ = Left "Unexpected value of OrderBy"
977 instance ToParamSchema OrderBy
978 instance FromJSON OrderBy
979 instance ToJSON OrderBy
980 instance ToSchema OrderBy
981 instance Arbitrary OrderBy
983 arbitrary = elements [minBound..maxBound]
985 needsScores :: Maybe OrderBy -> Bool
986 needsScores (Just ScoreAsc) = True
987 needsScores (Just ScoreDesc) = True
988 needsScores _ = False
990 type TableNgramsApiGet = Summary " Table Ngrams API Get"
991 :> QueryParamR "ngramsType" TabType
992 :> QueryParamR "list" ListId
993 :> QueryParamR "limit" Limit
994 :> QueryParam "offset" Offset
995 :> QueryParam "listType" ListType
996 :> QueryParam "minTermSize" MinSize
997 :> QueryParam "maxTermSize" MaxSize
998 :> QueryParam "orderBy" OrderBy
999 :> QueryParam "search" Text
1000 :> Get '[JSON] (Versioned NgramsTable)
1002 type TableNgramsApiPut = Summary " Table Ngrams API Change"
1003 :> QueryParamR "ngramsType" TabType
1004 :> QueryParamR "list" ListId
1005 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
1006 :> Put '[JSON] (Versioned NgramsTablePatch)
1008 type TableNgramsApiPost = Summary " Table Ngrams API Adds new ngrams"
1009 :> QueryParamR "ngramsType" TabType
1010 :> QueryParamR "list" ListId
1011 :> ReqBody '[JSON] [NgramsTerm]
1014 type TableNgramsApi = TableNgramsApiGet
1015 :<|> TableNgramsApiPut
1016 :<|> TableNgramsApiPost
1018 getTableNgramsCorpus :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
1019 => NodeId -> TabType
1020 -> ListId -> Limit -> Maybe Offset
1022 -> Maybe MinSize -> Maybe MaxSize
1024 -> Maybe Text -- full text search
1025 -> m (Versioned NgramsTable)
1026 getTableNgramsCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy mt =
1027 getTableNgrams NodeCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1029 searchQuery = maybe (const True) isInfixOf mt
1031 -- | Text search is deactivated for now for ngrams by doc only
1032 getTableNgramsDoc :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
1034 -> ListId -> Limit -> Maybe Offset
1036 -> Maybe MinSize -> Maybe MaxSize
1038 -> Maybe Text -- full text search
1039 -> m (Versioned NgramsTable)
1040 getTableNgramsDoc dId tabType listId limit_ offset listType minSize maxSize orderBy _mt = do
1041 ns <- selectNodesWithUsername NodeList userMaster
1042 let ngramsType = ngramsTypeFromTabType tabType
1043 ngs <- selectNgramsByDoc (ns <> [listId]) dId ngramsType
1044 let searchQuery = flip S.member (S.fromList ngs)
1045 getTableNgrams NodeDocument dId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1051 apiNgramsTableCorpus :: ( RepoCmdM env err m
1053 , HasInvalidError err
1056 => NodeId -> ServerT TableNgramsApi m
1057 apiNgramsTableCorpus cId = getTableNgramsCorpus cId
1059 :<|> tableNgramsPost
1062 apiNgramsTableDoc :: ( RepoCmdM env err m
1064 , HasInvalidError err
1067 => DocId -> ServerT TableNgramsApi m
1068 apiNgramsTableDoc dId = getTableNgramsDoc dId
1070 :<|> tableNgramsPost
1071 -- > add new ngrams in database (TODO AD)
1072 -- > index all the corpus accordingly (TODO AD)