packages feed

bloodhound 0.7.0.1 → 0.8.0.0

raw patch · 8 files changed

+433/−193 lines, 8 filesdep +mtl-compatdep ~doctestdep ~semigroupsdep ~unordered-containerssetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: mtl-compat

Dependency ranges changed: doctest, semigroups, unordered-containers

API changes (from Hackage documentation)

- Database.Bloodhound.Types: data BH m a
- Database.Bloodhound.Types: found :: EsResult a -> Maybe Bool
+ Database.Bloodhound.Client: deleteTemplate :: MonadBH m => TemplateName -> m Reply
+ Database.Bloodhound.Client: putTemplate :: MonadBH m => IndexTemplate -> TemplateName -> m Reply
+ Database.Bloodhound.Client: scanSearch :: (FromJSON a, MonadBH m) => IndexName -> MappingName -> Search -> m [Hit a]
+ Database.Bloodhound.Client: templateExists :: MonadBH m => TemplateName -> m Bool
+ Database.Bloodhound.Types: BH :: ReaderT BHEnv m a -> BH m a
+ Database.Bloodhound.Types: DocumentParent :: DocId -> DocumentParent
+ Database.Bloodhound.Types: EsResultFound :: DocVersion -> a -> EsResultFound a
+ Database.Bloodhound.Types: Exclude :: [Pattern] -> Exclude
+ Database.Bloodhound.Types: FieldValueCount :: FieldName -> ValueCountAggregation
+ Database.Bloodhound.Types: FilterAgg :: FilterAggregation -> Aggregation
+ Database.Bloodhound.Types: FilterAggregation :: Filter -> Maybe Aggregations -> FilterAggregation
+ Database.Bloodhound.Types: Include :: [Pattern] -> Include
+ Database.Bloodhound.Types: IndexTemplate :: TemplatePattern -> Maybe IndexSettings -> [Value] -> IndexTemplate
+ Database.Bloodhound.Types: NoSource :: Source
+ Database.Bloodhound.Types: Pattern :: Text -> Pattern
+ Database.Bloodhound.Types: PopPattern :: Pattern -> PatternOrPatterns
+ Database.Bloodhound.Types: PopPatterns :: [Pattern] -> PatternOrPatterns
+ Database.Bloodhound.Types: Script :: Text -> Script
+ Database.Bloodhound.Types: ScriptValueCount :: Script -> ValueCountAggregation
+ Database.Bloodhound.Types: SearchTypeCount :: SearchType
+ Database.Bloodhound.Types: SearchTypeDfsQueryAndFetch :: SearchType
+ Database.Bloodhound.Types: SearchTypeDfsQueryThenFetch :: SearchType
+ Database.Bloodhound.Types: SearchTypeQueryAndFetch :: SearchType
+ Database.Bloodhound.Types: SearchTypeQueryThenFetch :: SearchType
+ Database.Bloodhound.Types: SearchTypeScan :: SearchType
+ Database.Bloodhound.Types: SourceIncludeExclude :: Include -> Exclude -> Source
+ Database.Bloodhound.Types: SourcePatterns :: PatternOrPatterns -> Source
+ Database.Bloodhound.Types: TemplateName :: Text -> TemplateName
+ Database.Bloodhound.Types: TemplatePattern :: Text -> TemplatePattern
+ Database.Bloodhound.Types: ValueCountAgg :: ValueCountAggregation -> Aggregation
+ Database.Bloodhound.Types: data EsResultFound a
+ Database.Bloodhound.Types: data Exclude
+ Database.Bloodhound.Types: data FilterAggregation
+ Database.Bloodhound.Types: data Include
+ Database.Bloodhound.Types: data IndexTemplate
+ Database.Bloodhound.Types: data PatternOrPatterns
+ Database.Bloodhound.Types: data SearchType
+ Database.Bloodhound.Types: data Source
+ Database.Bloodhound.Types: data ValueCountAggregation
+ Database.Bloodhound.Types: faAggs :: FilterAggregation -> Maybe Aggregations
+ Database.Bloodhound.Types: faFilter :: FilterAggregation -> Filter
+ Database.Bloodhound.Types: fields :: Search -> Maybe [FieldName]
+ Database.Bloodhound.Types: foundResult :: EsResult a -> Maybe (EsResultFound a)
+ Database.Bloodhound.Types: idsParent :: IndexDocumentSettings -> Maybe DocumentParent
+ Database.Bloodhound.Types: instance Constructor C1_0TemplateName
+ Database.Bloodhound.Types: instance Constructor C1_0TemplatePattern
+ Database.Bloodhound.Types: instance Datatype D1TemplateName
+ Database.Bloodhound.Types: instance Datatype D1TemplatePattern
+ Database.Bloodhound.Types: instance Eq DocumentParent
+ Database.Bloodhound.Types: instance Eq Exclude
+ Database.Bloodhound.Types: instance Eq FilterAggregation
+ Database.Bloodhound.Types: instance Eq Include
+ Database.Bloodhound.Types: instance Eq IndexDocumentSettings
+ Database.Bloodhound.Types: instance Eq Pattern
+ Database.Bloodhound.Types: instance Eq PatternOrPatterns
+ Database.Bloodhound.Types: instance Eq Script
+ Database.Bloodhound.Types: instance Eq SearchType
+ Database.Bloodhound.Types: instance Eq Source
+ Database.Bloodhound.Types: instance Eq TemplateName
+ Database.Bloodhound.Types: instance Eq TemplatePattern
+ Database.Bloodhound.Types: instance Eq ValueCountAggregation
+ Database.Bloodhound.Types: instance Eq a => Eq (EsResultFound a)
+ Database.Bloodhound.Types: instance FromJSON a => FromJSON (EsResultFound a)
+ Database.Bloodhound.Types: instance Generic TemplateName
+ Database.Bloodhound.Types: instance Generic TemplatePattern
+ Database.Bloodhound.Types: instance MonadCatch m => MonadCatch (BH m)
+ Database.Bloodhound.Types: instance MonadMask m => MonadMask (BH m)
+ Database.Bloodhound.Types: instance MonadThrow m => MonadThrow (BH m)
+ Database.Bloodhound.Types: instance Show DocumentParent
+ Database.Bloodhound.Types: instance Show Exclude
+ Database.Bloodhound.Types: instance Show FilterAggregation
+ Database.Bloodhound.Types: instance Show Include
+ Database.Bloodhound.Types: instance Show IndexDocumentSettings
+ Database.Bloodhound.Types: instance Show Pattern
+ Database.Bloodhound.Types: instance Show PatternOrPatterns
+ Database.Bloodhound.Types: instance Show Script
+ Database.Bloodhound.Types: instance Show SearchType
+ Database.Bloodhound.Types: instance Show Source
+ Database.Bloodhound.Types: instance Show TemplateName
+ Database.Bloodhound.Types: instance Show TemplatePattern
+ Database.Bloodhound.Types: instance Show ValueCountAggregation
+ Database.Bloodhound.Types: instance Show a => Show (EsResultFound a)
+ Database.Bloodhound.Types: instance ToJSON Exclude
+ Database.Bloodhound.Types: instance ToJSON Include
+ Database.Bloodhound.Types: instance ToJSON IndexTemplate
+ Database.Bloodhound.Types: instance ToJSON Pattern
+ Database.Bloodhound.Types: instance ToJSON PatternOrPatterns
+ Database.Bloodhound.Types: instance ToJSON Source
+ Database.Bloodhound.Types: instance ToJSON TemplateName
+ Database.Bloodhound.Types: instance ToJSON TemplatePattern
+ Database.Bloodhound.Types: matchQueryBoost :: MatchQuery -> Maybe Boost
+ Database.Bloodhound.Types: newtype BH m a
+ Database.Bloodhound.Types: newtype DocumentParent
+ Database.Bloodhound.Types: newtype Pattern
+ Database.Bloodhound.Types: newtype Script
+ Database.Bloodhound.Types: newtype TemplateName
+ Database.Bloodhound.Types: newtype TemplatePattern
+ Database.Bloodhound.Types: scriptText :: Script -> Text
+ Database.Bloodhound.Types: scrollId :: SearchResult a -> Maybe ScrollId
+ Database.Bloodhound.Types: searchType :: Search -> SearchType
+ Database.Bloodhound.Types: source :: Search -> Maybe Source
+ Database.Bloodhound.Types: templateMappings :: IndexTemplate -> [Value]
+ Database.Bloodhound.Types: templatePattern :: IndexTemplate -> TemplatePattern
+ Database.Bloodhound.Types: templateSettings :: IndexTemplate -> Maybe IndexSettings
+ Database.Bloodhound.Types: type ScrollId = Text
+ Database.Bloodhound.Types: unBH :: BH m a -> ReaderT BHEnv m a
- Database.Bloodhound.Types: EsResult :: Text -> Text -> Text -> DocVersion -> Maybe Bool -> a -> EsResult a
+ Database.Bloodhound.Types: EsResult :: Text -> Text -> Text -> Maybe (EsResultFound a) -> EsResult a
- Database.Bloodhound.Types: IndexDocumentSettings :: VersionControl -> IndexDocumentSettings
+ Database.Bloodhound.Types: IndexDocumentSettings :: VersionControl -> Maybe DocumentParent -> IndexDocumentSettings
- Database.Bloodhound.Types: MatchQuery :: FieldName -> QueryString -> BooleanOperator -> ZeroTermsQuery -> Maybe CutoffFrequency -> Maybe MatchQueryType -> Maybe Analyzer -> Maybe MaxExpansions -> Maybe Lenient -> MatchQuery
+ Database.Bloodhound.Types: MatchQuery :: FieldName -> QueryString -> BooleanOperator -> ZeroTermsQuery -> Maybe CutoffFrequency -> Maybe MatchQueryType -> Maybe Analyzer -> Maybe MaxExpansions -> Maybe Lenient -> Maybe Boost -> MatchQuery
- Database.Bloodhound.Types: Search :: Maybe Query -> Maybe Filter -> Maybe Sort -> Maybe Aggregations -> Maybe Highlights -> TrackSortScores -> From -> Size -> Search
+ Database.Bloodhound.Types: Search :: Maybe Query -> Maybe Filter -> Maybe Sort -> Maybe Aggregations -> Maybe Highlights -> TrackSortScores -> From -> Size -> SearchType -> Maybe [FieldName] -> Maybe Source -> Search
- Database.Bloodhound.Types: SearchResult :: Int -> Bool -> ShardResult -> SearchHits a -> Maybe AggregationResults -> SearchResult a
+ Database.Bloodhound.Types: SearchResult :: Int -> Bool -> ShardResult -> SearchHits a -> Maybe AggregationResults -> Maybe ScrollId -> SearchResult a
- Database.Bloodhound.Types: _source :: EsResult a -> a
+ Database.Bloodhound.Types: _source :: EsResultFound a -> a
- Database.Bloodhound.Types: _version :: EsResult a -> DocVersion
+ Database.Bloodhound.Types: _version :: EsResultFound a -> DocVersion

Files

Setup.hs view
@@ -1,51 +1,7 @@-{-# OPTIONS_GHC -Wall #-}-module Main (main) where+#!/usr/bin/env runhaskell -import Data.List ( nub )-import Data.Version ( showVersion )-import Distribution.Package ( PackageName(PackageName), Package, PackageId, InstalledPackageId, packageVersion, packageName )-import Distribution.PackageDescription ( PackageDescription(), TestSuite(..) )-import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )-import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose, copyFiles )-import Distribution.Simple.BuildPaths ( autogenModulesDir )-import Distribution.Simple.Setup ( BuildFlags(buildVerbosity), Flag(..), fromFlag, HaddockFlags(haddockDistPref))-import Distribution.Simple.LocalBuildInfo ( withLibLBI, withTestLBI, LocalBuildInfo(), ComponentLocalBuildInfo(componentPackageDeps) )-import Distribution.Text ( display )-import Distribution.Verbosity ( Verbosity, normal )-import System.FilePath ( (</>) )+module Main where+import Distribution.Simple  main :: IO ()-main = defaultMainWithHooks simpleUserHooks-  { buildHook = \pkg lbi hooks flags -> do-     generateBuildModule (fromFlag (buildVerbosity flags)) pkg lbi-     buildHook simpleUserHooks pkg lbi hooks flags-  , postHaddock = \args flags pkg lbi -> do-     -- copyFiles normal (haddockOutputDir flags pkg) [("images","Hierarchy.png")]-     postHaddock simpleUserHooks args flags pkg lbi-  }--haddockOutputDir :: Package p => HaddockFlags -> p -> FilePath-haddockOutputDir flags pkg = destDir where-  baseDir = case haddockDistPref flags of-    NoFlag -> "."-    Flag x -> x-  destDir = baseDir </> "doc" </> "html" </> display (packageName pkg)--generateBuildModule :: Verbosity -> PackageDescription -> LocalBuildInfo -> IO ()-generateBuildModule verbosity pkg lbi = do-  let dir = autogenModulesDir lbi-  createDirectoryIfMissingVerbose verbosity True dir-  withLibLBI pkg lbi $ \_ libcfg -> do-    withTestLBI pkg lbi $ \suite suitecfg -> do-      rewriteFile (dir </> "Build_" ++ testName suite ++ ".hs") $ unlines-        [ "module Build_" ++ testName suite ++ " where"-        , "deps :: [String]"-        , "deps = " ++ (show $ formatdeps (testDeps libcfg suitecfg))-        ]-  where-    formatdeps = map (formatone . snd)-    formatone p = case packageName p of-      PackageName n -> n ++ "-" ++ showVersion (packageVersion p)--testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)]-testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys+main = defaultMain
bloodhound.cabal view
@@ -1,5 +1,5 @@ name:                bloodhound-version:             0.7.0.1+version:             0.8.0.0 synopsis:            ElasticSearch client library for Haskell description:         ElasticSearch made awesome for Haskell hackers homepage:            https://github.com/bitemyapp/bloodhound@@ -28,7 +28,7 @@                        containers       >= 0.5.0.0 && <0.6,                        aeson            >= 0.7     && <0.10,                        http-client      >= 0.3     && <0.5,-                       semigroups       >= 0.15    && <0.17,+                       semigroups       >= 0.15    && <0.18,                        time             >= 1.4     && <1.6,                        text             >= 0.11    && <1.3,                        mtl              >= 1.0     && <2.3,@@ -38,7 +38,9 @@                        uri-bytestring   >= 0.1     && <0.2,                        exceptions,                        data-default-class,-                       blaze-builder+                       blaze-builder,+                       unordered-containers,+                       mtl-compat   default-language:    Haskell2010  test-suite tests@@ -65,15 +67,20 @@   default-language:    Haskell2010  test-suite doctests+  ghc-options:      -threaded -Wall   default-language: Haskell2010   type:             exitcode-stdio-1.0   main-is:          doctests.hs-  hs-source-dirs:   tests+  hs-source-dirs:   tests, src   if impl(ghc >= 7.8)     build-depends:    base,                       directory,-                      doctest,+                      doctest >= 0.10.1,                       doctest-prop,                       filepath   else     buildable: False++Source-Repository head+  Type:     git+  Location: git://github.com/bitemyapp/bloodhound.git
src/Database/Bloodhound/Client.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE RecordWildCards     #-}  ------------------------------------------------------------------------------- -- |@@ -27,6 +27,9 @@        , indexExists        , openIndex        , closeIndex+       , putTemplate+       , templateExists+       , deleteTemplate        , putMapping        , deleteMapping        , indexDocument@@ -36,6 +39,7 @@        , searchAll        , searchByIndex        , searchByType+       , scanSearch        , refreshIndex        , mkSearch        , mkAggregateSearch@@ -88,7 +92,7 @@ -- >>> let runBH' = withBH defaultManagerSettings testServer -- >>> let testIndex = IndexName "twitter" -- >>> let testMapping = MappingName "tweet"--- >>> let defaultIndexSettings = IndexSettings (ShardCount 3) (ReplicaCount 2)+-- >>> let defaultIndexSettings = IndexSettings (ShardCount 1) (ReplicaCount 0) -- >>> data TweetMapping = TweetMapping deriving (Eq, Show) -- >>> _ <- runBH' $ deleteIndex testIndex >> deleteMapping testIndex testMapping -- >>> import GHC.Generics@@ -168,6 +172,18 @@   Server s <- bhServer <$> getBHEnv   return $ joinPath' (s:ps) +appendSearchTypeParam :: Text -> SearchType -> Text+appendSearchTypeParam originalUrl st = addQuery params originalUrl+  where stText = "search_type"+        params+          | st == SearchTypeDfsQueryThenFetch = [(stText, Just "dfs_query_then_fetch")]+          | st == SearchTypeCount             = [(stText, Just "count")]+          | st == SearchTypeScan              = [(stText, Just "scan"), ("scroll", Just "1m")]+          | st == SearchTypeQueryAndFetch     = [(stText, Just "query_and_fetch")]+          | st == SearchTypeDfsQueryAndFetch  = [(stText, Just "dfs_query_and_fetch")]+        -- used to catch 'SearchTypeQueryThenFetch', which is also the default+          | otherwise                         = [(stText, Just "query_then_fetch")]+ -- | Severely dumbed down query renderer. Assumes your data doesn't -- need any encoding addQuery :: [(Text, Maybe Text)] -> Text -> Text@@ -177,17 +193,17 @@       T.decodeUtf8 $ BB.toByteString $ NHTU.renderQueryText prependQuestionMark q     prependQuestionMark = True - bindM2 :: (Applicative m, Monad m) => (a -> b -> m c) -> m a -> m b -> m c bindM2 f ma mb = join (f <$> ma <*> mb) --- | Convenience function that sets up a mananager and BHEnv and runs+-- | Convenience function that sets up a manager and BHEnv and runs -- the given set of bloodhound operations. Connections will be -- pipelined automatically in accordance with the given manager -- settings in IO. If you've got your own monad transformer stack, you -- should use 'runBH' directly. withBH :: ManagerSettings -> Server -> BH IO a -> IO a-withBH ms s f = withManager ms $ \mgr -> do+withBH ms s f = do+  mgr <- newManager ms   let env = BHEnv { bhServer  = s                   , bhManager = mgr }   runBH env f@@ -200,7 +216,7 @@ head   :: MonadBH m => Text -> m Reply head   = flip (dispatch NHTM.methodHead) Nothing put    :: MonadBH m => Text -> Maybe L.ByteString -> m Reply-put    = dispatch NHTM.methodPost+put    = dispatch NHTM.methodPut post   :: MonadBH m => Text -> Maybe L.ByteString -> m Reply post   = dispatch NHTM.methodPost @@ -301,6 +317,35 @@ closeIndex :: MonadBH m => IndexName -> m Reply closeIndex = openOrCloseIndexes CloseIndex +-- | 'putTemplate' creates a template given an 'IndexTemplate' and a 'TemplateName'.+--   Explained in further detail at+--   <https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html>+--+--   >>> let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]+--   >>> resp <- runBH' $ putTemplate idxTpl (TemplateName "tweet-tpl")+putTemplate :: MonadBH m => IndexTemplate -> TemplateName -> m Reply+putTemplate indexTemplate (TemplateName templateName) =+  bindM2 put url (return body)+  where url = joinPath ["_template", templateName]+        body = Just $ encode indexTemplate++-- | 'templateExists' checks to see if a template exists.+--+--   >>> exists <- runBH' $ templateExists (TemplateName "tweet-tpl")+templateExists :: MonadBH m => TemplateName -> m Bool+templateExists (TemplateName templateName) = do+  (_, exists) <- existentialQuery =<< joinPath ["_template", templateName]+  return exists++-- | 'deleteTemplate' is an HTTP DELETE and deletes a template.+--+--   >>> let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]+--   >>> _ <- runBH' $ putTemplate idxTpl (TemplateName "tweet-tpl")+--   >>> resp <- runBH' $ deleteTemplate (TemplateName "tweet-tpl")+deleteTemplate :: MonadBH m => TemplateName -> m Reply+deleteTemplate (TemplateName templateName) =+  delete =<< joinPath ["_template", templateName]+ -- | 'putMapping' is an HTTP PUT and has upsert semantics. Mappings are schemas -- for documents in indexes. --@@ -312,7 +357,9 @@                  -> MappingName -> a -> m Reply putMapping (IndexName indexName) (MappingName mappingName) mapping =   bindM2 put url (return body)-  where url = joinPath [indexName, mappingName, "_mapping"]+  where url = joinPath [indexName, "_mapping", mappingName]+        -- "_mapping" and mappingName above were originally transposed+        -- erroneously. The correct API call is: "/INDEX/_mapping/MAPPING_NAME"         body = Just $ encode mapping  -- | 'deleteMapping' is an HTTP DELETE and deletes a mapping for a given index.@@ -326,7 +373,9 @@ deleteMapping :: MonadBH m => IndexName -> MappingName -> m Reply deleteMapping (IndexName indexName)   (MappingName mappingName) =-  delete =<< joinPath [indexName, mappingName, "_mapping"]+  -- "_mapping" and mappingName below were originally transposed+  -- erroneously. The correct API call is: "/INDEX/_mapping/MAPPING_NAME"+  delete =<< joinPath [indexName, "_mapping", mappingName]  -- | 'indexDocument' is the primary way to save a single document in --   Elasticsearch. The document itself is simply something we can@@ -342,7 +391,7 @@   (MappingName mappingName) cfg document (DocId docId) =   bindM2 put url (return body)   where url = addQuery params <$> joinPath [indexName, mappingName, docId]-        params = case idsVersionControl cfg of+        versionCtlParams = case idsVersionControl cfg of           NoVersionControl -> []           InternalVersion v -> versionParams v "internal"           ExternalGT (ExternalDocVersion v) -> versionParams v "external_gt"@@ -352,6 +401,10 @@         versionParams v t = [ ("version", Just $ vt v)                             , ("version_type", Just t)                             ]+        parentParams = case idsParent cfg of+          Nothing -> []+          Just (DocumentParent (DocId p)) -> [ ("parent", Just p) ]+        params = versionCtlParams ++ parentParams         body = Just (encode document)  -- | 'deleteDocument' is the primary way to delete a single document.@@ -456,7 +509,8 @@   where url = joinPath [indexName, mappingName, docId]  dispatchSearch :: MonadBH m => Text -> Search -> m Reply-dispatchSearch url search = post url (Just (encode search))+dispatchSearch url search = post url' (Just (encode search))+  where url' = appendSearchTypeParam url (searchType search)  -- | 'searchAll', given a 'Search', will perform that search against all indexes --   on an Elasticsearch server. Try to avoid doing this if it can be helped.@@ -490,6 +544,42 @@   (MappingName mappingName) = bindM2 dispatchSearch url . return   where url = joinPath [indexName, mappingName, "_search"] +scanSearch' :: MonadBH m => IndexName -> MappingName -> Search -> m (Maybe ScrollId)+scanSearch' (IndexName indexName) (MappingName mappingName) search = do+    let url = joinPath [indexName, mappingName, "_search"]+        search' = search { searchType = SearchTypeScan }+    resp' <- bindM2 dispatchSearch url (return search')+    let msr = decode' $ responseBody resp' :: Maybe (SearchResult ())+        msid = maybe Nothing scrollId msr+    return msid++scroll' :: (FromJSON a, MonadBH m) => Maybe ScrollId -> m ([Hit a], Maybe ScrollId)+scroll' Nothing = return ([], Nothing)+scroll' (Just sid) = do+    url <- joinPath ["_search/scroll?scroll=1m"]+    resp' <- post url (Just . L.fromStrict $ T.encodeUtf8 sid)+    let msr = decode' $ responseBody resp' :: FromJSON a => Maybe (SearchResult a)+        resp = case msr of+            Just sr -> (hits $ searchHits sr, scrollId sr)+            _       -> ([], Nothing)+    return resp++simpleAccumilator :: (FromJSON a, MonadBH m) => [Hit a] -> ([Hit a], Maybe ScrollId) -> m ([Hit a], Maybe ScrollId)+simpleAccumilator oldHits (newHits, Nothing) = return (oldHits ++ newHits, Nothing)+simpleAccumilator oldHits ([], _) = return (oldHits, Nothing)+simpleAccumilator oldHits (newHits, msid) = do+    (newHits', msid') <- scroll' msid+    simpleAccumilator (oldHits ++ newHits) (newHits', msid')++-- | 'scanSearch' uses the 'scan&scroll' API of elastic,+-- for a given 'IndexName' and 'MappingName',+scanSearch :: (FromJSON a, MonadBH m) => IndexName -> MappingName -> Search -> m [Hit a]+scanSearch indexName mappingName search = do+    msid <- scanSearch' indexName mappingName search+    (hits, msid') <- scroll' msid+    (totalHits, _) <- simpleAccumilator [] (hits, msid')+    return totalHits+ -- | 'mkSearch' is a helper function for defaulting additional fields of a 'Search' --   to Nothing in case you only care about your 'Query' and 'Filter'. Use record update --   syntax if you want to add things like aggregations or highlights while still using@@ -497,9 +587,9 @@ -- -- >>> let query = TermQuery (Term "user" "bitemyapp") Nothing -- >>> mkSearch (Just query) Nothing--- Search {queryBody = Just (TermQuery (Term {termField = "user", termValue = "bitemyapp"}) Nothing), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 0, size = Size 10}+-- Search {queryBody = Just (TermQuery (Term {termField = "user", termValue = "bitemyapp"}) Nothing), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 0, size = Size 10, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing} mkSearch :: Maybe Query -> Maybe Filter -> Search-mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10)+mkSearch query filter = Search query filter Nothing Nothing Nothing False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing  -- | 'mkAggregateSearch' is a helper function that defaults everything in a 'Search' except for --   the 'Query' and the 'Aggregation'.@@ -509,7 +599,7 @@ -- TermsAgg (TermsAggregation {term = Left "user", termInclude = Nothing, termExclude = Nothing, termOrder = Nothing, termMinDocCount = Nothing, termSize = Nothing, termShardSize = Nothing, termCollectMode = Just BreadthFirst, termExecutionHint = Nothing, termAggs = Nothing}) -- >>> let myAggregation = mkAggregateSearch Nothing $ mkAggregations "users" terms mkAggregateSearch :: Maybe Query -> Aggregations -> Search-mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0)+mkAggregateSearch query mkSearchAggs = Search query Nothing Nothing (Just mkSearchAggs) Nothing False (From 0) (Size 0) SearchTypeQueryThenFetch Nothing Nothing  -- | 'mkHighlightSearch' is a helper function that defaults everything in a 'Search' except for --   the 'Query' and the 'Aggregation'.@@ -518,7 +608,7 @@ -- >>> let testHighlight = Highlights Nothing [FieldHighlight (FieldName "message") Nothing] -- >>> let search = mkHighlightSearch (Just query) testHighlight mkHighlightSearch :: Maybe Query -> Highlights -> Search-mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10)+mkHighlightSearch query searchHighlights = Search query Nothing Nothing Nothing (Just searchHighlights) False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing  -- | 'pageSearch' is a helper function that takes a search and assigns the from --    and size fields for the search. The from parameter defines the offset@@ -528,9 +618,9 @@ -- >>> let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell") -- >>> let search = mkSearch (Just query) Nothing -- >>> search--- Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 0, size = Size 10}+-- Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing, matchQueryBoost = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 0, size = Size 10, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing} -- >>> pageSearch (From 10) (Size 100) search--- Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 10, size = Size 100}+-- Search {queryBody = Just (QueryMatchQuery (MatchQuery {matchQueryField = FieldName "_all", matchQueryQueryString = QueryString "haskell", matchQueryOperator = Or, matchQueryZeroTerms = ZeroTermsNone, matchQueryCutoffFrequency = Nothing, matchQueryMatchType = Nothing, matchQueryAnalyzer = Nothing, matchQueryMaxExpansions = Nothing, matchQueryLenient = Nothing, matchQueryBoost = Nothing})), filterBody = Nothing, sortBody = Nothing, aggBody = Nothing, highlight = Nothing, trackSortScores = False, from = From 10, size = Size 100, searchType = SearchTypeQueryThenFetch, fields = Nothing, source = Nothing} pageSearch :: From     -- ^ The result offset            -> Size     -- ^ The number of results to return            -> Search  -- ^ The current seach
src/Database/Bloodhound/Types.hs view
@@ -47,7 +47,7 @@        , toTerms        , toDateHistogram        , omitNulls-       , BH+       , BH(..)        , runBH        , BHEnv(..)        , MonadBH(..)@@ -56,20 +56,30 @@        , Existence(..)        , NullValue(..)        , IndexSettings(..)+       , IndexTemplate(..)        , Server(..)        , Reply        , EsResult(..)+       , EsResultFound(..)        , DocVersion        , ExternalDocVersion(..)        , VersionControl(..)+       , DocumentParent(..)        , IndexDocumentSettings(..)        , Query(..)        , Search(..)+       , SearchType(..)        , SearchResult(..)+       , ScrollId        , SearchHits(..)        , TrackSortScores        , From(..)        , Size(..)+       , Source(..)+       , PatternOrPatterns(..)+       , Include(..)+       , Exclude(..)+       , Pattern(..)        , ShardResult(..)        , Hit(..)        , Filter(..)@@ -100,7 +110,10 @@        , RegexpFlags(..)        , RegexpFlag(..)        , FieldName(..)+       , Script(..)        , IndexName(..)+       , TemplateName(..)+       , TemplatePattern(..)        , MappingName(..)        , DocId(..)        , CacheName(..)@@ -196,6 +209,8 @@        , Bucket(..)        , BucketAggregation(..)        , TermsAggregation(..)+       , ValueCountAggregation(..)+       , FilterAggregation(..)        , DateHistogramAggregation(..)         , Highlights(..)@@ -215,14 +230,16 @@          ) where  import           Control.Applicative-import           Control.Monad.Error+import           Control.Monad.Catch+import           Control.Monad.Except import           Control.Monad.Reader import           Control.Monad.State import           Control.Monad.Writer import           Data.Aeson import           Data.Aeson.Types                (Pair, emptyObject, parseMaybe) import qualified Data.ByteString.Lazy.Char8      as L-import           Data.List                       (nub)+import qualified Data.HashMap.Strict             as HM (union)+import           Data.List                       (foldl', nub) import           Data.List.NonEmpty              (NonEmpty (..), toList) import qualified Data.Map.Strict                 as M import           Data.Maybe@@ -274,7 +291,10 @@                , MonadError e                , Alternative                , MonadPlus-               , MonadFix)+               , MonadFix+               , MonadThrow+               , MonadCatch+               , MonadMask)  instance MonadTrans BH where   lift = BH . lift@@ -348,6 +368,20 @@ data FieldDefinition =   FieldDefinition { fieldType :: FieldType } deriving (Eq, Show) +{-| An 'IndexTemplate' defines a template that will automatically be+    applied to new indices created. The templates include both+    'IndexSettings' and mappings, and a simple 'TemplatePattern' that+    controls if the template will be applied to the index created.+    Specify mappings as follows: @[toJSON TweetMapping, ...]@++    https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices-templates.html+-}+data IndexTemplate =+  IndexTemplate { templatePattern  :: TemplatePattern+                , templateSettings :: Maybe IndexSettings+                , templateMappings :: [Value]+                }+ data MappingField =   MappingField   { mappingFieldName :: FieldName                  , fieldDefinition  :: FieldDefinition } deriving (Eq, Show)@@ -375,15 +409,22 @@   | BulkUpdate IndexName MappingName DocId Value deriving (Eq, Show)  {-| 'EsResult' describes the standard wrapper JSON document that you see in-    successful Elasticsearch responses.+    successful Elasticsearch lookups or lookups that couldn't find the document. -}-data EsResult a = EsResult { _index   :: Text-                           , _type    :: Text-                           , _id      :: Text-                           , _version :: DocVersion-                           , found    :: Maybe Bool-                           , _source  :: a } deriving (Eq, Show)+data EsResult a = EsResult { _index      :: Text+                           , _type       :: Text+                           , _id         :: Text+                           , foundResult :: Maybe (EsResultFound a)} deriving (Eq, Show) ++{-| 'EsResultFound' contains the document and its metadata inside of an+    'EsResult' when the document was successfully found.+-}+data EsResultFound a = EsResultFound {  _version :: DocVersion+                                     , _source   :: a } deriving (Eq, Show)+++ {-| 'DocVersion' is an integer version number for a document between 1 and 9.2e+18 used for <<https://www.elastic.co/guide/en/elasticsearch/guide/current/optimistic-concurrency-control.html optimistic concurrency control>>. -}@@ -439,18 +480,24 @@                     -- care, as this could result in data loss.                     deriving (Show, Eq, Ord) +{-| 'DocumentParent' is used to specify a parent document.+-}+newtype DocumentParent = DocumentParent DocId+  deriving (Eq, Show)+ {-| 'IndexDocumentSettings' are special settings supplied when indexing a document. For the best backwards compatiblity when new fields are added, you should probably prefer to start with 'defaultIndexDocumentSettings' -}-data IndexDocumentSettings = IndexDocumentSettings {-      idsVersionControl :: VersionControl-    }+data IndexDocumentSettings =+  IndexDocumentSettings { idsVersionControl :: VersionControl+                        , idsParent         :: Maybe DocumentParent+                        } deriving (Eq, Show) -{-| Reasonable default settings. Chooses no version control.+{-| Reasonable default settings. Chooses no version control and no parent. -} defaultIndexDocumentSettings :: IndexDocumentSettings-defaultIndexDocumentSettings = IndexDocumentSettings NoVersionControl+defaultIndexDocumentSettings = IndexDocumentSettings NoVersionControl Nothing  {-| 'Sort' is a synonym for a list of 'SortSpec's. Sort behavior is order     dependent with later sorts acting as tie-breakers for earlier sorts.@@ -546,6 +593,14 @@ -} newtype IndexName = IndexName Text deriving (Eq, Generic, Show) +{-| 'TemplateName' is used to describe which template to query/create/delete+-}+newtype TemplateName = TemplateName Text deriving (Eq, Show, Generic)++{-| 'TemplatePattern' represents a pattern which is matched against index names+-}+newtype TemplatePattern = TemplatePattern Text deriving (Eq, Show, Generic)+ {-| 'MappingName' is part of mappings which are how ES describes and schematizes     the data in the indices. -}@@ -566,6 +621,12 @@ -} newtype FieldName = FieldName Text deriving (Eq, Show) ++{-| 'Script' is often used in place of 'FieldName' to specify more+complex ways of extracting a value from a document.+-}+newtype Script = Script { scriptText :: Text } deriving (Eq, Show)+ {-| 'CacheName' is used in 'RegexpFilter' for describing the     'CacheKey' keyed caching behavior. -}@@ -681,8 +742,33 @@                        -- default False                      , trackSortScores :: TrackSortScores                      , from            :: From-                     , size            :: Size } deriving (Eq, Show)+                     , size            :: Size+                     , searchType      :: SearchType+                     , fields          :: Maybe [FieldName]+                     , source          :: Maybe Source } deriving (Eq, Show) +data SearchType = SearchTypeQueryThenFetch+                | SearchTypeDfsQueryThenFetch+                | SearchTypeCount+                | SearchTypeScan+                | SearchTypeQueryAndFetch+                | SearchTypeDfsQueryAndFetch+  deriving (Eq, Show)++data Source =+  NoSource+  | SourcePatterns PatternOrPatterns+  | SourceIncludeExclude Include Exclude+    deriving (Show, Eq)++data PatternOrPatterns = PopPattern   Pattern+                       | PopPatterns [Pattern] deriving (Eq, Show)++data Include = Include [Pattern] deriving (Eq, Show)+data Exclude = Exclude [Pattern] deriving (Eq, Show)++newtype Pattern = Pattern Text deriving (Eq, Show)+ data Highlights = Highlights { globalsettings  :: Maybe HighlightSettings                              , highlightFields :: [FieldHighlight]                              } deriving (Show, Eq)@@ -969,13 +1055,14 @@              , matchQueryMatchType       :: Maybe MatchQueryType              , matchQueryAnalyzer        :: Maybe Analyzer              , matchQueryMaxExpansions   :: Maybe MaxExpansions-             , matchQueryLenient         :: Maybe Lenient } deriving (Eq, Show)+             , matchQueryLenient         :: Maybe Lenient+             , matchQueryBoost           :: Maybe Boost } deriving (Eq, Show)  {-| 'mkMatchQuery' is a convenience function that defaults the less common parameters,     enabling you to provide only the 'FieldName' and 'QueryString' to make a 'MatchQuery' -} mkMatchQuery :: FieldName -> QueryString -> MatchQuery-mkMatchQuery field query = MatchQuery field query Or ZeroTermsNone Nothing Nothing Nothing Nothing Nothing+mkMatchQuery field query = MatchQuery field query Or ZeroTermsNone Nothing Nothing Nothing Nothing Nothing Nothing  data MatchQueryType =   MatchPhrase@@ -1194,8 +1281,11 @@                , timedOut     :: Bool                , shards       :: ShardResult                , searchHits   :: SearchHits a-               , aggregations :: Maybe AggregationResults } deriving (Eq, Show)+               , aggregations :: Maybe AggregationResults+               , scrollId     :: Maybe ScrollId } deriving (Eq, Show) +type ScrollId = Text  -- Fixme: Newtype+ type Score = Maybe Double  data SearchHits a =@@ -1268,7 +1358,9 @@               | FractionalInterval Float TimeInterval deriving (Eq, Show)  data Aggregation = TermsAgg TermsAggregation-                 | DateHistogramAgg DateHistogramAggregation deriving (Eq, Show)+                 | DateHistogramAgg DateHistogramAggregation+                 | ValueCountAgg ValueCountAggregation+                 | FilterAgg FilterAggregation deriving (Eq, Show)   data TermsAggregation = TermsAggregation { term              :: Either Text Text@@ -1294,7 +1386,14 @@                                                          , dateAggs       :: Maybe Aggregations                                                          } deriving (Eq, Show) +-- | See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html> for more information.+data ValueCountAggregation = FieldValueCount FieldName+                           | ScriptValueCount Script deriving (Eq, Show) +-- | Single-bucket filter aggregations. See <https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html#search-aggregations-bucket-filter-aggregation> for more information.+data FilterAggregation = FilterAggregation { faFilter :: Filter+                                           , faAggs   :: Maybe Aggregations} deriving (Eq, Show)+ mkTermsAggregation :: Text -> TermsAggregation mkTermsAggregation t = TermsAggregation (Left t) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing @@ -1367,6 +1466,13 @@                                                "post_offset" .= postOffset                                              ],                "aggs"           .= dateHistoAggs ]+  toJSON (ValueCountAgg a) = object ["value_count" .= v]+    where v = case a of+                (FieldValueCount (FieldName n)) -> object ["field" .= n]+                (ScriptValueCount (Script s))   -> object ["script" .= s]+  toJSON (FilterAgg (FilterAggregation filt ags)) =+    omitNulls [ "filter" .= filt+              , "aggs" .= ags]  type AggregationResults = M.Map Text Value @@ -1886,7 +1992,7 @@   toJSON (MatchQuery (FieldName fieldName)           (QueryString mqQueryString) booleanOperator           zeroTermsQuery cutoffFrequency matchQueryType-          analyzer maxExpansions lenient) =+          analyzer maxExpansions lenient boost) =     object [ fieldName .= omitNulls base ]     where base = [ "query" .= mqQueryString                  , "operator" .= booleanOperator@@ -1895,7 +2001,8 @@                  , "type" .= matchQueryType                  , "analyzer" .= analyzer                  , "max_expansions" .= maxExpansions-                 , "lenient" .= lenient ]+                 , "lenient" .= lenient+                 , "boost" .= boost ]   instance ToJSON MultiMatchQuery where@@ -1953,6 +2060,8 @@ instance ToJSON MaxQueryTerms instance ToJSON TypeName instance ToJSON IndexName+instance ToJSON TemplateName+instance ToJSON TemplatePattern instance ToJSON BoostTerms instance ToJSON MaxWordLength instance ToJSON MinWordLength@@ -1989,22 +2098,43 @@   instance ToJSON IndexSettings where-  toJSON (IndexSettings s r) = object ["settings" .= object ["shards" .= s, "replicas" .= r]]+  toJSON (IndexSettings s r) = object ["settings" .=+                                 object ["index" .=+                                   object ["number_of_shards" .= s, "number_of_replicas" .= r]+                                 ]+                               ] +instance ToJSON IndexTemplate where+  toJSON (IndexTemplate p s m) = merge+    (object [ "template" .= p+            , "mappings" .= foldl' merge (object []) m+            ])+    (toJSON s)+   where+     merge (Object o1) (Object o2) = toJSON $ HM.union o1 o2+     merge o           Null        = o+     merge _           _           = undefined  instance (FromJSON a) => FromJSON (EsResult a) where-  parseJSON (Object v) = EsResult <$>-                         v .:  "_index"   <*>-                         v .:  "_type"    <*>-                         v .:  "_id"      <*>-                         v .:  "_version" <*>-                         v .:? "found"    <*>-                         v .:  "_source"+  parseJSON jsonVal@(Object v) = do+    found <- v .:? "found" .!= False+    fr <- if found+             then parseJSON jsonVal+             else return Nothing+    EsResult <$> v .:  "_index"   <*>+                 v .:  "_type"    <*>+                 v .:  "_id"      <*>+                 pure fr   parseJSON _          = empty +instance (FromJSON a) => FromJSON (EsResultFound a) where+  parseJSON (Object v) = EsResultFound <$>+                         v .: "_version" <*>+                         v .: "_source"+  parseJSON _          = empty  instance ToJSON Search where-  toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize) =+  toJSON (Search query sFilter sort searchAggs highlight sTrackSortScores sFrom sSize _ sFields sSource) =     omitNulls [ "query"        .= query               , "filter"       .= sFilter               , "sort"         .= sort@@ -2012,9 +2142,30 @@               , "highlight"    .= highlight               , "from"         .= sFrom               , "size"         .= sSize-              , "track_scores" .= sTrackSortScores]+              , "track_scores" .= sTrackSortScores+              , "fields"       .= sFields+              , "_source"      .= sSource]  +instance ToJSON Source where+    toJSON NoSource                         = toJSON False+    toJSON (SourcePatterns patterns)        = toJSON patterns+    toJSON (SourceIncludeExclude incl excl) = object [ "include" .= incl, "exclude" .= excl ]++instance ToJSON PatternOrPatterns where+  toJSON (PopPattern pattern)   = toJSON pattern+  toJSON (PopPatterns patterns) = toJSON patterns++instance ToJSON Include where+  toJSON (Include patterns) = toJSON patterns++instance ToJSON Exclude where+  toJSON (Exclude patterns) = toJSON patterns++instance ToJSON Pattern where+  toJSON (Pattern pattern) = toJSON pattern++ instance ToJSON FieldHighlight where     toJSON (FieldHighlight (FieldName fName) (Just fSettings)) =         object [ fName .= fSettings ]@@ -2232,7 +2383,8 @@                          v .:  "timed_out"    <*>                          v .:  "_shards"      <*>                          v .:  "hits"         <*>-                         v .:? "aggregations"+                         v .:? "aggregations" <*>+                         v .:? "_scroll_id"   parseJSON _          = empty  instance (FromJSON a) => FromJSON (SearchHits a) where
src/Database/Bloodhound/Types/Class.hs view
@@ -1,8 +1,11 @@+{-# LANGUAGE CPP #-} module Database.Bloodhound.Types.Class        ( Seminearring(..) )        where +#if !MIN_VERSION_base(4,8,0) import Data.Monoid+#endif  class Monoid a => Seminearring a where   -- 0, +, *
+ tests/doctests.hs view
@@ -0,0 +1,4 @@+import Test.DocTest++main :: IO ()+main = doctest ["-i src", "Database.Bloodhound"]
− tests/doctests.hsc
@@ -1,74 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ForeignFunctionInterface #-}--------------------------------------------------------------------------------- |--- Module      :  Main (doctests)--- Copyright   :  (C) 2014 Chris Allen--- License     :  BSD-style (see the file LICENSE)--- Maintainer  :  Chris Allen <cma@bitemyapp.com>--- Stability   :  provisional--- Portability :  portable------ This module provides doctests for a project based on the actual versions--- of the packages it was built with. It requires a corresponding Setup.lhs--- to be added to the project--------------------------------------------------------------------------------module Main where--import Build_doctests (deps)-import Control.Applicative-import Control.Monad-import Data.List-import System.Directory-import System.FilePath-import Test.DocTest--##if defined(mingw32_HOST_OS)-##if defined(i386_HOST_ARCH)-##define USE_CP-import Control.Applicative-import Control.Exception-import Foreign.C.Types-foreign import stdcall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool-foreign import stdcall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt-##elif defined(x86_64_HOST_ARCH)-##define USE_CP-import Control.Applicative-import Control.Exception-import Foreign.C.Types-foreign import ccall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool-foreign import ccall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt-##endif-##endif---- | Run in a modified codepage where we can print UTF-8 values on Windows.-withUnicode :: IO a -> IO a-##ifdef USE_CP-withUnicode m = do-  cp <- c_GetConsoleCP-  (c_SetConsoleCP 65001 >> m) `finally` c_SetConsoleCP cp-##else-withUnicode m = m-##endif--main :: IO ()-main = withUnicode $ getSources >>= \sources -> doctest $-    "-isrc"-  : "-idist/build/autogen"-  : "-optP-include"-  : "-optPdist/build/autogen/cabal_macros.h"-  : "-hide-all-packages"-  : map ("-package="++) deps ++ sources--getSources :: IO [FilePath]-getSources = filter (isSuffixOf ".hs") <$> go "src"-  where-    go dir = do-      (dirs, files) <- getFilesAndDirectories dir-      (files ++) . concat <$> mapM go dirs--getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])-getFilesAndDirectories dir = do-  c <- map (dir </>) . filter (`notElem` ["..", "."]) <$> getDirectoryContents dir-  (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c
tests/tests.hs view
@@ -14,6 +14,7 @@ import           Data.List.NonEmpty              (NonEmpty (..)) import qualified Data.List.NonEmpty              as NE import qualified Data.Map.Strict                 as M+import           Data.Monoid import           Data.Text                       (Text) import qualified Data.Text                       as T import           Data.Time.Calendar              (Day (..))@@ -47,7 +48,7 @@   `shouldBe` (expected :: Int)  createExampleIndex :: BH IO Reply-createExampleIndex = createIndex defaultIndexSettings testIndex+createExampleIndex = createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0)) testIndex deleteExampleIndex :: BH IO Reply deleteExampleIndex = deleteIndex testIndex @@ -174,15 +175,16 @@  searchTweet :: Search -> BH IO (Either String Tweet) searchTweet search = do-  reply <- searchByIndex testIndex search-  let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)+  result <- searchTweets search   let myTweet = fmap (hitSource . head . hits . searchHits) result   return myTweet +searchTweets :: Search -> BH IO (Either String (SearchResult Tweet))+searchTweets search = eitherDecode . responseBody <$> searchByIndex testIndex search+ searchExpectNoResults :: Search -> BH IO () searchExpectNoResults search = do-  reply <- searchByIndex testIndex search-  let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)+  result <- searchTweets search   let emptyHits = fmap (hits . searchHits) result   liftIO $     emptyHits `shouldBe` Right []@@ -214,11 +216,21 @@  searchTweetHighlight :: Search -> BH IO (Either String (Maybe HitHighlight)) searchTweetHighlight search = do-  reply <- searchByIndex testIndex search-  let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)+  result <- searchTweets search   let myHighlight = fmap (hitHighlight . head . hits . searchHits) result   return myHighlight +searchExpectSource :: Source -> Either String Value -> BH IO ()+searchExpectSource src expected = do+  _ <- insertData+  let query = QueryMatchQuery $ mkMatchQuery (FieldName "_all") (QueryString "haskell")+  let search = (mkSearch (Just query) Nothing) { source = Just src }+  reply <- searchAll search+  let result = eitherDecode (responseBody reply) :: Either String (SearchResult Value)+  let value = fmap (hitSource . head . hits . searchHits) result+  liftIO $+    value `shouldBe` expected+ data BulkTest = BulkTest { name :: Text } deriving (Eq, Generic, Show) instance FromJSON BulkTest instance ToJSON BulkTest@@ -278,6 +290,9 @@     hs <- arbitrary     return $ SearchHits tot score hs +getSource :: EsResult a -> Maybe a+getSource = fmap _source . foundResult+ main :: IO () main = hspec $ do @@ -298,8 +313,14 @@       docInserted <- getDocument testIndex testMapping (DocId "1")       let newTweet = eitherDecode                      (responseBody docInserted) :: Either String (EsResult Tweet)-      liftIO $ (fmap _source newTweet `shouldBe` Right exampleTweet)+      liftIO $ (fmap getSource newTweet `shouldBe` Right (Just exampleTweet)) +    it "produces a parseable result when looking up a bogus document" $ withTestEnv $ do+      doc <- getDocument testIndex testMapping  (DocId "bogus")+      let noTweet = eitherDecode+                    (responseBody doc) :: Either String (EsResult Tweet)+      liftIO $ fmap foundResult noTweet `shouldBe` Right Nothing+     it "can use optimistic concurrency control" $ withTestEnv $ do       let ev = ExternalDocVersion minBound       let cfg = defaultIndexDocumentSettings { idsVersionControl = ExternalGT ev }@@ -309,6 +330,24 @@       res' <- insertData' cfg       liftIO $ isVersionConflict res' `shouldBe` True +  describe "template API" $ do+    it "can create a template" $ withTestEnv $ do+      let idxTpl = IndexTemplate (TemplatePattern "tweet-*") (Just (IndexSettings (ShardCount 1) (ReplicaCount 1))) [toJSON TweetMapping]+      resp <- putTemplate idxTpl (TemplateName "tweet-tpl")+      liftIO $ validateStatus resp 200++    it "can detect if a template exists" $ withTestEnv $ do+      exists <- templateExists (TemplateName "tweet-tpl")+      liftIO $ exists `shouldBe` True++    it "can delete a template" $ withTestEnv $ do+      resp <- deleteTemplate (TemplateName "tweet-tpl")+      liftIO $ validateStatus resp 200++    it "can detect if a template doesn't exist" $ withTestEnv $ do+      exists <- templateExists (TemplateName "tweet-tpl")+      liftIO $ exists `shouldBe` False+   describe "bulk API" $ do     it "inserts all documents we request" $ withTestEnv $ do       _ <- insertData@@ -326,8 +365,8 @@       let maybeFirst  = eitherDecode $ responseBody fDoc :: Either String (EsResult BulkTest)       let maybeSecond = eitherDecode $ responseBody sDoc :: Either String (EsResult BulkTest)       liftIO $ do-        fmap _source maybeFirst `shouldBe` Right firstTest-        fmap _source maybeSecond `shouldBe` Right secondTest+        fmap getSource maybeFirst `shouldBe` Right (Just firstTest)+        fmap getSource maybeSecond `shouldBe` Right (Just secondTest)     describe "query API" $ do@@ -359,8 +398,8 @@      it "returns document for multi-match query" $ withTestEnv $ do       _ <- insertData-      let fields = [FieldName "user", FieldName "message"]-      let query = QueryMultiMatchQuery $ mkMultiMatchQuery fields (QueryString "bitemyapp")+      let flds = [FieldName "user", FieldName "message"]+      let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")       let search = mkSearch (Just query) Nothing       myTweet <- searchTweet search       liftIO $@@ -407,9 +446,8 @@       let sortSpec = DefaultSortSpec $ mkSort (FieldName "age") Ascending       let search = Search Nothing                    (Just IdentityFilter) (Just [sortSpec]) Nothing Nothing-                   False (From 0) (Size 10)-      reply <- searchByIndex testIndex search-      let result = eitherDecode (responseBody reply) :: Either String (SearchResult Tweet)+                   False (From 0) (Size 10) SearchTypeQueryThenFetch Nothing Nothing+      result <- searchTweets search       let myTweet = fmap (hitSource . head . hits . searchHits) result       liftIO $         myTweet `shouldBe` Right otherTweet@@ -601,6 +639,32 @@       _ <- insertData       searchTermsAggHint [GlobalOrdinals, GlobalOrdinalsHash, GlobalOrdinalsLowCardinality, Map] ++    it "can execute value_count aggregations" $ withTestEnv $ do+      _ <- insertData+      _ <- insertOther+      let ags = mkAggregations "user_count" (ValueCountAgg (FieldValueCount (FieldName "user"))) <>+                mkAggregations "bogus_count" (ValueCountAgg (FieldValueCount (FieldName "bogus")))+      let search = mkAggregateSearch Nothing ags+      let docCountPair k n = (k, object ["value" .= Number n])+      res <- searchTweets search+      liftIO $+        fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "user_count" 2+                                                                 , docCountPair "bogus_count" 0+                                                                 ]))++    it "can execute filter aggregations" $ withTestEnv $ do+      _ <- insertData+      _ <- insertOther+      let ags = mkAggregations "bitemyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "bitemyapp") defaultCache) Nothing)) <>+                mkAggregations "notmyapps" (FilterAgg (FilterAggregation (TermFilter (Term "user" "notmyapp") defaultCache) Nothing))+      let search = mkAggregateSearch Nothing ags+      let docCountPair k n = (k, object ["doc_count" .= Number n])+      res <- searchTweets search+      liftIO $+        fmap aggregations res `shouldBe` Right (Just (M.fromList [ docCountPair "bitemyapps" 1+                                                                 , docCountPair "notmyapps" 1+                                                                 ]))     -- Interaction of date serialization and date histogram aggregation is broken.     -- it "returns date histogram aggregation results" $ withTestEnv $ do     --   _ <- insertData@@ -645,8 +709,33 @@       liftIO $         myHighlight `shouldBe` Right Nothing +  describe "Source filtering" $ do +    it "doesn't include source when sources are disabled" $ withTestEnv $ do+      searchExpectSource+        NoSource+        (Left "key \"_source\" not present") +    it "includes a source" $ withTestEnv $ do+      searchExpectSource+        (SourcePatterns (PopPattern (Pattern "message")))+        (Right (Object (HM.fromList [("message", String "Use haskell!")])))++    it "includes sources" $ withTestEnv $ do+      searchExpectSource+        (SourcePatterns (PopPatterns [Pattern "user", Pattern "message"]))+        (Right (Object (HM.fromList [("user",String "bitemyapp"),("message", String "Use haskell!")])))++    it "includes source patterns" $ withTestEnv $ do+      searchExpectSource+        (SourcePatterns (PopPattern (Pattern "*ge")))+        (Right (Object (HM.fromList [("age", Number 10000),("message", String "Use haskell!")])))++    it "excludes source patterns" $ withTestEnv $ do+      searchExpectSource+        (SourceIncludeExclude (Include []) (Exclude [Pattern "l*", Pattern "*ge", Pattern "postDate"]))+        (Right (Object (HM.fromList [("user",String "bitemyapp")])))+   describe "ToJSON RegexpFlags" $ do     it "generates the correct JSON for AllRegexpFlags" $       toJSON AllRegexpFlags `shouldBe` String "ALL"@@ -709,3 +798,16 @@       enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]       enumFrom (pred maxBound :: DocVersion) `shouldBe` [pred maxBound, maxBound]       enumFromThen minBound (pred maxBound :: DocVersion) `shouldBe` [minBound, pred maxBound]++  describe "scan&scroll API" $ do+    it "returns documents using the scan&scroll API" $ withTestEnv $ do+      _ <- insertData+      _ <- insertOther+      let search = (mkSearch (Just $ MatchAllQuery Nothing) Nothing) { size = (Size 1) }+      regular_search <- searchTweet search+      scan_search' <- scanSearch testIndex testMapping search :: BH IO [Hit Tweet]+      let scan_search = map hitSource scan_search'+      liftIO $+        regular_search `shouldBe` Right exampleTweet -- Check that the size restrtiction is being honored +      liftIO $+        scan_search `shouldMatchList` [exampleTweet, otherTweet]