{-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} module Database.InfluxDB.Line ( Line(Line) , measurement , tagSet , fieldSet , timestamp , buildLine , buildLines , encodeLine , encodeLines ) where import Data.List (intersperse) import Data.Int (Int64) import Data.Monoid import Control.Lens import Data.Map (Map) import Data.Text (Text) import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Lazy as L import qualified Data.Map.Strict as Map import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Database.InfluxDB.Types -- | Placeholder for the Line Protocol -- -- See https://docs.influxdata.com/influxdb/v1.0/write_protocols/line_protocol_tutorial/ for the -- concrete syntax. data Line time = Line { _measurement :: !Key -- ^ Measurement name , _tagSet :: !(Map Key Text) -- ^ Set of tags (optional) , _fieldSet :: !(Map Key FieldValue) -- ^ Set of fields -- -- It shouldn't be empty. , _timestamp :: !(Maybe time) -- ^ Timestamp (optional) } encodeLine :: (time -> Int64) -> Line time -> L.ByteString encodeLine toTimestamp = B.toLazyByteString . buildLine toTimestamp encodeLines :: Foldable f => (time -> Int64) -> f (Line time) -> L.ByteString encodeLines toTimestamp = B.toLazyByteString . buildLines toTimestamp buildLine :: (time -> Int64) -> Line time -> B.Builder buildLine toTimestamp Line {..} = key <> " " <> fields <> maybe "" (" " <>) timestamp where measurement = buildKey _measurement tags = buildMap TE.encodeUtf8Builder _tagSet key = if Map.null _tagSet then measurement else measurement <> "," <> tags fields = buildMap buildFieldValue _fieldSet timestamp = B.int64Dec . toTimestamp <$> _timestamp buildMap encodeVal = mconcat . intersperse "," . map encodeKeyVal . Map.toList where encodeKeyVal (name, val) = mconcat [ buildKey name , "=" , encodeVal val ] buildKey :: Key -> B.Builder buildKey = TE.encodeUtf8Builder . escapeKey escapeKey :: Key -> Text escapeKey (Key text) = T.replace " " "\\ " $ T.replace "," "\\," text buildFieldValue :: FieldValue -> B.Builder buildFieldValue = \case FieldInt i -> B.int64Dec i <> "i" FieldFloat d -> B.doubleDec d FieldString t -> "\"" <> TE.encodeUtf8Builder t <> "\"" FieldBool b -> if b then "true" else "false" FieldNull -> "null" buildLines :: Foldable f => (time -> Int64) -> f (Line time) -> B.Builder buildLines toTimestamp = foldMap ((<> "\n") . buildLine toTimestamp) makeLensesWith (lensRules & generateSignatures .~ False) ''Line -- | Name of the measurement that you want to write your data to. measurement :: Lens' (Line time) Key -- | Tag(s) that you want to include with your data point. Tags are optional in -- the Line Protocol, so you can set it 'empty'. tagSet :: Lens' (Line time) (Map Key Text) -- | Field(s) for your data point. Every data point requires at least one field -- in the Line Protocol, so it shouldn't be 'empty'. fieldSet :: Lens' (Line time) (Map Key FieldValue) -- | Timestamp for your data point. You can put whatever type of timestamp that -- is an instance of the 'Timestamp' class. timestamp :: Lens' (Line time) (Maybe time)