{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Module : Database.Bloodhound.Dynamic.Client
-- Copyright : (C) 2014, 2018 Chris Allen
-- License : BSD-style (see the file LICENSE)
-- Maintainer : Chris Allen <cma@bitemyapp.com>
-- Stability : provisional
-- Portability : GHC
--
-- Dynamically route the query depending on the backend.
--
-- @
-- withFetchedBackendType $ \backend ->
--   pitSearch backend index search
-- @
module Database.Bloodhound.Dynamic.Client
  ( module Reexport,
    guessBackendType,
    withFetchedBackendType,
    pitSearch,
  )
where

import Data.Aeson
import Data.Maybe (fromMaybe, listToMaybe)
import qualified Data.Versions as Versions
import Database.Bloodhound.Client.Cluster
import Database.Bloodhound.Common.Client as Reexport
import qualified Database.Bloodhound.ElasticSearch7.Client as ClientES2
import qualified Database.Bloodhound.OpenSearch2.Client as ClientOS2
import Database.Bloodhound.OpenSearch2.Types
import Lens.Micro (toListOf)
import Prelude hiding (filter, head)

-- | Try to guess the current 'BackendType'
guessBackendType :: NodeInfo -> Maybe BackendType
guessBackendType :: NodeInfo -> Maybe BackendType
guessBackendType NodeInfo
nodeInfo =
  case [Word] -> Maybe Word
forall a. [a] -> Maybe a
listToMaybe ([Word] -> Maybe Word) -> [Word] -> Maybe Word
forall a b. (a -> b) -> a -> b
$ Getting (Endo [Word]) Version Word -> Version -> [Word]
forall a s. Getting (Endo [a]) s a -> s -> [a]
toListOf Getting (Endo [Word]) Version Word
forall v. Semantic v => Traversal' v Word
Traversal' Version Word
Versions.major (Version -> [Word]) -> Version -> [Word]
forall a b. (a -> b) -> a -> b
$ VersionNumber -> Version
versionNumber (VersionNumber -> Version) -> VersionNumber -> Version
forall a b. (a -> b) -> a -> b
$ NodeInfo -> VersionNumber
nodeInfoESVersion NodeInfo
nodeInfo of
    Just Word
7 -> BackendType -> Maybe BackendType
forall a. a -> Maybe a
Just BackendType
ElasticSearch7
    Just Word
1 -> BackendType -> Maybe BackendType
forall a. a -> Maybe a
Just BackendType
OpenSearch1
    Just Word
2 -> BackendType -> Maybe BackendType
forall a. a -> Maybe a
Just BackendType
OpenSearch2
    Maybe Word
_ -> Maybe BackendType
forall a. Maybe a
Nothing

-- | Fetch the currently running backend and run backend-dependent code
withFetchedBackendType :: (MonadBH m) => (forall backend. SBackendType backend -> m a) -> m a
withFetchedBackendType :: forall (m :: * -> *) a.
MonadBH m =>
(forall (backend :: BackendType). SBackendType backend -> m a)
-> m a
withFetchedBackendType forall (backend :: BackendType). SBackendType backend -> m a
f = do
  NodesInfo
nodeInfo <- NodeSelection -> m NodesInfo
forall (m :: * -> *). MonadBH m => NodeSelection -> m NodesInfo
getNodesInfo NodeSelection
LocalNode
  let backend :: BackendType
backend = BackendType -> Maybe BackendType -> BackendType
forall a. a -> Maybe a -> a
fromMaybe BackendType
Dynamic (Maybe BackendType -> BackendType)
-> Maybe BackendType -> BackendType
forall a b. (a -> b) -> a -> b
$ [NodeInfo] -> Maybe NodeInfo
forall a. [a] -> Maybe a
listToMaybe (NodesInfo -> [NodeInfo]
nodesInfo NodesInfo
nodeInfo) Maybe NodeInfo
-> (NodeInfo -> Maybe BackendType) -> Maybe BackendType
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= NodeInfo -> Maybe BackendType
guessBackendType
  BackendType
-> (forall {backend :: BackendType}.
    SBackendType backend -> StaticBH backend m a)
-> m a
forall (m :: * -> *) a.
MonadBH m =>
BackendType
-> (forall (backend :: BackendType).
    SBackendType backend -> StaticBH backend m a)
-> m a
withDynamicBH BackendType
backend ((forall {backend :: BackendType}.
  SBackendType backend -> StaticBH backend m a)
 -> m a)
-> (forall {backend :: BackendType}.
    SBackendType backend -> StaticBH backend m a)
-> m a
forall a b. (a -> b) -> a -> b
$ m a -> StaticBH backend m a
forall (backend :: BackendType) (m :: * -> *) a.
m a -> StaticBH backend m a
StaticBH (m a -> StaticBH backend m a)
-> (SBackendType backend -> m a)
-> SBackendType backend
-> StaticBH backend m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SBackendType backend -> m a
forall (backend :: BackendType). SBackendType backend -> m a
f

-- | 'pitSearch' uses the point in time (PIT) API of elastic, for a given
-- 'IndexName'. Requires Elasticsearch >=7.10 or OpenSearch >=2. Note that this will consume the
-- entire search result set and will be doing O(n) list appends so this may
-- not be suitable for large result sets. In that case, the point in time API
-- should be used directly with `openPointInTime` and `closePointInTime`.
--
-- Note that 'pitSearch' utilizes the 'search_after' parameter under the hood,
-- which requires a non-empty 'sortBody' field in the provided 'Search' value.
-- Otherwise, 'pitSearch' will fail to return all matching documents.
--
-- For more information see
-- <https://opensearch.org/docs/latest/search-plugins/point-in-time/>.
pitSearch ::
  forall a m backend.
  (FromJSON a, MonadBH m) =>
  SBackendType backend ->
  IndexName ->
  Search ->
  m [Hit a]
pitSearch :: forall a (m :: * -> *) (backend :: BackendType).
(FromJSON a, MonadBH m) =>
SBackendType backend -> IndexName -> Search -> m [Hit a]
pitSearch SBackendType backend
backend IndexName
indexName Search
search =
  case SBackendType backend
backend of
    SBackendType backend
SElasticSearch7 -> forall (backend :: BackendType) (m :: * -> *) a.
StaticBH backend m a -> m a
unsafePerformBH @'ElasticSearch7 (StaticBH 'ElasticSearch7 m [Hit a] -> m [Hit a])
-> StaticBH 'ElasticSearch7 m [Hit a] -> m [Hit a]
forall a b. (a -> b) -> a -> b
$ IndexName -> Search -> StaticBH 'ElasticSearch7 m [Hit a]
forall a (m :: * -> *).
(FromJSON a, MonadBH m, WithBackend 'ElasticSearch7 m) =>
IndexName -> Search -> m [Hit a]
ClientES2.pitSearch IndexName
indexName Search
search
    SBackendType backend
SOpenSearch1 -> EsError -> m [Hit a]
forall a. EsError -> m a
forall (m :: * -> *) a. MonadBH m => EsError -> m a
throwEsError (EsError -> m [Hit a]) -> EsError -> m [Hit a]
forall a b. (a -> b) -> a -> b
$ Maybe Int -> Text -> EsError
EsError Maybe Int
forall a. Maybe a
Nothing Text
"pitSearch is not supported by OpenSearch1"
    SBackendType backend
SOpenSearch2 -> forall (backend :: BackendType) (m :: * -> *) a.
StaticBH backend m a -> m a
unsafePerformBH @'OpenSearch2 (StaticBH 'OpenSearch2 m [Hit a] -> m [Hit a])
-> StaticBH 'OpenSearch2 m [Hit a] -> m [Hit a]
forall a b. (a -> b) -> a -> b
$ IndexName -> Search -> StaticBH 'OpenSearch2 m [Hit a]
forall a (m :: * -> *).
(FromJSON a, MonadBH m, WithBackend 'OpenSearch2 m) =>
IndexName -> Search -> m [Hit a]
ClientOS2.pitSearch IndexName
indexName Search
search