2 Module : Gargantext.API.Ngrams
3 Description : Server API
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 get ngrams filtered by NgramsType
18 {-# LANGUAGE ConstraintKinds #-}
19 {-# LANGUAGE DataKinds #-}
20 {-# LANGUAGE DeriveGeneric #-}
21 {-# LANGUAGE NoImplicitPrelude #-}
22 {-# LANGUAGE OverloadedStrings #-}
23 {-# LANGUAGE ScopedTypeVariables #-}
24 {-# LANGUAGE TemplateHaskell #-}
25 {-# LANGUAGE TypeOperators #-}
26 {-# LANGUAGE FlexibleContexts #-}
27 {-# LANGUAGE FlexibleInstances #-}
28 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
29 {-# LANGUAGE MultiParamTypeClasses #-}
30 {-# LANGUAGE RankNTypes #-}
31 {-# LANGUAGE TypeFamilies #-}
32 {-# OPTIONS -fno-warn-orphans #-}
34 module Gargantext.API.Ngrams
37 -- import Debug.Trace (trace)
38 import Prelude (Enum, Bounded, Semigroup(..), minBound, maxBound {-, round-}, error)
39 -- import Gargantext.Database.Schema.User (UserId)
40 import Data.Patch.Class (Replace, replace, Action(act), Applicable(..),
41 Composable(..), Transformable(..),
42 PairPatch(..), Patched, ConflictResolution,
43 ConflictResolutionReplace, ours)
44 import qualified Data.Map.Strict.Patch as PM
47 --import Data.Semigroup
49 -- import qualified Data.List as List
50 import Data.Maybe (fromMaybe)
51 -- import Data.Tuple.Extra (first)
52 import qualified Data.Map.Strict as Map
53 import Data.Map.Strict (Map)
54 import qualified Data.Set as Set
55 import Control.Category ((>>>))
56 import Control.Concurrent
57 import Control.Lens (makeLenses, makePrisms, Getter, Iso', iso, from, (.~), (?=), (#), to, folded, {-withIndex, ifolded,-} view, use, (^.), (^..), (^?), (+~), (%~), (%=), sumOf, at, _Just, Each(..), itraverse_, both, mapped, forOf_)
58 import Control.Monad.Error.Class (MonadError)
59 import Control.Monad.Reader
60 import Control.Monad.State
61 import Data.Aeson hiding ((.=))
62 import Data.Aeson.TH (deriveJSON)
63 import Data.Either(Either(Left))
64 -- import Data.Map (lookup)
65 import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
66 import Data.Swagger hiding (version, patch)
67 import Data.Text (Text, isInfixOf, count)
69 import GHC.Generics (Generic)
70 import Gargantext.Core.Utils.Prefix (unPrefix)
71 -- import Gargantext.Database.Schema.Ngrams (NgramsTypeId, ngramsTypeId, NgramsTableData(..))
72 --import Gargantext.Database.Config (userMaster)
73 import Gargantext.Database.Metrics.NgramsByNode (getOccByNgramsOnlySafe)
74 import Gargantext.Database.Schema.Ngrams (NgramsType)
75 import Gargantext.Database.Utils (fromField', HasConnection)
76 --import Gargantext.Database.Lists (listsWith)
77 import Gargantext.Database.Schema.Node (HasNodeError)
78 import Database.PostgreSQL.Simple.FromField (FromField, fromField)
79 import qualified Gargantext.Database.Schema.Ngrams as Ngrams
80 -- import Gargantext.Database.Schema.NodeNgram hiding (Action)
81 import Gargantext.Prelude
82 -- import Gargantext.Core.Types (ListTypeId, listTypeId)
83 import Gargantext.Core.Types (ListType(..), NodeId, ListId, CorpusId, Limit, Offset, HasInvalidError, assertValid)
84 import Servant hiding (Patch)
85 import System.FileLock (FileLock)
86 import Test.QuickCheck (elements)
87 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
92 instance ToSchema TODO where
94 ------------------------------------------------------------------------
95 --data FacetFormat = Table | Chart
96 data TabType = Docs | Terms | Sources | Authors | Institutes | Trash
98 deriving (Generic, Enum, Bounded)
100 instance FromHttpApiData TabType
102 parseUrlPiece "Docs" = pure Docs
103 parseUrlPiece "Terms" = pure Terms
104 parseUrlPiece "Sources" = pure Sources
105 parseUrlPiece "Institutes" = pure Institutes
106 parseUrlPiece "Authors" = pure Authors
107 parseUrlPiece "Trash" = pure Trash
109 parseUrlPiece "Contacts" = pure Contacts
111 parseUrlPiece _ = Left "Unexpected value of TabType"
113 instance ToParamSchema TabType
114 instance ToJSON TabType
115 instance FromJSON TabType
116 instance ToSchema TabType
117 instance Arbitrary TabType
119 arbitrary = elements [minBound .. maxBound]
121 newtype MSet a = MSet (Map a ())
122 deriving (Eq, Ord, Show, Generic, Arbitrary, Semigroup, Monoid)
124 instance ToJSON a => ToJSON (MSet a) where
125 toJSON (MSet m) = toJSON (Map.keys m)
126 toEncoding (MSet m) = toEncoding (Map.keys m)
128 mSetFromSet :: Set a -> MSet a
129 mSetFromSet = MSet . Map.fromSet (const ())
131 mSetFromList :: Ord a => [a] -> MSet a
132 mSetFromList = MSet . Map.fromList . map (\x -> (x, ()))
134 instance Foldable MSet where
135 foldMap f (MSet m) = Map.foldMapWithKey (\k _ -> f k) m
137 instance (Ord a, FromJSON a) => FromJSON (MSet a) where
138 parseJSON = fmap mSetFromList . parseJSON
140 instance (ToJSONKey a, ToSchema a) => ToSchema (MSet a) where
142 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
144 ------------------------------------------------------------------------
145 type NgramsTerm = Text
147 data RootParent = RootParent
148 { _rp_root :: NgramsTerm
149 , _rp_parent :: NgramsTerm
151 deriving (Ord, Eq, Show, Generic)
153 deriveJSON (unPrefix "_rp_") ''RootParent
154 makeLenses ''RootParent
156 data NgramsRepoElement = NgramsRepoElement
158 , _nre_list :: ListType
159 --, _nre_root_parent :: Maybe RootParent
160 , _nre_root :: Maybe NgramsTerm
161 , _nre_parent :: Maybe NgramsTerm
162 , _nre_children :: MSet NgramsTerm
164 deriving (Ord, Eq, Show, Generic)
166 deriveJSON (unPrefix "_nre_") ''NgramsRepoElement
167 makeLenses ''NgramsRepoElement
170 NgramsElement { _ne_ngrams :: NgramsTerm
172 , _ne_list :: ListType
173 , _ne_occurrences :: Int
174 , _ne_root :: Maybe NgramsTerm
175 , _ne_parent :: Maybe NgramsTerm
176 , _ne_children :: MSet NgramsTerm
178 deriving (Ord, Eq, Show, Generic)
180 deriveJSON (unPrefix "_ne_") ''NgramsElement
181 makeLenses ''NgramsElement
183 mkNgramsElement :: NgramsTerm -> ListType -> Maybe RootParent -> MSet NgramsTerm -> NgramsElement
184 mkNgramsElement ngrams list rp children =
185 NgramsElement ngrams size list 1 (_rp_root <$> rp) (_rp_parent <$> rp) children
188 size = 1 + count " " ngrams
190 instance ToSchema NgramsElement
191 instance Arbitrary NgramsElement where
192 arbitrary = elements [mkNgramsElement "sport" GraphTerm Nothing mempty]
194 ngramsElementToRepo :: NgramsElement -> NgramsRepoElement
196 (NgramsElement { _ne_size = s
210 ngramsElementFromRepo :: (NgramsTerm, NgramsRepoElement) -> NgramsElement
211 ngramsElementFromRepo
220 NgramsElement { _ne_size = s
225 , _ne_ngrams = ngrams
226 , _ne_occurrences = panic "API.Ngrams._ne_occurrences"
227 -- ^ Here we could use 0 if we want to avoid any `panic`.
228 -- It will not happen using getTableNgrams if
229 -- getOccByNgramsOnly provides a count of occurrences for
230 -- all the ngrams given.
233 ------------------------------------------------------------------------
234 newtype NgramsTable = NgramsTable [NgramsElement]
235 deriving (Ord, Eq, Generic, ToJSON, FromJSON, Show)
237 type ListNgrams = NgramsTable
239 makePrisms ''NgramsTable
241 -- | Question: why these repetition of Type in this instance
242 -- may you document it please ?
243 instance Each NgramsTable NgramsTable NgramsElement NgramsElement where
244 each = _NgramsTable . each
247 -- | TODO Check N and Weight
249 toNgramsElement :: [NgramsTableData] -> [NgramsElement]
250 toNgramsElement ns = map toNgramsElement' ns
252 toNgramsElement' (NgramsTableData _ p t _ lt w) = NgramsElement t lt' (round w) p' c'
256 Just x -> lookup x mapParent
257 c' = maybe mempty identity $ lookup t mapChildren
258 lt' = maybe (panic "API.Ngrams: listypeId") identity lt
260 mapParent :: Map Int Text
261 mapParent = Map.fromListWith (<>) $ map (\(NgramsTableData i _ t _ _ _) -> (i,t)) ns
263 mapChildren :: Map Text (Set Text)
264 mapChildren = Map.mapKeys (\i -> (maybe (panic "API.Ngrams.mapChildren: ParentId with no Terms: Impossible") identity $ lookup i mapParent))
265 $ Map.fromListWith (<>)
266 $ map (first fromJust)
267 $ filter (isJust . fst)
268 $ map (\(NgramsTableData _ p t _ _ _) -> (p, Set.singleton t)) ns
271 mockTable :: NgramsTable
272 mockTable = NgramsTable
273 [ mkNgramsElement "animal" GraphTerm Nothing (mSetFromList ["dog", "cat"])
274 , mkNgramsElement "cat" GraphTerm (rp "animal") mempty
275 , mkNgramsElement "cats" StopTerm Nothing mempty
276 , mkNgramsElement "dog" GraphTerm (rp "animal") (mSetFromList ["dogs"])
277 , mkNgramsElement "dogs" StopTerm (rp "dog") mempty
278 , mkNgramsElement "fox" GraphTerm Nothing mempty
279 , mkNgramsElement "object" CandidateTerm Nothing mempty
280 , mkNgramsElement "nothing" StopTerm Nothing mempty
281 , mkNgramsElement "organic" GraphTerm Nothing (mSetFromList ["flower"])
282 , mkNgramsElement "flower" GraphTerm (rp "organic") mempty
283 , mkNgramsElement "moon" CandidateTerm Nothing mempty
284 , mkNgramsElement "sky" StopTerm Nothing mempty
287 rp n = Just $ RootParent n n
289 instance Arbitrary NgramsTable where
290 arbitrary = pure mockTable
292 instance ToSchema NgramsTable
294 ------------------------------------------------------------------------
295 type NgramsTableMap = Map NgramsTerm NgramsRepoElement
297 ------------------------------------------------------------------------
298 -- On the Client side:
299 --data Action = InGroup NgramsId NgramsId
300 -- | OutGroup NgramsId NgramsId
301 -- | SetListType NgramsId ListType
303 data PatchSet a = PatchSet
307 deriving (Eq, Ord, Show, Generic)
309 makeLenses ''PatchSet
310 makePrisms ''PatchSet
312 instance ToJSON a => ToJSON (PatchSet a) where
313 toJSON = genericToJSON $ unPrefix "_"
314 toEncoding = genericToEncoding $ unPrefix "_"
316 instance (Ord a, FromJSON a) => FromJSON (PatchSet a) where
317 parseJSON = genericParseJSON $ unPrefix "_"
320 instance (Ord a, Arbitrary a) => Arbitrary (PatchSet a) where
321 arbitrary = PatchSet <$> arbitrary <*> arbitrary
323 type instance Patched (PatchSet a) = Set a
325 type ConflictResolutionPatchSet a = SimpleConflictResolution' (Set a)
326 type instance ConflictResolution (PatchSet a) = ConflictResolutionPatchSet a
328 instance Ord a => Semigroup (PatchSet a) where
329 p <> q = PatchSet { _rem = (q ^. rem) `Set.difference` (p ^. add) <> p ^. rem
330 , _add = (q ^. add) `Set.difference` (p ^. rem) <> p ^. add
333 instance Ord a => Monoid (PatchSet a) where
334 mempty = PatchSet mempty mempty
336 instance Ord a => Group (PatchSet a) where
337 invert (PatchSet r a) = PatchSet a r
339 instance Ord a => Composable (PatchSet a) where
340 composable _ _ = undefined
342 instance Ord a => Action (PatchSet a) (Set a) where
343 act p source = (source `Set.difference` (p ^. rem)) <> p ^. add
345 instance Applicable (PatchSet a) (Set a) where
346 applicable _ _ = mempty
348 instance Ord a => Validity (PatchSet a) where
349 validate p = check (Set.disjoint (p ^. rem) (p ^. add)) "_rem and _add should be dijoint"
351 instance Ord a => Transformable (PatchSet a) where
352 transformable = undefined
354 conflicts _p _q = undefined
356 transformWith conflict p q = undefined conflict p q
358 instance ToSchema a => ToSchema (PatchSet a)
361 type AddRem = Replace (Maybe ())
363 remPatch, addPatch :: AddRem
364 remPatch = replace (Just ()) Nothing
365 addPatch = replace Nothing (Just ())
367 isRem :: Replace (Maybe ()) -> Bool
368 isRem = (== remPatch)
370 type PatchMap = PM.PatchMap
372 newtype PatchMSet a = PatchMSet (PatchMap a AddRem)
373 deriving (Eq, Show, Generic, Validity, Semigroup, Monoid,
374 Transformable, Composable)
376 type ConflictResolutionPatchMSet a = a -> ConflictResolutionReplace (Maybe ())
377 type instance ConflictResolution (PatchMSet a) = ConflictResolutionPatchMSet a
379 -- TODO this breaks module abstraction
380 makePrisms ''PM.PatchMap
382 makePrisms ''PatchMSet
384 _PatchMSetIso :: Ord a => Iso' (PatchMSet a) (PatchSet a)
385 _PatchMSetIso = _PatchMSet . _PatchMap . iso f g . from _PatchSet
387 f :: Ord a => Map a (Replace (Maybe ())) -> (Set a, Set a)
388 f = Map.partition isRem >>> both %~ Map.keysSet
390 g :: Ord a => (Set a, Set a) -> Map a (Replace (Maybe ()))
391 g (rems, adds) = Map.fromSet (const remPatch) rems
392 <> Map.fromSet (const addPatch) adds
394 instance Ord a => Action (PatchMSet a) (MSet a) where
395 act (PatchMSet p) (MSet m) = MSet $ act p m
397 instance Ord a => Applicable (PatchMSet a) (MSet a) where
398 applicable (PatchMSet p) (MSet m) = applicable p m
400 instance (Ord a, ToJSON a) => ToJSON (PatchMSet a) where
401 toJSON = toJSON . view _PatchMSetIso
402 toEncoding = toEncoding . view _PatchMSetIso
404 instance (Ord a, FromJSON a) => FromJSON (PatchMSet a) where
405 parseJSON = fmap (_PatchMSetIso #) . parseJSON
407 instance (Ord a, Arbitrary a) => Arbitrary (PatchMSet a) where
408 arbitrary = (PatchMSet . PM.fromMap) <$> arbitrary
410 instance ToSchema a => ToSchema (PatchMSet a) where
412 declareNamedSchema _ = declareNamedSchema (Proxy :: Proxy TODO)
414 type instance Patched (PatchMSet a) = MSet a
416 instance (Eq a, Arbitrary a) => Arbitrary (Replace a) where
417 arbitrary = uncurry replace <$> arbitrary
418 -- If they happen to be equal then the patch is Keep.
420 instance ToSchema a => ToSchema (Replace a) where
421 declareNamedSchema (_ :: proxy (Replace a)) = do
422 -- TODO Keep constructor is not supported here.
423 aSchema <- declareSchemaRef (Proxy :: Proxy a)
424 return $ NamedSchema (Just "Replace") $ mempty
425 & type_ .~ SwaggerObject
427 InsOrdHashMap.fromList
431 & required .~ [ "old", "new" ]
434 NgramsPatch { _patch_children :: PatchMSet NgramsTerm
435 , _patch_list :: Replace ListType -- TODO Map UserId ListType
437 deriving (Eq, Show, Generic)
439 deriveJSON (unPrefix "_") ''NgramsPatch
440 makeLenses ''NgramsPatch
442 instance ToSchema NgramsPatch
444 instance Arbitrary NgramsPatch where
445 arbitrary = NgramsPatch <$> arbitrary <*> (replace <$> arbitrary <*> arbitrary)
447 type NgramsPatchIso = PairPatch (PatchMSet NgramsTerm) (Replace ListType)
449 _NgramsPatch :: Iso' NgramsPatch NgramsPatchIso
450 _NgramsPatch = iso (\(NgramsPatch c l) -> c :*: l) (\(c :*: l) -> NgramsPatch c l)
452 instance Semigroup NgramsPatch where
453 p <> q = _NgramsPatch # (p ^. _NgramsPatch <> q ^. _NgramsPatch)
455 instance Monoid NgramsPatch where
456 mempty = _NgramsPatch # mempty
458 instance Validity NgramsPatch where
459 validate p = p ^. _NgramsPatch . to validate
461 instance Transformable NgramsPatch where
462 transformable p q = transformable (p ^. _NgramsPatch) (q ^. _NgramsPatch)
464 conflicts p q = conflicts (p ^. _NgramsPatch) (q ^. _NgramsPatch)
466 transformWith conflict p q = (_NgramsPatch # p', _NgramsPatch # q')
468 (p', q') = transformWith conflict (p ^. _NgramsPatch) (q ^. _NgramsPatch)
470 type ConflictResolutionNgramsPatch =
471 ( ConflictResolutionPatchMSet NgramsTerm
472 , ConflictResolutionReplace ListType
474 type instance ConflictResolution NgramsPatch =
475 ConflictResolutionNgramsPatch
477 type PatchedNgramsPatch = (Set NgramsTerm, ListType)
478 -- ~ Patched NgramsPatchIso
479 type instance Patched NgramsPatch = PatchedNgramsPatch
481 instance Applicable NgramsPatch (Maybe NgramsRepoElement) where
482 applicable p Nothing = check (p == mempty) "NgramsPatch should be empty here"
483 applicable p (Just nre) =
484 applicable (p ^. patch_children) (nre ^. nre_children) <>
485 applicable (p ^. patch_list) (nre ^. nre_list)
487 instance Action NgramsPatch NgramsRepoElement where
488 act p = (nre_children %~ act (p ^. patch_children))
489 . (nre_list %~ act (p ^. patch_list))
491 instance Action NgramsPatch (Maybe NgramsRepoElement) where
494 newtype NgramsTablePatch = NgramsTablePatch (PatchMap NgramsTerm NgramsPatch)
495 deriving (Eq, Show, Generic, ToJSON, FromJSON, Semigroup, Monoid, Validity, Transformable)
497 instance FromField NgramsTablePatch
499 fromField = fromField'
501 instance FromField (PatchMap NgramsType (PatchMap NodeId NgramsTablePatch))
503 fromField = fromField'
505 --instance (Ord k, Action pv (Maybe v)) => Action (PatchMap k pv) (Map k v) where
507 type instance ConflictResolution NgramsTablePatch =
508 NgramsTerm -> ConflictResolutionNgramsPatch
510 type PatchedNgramsTablePatch = Map NgramsTerm PatchedNgramsPatch
511 -- ~ Patched (PatchMap NgramsTerm NgramsPatch)
512 type instance Patched NgramsTablePatch = PatchedNgramsTablePatch
514 makePrisms ''NgramsTablePatch
515 instance ToSchema (PatchMap NgramsTerm NgramsPatch)
516 instance ToSchema NgramsTablePatch
518 instance Applicable NgramsTablePatch (Maybe NgramsTableMap) where
519 applicable p = applicable (p ^. _NgramsTablePatch)
521 instance Action NgramsTablePatch (Maybe NgramsTableMap) where
523 fmap (execState (reParentNgramsTablePatch p)) .
524 act (p ^. _NgramsTablePatch)
526 instance Arbitrary NgramsTablePatch where
527 arbitrary = NgramsTablePatch <$> PM.fromMap <$> arbitrary
529 -- Should it be less than an Lens' to preserve PatchMap's abstraction.
530 -- ntp_ngrams_patches :: Lens' NgramsTablePatch (Map NgramsTerm NgramsPatch)
531 -- ntp_ngrams_patches = _NgramsTablePatch . undefined
533 type ReParent a = forall m. MonadState NgramsTableMap m => a -> m ()
535 reRootChildren :: NgramsTerm -> ReParent NgramsTerm
536 reRootChildren root ngram = do
537 nre <- use $ at ngram
538 forOf_ (_Just . nre_children . folded) nre $ \child -> do
539 at child . _Just . nre_root ?= root
540 reRootChildren root child
542 reParent :: Maybe RootParent -> ReParent NgramsTerm
543 reParent rp child = do
544 at child . _Just %= ( (nre_parent .~ (_rp_parent <$> rp))
545 . (nre_root .~ (_rp_root <$> rp))
547 reRootChildren (fromMaybe child (rp ^? _Just . rp_root)) child
549 reParentAddRem :: RootParent -> NgramsTerm -> ReParent AddRem
550 reParentAddRem rp child p =
551 reParent (if isRem p then Nothing else Just rp) child
553 reParentNgramsPatch :: NgramsTerm -> ReParent NgramsPatch
554 reParentNgramsPatch parent ngramsPatch = do
555 root_of_parent <- use (at parent . _Just . nre_root)
557 root = fromMaybe parent root_of_parent
558 rp = RootParent { _rp_root = root, _rp_parent = parent }
559 itraverse_ (reParentAddRem rp) (ngramsPatch ^. patch_children . _PatchMSet . _PatchMap)
560 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
562 reParentNgramsTablePatch :: ReParent NgramsTablePatch
563 reParentNgramsTablePatch p = itraverse_ reParentNgramsPatch (p ^. _NgramsTablePatch. _PatchMap)
564 -- TODO FoldableWithIndex/TraversableWithIndex for PatchMap
566 ------------------------------------------------------------------------
567 ------------------------------------------------------------------------
570 data Versioned a = Versioned
571 { _v_version :: Version
574 deriving (Generic, Show)
575 deriveJSON (unPrefix "_v_") ''Versioned
576 makeLenses ''Versioned
577 instance ToSchema a => ToSchema (Versioned a)
578 instance Arbitrary a => Arbitrary (Versioned a) where
579 arbitrary = Versioned 1 <$> arbitrary -- TODO 1 is constant so far
582 -- TODO sequencs of modifications (Patchs)
583 type NgramsIdPatch = Patch NgramsId NgramsPatch
585 ngramsPatch :: Int -> NgramsPatch
586 ngramsPatch n = NgramsPatch (DM.fromList [(1, StopTerm)]) (Set.fromList [n]) Set.empty
588 toEdit :: NgramsId -> NgramsPatch -> Edit NgramsId NgramsPatch
589 toEdit n p = Edit n p
590 ngramsIdPatch :: Patch NgramsId NgramsPatch
591 ngramsIdPatch = fromList $ catMaybes $ reverse [ replace (1::NgramsId) (Just $ ngramsPatch 1) Nothing
592 , replace (1::NgramsId) Nothing (Just $ ngramsPatch 2)
593 , replace (2::NgramsId) Nothing (Just $ ngramsPatch 2)
596 -- applyPatchBack :: Patch -> IO Patch
597 -- isEmptyPatch = Map.all (\x -> Set.isEmpty (add_children x) && Set.isEmpty ... )
599 ------------------------------------------------------------------------
600 ------------------------------------------------------------------------
601 ------------------------------------------------------------------------
603 -- TODO: find a better place for this Gargantext.API.{Common|Prelude|Core} ?
604 type QueryParamR = QueryParam' '[Required, Strict]
606 type TableNgramsApiGet = Summary " Table Ngrams API Get"
607 :> QueryParamR "ngramsType" TabType
608 :> QueryParamR "list" ListId
609 :> QueryParamR "limit" Limit
610 :> QueryParam "offset" Offset
611 :> QueryParam "listType" ListType
612 :> QueryParam "minTermSize" Int
613 :> QueryParam "maxTermSize" Int
614 :> QueryParam "search" Text
615 :> Get '[JSON] (Versioned NgramsTable)
617 type TableNgramsApi = Summary " Table Ngrams API Change"
618 :> QueryParamR "ngramsType" TabType
619 :> QueryParamR "list" ListId
620 :> ReqBody '[JSON] (Versioned NgramsTablePatch)
621 :> Put '[JSON] (Versioned NgramsTablePatch)
624 -- TODO: Replace.old is ignored which means that if the current list
625 -- `GraphTerm` and that the patch is `Replace CandidateTerm StopTerm` then
626 -- the list is going to be `StopTerm` while it should keep `GraphTerm`.
627 -- However this should not happen in non conflicting situations.
628 mkListsUpdate :: NgramsType -> NgramsTablePatch -> [(NgramsTypeId, NgramsTerm, ListTypeId)]
629 mkListsUpdate nt patches =
630 [ (ngramsTypeId nt, ng, listTypeId lt)
631 | (ng, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
632 , lt <- patch ^.. patch_list . new
635 mkChildrenGroups :: (PatchSet NgramsTerm -> Set NgramsTerm)
638 -> [(NgramsTypeId, NgramsParent, NgramsChild)]
639 mkChildrenGroups addOrRem nt patches =
640 [ (ngramsTypeId nt, parent, child)
641 | (parent, patch) <- patches ^.. ntp_ngrams_patches . ifolded . withIndex
642 , child <- patch ^.. patch_children . to addOrRem . folded
646 ngramsTypeFromTabType :: TabType -> NgramsType
647 ngramsTypeFromTabType tabType =
648 let lieu = "Garg.API.Ngrams: " :: Text in
650 Sources -> Ngrams.Sources
651 Authors -> Ngrams.Authors
652 Institutes -> Ngrams.Institutes
653 Terms -> Ngrams.NgramsTerms
654 _ -> panic $ lieu <> "No Ngrams for this tab"
655 -- ^ TODO: This `panic` would disapear with custom NgramsType.
657 ------------------------------------------------------------------------
659 { _r_version :: Version
662 -- ^ first patch in the list is the most recent
666 instance (FromJSON s, FromJSON p) => FromJSON (Repo s p) where
667 parseJSON = genericParseJSON $ unPrefix "_r_"
669 instance (ToJSON s, ToJSON p) => ToJSON (Repo s p) where
670 toJSON = genericToJSON $ unPrefix "_r_"
671 toEncoding = genericToEncoding $ unPrefix "_r_"
675 initRepo :: Monoid s => Repo s p
676 initRepo = Repo 1 mempty []
678 type NgramsRepo = Repo NgramsState NgramsStatePatch
679 type NgramsState = Map NgramsType (Map NodeId NgramsTableMap)
680 type NgramsStatePatch = PatchMap NgramsType (PatchMap NodeId NgramsTablePatch)
682 initMockRepo :: NgramsRepo
683 initMockRepo = Repo 1 s []
685 s = Map.singleton Ngrams.NgramsTerms
686 $ Map.singleton 47254
688 [ (n ^. ne_ngrams, ngramsElementToRepo n) | n <- mockTable ^. _NgramsTable ]
690 data RepoEnv = RepoEnv
691 { _renv_var :: !(MVar NgramsRepo)
692 , _renv_saver :: !(IO ())
693 , _renv_lock :: !FileLock
699 class HasRepoVar env where
700 repoVar :: Getter env (MVar NgramsRepo)
702 instance HasRepoVar (MVar NgramsRepo) where
705 class HasRepoSaver env where
706 repoSaver :: Getter env (IO ())
708 class (HasRepoVar env, HasRepoSaver env) => HasRepo env where
709 repoEnv :: Getter env RepoEnv
711 instance HasRepo RepoEnv where
714 instance HasRepoVar RepoEnv where
717 instance HasRepoSaver RepoEnv where
718 repoSaver = renv_saver
720 type RepoCmdM env err m =
726 ------------------------------------------------------------------------
728 saveRepo :: ( MonadReader env m, MonadIO m, HasRepoSaver env )
730 saveRepo = liftIO =<< view repoSaver
732 listTypeConflictResolution :: ListType -> ListType -> ListType
733 listTypeConflictResolution _ _ = undefined -- TODO Use Map User ListType
735 ngramsStatePatchConflictResolution
736 :: NgramsType -> NodeId -> NgramsTerm
737 -> ConflictResolutionNgramsPatch
738 ngramsStatePatchConflictResolution _ngramsType _nodeId _ngramsTerm
740 -- undefined {- TODO think this through -}, listTypeConflictResolution)
743 -- Insertions are not considered as patches,
744 -- they do not extend history,
745 -- they do not bump version.
746 insertNewOnly :: a -> Maybe b -> a
747 insertNewOnly m = maybe m (const $ error "insertNewOnly: impossible")
748 -- TODO error handling
750 something :: Monoid a => Maybe a -> a
751 something Nothing = mempty
752 something (Just a) = a
755 -- TODO refactor with putListNgrams
756 copyListNgrams :: RepoCmdM env err m
757 => NodeId -> NodeId -> NgramsType
759 copyListNgrams srcListId dstListId ngramsType = do
761 liftIO $ modifyMVar_ var $
762 pure . (r_state . at ngramsType %~ (Just . f . something))
765 f :: Map NodeId NgramsTableMap -> Map NodeId NgramsTableMap
766 f m = m & at dstListId %~ insertNewOnly (m ^. at srcListId)
768 -- TODO refactor with putListNgrams
769 -- The list must be non-empty!
770 -- The added ngrams must be non-existent!
771 addListNgrams :: RepoCmdM env err m
772 => NodeId -> NgramsType
773 -> [NgramsElement] -> m ()
774 addListNgrams listId ngramsType nes = do
776 liftIO $ modifyMVar_ var $
777 pure . (r_state . at ngramsType . _Just . at listId . _Just <>~ m)
780 m = Map.fromList $ (\n -> (n ^. ne_ngrams, n)) <$> nes
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 -- Apply the given patch to the DB and returns the patch to be applied on the
798 tableNgramsPatch :: (HasInvalidError err, RepoCmdM env err m)
799 => CorpusId -> TabType -> ListId
800 -> Versioned NgramsTablePatch
801 -> m (Versioned NgramsTablePatch)
802 tableNgramsPatch _corpusId tabType listId (Versioned p_version p_table)
803 | p_table == mempty = do
804 let ngramsType = ngramsTypeFromTabType tabType
807 r <- liftIO $ readMVar var
810 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
811 q_table = q ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
813 pure (Versioned (r ^. r_version) q_table)
816 let ngramsType = ngramsTypeFromTabType tabType
817 (p0, p0_validity) = PM.singleton listId p_table
818 (p, p_validity) = PM.singleton ngramsType p0
820 assertValid p0_validity
821 assertValid p_validity
824 vq' <- liftIO $ modifyMVar var $ \r -> do
826 q = mconcat $ take (r ^. r_version - p_version) (r ^. r_history)
827 (p', q') = transformWith ngramsStatePatchConflictResolution p q
828 r' = r & r_version +~ 1
830 & r_history %~ (p' :)
831 q'_table = q' ^. _PatchMap . at ngramsType . _Just . _PatchMap . at listId . _Just
833 -- Ideally we would like to check these properties. However:
834 -- * They should be checked only to debug the code. The client data
835 -- should be able to trigger these.
836 -- * What kind of error should they throw (we are in IO here)?
837 -- * Should we keep modifyMVar?
838 -- * Should we throw the validation in an Exception, catch it around
839 -- modifyMVar and throw it back as an Error?
840 assertValid $ transformable p q
841 assertValid $ applicable p' (r ^. r_state)
843 pure (r', Versioned (r' ^. r_version) q'_table)
848 mergeNgramsElement :: NgramsRepoElement -> NgramsRepoElement -> NgramsRepoElement
849 mergeNgramsElement _neOld neNew = neNew
851 { _ne_list :: ListType
852 If we merge the parents/children we can potentially create cycles!
853 , _ne_parent :: Maybe NgramsTerm
854 , _ne_children :: MSet NgramsTerm
858 getNgramsTableMap :: RepoCmdM env err m
859 => NodeId -> NgramsType -> m (Versioned NgramsTableMap)
860 getNgramsTableMap nodeId ngramsType = do
862 repo <- liftIO $ readMVar v
863 pure $ Versioned (repo ^. r_version)
864 (repo ^. r_state . at ngramsType . _Just . at nodeId . _Just)
869 -- | TODO Errors management
870 -- TODO: polymorphic for Annuaire or Corpus or ...
871 -- | Table of Ngrams is a ListNgrams formatted (sorted and/or cut).
872 -- TODO: should take only one ListId
873 getTableNgrams :: (RepoCmdM env err m, HasNodeError err, HasConnection env)
874 => CorpusId -> TabType
875 -> ListId -> Limit -> Maybe Offset
877 -> Maybe MinSize -> Maybe MaxSize
878 -> Maybe Text -- full text search
879 -> m (Versioned NgramsTable)
880 getTableNgrams cId tabType listId limit_ moffset
881 mlistType mminSize mmaxSize msearchQuery = do
882 let ngramsType = ngramsTypeFromTabType tabType
885 offset_ = maybe 0 identity moffset
886 listType = maybe (const True) (==) mlistType
887 minSize = maybe (const True) (<=) mminSize
888 maxSize = maybe (const True) (>=) mmaxSize
889 searchQuery = maybe (const True) isInfixOf msearchQuery
890 selected_node n = minSize s
892 && searchQuery (n ^. ne_ngrams)
893 && listType (n ^. ne_list)
897 selected_inner roots n = maybe False (`Set.member` roots) (n ^. ne_root)
899 finalize tableMap = NgramsTable $ roots <> inners
901 rootOf ne = maybe ne (\r -> ngramsElementFromRepo (r, fromMaybe (panic "getTableNgrams: invalid root") (tableMap ^. at r)))
903 list = ngramsElementFromRepo <$> Map.toList tableMap
904 selected_nodes = list & take limit_ . drop offset_ . filter selected_node
905 roots = rootOf <$> selected_nodes
906 rootsSet = Set.fromList (_ne_ngrams <$> roots)
907 inners = list & filter (selected_inner rootsSet)
909 -- lists <- catMaybes <$> listsWith userMaster
910 -- trace (show lists) $
911 -- getNgramsTableMap ({-lists <>-} listIds) ngramsType
913 table <- getNgramsTableMap listId ngramsType & mapped . v_data %~ finalize
914 occurrences <- getOccByNgramsOnlySafe cId ngramsType (table ^.. v_data . _NgramsTable . each . ne_ngrams)
917 setOcc ne = ne & ne_occurrences .~ sumOf (at (ne ^. ne_ngrams) . _Just) occurrences
919 pure $ table & v_data . _NgramsTable . each %~ setOcc