{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
module Database.InfluxDB.Write.UDP
  ( -- $intro

  -- * Writers
    write
  , writeBatch
  , writeByteString

  -- * Writer parameters
  , WriteParams
  , writeParams
  , socket
  , sockAddr
  , Types.precision
  ) where

import Control.Lens
import Network.Socket (SockAddr, Socket)
import Network.Socket.ByteString (sendManyTo)
import qualified Data.ByteString.Lazy as BL

import Database.InfluxDB.Line
import Database.InfluxDB.Types as Types

{- $intro
This module is desined to be used with the [network]
(https://hackage.haskell.org/package/network) package and be imported qualified.

>>> :set -XOverloadedStrings -XOverloadedLists
>>> import Data.Time
>>> import Network.Socket
>>> import Database.InfluxDB
>>> import qualified Database.InfluxDB.Write.UDP as UDP
>>> sock <- Network.Socket.socket AF_INET Datagram defaultProtocol
>>> let localhost = tupleToHostAddress (127, 0, 0, 1)
>>> let params = UDP.writeParams sock $ SockAddrInet 8089 localhost
>>> UDP.write params $ Line "measurement1" [] [("value", FieldInt 42)] (Nothing :: Maybe UTCTime)
>>> close sock

Make sure that the UDP service is enabled in the InfluxDB config. This API
doesn't tell you if any error occurs. See [the official doc]
(https://docs.influxdata.com/influxdb/v1.6/supported_protocols/udp/) for
details.
-}

-- | The full set of parameters for the UDP writer.
data WriteParams = WriteParams
  { WriteParams -> Socket
_socket :: !Socket
  , WriteParams -> SockAddr
_sockAddr :: !SockAddr
  , WriteParams -> Precision 'WriteRequest
_precision :: !(Precision 'WriteRequest)
  }

-- | Smart constructor for 'WriteParams'
--
-- Default parameters:
--
--   ['L.precision'] 'Nanosecond'
writeParams :: Socket -> SockAddr -> WriteParams
writeParams :: Socket -> SockAddr -> WriteParams
writeParams Socket
_socket SockAddr
_sockAddr = WriteParams :: Socket -> SockAddr -> Precision 'WriteRequest -> WriteParams
WriteParams
  { _precision :: Precision 'WriteRequest
_precision = Precision 'WriteRequest
forall (ty :: RequestType). Precision ty
Nanosecond
  , Socket
SockAddr
_sockAddr :: SockAddr
_socket :: Socket
_sockAddr :: SockAddr
_socket :: Socket
..
  }

-- | Write a 'Line'
write
  :: Timestamp time
  => WriteParams
  -> Line time
  -> IO ()
write :: WriteParams -> Line time -> IO ()
write p :: WriteParams
p@WriteParams {Precision 'WriteRequest
_precision :: Precision 'WriteRequest
_precision :: WriteParams -> Precision 'WriteRequest
_precision} =
  WriteParams -> ByteString -> IO ()
writeByteString WriteParams
p (ByteString -> IO ())
-> (Line time -> ByteString) -> Line time -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (time -> Int64) -> Line time -> ByteString
forall time. (time -> Int64) -> Line time -> ByteString
encodeLine (Precision 'WriteRequest -> time -> Int64
forall time.
Timestamp time =>
Precision 'WriteRequest -> time -> Int64
roundTo Precision 'WriteRequest
_precision)

-- | Write 'Line's in a batch
--
-- This is more efficient than 'write'.
writeBatch
  :: (Timestamp time, Foldable f)
  => WriteParams
  -> f (Line time)
  -> IO ()
writeBatch :: WriteParams -> f (Line time) -> IO ()
writeBatch p :: WriteParams
p@WriteParams {Precision 'WriteRequest
_precision :: Precision 'WriteRequest
_precision :: WriteParams -> Precision 'WriteRequest
_precision} =
  WriteParams -> ByteString -> IO ()
writeByteString WriteParams
p (ByteString -> IO ())
-> (f (Line time) -> ByteString) -> f (Line time) -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (time -> Int64) -> f (Line time) -> ByteString
forall (f :: * -> *) time.
Foldable f =>
(time -> Int64) -> f (Line time) -> ByteString
encodeLines (Precision 'WriteRequest -> time -> Int64
forall time.
Timestamp time =>
Precision 'WriteRequest -> time -> Int64
roundTo Precision 'WriteRequest
_precision)

-- | Write a lazy 'L.ByteString'
writeByteString :: WriteParams -> BL.ByteString -> IO ()
writeByteString :: WriteParams -> ByteString -> IO ()
writeByteString WriteParams {Socket
SockAddr
Precision 'WriteRequest
_precision :: Precision 'WriteRequest
_sockAddr :: SockAddr
_socket :: Socket
_precision :: WriteParams -> Precision 'WriteRequest
_sockAddr :: WriteParams -> SockAddr
_socket :: WriteParams -> Socket
..} ByteString
payload =
  Socket -> [ByteString] -> SockAddr -> IO ()
sendManyTo Socket
_socket (ByteString -> [ByteString]
BL.toChunks ByteString
payload) SockAddr
_sockAddr

makeLensesWith (lensRules & generateSignatures .~ False) ''WriteParams

-- | Open UDP socket
socket :: Lens' WriteParams Socket

-- | UDP endopoint of the database
sockAddr :: Lens' WriteParams SockAddr

precision :: Lens' WriteParams (Precision 'WriteRequest)

-- | Timestamp precision.
--
-- In the UDP API, all timestamps are sent in nanosecond but you can specify
-- lower precision. The writer just rounds timestamps to the specified
-- precision.
instance HasPrecision 'WriteRequest WriteParams where
  precision :: (Precision 'WriteRequest -> f (Precision 'WriteRequest))
-> WriteParams -> f WriteParams
precision = (Precision 'WriteRequest -> f (Precision 'WriteRequest))
-> WriteParams -> f WriteParams
Lens' WriteParams (Precision 'WriteRequest)
Database.InfluxDB.Write.UDP.precision