1 {-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
3 Module : Gargantext.API.Ngrams
4 Description : Server API
5 Copyright : (c) CNRS, 2017-Present
6 License : AGPL + CECILL v3
7 Maintainer : team@gargantext.org
8 Stability : experimental
14 get ngrams filtered by NgramsType
19 {-# LANGUAGE ConstraintKinds #-}
20 {-# LANGUAGE DataKinds #-}
21 {-# LANGUAGE DeriveGeneric #-}
22 {-# LANGUAGE NoImplicitPrelude #-}
23 {-# LANGUAGE OverloadedStrings #-}
24 {-# LANGUAGE ScopedTypeVariables #-}
25 {-# LANGUAGE TemplateHaskell #-}
26 {-# LANGUAGE TypeOperators #-}
27 {-# LANGUAGE FlexibleContexts #-}
28 {-# LANGUAGE FlexibleInstances #-}
29 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
30 {-# LANGUAGE MultiParamTypeClasses #-}
31 {-# LANGUAGE RankNTypes #-}
32 {-# LANGUAGE TypeFamilies #-}
33 {-# OPTIONS -fno-warn-orphans #-}
35 module Gargantext.API.Ngrams
44 , apiNgramsTableCorpus
64 , NgramsRepoElement(..)
73 , ngramsTypeFromTabType
84 -- import Debug.Trace (trace)
85 import Prelude (Enum, Bounded, Semigroup(..), minBound, maxBound {-, round-}, error)
86 -- import Gargantext.Database.Schema.User (UserId)
87 import Data.Patch.Class (Replace, replace, Action(act), Applicable(..),
88 Composable(..), Transformable(..),
89 PairPatch(..), Patched, ConflictResolution,
90 ConflictResolutionReplace, ours)
91 import qualified Data.Map.Strict.Patch as PM
93 import Data.Ord (Down(..))
95 --import Data.Semigroup
97 import qualified Data.Set as S
98 import qualified Data.List as List
99 import Data.Maybe (fromMaybe)
100 -- import Data.Tuple.Extra (first)
101 import qualified Data.Map.Strict as Map
102 import Data.Map.Strict (Map)
103 import qualified Data.Set as Set
104 import Control.Category ((>>>))
105 import Control.Concurrent
106 import Control.Lens (makeLenses, makePrisms, Getter, Iso', iso, from, (.~), (?=), (#), to, folded, {-withIndex, ifolded,-} view, use, (^.), (^..), (^?), (+~), (%~), (%=), sumOf, at, _Just, Each(..), itraverse_, both, forOf_, (%%~), (?~))
107 import Control.Monad.Error.Class (MonadError)
108 import Control.Monad.Reader
109 import Control.Monad.State
110 import Data.Aeson hiding ((.=))
111 import Data.Aeson.TH (deriveJSON)
112 import Data.Either(Either(Left))
113 -- import Data.Map (lookup)
114 import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
115 import Data.Swagger hiding (version, patch)
116 import Data.Text (Text, isInfixOf, count)
118 import GHC.Generics (Generic)
119 import Gargantext.Core.Utils.Prefix (unPrefix)
120 -- import Gargantext.Database.Schema.Ngrams (NgramsTypeId, ngramsTypeId, NgramsTableData(..))
121 import Gargantext.Database.Config (userMaster)
122 import Gargantext.Database.Metrics.NgramsByNode (getOccByNgramsOnlyFast)
123 import Gargantext.Database.Schema.Ngrams (NgramsType)
124 import Gargantext.Database.Types.Node (NodeType(..))
125 import Gargantext.Database.Utils (fromField', HasConnection)
126 import Gargantext.Database.Node.Select
127 import Gargantext.Database.Ngrams
128 --import Gargantext.Database.Lists (listsWith)
129 import Gargantext.Database.Schema.Node (HasNodeError)
130 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
131 import qualified Gargantext.Database.Schema.Ngrams as Ngrams
132 -- import Gargantext.Database.Schema.NodeNgram hiding (Action)
133 import Gargantext.Prelude
134 -- import Gargantext.Core.Types (ListTypeId, listTypeId)
135 import Gargantext.Core.Types (ListType(..), NodeId, ListId, DocId, Limit, Offset, HasInvalidError, assertValid)
136 import Servant hiding (Patch)
137 import System.FileLock (FileLock)
138 import Test.QuickCheck (elements)
139 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
144 instance ToSchema TODO where
145 instance ToParamSchema TODO where
147 ------------------------------------------------------------------------
148 --data FacetFormat = Table | Chart
149 data TabType = Docs | Trash | MoreFav | MoreTrash
150 | Terms | Sources | Authors | Institutes
152 deriving (Generic, Enum, Bounded, Show)
154 instance FromHttpApiData TabType
156 parseUrlPiece "Docs" = pure Docs
157 parseUrlPiece "Trash" = pure Trash
158 parseUrlPiece "MoreFav" = pure MoreFav
159 parseUrlPiece "MoreTrash" = pure MoreTrash
161 parseUrlPiece "Terms" = pure Terms
162 parseUrlPiece "Sources" = pure Sources
163 parseUrlPiece "Institutes" = pure Institutes
164 parseUrlPiece "Authors" = pure Authors
166 parseUrlPiece "Contacts" = pure Contacts
168 parseUrlPiece _ = Left "Unexpected value of TabType"
170 instance ToParamSchema TabType
171 instance ToJSON TabType
172 instance FromJSON TabType
173 instance ToSchema TabType
174 instance Arbitrary TabType
176 arbitrary = elements [minBound .. maxBound]
178 newtype MSet a = MSet (Map a ())
179 deriving (Eq, Ord, Show, Generic, Arbitrary, Semigroup, Monoid)
181 instance ToJSON a => ToJSON (MSet a) where
182 toJSON (MSet m) = toJSON (Map.keys m)
183 toEncoding (MSet m) = toEncoding (Map.keys m)
185 mSetFromSet :: Set a -> MSet a
186 mSetFromSet = MSet . Map.fromSet (const ())
188 mSetFromList :: Ord a => [a] -> MSet a
189 mSetFromList = MSet . Map.fromList . map (\x -> (x, ()))
191 -- mSetToSet :: Ord a => MSet a -> Set a
192 -- mSetToSet (MSet a) = Set.fromList ( Map.keys a)
193 mSetToSet :: Ord a => MSet a -> Set a
194 mSetToSet = Set.fromList . mSetToList
196 mSetToList :: MSet a -> [a]
197 mSetToList (MSet a) = Map.keys a
199 instance Foldable MSet where
200 foldMap f (MSet m) = Map.foldMapWithKey (\k _ -> f k) m
202 instance (Ord a, FromJSON a) => FromJSON (MSet a) where
203 parseJSON = fmap mSetFromList . parseJSON
205 instance (ToJSONKey a, ToSchema a) => ToSchema (MSet a) where
207 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
209 ------------------------------------------------------------------------
210 type NgramsTerm = Text
212 data RootParent = RootParent
213 { _rp_root :: NgramsTerm
214 , _rp_parent :: NgramsTerm
216 deriving (Ord, Eq, Show, Generic)
218 deriveJSON (unPrefix "_rp_") ''RootParent
219 makeLenses ''RootParent
221 data NgramsRepoElement = NgramsRepoElement
223 , _nre_list :: ListType
224 --, _nre_root_parent :: Maybe RootParent
225 , _nre_root :: Maybe NgramsTerm
226 , _nre_parent :: Maybe NgramsTerm
227 , _nre_children :: MSet NgramsTerm
229 deriving (Ord, Eq, Show, Generic)
231 deriveJSON (unPrefix "_nre_") ''NgramsRepoElement
232 makeLenses ''NgramsRepoElement
235 NgramsElement { _ne_ngrams :: NgramsTerm
237 , _ne_list :: ListType
238 , _ne_occurrences :: Int
239 , _ne_root :: Maybe NgramsTerm
240 , _ne_parent :: Maybe NgramsTerm
241 , _ne_children :: MSet NgramsTerm
243 deriving (Ord, Eq, Show, Generic)
245 deriveJSON (unPrefix "_ne_") ''NgramsElement
246 makeLenses ''NgramsElement
248 mkNgramsElement :: NgramsTerm -> ListType -> Maybe RootParent -> MSet NgramsTerm -> NgramsElement
249 mkNgramsElement ngrams list rp children =
250 NgramsElement ngrams size list 1 (_rp_root <$> rp) (_rp_parent <$> rp) children
253 size = 1 + count " " ngrams
255 newNgramsElement :: Maybe ListType -> NgramsTerm -> NgramsElement
256 newNgramsElement mayList ngrams = mkNgramsElement ngrams (fromMaybe GraphTerm mayList) Nothing mempty
258 instance ToSchema NgramsElement
259 instance Arbitrary NgramsElement where
260 arbitrary = elements [newNgramsElement Nothing "sport"]
262 ngramsElementToRepo :: NgramsElement -> NgramsRepoElement
264 (NgramsElement { _ne_size = s
278 ngramsElementFromRepo :: NgramsTerm -> NgramsRepoElement -> NgramsElement
279 ngramsElementFromRepo
288 NgramsElement { _ne_size = s
293 , _ne_ngrams = ngrams
294 , _ne_occurrences = panic $ "API.Ngrams._ne_occurrences"
296 -- Here we could use 0 if we want to avoid any `panic`.
297 -- It will not happen using getTableNgrams if
298 -- getOccByNgramsOnly provides a count of occurrences for
299 -- all the ngrams given.
303 ------------------------------------------------------------------------
304 newtype NgramsTable = NgramsTable [NgramsElement]
305 deriving (Ord, Eq, Generic, ToJSON, FromJSON, Show)
307 type ListNgrams = NgramsTable
309 makePrisms ''NgramsTable
311 -- | Question: why these repetition of Type in this instance
312 -- may you document it please ?
313 instance Each NgramsTable NgramsTable NgramsElement NgramsElement where
314 each = _NgramsTable . each
317 -- | TODO Check N and Weight
319 toNgramsElement :: [NgramsTableData] -> [NgramsElement]
320 toNgramsElement ns = map toNgramsElement' ns
322 toNgramsElement' (NgramsTableData _ p t _ lt w) = NgramsElement t lt' (round w) p' c'
326 Just x -> lookup x mapParent
327 c' = maybe mempty identity $ lookup t mapChildren
328 lt' = maybe (panic "API.Ngrams: listypeId") identity lt
330 mapParent :: Map Int Text
331 mapParent = Map.fromListWith (<>) $ map (\(NgramsTableData i _ t _ _ _) -> (i,t)) ns
333 mapChildren :: Map Text (Set Text)
334 mapChildren = Map.mapKeys (\i -> (maybe (panic "API.Ngrams.mapChildren: ParentId with no Terms: Impossible") identity $ lookup i mapParent))
335 $ Map.fromListWith (<>)
336 $ map (first fromJust)
337 $ filter (isJust . fst)
338 $ map (\(NgramsTableData _ p t _ _ _) -> (p, Set.singleton t)) ns
341 mockTable :: NgramsTable
342 mockTable = NgramsTable
343 [ mkNgramsElement "animal" GraphTerm Nothing (mSetFromList ["dog", "cat"])
344 , mkNgramsElement "cat" GraphTerm (rp "animal") mempty
345 , mkNgramsElement "cats" StopTerm Nothing mempty
346 , mkNgramsElement "dog" GraphTerm (rp "animal") (mSetFromList ["dogs"])
347 , mkNgramsElement "dogs" StopTerm (rp "dog") mempty
348 , mkNgramsElement "fox" GraphTerm Nothing mempty
349 , mkNgramsElement "object" CandidateTerm Nothing mempty
350 , mkNgramsElement "nothing" StopTerm Nothing mempty
351 , mkNgramsElement "organic" GraphTerm Nothing (mSetFromList ["flower"])
352 , mkNgramsElement "flower" GraphTerm (rp "organic") mempty
353 , mkNgramsElement "moon" CandidateTerm Nothing mempty
354 , mkNgramsElement "sky" StopTerm Nothing mempty
357 rp n = Just $ RootParent n n
359 instance Arbitrary NgramsTable where
360 arbitrary = pure mockTable
362 instance ToSchema NgramsTable
364 ------------------------------------------------------------------------
365 type NgramsTableMap = Map NgramsTerm NgramsRepoElement
367 ------------------------------------------------------------------------
368 -- On the Client side:
369 --data Action = InGroup NgramsId NgramsId
370 -- | OutGroup NgramsId NgramsId
371 -- | SetListType NgramsId ListType
373 data PatchSet a = PatchSet
377 deriving (Eq, Ord, Show, Generic)
379 makeLenses ''PatchSet
380 makePrisms ''PatchSet
382 instance ToJSON a => ToJSON (PatchSet a) where
383 toJSON = genericToJSON $ unPrefix "_"
384 toEncoding = genericToEncoding $ unPrefix "_"
386 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
387 parseJSON = genericParseJSON $ unPrefix "_"
390 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
391 arbitrary = PatchSet <$> arbitrary <*> arbitrary
393 type instance Patched (PatchSet a) = Set a
395 type ConflictResolutionPatchSet a = SimpleConflictResolution' (Set a)
396 type instance ConflictResolution (PatchSet a) = ConflictResolutionPatchSet a
398 instance Ord a => Semigroup (PatchSet a) where
399 p <> q = PatchSet { _rem = (q ^. rem) `Set.difference` (p ^. add) <> p ^. rem
400 , _add = (q ^. add) `Set.difference` (p ^. rem) <> p ^. add
403 instance Ord a => Monoid (PatchSet a) where
404 mempty = PatchSet mempty mempty
406 instance Ord a => Group (PatchSet a) where
407 invert (PatchSet r a) = PatchSet a r
409 instance Ord a => Composable (PatchSet a) where
410 composable _ _ = undefined
412 instance Ord a => Action (PatchSet a) (Set a) where
413 act p source = (source `Set.difference` (p ^. rem)) <> p ^. add
415 instance Applicable (PatchSet a) (Set a) where
416 applicable _ _ = mempty
418 instance Ord a => Validity (PatchSet a) where
419 validate p = check (Set.disjoint (p ^. rem) (p ^. add)) "_rem and _add should be dijoint"
421 instance Ord a => Transformable (PatchSet a) where
422 transformable = undefined
424 conflicts _p _q = undefined
426 transformWith conflict p q = undefined conflict p q
428 instance ToSchema a => ToSchema (PatchSet a)
431 type AddRem = Replace (Maybe ())
433 remPatch, addPatch :: AddRem
434 remPatch = replace (Just ()) Nothing
435 addPatch = replace Nothing (Just ())
437 isRem :: Replace (Maybe ()) -> Bool
438 isRem = (== remPatch)
440 type PatchMap = PM.PatchMap
442 newtype PatchMSet a = PatchMSet (PatchMap a AddRem)
443 deriving (Eq, Show, Generic, Validity, Semigroup, Monoid,
444 Transformable, Composable)
446 type ConflictResolutionPatchMSet a = a -> ConflictResolutionReplace (Maybe ())
447 type instance ConflictResolution (PatchMSet a) = ConflictResolutionPatchMSet a
449 -- TODO this breaks module abstraction
450 makePrisms ''PM.PatchMap
452 makePrisms ''PatchMSet
454 _PatchMSetIso :: Ord a => Iso' (PatchMSet a) (PatchSet a)
455 _PatchMSetIso = _PatchMSet . _PatchMap . iso f g . from _PatchSet
457 f :: Ord a => Map a (Replace (Maybe ())) -> (Set a, Set a)
458 f = Map.partition isRem >>> both %~ Map.keysSet
460 g :: Ord a => (Set a, Set a) -> Map a (Replace (Maybe ()))
461 g (rems, adds) = Map.fromSet (const remPatch) rems
462 <> Map.fromSet (const addPatch) adds
464 instance Ord a => Action (PatchMSet a) (MSet a) where
465 act (PatchMSet p) (MSet m) = MSet $ act p m
467 instance Ord a => Applicable (PatchMSet a) (MSet a) where
468 applicable (PatchMSet p) (MSet m) = applicable p m
470 instance (Ord a, ToJSON a) => ToJSON (PatchMSet a) where
471 toJSON = toJSON . view _PatchMSetIso
472 toEncoding = toEncoding . view _PatchMSetIso
474 instance (Ord a, FromJSON a) => FromJSON (PatchMSet a) where
475 parseJSON = fmap (_PatchMSetIso #) . parseJSON
477 instance (Ord a, Arbitrary a) => Arbitrary (PatchMSet a) where
478 arbitrary = (PatchMSet . PM.fromMap) <$> arbitrary
480 instance ToSchema a => ToSchema (PatchMSet a) where
482 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
484 type instance Patched (PatchMSet a) = MSet a
486 instance (Eq a, Arbitrary a) => Arbitrary (Replace a) where
487 arbitrary = uncurry replace <$> arbitrary
488 -- If they happen to be equal then the patch is Keep.
490 instance ToSchema a => ToSchema (Replace a) where
491 declareNamedSchema (_ :: Proxy (Replace a)) = do
492 -- TODO Keep constructor is not supported here.
493 aSchema <- declareSchemaRef (Proxy :: Proxy a)
494 return $ NamedSchema (Just "Replace") $ mempty
495 & type_ ?~ SwaggerObject
497 InsOrdHashMap.fromList
501 & required .~ [ "old", "new" ]
504 NgramsPatch { _patch_children :: PatchMSet NgramsTerm
505 , _patch_list :: Replace ListType -- TODO Map UserId ListType
507 deriving (Eq, Show, Generic)
509 deriveJSON (unPrefix "_") ''NgramsPatch
510 makeLenses ''NgramsPatch
512 instance ToSchema NgramsPatch
514 instance Arbitrary NgramsPatch where
515 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
517 type NgramsPatchIso = PairPatch (PatchMSet NgramsTerm) (Replace ListType)
519 _NgramsPatch :: Iso' NgramsPatch NgramsPatchIso
520 _NgramsPatch = iso (\(NgramsPatch c l) -> c :*: l) (\(c :*: l) -> NgramsPatch c l)
522 instance Semigroup NgramsPatch where
523 p <> q = _NgramsPatch # (p ^. _NgramsPatch <> q ^. _NgramsPatch)
525 instance Monoid NgramsPatch where
526 mempty = _NgramsPatch # mempty
528 instance Validity NgramsPatch where
529 validate p = p ^. _NgramsPatch . to validate
531 instance Transformable NgramsPatch where
532 transformable p q = transformable (p ^. _NgramsPatch) (q ^. _NgramsPatch)
534 conflicts p q = conflicts (p ^. _NgramsPatch) (q ^. _NgramsPatch)
536 transformWith conflict p q = (_NgramsPatch # p', _NgramsPatch # q')
538 (p', q') = transformWith conflict (p ^. _NgramsPatch) (q ^. _NgramsPatch)
540 type ConflictResolutionNgramsPatch =
541 ( ConflictResolutionPatchMSet NgramsTerm
542 , ConflictResolutionReplace ListType
544 type instance ConflictResolution NgramsPatch =
545 ConflictResolutionNgramsPatch
547 type PatchedNgramsPatch = (Set NgramsTerm, ListType)
548 -- ~ Patched NgramsPatchIso
549 type instance Patched NgramsPatch = PatchedNgramsPatch
551 instance Applicable NgramsPatch (Maybe NgramsRepoElement) where
552 applicable p Nothing = check (p == mempty) "NgramsPatch should be empty here"
553 applicable p (Just nre) =
554 applicable (p ^. patch_children) (nre ^. nre_children) <>
555 applicable (p ^. patch_list) (nre ^. nre_list)
557 instance Action NgramsPatch NgramsRepoElement where
558 act p = (nre_children %~ act (p ^. patch_children))
559 . (nre_list %~ act (p ^. patch_list))
561 instance Action NgramsPatch (Maybe NgramsRepoElement) where
564 newtype NgramsTablePatch = NgramsTablePatch (PatchMap NgramsTerm NgramsPatch)
565 deriving (Eq, Show, Generic, ToJSON, FromJSON, Semigroup, Monoid, Validity, Transformable)
567 instance FromField NgramsTablePatch
569 fromField = fromField'
571 instance FromField (PatchMap NgramsType (PatchMap NodeId NgramsTablePatch))
573 fromField = fromField'
575 --instance (Ord k, Action pv (Maybe v)) => Action (PatchMap k pv) (Map k v) where
577 type instance ConflictResolution NgramsTablePatch =
578 NgramsTerm -> ConflictResolutionNgramsPatch
580 type PatchedNgramsTablePatch = Map NgramsTerm PatchedNgramsPatch
581 -- ~ Patched (PatchMap NgramsTerm NgramsPatch)
582 type instance Patched NgramsTablePatch = PatchedNgramsTablePatch
584 makePrisms ''NgramsTablePatch
585 instance ToSchema (PatchMap NgramsTerm NgramsPatch)
586 instance ToSchema NgramsTablePatch
588 instance Applicable NgramsTablePatch (Maybe NgramsTableMap) where
589 applicable p = applicable (p ^. _NgramsTablePatch)
591 instance Action NgramsTablePatch (Maybe NgramsTableMap) where
593 fmap (execState (reParentNgramsTablePatch p)) .
594 act (p ^. _NgramsTablePatch)
596 instance Arbitrary NgramsTablePatch where
597 arbitrary = NgramsTablePatch <$> PM.fromMap <$> arbitrary
599 -- Should it be less than an Lens' to preserve PatchMap's abstraction.
600 -- ntp_ngrams_patches :: Lens' NgramsTablePatch (Map NgramsTerm NgramsPatch)
601 -- ntp_ngrams_patches = _NgramsTablePatch . undefined
603 type ReParent a = forall m. MonadState NgramsTableMap m => a -> m ()
605 reRootChildren :: NgramsTerm -> ReParent NgramsTerm
606 reRootChildren root ngram = do
607 nre <- use $ at ngram
608 forOf_ (_Just . nre_children . folded) nre $ \child -> do
609 at child . _Just . nre_root ?= root
610 reRootChildren root child
612 reParent :: Maybe RootParent -> ReParent NgramsTerm
613 reParent rp child = do
614 at child . _Just %= ( (nre_parent .~ (_rp_parent <$> rp))
615 . (nre_root .~ (_rp_root <$> rp))
617 reRootChildren (fromMaybe child (rp ^? _Just . rp_root)) child
619 reParentAddRem :: RootParent -> NgramsTerm -> ReParent AddRem
620 reParentAddRem rp child p =
621 reParent (if isRem p then Nothing else Just rp) child
623 reParentNgramsPatch :: NgramsTerm -> ReParent NgramsPatch
624 reParentNgramsPatch parent ngramsPatch = do
625 root_of_parent <- use (at parent . _Just . nre_root)
627 root = fromMaybe parent root_of_parent
628 rp = RootParent { _rp_root = root, _rp_parent = parent }
629 itraverse_ (reParentAddRem rp) (ngramsPatch ^. patch_children . _PatchMSet . _PatchMap)
630 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
632 reParentNgramsTablePatch :: ReParent NgramsTablePatch
633 reParentNgramsTablePatch p = itraverse_ reParentNgramsPatch (p ^. _NgramsTablePatch. _PatchMap)
634 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
636 ------------------------------------------------------------------------
637 ------------------------------------------------------------------------
640 data Versioned a = Versioned
641 { _v_version :: Version
644 deriving (Generic, Show)
645 deriveJSON (unPrefix "_v_") ''Versioned
646 makeLenses ''Versioned
647 instance ToSchema a => ToSchema (Versioned a)
648 instance Arbitrary a => Arbitrary (Versioned a) where
649 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
652 -- TODO sequencs of modifications (Patchs)
653 type NgramsIdPatch = Patch NgramsId NgramsPatch
655 ngramsPatch :: Int -> NgramsPatch
656 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopTerm)]) (Set.fromList [n]) Set.empty
658 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
659 toEdit n p = Edit n p
660 ngramsIdPatch :: Patch NgramsId NgramsPatch
661 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
662 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
663 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
666 -- applyPatchBack :: Patch -> IO Patch
667 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
669 ------------------------------------------------------------------------
670 ------------------------------------------------------------------------
671 ------------------------------------------------------------------------
674 -- TODO: Replace.old is ignored which means that if the current list
675 -- `GraphTerm` and that the patch is `Replace CandidateTerm StopTerm` then
676 -- the list is going to be `StopTerm` while it should keep `GraphTerm`.
677 -- However this should not happen in non conflicting situations.
678 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
679 mkListsUpdate nt patches =
680 [ (ngramsTypeId nt, ng, listTypeId lt)
681 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
682 , lt <- patch ^.. patch_list . new
685 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
688 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
689 mkChildrenGroups addOrRem nt patches =
690 [ (ngramsTypeId nt, parent, child)
691 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
692 , child <- patch ^.. patch_children . to addOrRem . folded
696 ngramsTypeFromTabType :: TabType -> NgramsType
697 ngramsTypeFromTabType tabType =
698 let lieu = "Garg.API.Ngrams: " :: Text in
700 Sources -> Ngrams.Sources
701 Authors -> Ngrams.Authors
702 Institutes -> Ngrams.Institutes
703 Terms -> Ngrams.NgramsTerms
704 _ -> panic $ lieu <> "No Ngrams for this tab"
705 -- TODO: This `panic` would disapear with custom NgramsType.
707 ------------------------------------------------------------------------
709 { _r_version :: Version
712 -- first patch in the list is the most recent
716 instance (FromJSON s, FromJSON p) => FromJSON (Repo s p) where
717 parseJSON = genericParseJSON $ unPrefix "_r_"
719 instance (ToJSON s, ToJSON p) => ToJSON (Repo s p) where
720 toJSON = genericToJSON $ unPrefix "_r_"
721 toEncoding = genericToEncoding $ unPrefix "_r_"
725 initRepo :: Monoid s => Repo s p
726 initRepo = Repo 1 mempty []
728 type NgramsRepo = Repo NgramsState NgramsStatePatch
729 type NgramsState = Map NgramsType (Map NodeId NgramsTableMap)
730 type NgramsStatePatch = PatchMap NgramsType (PatchMap NodeId NgramsTablePatch)
732 initMockRepo :: NgramsRepo
733 initMockRepo = Repo 1 s []
735 s = Map.singleton Ngrams.NgramsTerms
736 $ Map.singleton 47254
738 [ (n ^. ne_ngrams, ngramsElementToRepo n) | n <- mockTable ^. _NgramsTable ]
740 data RepoEnv = RepoEnv
741 { _renv_var :: !(MVar NgramsRepo)
742 , _renv_saver :: !(IO ())
743 , _renv_lock :: !FileLock
749 class HasRepoVar env where
750 repoVar :: Getter env (MVar NgramsRepo)
752 instance HasRepoVar (MVar NgramsRepo) where
755 class HasRepoSaver env where
756 repoSaver :: Getter env (IO ())
758 class (HasRepoVar env, HasRepoSaver env) => HasRepo env where
759 repoEnv :: Getter env RepoEnv
761 instance HasRepo RepoEnv where
764 instance HasRepoVar RepoEnv where
767 instance HasRepoSaver RepoEnv where
768 repoSaver = renv_saver
770 type RepoCmdM env err m =
776 ------------------------------------------------------------------------
778 saveRepo :: ( MonadReader env m, MonadIO m, HasRepoSaver env )
780 saveRepo = liftIO =<< view repoSaver
782 listTypeConflictResolution :: ListType -> ListType -> ListType
783 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
785 ngramsStatePatchConflictResolution
786 :: NgramsType -> NodeId -> NgramsTerm
787 -> ConflictResolutionNgramsPatch
788 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
790 -- undefined {- TODO think this through -}, listTypeConflictResolution)
793 -- Insertions are not considered as patches,
794 -- they do not extend history,
795 -- they do not bump version.
796 insertNewOnly :: a -> Maybe b -> a
797 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
798 -- TODO error handling
800 something :: Monoid a => Maybe a -> a
801 something Nothing = mempty
802 something (Just a) = a
805 -- TODO refactor with putListNgrams
806 copyListNgrams :: RepoCmdM env err m
807 => NodeId -> NodeId -> NgramsType
809 copyListNgrams srcListId dstListId ngramsType = do
811 liftIO $ modifyMVar_ var $
812 pure . (r_state . at ngramsType %~ (Just . f . something))
815 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
816 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
818 -- TODO refactor with putListNgrams
819 -- The list must be non-empty!
820 -- The added ngrams must be non-existent!
821 addListNgrams :: RepoCmdM env err m
822 => NodeId -> NgramsType
823 -> [NgramsElement] -> m ()
824 addListNgrams listId ngramsType nes = do
826 liftIO $ modifyMVar_ var $
827 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
830 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
833 -- If the given list of ngrams elements contains ngrams already in
834 -- the repo, they will be ignored.
835 putListNgrams :: RepoCmdM env err m
836 => NodeId -> NgramsType
837 -> [NgramsElement] -> m ()
838 putListNgrams _ _ [] = pure ()
839 putListNgrams listId ngramsType nes = do
840 -- printDebug "putListNgrams" (length nes)
842 liftIO $ modifyMVar_ var $
843 pure . (r_state . at ngramsType %~ (Just . (at listId %~ (Just . (<> m) . something)) . something))
846 m = Map.fromList $ (\n -> (n ^. ne_ngrams, ngramsElementToRepo n)) <$> nes
849 tableNgramsPost :: RepoCmdM env err m => TabType -> NodeId -> Maybe ListType -> [NgramsTerm] -> m ()
850 tableNgramsPost tabType listId mayList =
851 putListNgrams listId (ngramsTypeFromTabType tabType) . fmap (newNgramsElement mayList)
853 -- Apply the given patch to the DB and returns the patch to be applied on the
856 tableNgramsPut :: (HasInvalidError err, RepoCmdM env err m)
858 -> Versioned NgramsTablePatch
859 -> m (Versioned NgramsTablePatch)
860 tableNgramsPut tabType listId (Versioned p_version p_table)
861 | p_table == mempty = do
862 let ngramsType = ngramsTypeFromTabType tabType
865 r <- liftIO $ readMVar var
868 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
869 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
871 pure (Versioned (r ^. r_version) q_table)
874 let ngramsType = ngramsTypeFromTabType tabType
875 (p0, p0_validity) = PM.singleton listId p_table
876 (p, p_validity) = PM.singleton ngramsType p0
878 assertValid p0_validity
879 assertValid p_validity
882 vq' <- liftIO $ modifyMVar var $ \r -> do
884 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
885 (p', q') = transformWith ngramsStatePatchConflictResolution p q
886 r' = r & r_version +~ 1
888 & r_history %~ (p' :)
889 q'_table = q' ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
891 -- Ideally we would like to check these properties. However:
892 -- * They should be checked only to debug the code. The client data
893 -- should be able to trigger these.
894 -- * What kind of error should they throw (we are in IO here)?
895 -- * Should we keep modifyMVar?
896 -- * Should we throw the validation in an Exception, catch it around
897 -- modifyMVar and throw it back as an Error?
898 assertValid $ transformable p q
899 assertValid $ applicable p' (r ^. r_state)
901 pure (r', Versioned (r' ^. r_version) q'_table)
906 mergeNgramsElement :: NgramsRepoElement -> NgramsRepoElement -> NgramsRepoElement
907 mergeNgramsElement _neOld neNew = neNew
909 { _ne_list :: ListType
910 If we merge the parents/children we can potentially create cycles!
911 , _ne_parent :: Maybe NgramsTerm
912 , _ne_children :: MSet NgramsTerm
916 getNgramsTableMap :: RepoCmdM env err m
917 => NodeId -> NgramsType -> m (Versioned NgramsTableMap)
918 getNgramsTableMap nodeId ngramsType = do
920 repo <- liftIO $ readMVar v
921 pure $ Versioned (repo ^. r_version)
922 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
927 -- | TODO Errors management
928 -- TODO: polymorphic for Annuaire or Corpus or ...
929 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
930 -- TODO: should take only one ListId
935 getTableNgrams :: forall env err m.
936 (RepoCmdM env err m, HasNodeError err, HasConnection env)
937 => NodeType -> NodeId -> TabType
938 -> ListId -> Limit -> Maybe Offset
940 -> Maybe MinSize -> Maybe MaxSize
942 -> (NgramsTerm -> Bool)
943 -> m (Versioned NgramsTable)
944 getTableNgrams _nType nId tabType listId limit_ offset
945 listType minSize maxSize orderBy searchQuery = do
947 _lIds <- selectNodesWithUsername NodeList userMaster
949 ngramsType = ngramsTypeFromTabType tabType
950 offset' = maybe 0 identity offset
951 listType' = maybe (const True) (==) listType
952 minSize' = maybe (const True) (<=) minSize
953 maxSize' = maybe (const True) (>=) maxSize
955 selected_node n = minSize' s
957 && searchQuery (n ^. ne_ngrams)
958 && listType' (n ^. ne_list)
962 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
964 ---------------------------------------
965 sortOnOrder Nothing = identity
966 sortOnOrder (Just TermAsc) = List.sortOn $ view ne_ngrams
967 sortOnOrder (Just TermDesc) = List.sortOn $ Down . view ne_ngrams
968 sortOnOrder (Just ScoreAsc) = List.sortOn $ view ne_occurrences
969 sortOnOrder (Just ScoreDesc) = List.sortOn $ Down . view ne_occurrences
971 ---------------------------------------
972 selectAndPaginate :: Map NgramsTerm NgramsElement -> [NgramsElement]
973 selectAndPaginate tableMap = roots <> inners
975 list = tableMap ^.. each
976 rootOf ne = maybe ne (\r -> fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r))
978 selected_nodes = list & take limit_
980 . filter selected_node
981 . sortOnOrder orderBy
982 roots = rootOf <$> selected_nodes
983 rootsSet = Set.fromList (_ne_ngrams <$> roots)
984 inners = list & filter (selected_inner rootsSet)
986 ---------------------------------------
987 setScores :: forall t. Each t t NgramsElement NgramsElement => Bool -> t -> m t
988 setScores False table = pure table
989 setScores True table = do
990 let ngrams_terms = (table ^.. each . ne_ngrams)
991 occurrences <- getOccByNgramsOnlyFast nId
995 occurrences <- getOccByNgramsOnlySlow nType nId
1001 setOcc ne = ne & ne_occurrences .~ sumOf (at (ne ^. ne_ngrams) . _Just) occurrences
1003 pure $ table & each %~ setOcc
1004 ---------------------------------------
1006 -- lists <- catMaybes <$> listsWith userMaster
1007 -- trace (show lists) $
1008 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
1010 let nSco = needsScores orderBy
1011 tableMap1 <- getNgramsTableMap listId ngramsType
1012 tableMap2 <- tableMap1 & v_data %%~ setScores nSco
1013 . Map.mapWithKey ngramsElementFromRepo
1014 tableMap2 & v_data %%~ fmap NgramsTable
1015 . setScores (not nSco)
1020 -- TODO: find a better place for the code above, All APIs stay here
1021 type QueryParamR = QueryParam' '[Required, Strict]
1024 data OrderBy = TermAsc | TermDesc | ScoreAsc | ScoreDesc
1025 deriving (Generic, Enum, Bounded, Read, Show)
1027 instance FromHttpApiData OrderBy
1029 parseUrlPiece "TermAsc" = pure TermAsc
1030 parseUrlPiece "TermDesc" = pure TermDesc
1031 parseUrlPiece "ScoreAsc" = pure ScoreAsc
1032 parseUrlPiece "ScoreDesc" = pure ScoreDesc
1033 parseUrlPiece _ = Left "Unexpected value of OrderBy"
1035 instance ToParamSchema OrderBy
1036 instance FromJSON OrderBy
1037 instance ToJSON OrderBy
1038 instance ToSchema OrderBy
1039 instance Arbitrary OrderBy
1041 arbitrary = elements [minBound..maxBound]
1043 needsScores :: Maybe OrderBy -> Bool
1044 needsScores (Just ScoreAsc) = True
1045 needsScores (Just ScoreDesc) = True
1046 needsScores _ = False
1048 type TableNgramsApiGet = Summary " Table Ngrams API Get"
1049 :> QueryParamR "ngramsType" TabType
1050 :> QueryParamR "list" ListId
1051 :> QueryParamR "limit" Limit
1052 :> QueryParam "offset" Offset
1053 :> QueryParam "listType" ListType
1054 :> QueryParam "minTermSize" MinSize
1055 :> QueryParam "maxTermSize" MaxSize
1056 :> QueryParam "orderBy" OrderBy
1057 :> QueryParam "search" Text
1058 :> Get '[JSON] (Versioned NgramsTable)
1060 type TableNgramsApiPut = Summary " Table Ngrams API Change"
1061 :> QueryParamR "ngramsType" TabType
1062 :> QueryParamR "list" ListId
1063 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
1064 :> Put '[JSON] (Versioned NgramsTablePatch)
1066 type TableNgramsApiPost = Summary " Table Ngrams API Adds new ngrams"
1067 :> QueryParamR "ngramsType" TabType
1068 :> QueryParamR "list" ListId
1069 :> QueryParam "listType" ListType
1070 :> ReqBody '[JSON] [NgramsTerm]
1073 type TableNgramsApi = TableNgramsApiGet
1074 :<|> TableNgramsApiPut
1075 :<|> TableNgramsApiPost
1077 getTableNgramsCorpus :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
1078 => NodeId -> TabType
1079 -> ListId -> Limit -> Maybe Offset
1081 -> Maybe MinSize -> Maybe MaxSize
1083 -> Maybe Text -- full text search
1084 -> m (Versioned NgramsTable)
1085 getTableNgramsCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy mt =
1086 getTableNgrams NodeCorpus nId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1088 searchQuery = maybe (const True) isInfixOf mt
1090 -- | Text search is deactivated for now for ngrams by doc only
1091 getTableNgramsDoc :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
1093 -> ListId -> Limit -> Maybe Offset
1095 -> Maybe MinSize -> Maybe MaxSize
1097 -> Maybe Text -- full text search
1098 -> m (Versioned NgramsTable)
1099 getTableNgramsDoc dId tabType listId limit_ offset listType minSize maxSize orderBy _mt = do
1100 ns <- selectNodesWithUsername NodeList userMaster
1101 let ngramsType = ngramsTypeFromTabType tabType
1102 ngs <- selectNgramsByDoc (ns <> [listId]) dId ngramsType
1103 let searchQuery = flip S.member (S.fromList ngs)
1104 getTableNgrams NodeDocument dId tabType listId limit_ offset listType minSize maxSize orderBy searchQuery
1110 apiNgramsTableCorpus :: ( RepoCmdM env err m
1112 , HasInvalidError err
1115 => NodeId -> ServerT TableNgramsApi m
1116 apiNgramsTableCorpus cId = getTableNgramsCorpus cId
1118 :<|> tableNgramsPost
1121 apiNgramsTableDoc :: ( RepoCmdM env err m
1123 , HasInvalidError err
1126 => DocId -> ServerT TableNgramsApi m
1127 apiNgramsTableDoc dId = getTableNgramsDoc dId
1129 :<|> tableNgramsPost
1130 -- > add new ngrams in database (TODO AD)
1131 -- > index all the corpus accordingly (TODO AD)