packages feed

bloodhound-1.0.0.0: src/Database/Bloodhound/ElasticSearch7/Requests.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

module Database.Bloodhound.ElasticSearch7.Requests
  ( module Reexport,
    openPointInTime,
    openPointInTimeWith,
    openPointInTimeWithBody,
    closePointInTime,
    getDataStreams,
    getDataStreamStats,
    createDataStream,
    dataStreamExists,
    deleteDataStream,
    migrateToDataStream,
    promoteDataStream,
    modifyDataStream,
    eqlSearch,
    eqlSearchWith,
    getEQLSearch,
    getEQLStatus,
    deleteEQLSearch,
    getTermsEnum,
    getTermsEnumWith,

    -- * Deprecated index endpoints (ES 7.x only, removed in 8.0)
    syncedFlushIndex,
    syncedFlushIndexWith,
    freezeIndex,
    unfreezeIndex,
  )
where

import Data.Aeson
import Data.Text qualified as T
import Database.Bloodhound.Client.Cluster
import Database.Bloodhound.Common.Requests as Reexport
import Database.Bloodhound.ElasticSearch7.Types
import Database.Bloodhound.Internal.Utils.Requests
import Prelude hiding (filter, head)

-- | 'openPointInTime' opens a point in time for a single index given an
-- 'IndexName', using the supplied 'KeepAlive' duration (e.g.
-- @keepAliveMinutes 1@). Note that the point in time should be closed
-- with 'closePointInTime' as soon as it is no longer needed.
--
-- This is a convenience wrapper around 'openPointInTimeWith' that lifts
-- the single 'IndexName' into an 'IndexPattern'. To target multiple
-- comma-joined indices or a wildcard pattern (e.g. @\"logs-*,app-*\"@),
-- use 'openPointInTimeWith' directly with an 'IndexPattern'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html>.
openPointInTime ::
  IndexName ->
  KeepAlive ->
  BHRequest StatusDependant (ParsedEsResponse OpenPointInTimeResponse)
openPointInTime indexName keepAlive =
  openPointInTimeWith (IndexPattern (unIndexName indexName)) keepAlive defaultPITOptions

-- | 'openPointInTimeWith' is the parameterized variant of
-- 'openPointInTime': it takes an 'IndexPattern' as the target, which may
-- be a single index name, a comma-separated list of indices, or a
-- wildcard pattern (e.g. @\"logs-*\"@, @\"logs-2024,logs-2025\"@). It
-- forwards the supplied 'PITOptions' as query-string parameters in
-- addition to the mandatory @keep_alive@. Only the parameters accepted
-- by the Elasticsearch PIT endpoint are emitted (@routing@,
-- @preference@, @expand_wildcards@, @ignore_unavailable@,
-- @allow_partial_search_results@, @max_concurrent_shard_requests@); the
-- OpenSearch-only
-- 'Database.Bloodhound.Internal.Versions.Common.Types.PointInTime.pitoAllowPartialPITCreation'
-- field is silently dropped by this builder. Pass
-- 'Database.Bloodhound.Internal.Versions.Common.Types.PointInTime.defaultPITOptions'
-- to reproduce the wire shape of 'openPointInTime'. To send an
-- @index_filter@ body, use 'openPointInTimeWithBody'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html>.
openPointInTimeWith ::
  IndexPattern ->
  KeepAlive ->
  PITOptions ->
  BHRequest StatusDependant (ParsedEsResponse OpenPointInTimeResponse)
openPointInTimeWith indexPattern keepAlive opts =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint emptyBody
  where
    endpoint =
      [unIndexPattern indexPattern, "_pit"]
        `withQueries` (("keep_alive", Just (keepAliveToText keepAlive)) : pitOptionsParams opts)

-- | 'openPointInTimeWithBody' is the body-carrying variant of
-- 'openPointInTimeWith' on Elasticsearch 7: in addition to the
-- 'PITOptions' URI parameters (forwarded exactly as in
-- 'openPointInTimeWith') and the mandatory @keep_alive@, it sends the
-- supplied 'OpenPointInTimeBody' as the JSON request body. This is the
-- only way to supply an @index_filter@ query, which filters indices out
-- of the PIT when it rewrites to @match_none@ on every shard. The
-- 'IndexPattern' target follows the same rules as 'openPointInTimeWith'
-- (single name, comma-list, or wildcard). Pass
-- 'defaultOpenPointInTimeBody' to send an empty (@{}@) body.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html>.
openPointInTimeWithBody ::
  IndexPattern ->
  KeepAlive ->
  OpenPointInTimeBody ->
  PITOptions ->
  BHRequest StatusDependant (ParsedEsResponse OpenPointInTimeResponse)
openPointInTimeWithBody indexPattern keepAlive body opts =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint (encode body)
  where
    endpoint =
      [unIndexPattern indexPattern, "_pit"]
        `withQueries` (("keep_alive", Just (keepAliveToText keepAlive)) : pitOptionsParams opts)

-- | 'closePointInTime' closes a point in time given a 'ClosePointInTime'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html>.
closePointInTime ::
  ClosePointInTime ->
  BHRequest StatusDependant (ParsedEsResponse ClosePointInTimeResponse)
closePointInTime q = do
  withBHResponseParsedEsResponse $ deleteWithBody @StatusDependant ["_pit"] (encode q)

-- $dataStreams
--
-- /Data Streams/ are the recommended way to model time-series and
-- append-only data in Elasticsearch 7.x, 8.x and 9.x. The wire format is
-- identical across all three versions, so the request builder lives
-- here and is re-exported by the ES8 and ES9 client modules.

-- | 'getDataStreams' lists data streams.
--
-- * @'Nothing'@ or @'Just' []@  → @GET /_data_stream@ returns every data
--   stream on the cluster.
-- * @'Just' names@              → @GET /_data_stream\/{a,b,c}@ returns
--   only the named data streams (comma-joined path segment, mirroring
--   the @GET /_ilm/policy/{id}@ multi-id convention).
--
-- The response is wrapped in a @data_streams@ array, so the body
-- decoder unwraps 'GetDataStreamsResponse' and returns a flat list of
-- 'DataStreamInfo' (a type alias for 'DataStream').
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html#get-data-streams>.
getDataStreams ::
  Maybe [DataStreamName] ->
  BHRequest StatusDependant [DataStreamInfo]
getDataStreams mDataStreamNames =
  dataStreams <$> get @StatusDependant endpoint
  where
    endpoint = case mDataStreamNames of
      Nothing -> ["_data_stream"]
      Just [] -> ["_data_stream"]
      Just (first : rest) ->
        ["_data_stream", T.intercalate "," (renderDataStreamName <$> (first : rest))]
    renderDataStreamName (DataStreamName dsName) = dsName

-- | 'getDataStreamStats' returns statistics about one or more data
-- streams (document counts, store sizes, shard counts, etc.).
--
-- * @'Nothing'@ or @'Just' []@  → @GET /_data_stream/_stats@ returns
--   statistics for every data stream on the cluster.
-- * @'Just' names@              → @GET /_data_stream\/{a,b,c}/_stats@
--   returns statistics for only the named data streams (comma-joined
--   path segment, mirroring 'getDataStreams').
--
-- The response carries the @_shards@ summary, cluster-wide aggregates
-- (@data_stream_count@, @backing_indices@, @total_store_size_bytes@,
-- @total_store_size@) and a per-stream breakdown in @data_streams@.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html#get-data-stream-stats>.
getDataStreamStats ::
  Maybe [DataStreamName] ->
  BHRequest StatusDependant DataStreamStats
getDataStreamStats mDataStreamNames =
  get @StatusDependant endpoint
  where
    endpoint = case mDataStreamNames of
      Nothing -> ["_data_stream", "_stats"]
      Just [] -> ["_data_stream", "_stats"]
      Just (first : rest) ->
        ["_data_stream", T.intercalate "," (renderDataStreamName <$> (first : rest)), "_stats"]
    renderDataStreamName (DataStreamName dsName) = dsName

-- | 'createDataStream' creates a data stream with the given name. The
-- request carries no body: Elasticsearch derives the stream's
-- configuration (mappings, settings, lifecycle) from the matching index
-- template, which must already define a @data_stream@ object. Returns
-- 'Acknowledged' on success.
--
-- Requires Elasticsearch >= 7.9. The wire format is identical across
-- ES 7.x, 8.x and 9.x, so the request builder lives here and is
-- re-exported by the ES8 and ES9 client modules.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html#create-data-stream>.
createDataStream ::
  DataStreamName ->
  BHRequest StatusDependant Acknowledged
createDataStream (DataStreamName dsName) =
  put @StatusDependant ["_data_stream", dsName] emptyBody

-- | 'dataStreamExists' checks whether a data stream with the given
-- name exists. Issues a @HEAD /_data_stream\/{name}@ request and
-- returns 'True' on any 2xx response, 'False' otherwise. The wire
-- format is identical across ES 7.x, 8.x and 9.x, so the request
-- builder lives here and is re-exported by the ES8 and ES9 client
-- modules.
--
-- Requires Elasticsearch >= 7.9.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html>.
dataStreamExists ::
  DataStreamName ->
  BHRequest StatusDependant Bool
dataStreamExists (DataStreamName dsName) =
  doesExist ["_data_stream", dsName]

-- | 'deleteDataStream' deletes a data stream and its backing indices.
-- Returns 'Acknowledged' on success. Deleting a non-existent stream
-- fails with an HTTP error (hence 'StatusDependant'), surfaced as an
-- 'EsError' rather than being swallowed.
--
-- Requires Elasticsearch >= 7.9. The wire format is identical across
-- ES 7.x, 8.x and 9.x, so the request builder lives here and is
-- re-exported by the ES8 and ES9 client modules.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html#delete-data-stream>.
deleteDataStream ::
  DataStreamName ->
  BHRequest StatusDependant Acknowledged
deleteDataStream (DataStreamName dsName) =
  delete @StatusDependant ["_data_stream", dsName]

-- | 'migrateToDataStream' converts an existing alias (whose write index
-- is a hidden or data-stream-compatible backing index) into a data
-- stream. The request carries no body: Elasticsearch derives the
-- stream's configuration from the alias and its write index, which
-- must already be set up correctly. Returns 'Acknowledged' on success.
--
-- Migrating an alias that does not exist, or whose write index is not
-- eligible, fails with an HTTP error (hence 'StatusDependant'),
-- surfaced as an 'EsError'.
--
-- Requires Elasticsearch >= 7.9. The wire format is identical across
-- ES 7.x, 8.x and 9.x, so the request builder lives here and is
-- re-exported by the ES8 and ES9 client modules.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html#migrate-to-data-stream>.
migrateToDataStream ::
  IndexAliasName ->
  BHRequest StatusDependant Acknowledged
migrateToDataStream aliasName =
  post @StatusDependant ["_data_stream", "_migrate", unIndexName (indexAliasName aliasName)] emptyBody

-- | 'promoteDataStream' promotes a frozen or cloned data stream back to a
-- regular data stream. Used during disaster recovery: a stream that was
-- fenced (e.g. cloned during failover) is read-only until promoted. The
-- request carries no body. Returns 'Acknowledged' on success.
--
-- Promoting a non-existent stream, or a stream that is not currently
-- frozen, fails with an HTTP error (hence 'StatusDependant'), surfaced
-- as an 'EsError'.
--
-- Requires Elasticsearch >= 7.9. The wire format is identical across
-- ES 7.x, 8.x and 9.x, so the request builder lives here and is
-- re-exported by the ES8 and ES9 client modules.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html#promote-data-stream>.
promoteDataStream ::
  DataStreamName ->
  BHRequest StatusDependant Acknowledged
promoteDataStream (DataStreamName dsName) =
  post @StatusDependant ["_data_stream", "_promote", dsName] emptyBody

-- | 'modifyDataStream' changes the backing indices of one or more data
-- streams in a single batched request. The body carries a list of
-- 'ModifyDataStreamAction's (add\/remove backing index); each action
-- references its own 'DataStreamName', so the path carries no stream
-- name. Returns 'Acknowledged' on success.
--
-- Referencing a non-existent data stream, or adding\/removing an index
-- that is not eligible, fails with an HTTP error (hence
-- 'StatusDependant'), surfaced as an 'EsError'. An empty actions list
-- is rejected by the server.
--
-- Requires Elasticsearch >= 7.16. The wire format is identical across
-- ES 7.x, 8.x and 9.x, so the request builder lives here and is
-- re-exported by the ES8 and ES9 client modules.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/data-stream-apis.html#modify-data-stream>.
modifyDataStream ::
  ModifyDataStreamRequest ->
  BHRequest StatusDependant Acknowledged
modifyDataStream req =
  post @StatusDependant ["_data_stream", "_modify"] (encode req)

-- $eql
--
-- /Event Query Language/ (EQL) is an event-based query language for
-- sequences and time-series data. The synchronous search endpoint
-- @POST /{index}/_eql/search@ was added in Elasticsearch 7.10. The wire
-- shape is /almost/ identical across ES 7.x, 8.x and 9.x: 8.x added the
-- @allow_partial_search_results@, @allow_partial_sequence_results@ and
-- @case_sensitive@ body fields plus the two URI parameters
-- @allow_partial_search_results@ and @allow_partial_sequence_results@.
-- The ES7 builder in this module gates those out via the
-- 'EQLRequestBodyES7' newtype and 'eqlSearchOptionsParams7'; the ES8 and
-- ES9 client modules ship their own 'eqlSearchWith' that emits them.

-- | 'eqlSearch' builds the 'BHRequest' for the EQL
-- @POST /{index}/_eql/search@ endpoint (Elasticsearch >= 7.10). The body
-- is the encoded 'EQLRequest' (whose 'eqlQuery' field holds the EQL
-- source); the response is parsed into 'EQLResult'.
--
-- The result is parameterized by the document-source type @a@: pass the
-- concrete type you want @_source@ decoded into (e.g.
-- @eqlSearch \@MyEvent ...@).
--
-- Equivalent to @'eqlSearchWith' indexName 'defaultEQLSearchOptions' req@.
-- For URI query parameters (@allow_no_indices@, @expand_wildcards@,
-- @ignore_unavailable@, @ccs_minimize_roundtrips@, @keep_alive@,
-- @keep_on_completion@, @wait_for_completion_timeout@), use
-- 'eqlSearchWith'. The 8.x-only @allow_partial_search_results@ and
-- @allow_partial_sequence_results@ URI parameters are not emitted on the
-- ES7 wire; see 'eqlSearchWith' for details.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/eql-search-api.html>.
eqlSearch ::
  (FromJSON a) =>
  IndexName ->
  EQLRequest ->
  BHRequest StatusDependant (ParsedEsResponse (EQLResult a))
eqlSearch indexName = eqlSearchWith indexName defaultEQLSearchOptions

-- | 'eqlSearchWith' is the fully-parameterised form of 'eqlSearch'. Every
-- URI parameter accepted by @/{index}/_eql/search@ is exposed via
-- 'EQLSearchOptions'; the 'EQLRequest' body carries the @query@ and every
-- other body parameter. 'defaultEQLSearchOptions' makes this
-- byte-for-byte identical to the simple form (modulo the extra body
-- fields you set).
--
-- The body is encoded via the 'EQLRequestBodyES7' newtype, which omits
-- the three 8.x-only body keys (@allow_partial_search_results@,
-- @allow_partial_sequence_results@, @case_sensitive@) so the wire shape
-- matches the 7.17 spec. The URI parameters are rendered with
-- 'eqlSearchOptionsParams7', which likewise omits the 8.x-only URI
-- parameters. The ES8\/ES9 'eqlSearchWith' variants use the 8-form
-- encoders.
--
-- The result is parameterized by the document-source type @a@: pass the
-- concrete type you want @_source@ decoded into (e.g.
-- @eqlSearchWith \@MyEvent ...@).
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/eql-search-api.html>.
eqlSearchWith ::
  (FromJSON a) =>
  IndexName ->
  EQLSearchOptions ->
  EQLRequest ->
  BHRequest StatusDependant (ParsedEsResponse (EQLResult a))
eqlSearchWith indexName opts req =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint (encode (EQLRequestBodyES7 req))
  where
    endpoint =
      mkEndpoint [unIndexName indexName, "_eql", "search"]
        `withQueries` eqlSearchOptionsParams7 opts

-- | 'getEQLSearch' builds the 'BHRequest' for the async EQL
-- @GET /_eql/search\/{id}@ endpoint (Elasticsearch >= 7.10), which
-- fetches the deferred results of an EQL search whose
-- 'eqlWaitForCompletionTimeout' elapsed before completion.
--
-- The @id@ is the 'EQLSearchId' returned in the initial 'eqlSearch'
-- response; the result is the same 'EQLResult' shape, parameterized by
-- the document-source type @a@ (pass the concrete type with
-- @getEQLSearch \@MyEvent ...@).
--
-- The optional 'Text' sets @wait_for_completion_timeout@ as a query
-- parameter (duration string such as @"5s"@); 'Nothing' uses the
-- server default (return immediately with the current state). This
-- matches the 'getAsyncESQL' polling primitive so callers can long-poll
-- a still-running search instead of tight-looping.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-eql-get>.
getEQLSearch ::
  (FromJSON a) =>
  EQLSearchId ->
  Maybe T.Text ->
  BHRequest StatusDependant (ParsedEsResponse (EQLResult a))
getEQLSearch (EQLSearchId searchId) mTimeout =
  withBHResponseParsedEsResponse $
    get @StatusDependant endpoint
  where
    endpoint =
      mkEndpoint ["_eql", "search", searchId]
        `withQueries` eqlWaitForCompletionTimeoutQuery mTimeout

-- | Render the @wait_for_completion_timeout@ query parameter used by
-- @GET /_eql/search\/{id}@. 'Nothing' omits the parameter entirely,
-- letting Elasticsearch apply its own default. The analogous async
-- ES|QL poll ('Database.Bloodhound.ElasticSearch8.Requests.getAsyncESQLWith')
-- renders its parameters via 'Database.Bloodhound.ElasticSearch8.Types.GetAsyncESQLOptions'.
eqlWaitForCompletionTimeoutQuery :: Maybe T.Text -> [(T.Text, Maybe T.Text)]
eqlWaitForCompletionTimeoutQuery = \case
  Nothing -> []
  Just t -> [("wait_for_completion_timeout", Just t)]

-- | 'getEQLStatus' builds the 'BHRequest' for the async EQL
-- @GET /_eql/search/status\/{id}@ endpoint (Elasticsearch >= 7.10),
-- which returns only the status of an async EQL search without fetching
-- the results.
--
-- The @id@ is the 'EQLSearchId' returned in the initial 'eqlSearch'
-- response. The response is an 'EQLStatus' carrying 'eqlStatusIsRunning',
-- 'eqlStatusIsPartial', and (once complete) 'eqlStatusCompletionStatus'.
--
-- The wire format is identical across ES 7.x, 8.x and 9.x, so the
-- request builder lives here and is re-exported by the ES8 and ES9
-- client modules.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-eql-get-status>.
getEQLStatus ::
  EQLSearchId ->
  BHRequest StatusDependant EQLStatus
getEQLStatus (EQLSearchId searchId) =
  get @StatusDependant (mkEndpoint ["_eql", "search", "status", searchId])

-- | 'deleteEQLSearch' builds the 'BHRequest' for the EQL
-- @DELETE /_eql/search/{id}@ endpoint (Elasticsearch >= 7.10), which
-- deletes an async EQL search result and its context by id. Returns
-- 'Acknowledged' on success.
--
-- The wire format is identical across ES 7.x, 8.x and 9.x, so the
-- request builder lives here and is re-exported by the ES8 and ES9
-- client modules.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-eql-delete>.
deleteEQLSearch ::
  EQLSearchId ->
  BHRequest StatusIndependant Acknowledged
deleteEQLSearch (EQLSearchId i) =
  delete (mkEndpoint ["_eql", "search", i])

-- $termsEnum
--
-- /Terms Enum/ (Elasticsearch >= 7.10) returns the terms of a field
-- for auto-complete use cases. The endpoint is @GET \/{index}\/_terms_enum@
-- and carries its parameters (the @field@ to inspect, an optional
-- @string@ prefix, @size@, @timeout@, @case_insensitive@, @index_filter@,
-- @search_after@) in the request body — see 'getWithBody'. The wire format is identical across
-- ES 7.x, 8.x and 9.x, so the request builders live here and are
-- re-exported by the ES8 and ES9 client modules.

-- | 'getTermsEnum' returns a list of terms in @field@ for
-- auto-complete use cases. Pass a non-empty index list (or a single
-- index) to target; @'Nothing'@ or @'Just' []@ produces the bare
-- @GET /_terms_enum@ path, which Elasticsearch rejects because a
-- @<target>@ path segment is mandatory — callers should therefore
-- always pass a non-empty list. The optional 'Text' is the @string@
-- prefix to match (auto-complete seed); 'Nothing' asks for any term
-- the server is willing to return.
--
-- Equivalent to @'getTermsEnumWith' mIndices 'defaultTermsEnumOptions' req@
-- with @req@ carrying only the @field@ and the optional @string@. For
-- @size@, @timeout@, @case_insensitive@, @index_filter@ or
-- @search_after@, use 'getTermsEnumWith'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-terms-enum.html>.
getTermsEnum ::
  Maybe [IndexName] ->
  FieldName ->
  Maybe T.Text ->
  BHRequest StatusDependant TermsEnumResponse
getTermsEnum mIndices field mString =
  getTermsEnumWith mIndices defaultTermsEnumOptions req
  where
    req =
      (defaultTermsEnumRequest field)
        { termsEnumRequestString = mString
        }

-- | 'getTermsEnumWith' is the fully-parameterised form of
-- 'getTermsEnum'. Every URI parameter accepted by
-- @/{index}/_terms_enum@ is exposed via 'TermsEnumOptions'
-- (@allow_no_indices@, @expand_wildcards@, @ignore_unavailable@); the
-- 'TermsEnumRequest' body carries the @field@, the optional @string@
-- prefix and every other body parameter. 'defaultTermsEnumOptions'
-- makes this byte-for-byte identical to the simple form (modulo the
-- extra body fields you set).
getTermsEnumWith ::
  Maybe [IndexName] ->
  TermsEnumOptions ->
  TermsEnumRequest ->
  BHRequest StatusDependant TermsEnumResponse
getTermsEnumWith mIndices opts req =
  getWithBody @StatusDependant endpoint (encode req)
  where
    endpoint = path `withQueries` termsEnumOptionsParams opts
    path =
      case mIndices of
        Nothing -> ["_terms_enum"]
        Just [] -> ["_terms_enum"]
        Just (first : rest) ->
          [T.intercalate "," (map unIndexName (first : rest)), "_terms_enum"]

-- | 'syncedFlushIndex' performs a synced flush on an index. Maps to the
-- deprecated @POST \/{index}/_flush/synced@ endpoint, which was
-- deprecated in 7.6 and /removed in Elasticsearch 8.0/. A regular
-- 'Database.Bloodhound.Common.Requests.flushIndex' has the same effect
-- on 7.6+; prefer it for new code.
--
-- Equivalent to
-- @'syncedFlushIndexWith' 'defaultSyncedFlushOptions'@. Use the @With@
-- variant to pass @ignore_unavailable@, @allow_no_indices@ or
-- @expand_wildcards@.
--
-- Returns 'SyncedFlushResult' (a top-level @_shards@ summary plus a
-- per-index breakdown of how many shards sync-flushed cleanly).
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-synced-flush-api.html>.
--
-- @since 0.26.0.0
{-# DEPRECATED syncedFlushIndex "Synced flush was deprecated in ES 7.6 and removed in 8.0. Use flushIndex instead. See <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-synced-flush-api.html>" #-}
syncedFlushIndex ::
  IndexName ->
  BHRequest StatusDependant SyncedFlushResult
syncedFlushIndex = syncedFlushIndexWith defaultSyncedFlushOptions

-- | 'syncedFlushIndexWith' is the fully-parameterised form of
-- 'syncedFlushIndex'. Every URI parameter accepted by
-- @POST \/{index}/_flush/synced@ is exposed via 'SyncedFlushOptions';
-- pass 'defaultSyncedFlushOptions' to reproduce the parameterless
-- behaviour.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-synced-flush-api.html>.
--
-- @since 0.26.0.0
{-# DEPRECATED syncedFlushIndexWith "Synced flush was deprecated in ES 7.6 and removed in 8.0. Use flushIndex instead. See <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-synced-flush-api.html>" #-}
syncedFlushIndexWith ::
  SyncedFlushOptions ->
  IndexName ->
  BHRequest StatusDependant SyncedFlushResult
syncedFlushIndexWith opts indexName =
  post endpoint emptyBody
  where
    endpoint =
      [unIndexName indexName, "_flush", "synced"]
        `withQueries` syncedFlushOptionsParams opts

-- | 'freezeIndex' freezes an index. Maps to the deprecated
-- @POST \/{index}/_freeze@ endpoint, which was deprecated in 7.14 and
-- /removed in Elasticsearch 8.0/. Frozen indices are not supported on
-- newer versions; avoid this in new code.
--
-- Returns 'ShardsResult' (the @{\"_shards\": {...}}@ envelope).
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-lifecycle-management.html>
-- and the freeze API notes.
--
-- @since 0.26.0.0
{-# DEPRECATED freezeIndex "Freeze index was deprecated in ES 7.14 and removed in 8.0. See <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-modules-block.html>" #-}
freezeIndex :: IndexName -> BHRequest StatusDependant ShardsResult
freezeIndex indexName =
  post [unIndexName indexName, "_freeze"] emptyBody

-- | 'unfreezeIndex' unfreezes an index. Maps to the deprecated
-- @POST \/{index}/_unfreeze@ endpoint, which was deprecated in 7.14 and
-- /removed in Elasticsearch 8.0\. Avoid this in new code.
--
-- Returns 'ShardsResult' (the @{\"_shards\": {...}}@ envelope).
--
-- @since 0.26.0.0
{-# DEPRECATED unfreezeIndex "Unfreeze index was deprecated in ES 7.14 and removed in 8.0. See <https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-modules-block.html>" #-}
unfreezeIndex :: IndexName -> BHRequest StatusDependant ShardsResult
unfreezeIndex indexName =
  post [unIndexName indexName, "_unfreeze"] emptyBody