influxdb 0.10.0 → 1.0.0
raw patch · 24 files changed
+1807/−2358 lines, 24 filesdep +clockdep +foldldep +http-typesdep −dlistdep −exceptionsdep −ghc-primdep ~aesondep ~basedep ~bytestringnew-component:exe:influx-write-udpPVP ok
version bump matches the API change (PVP)
Dependencies added: clock, foldl, http-types, lens, optional-args, unordered-containers
Dependencies removed: dlist, exceptions, ghc-prim, network-uri, retry, tagged, template-haskell
Dependency ranges changed: aeson, base, bytestring, containers, http-client, mtl, network, scientific, time, vector
API changes (from Hackage documentation)
- Database.InfluxDB: (.!=) :: Parser (Maybe a) -> a -> Parser a
- Database.InfluxDB: (.:) :: FromValue a => Vector Value -> Column -> ValueParser a
- Database.InfluxDB: (.:?) :: FromValue a => Vector Value -> Column -> ValueParser (Maybe a)
- Database.InfluxDB: Admin :: Text -> Admin
- Database.InfluxDB: Bool :: !Bool -> Value
- Database.InfluxDB: Config :: !Credentials -> !(IORef ServerPool) -> !Manager -> Config
- Database.InfluxDB: Credentials :: !Text -> !Text -> Credentials
- Database.InfluxDB: Database :: Text -> Database
- Database.InfluxDB: DatabaseRequest :: [ShardSpaceRequest] -> [Text] -> DatabaseRequest
- Database.InfluxDB: Done :: Stream m a
- Database.InfluxDB: Float :: !Double -> Value
- Database.InfluxDB: Int :: !Int64 -> Value
- Database.InfluxDB: JsonDecodeError :: String -> InfluxException
- Database.InfluxDB: MicrosecondsPrecision :: TimePrecision
- Database.InfluxDB: MillisecondsPrecision :: TimePrecision
- Database.InfluxDB: Null :: Value
- Database.InfluxDB: Ping :: Text -> Ping
- Database.InfluxDB: SecondsPrecision :: TimePrecision
- Database.InfluxDB: Series :: {-# UNPACK #-} !Text -> {-# UNPACK #-} !SeriesData -> Series
- Database.InfluxDB: SeriesData :: Vector Column -> [Vector Value] -> SeriesData
- Database.InfluxDB: SeriesDecodeError :: String -> InfluxException
- Database.InfluxDB: Server :: !Text -> !Int -> !Bool -> Server
- Database.InfluxDB: ShardSpace :: Maybe Text -> Text -> Text -> Text -> Text -> Word32 -> Word32 -> ShardSpace
- Database.InfluxDB: ShardSpaceRequest :: Text -> Text -> Text -> Text -> Word32 -> Word32 -> ShardSpaceRequest
- Database.InfluxDB: String :: !Text -> Value
- Database.InfluxDB: User :: Text -> Bool -> User
- Database.InfluxDB: Yield :: a -> (m (Stream m a)) -> Stream m a
- Database.InfluxDB: [adminName] :: Admin -> Text
- Database.InfluxDB: [configCreds] :: Config -> !Credentials
- Database.InfluxDB: [configHttpManager] :: Config -> !Manager
- Database.InfluxDB: [configServerPool] :: Config -> !(IORef ServerPool)
- Database.InfluxDB: [credsPassword] :: Credentials -> !Text
- Database.InfluxDB: [credsUser] :: Credentials -> !Text
- Database.InfluxDB: [databaseName] :: Database -> Text
- Database.InfluxDB: [databaseRequestContinuousQueries] :: DatabaseRequest -> [Text]
- Database.InfluxDB: [databaseRequestSpaces] :: DatabaseRequest -> [ShardSpaceRequest]
- Database.InfluxDB: [pingStatus] :: Ping -> Text
- Database.InfluxDB: [seriesDataColumns] :: SeriesData -> Vector Column
- Database.InfluxDB: [seriesDataPoints] :: SeriesData -> [Vector Value]
- Database.InfluxDB: [seriesData] :: Series -> {-# UNPACK #-} !SeriesData
- Database.InfluxDB: [seriesName] :: Series -> {-# UNPACK #-} !Text
- Database.InfluxDB: [serverHost] :: Server -> !Text
- Database.InfluxDB: [serverPort] :: Server -> !Int
- Database.InfluxDB: [serverSsl] :: Server -> !Bool
- Database.InfluxDB: [shardSpaceDatabase] :: ShardSpace -> Maybe Text
- Database.InfluxDB: [shardSpaceName] :: ShardSpace -> Text
- Database.InfluxDB: [shardSpaceRegex] :: ShardSpace -> Text
- Database.InfluxDB: [shardSpaceReplicationFactor] :: ShardSpace -> Word32
- Database.InfluxDB: [shardSpaceRequestName] :: ShardSpaceRequest -> Text
- Database.InfluxDB: [shardSpaceRequestRegex] :: ShardSpaceRequest -> Text
- Database.InfluxDB: [shardSpaceRequestReplicationFactor] :: ShardSpaceRequest -> Word32
- Database.InfluxDB: [shardSpaceRequestRetentionPolicy] :: ShardSpaceRequest -> Text
- Database.InfluxDB: [shardSpaceRequestShardDuration] :: ShardSpaceRequest -> Text
- Database.InfluxDB: [shardSpaceRequestSplit] :: ShardSpaceRequest -> Word32
- Database.InfluxDB: [shardSpaceRetentionPolicy] :: ShardSpace -> Text
- Database.InfluxDB: [shardSpaceShardDuration] :: ShardSpace -> Text
- Database.InfluxDB: [shardSpaceSplit] :: ShardSpace -> Word32
- Database.InfluxDB: [userIsAdmin] :: User -> Bool
- Database.InfluxDB: [userName] :: User -> Text
- Database.InfluxDB: addClusterAdmin :: Config -> Text -> Text -> IO Admin
- Database.InfluxDB: addDatabaseUser :: Config -> Text -> Text -> Text -> IO ()
- Database.InfluxDB: authenticateClusterAdmin :: Config -> IO ()
- Database.InfluxDB: authenticateDatabaseUser :: Config -> Text -> IO ()
- Database.InfluxDB: class FromSeries a
- Database.InfluxDB: class FromSeriesData a
- Database.InfluxDB: class FromValue a
- Database.InfluxDB: class ToSeriesData a
- Database.InfluxDB: class ToValue a
- Database.InfluxDB: configureDatabase :: Config -> Text -> DatabaseRequest -> IO ()
- Database.InfluxDB: createDatabase :: Config -> Text -> IO ()
- Database.InfluxDB: createShardSpace :: Config -> Text -> ShardSpaceRequest -> IO ()
- Database.InfluxDB: data Config
- Database.InfluxDB: data DatabaseRequest
- Database.InfluxDB: data PointT p m a
- Database.InfluxDB: data Series
- Database.InfluxDB: data SeriesData
- Database.InfluxDB: data SeriesT m a
- Database.InfluxDB: data ServerPool
- Database.InfluxDB: data ShardSpace
- Database.InfluxDB: data ShardSpaceRequest
- Database.InfluxDB: data Stream m a
- Database.InfluxDB: data TimePrecision
- Database.InfluxDB: data User
- Database.InfluxDB: data Value
- Database.InfluxDB: deleteClusterAdmin :: Config -> Admin -> IO ()
- Database.InfluxDB: deleteDatabaseUser :: Config -> Text -> Text -> IO ()
- Database.InfluxDB: deleteSeries :: Config -> Text -> Text -> IO ()
- Database.InfluxDB: dropDatabase :: Config -> Text -> IO ()
- Database.InfluxDB: dropShardSpace :: Config -> Text -> Text -> IO ()
- Database.InfluxDB: fromSeries :: FromSeries a => Series -> Either String a
- Database.InfluxDB: fromSeriesData :: FromSeriesData a => SeriesData -> Either String [a]
- Database.InfluxDB: fromValue :: FromValue a => Value -> Either String a
- Database.InfluxDB: grantAdminPrivilegeTo :: Config -> Text -> Text -> IO ()
- Database.InfluxDB: isInSync :: Config -> IO Bool
- Database.InfluxDB: listClusterAdmins :: Config -> IO [Admin]
- Database.InfluxDB: listDatabaseUsers :: Config -> Text -> IO [User]
- Database.InfluxDB: listDatabases :: Config -> IO [Database]
- Database.InfluxDB: listShardSpaces :: Config -> IO [ShardSpace]
- Database.InfluxDB: newServerPool :: Server -> [Server] -> IO (IORef ServerPool)
- Database.InfluxDB: newServerPoolWithRetryPolicy :: RetryPolicy -> Server -> [Server] -> IO (IORef ServerPool)
- Database.InfluxDB: newtype Admin
- Database.InfluxDB: newtype Database
- Database.InfluxDB: newtype Ping
- Database.InfluxDB: parseSeries :: FromSeries a => Series -> Parser a
- Database.InfluxDB: parseSeriesData :: FromSeriesData a => Vector Column -> Vector Value -> Parser a
- Database.InfluxDB: parseValue :: FromValue a => Value -> Parser a
- Database.InfluxDB: ping :: Config -> IO Ping
- Database.InfluxDB: post :: Config -> Text -> SeriesT IO a -> IO a
- Database.InfluxDB: postWithPrecision :: Config -> Text -> TimePrecision -> SeriesT IO a -> IO a
- Database.InfluxDB: revokeAdminPrivilegeFrom :: Config -> Text -> Text -> IO ()
- Database.InfluxDB: rootCreds :: Credentials
- Database.InfluxDB: seriesColumns :: Series -> Vector Column
- Database.InfluxDB: seriesPoints :: Series -> [Vector Value]
- Database.InfluxDB: toSeriesColumns :: ToSeriesData a => Proxy a -> Vector Column
- Database.InfluxDB: toSeriesPoints :: ToSeriesData a => a -> Vector Value
- Database.InfluxDB: toValue :: ToValue a => a -> Value
- Database.InfluxDB: typeMismatch :: String -> Value -> Parser a
- Database.InfluxDB: updateClusterAdminPassword :: Config -> Admin -> Text -> IO ()
- Database.InfluxDB: updateDatabaseUserPassword :: Config -> Text -> Text -> Text -> IO ()
- Database.InfluxDB: withSeries :: forall m a. (Monad m, ToSeriesData a) => Text -> PointT a m () -> SeriesT m ()
- Database.InfluxDB: withValues :: (Vector Value -> ValueParser a) -> Vector Column -> Vector Value -> Parser a
- Database.InfluxDB: writePoints :: (Monad m, ToSeriesData a) => a -> PointT a m ()
- Database.InfluxDB: writeSeries :: (Monad m, ToSeriesData a) => Text -> a -> SeriesT m ()
- Database.InfluxDB: writeSeriesData :: Monad m => Text -> SeriesData -> SeriesT m ()
- Database.InfluxDB.Decode: (.!=) :: Parser (Maybe a) -> a -> Parser a
- Database.InfluxDB.Decode: (.:) :: FromValue a => Vector Value -> Column -> ValueParser a
- Database.InfluxDB.Decode: (.:?) :: FromValue a => Vector Value -> Column -> ValueParser (Maybe a)
- Database.InfluxDB.Decode: class FromSeries a
- Database.InfluxDB.Decode: class FromSeriesData a
- Database.InfluxDB.Decode: class FromValue a
- Database.InfluxDB.Decode: data Parser a
- Database.InfluxDB.Decode: data ValueParser a
- Database.InfluxDB.Decode: fromSeries :: FromSeries a => Series -> Either String a
- Database.InfluxDB.Decode: fromSeriesData :: FromSeriesData a => SeriesData -> Either String [a]
- Database.InfluxDB.Decode: fromSeriesData_ :: FromSeriesData a => SeriesData -> [a]
- Database.InfluxDB.Decode: fromValue :: FromValue a => Value -> Either String a
- Database.InfluxDB.Decode: instance Control.Monad.Reader.Class.MonadReader Database.InfluxDB.Decode.ColumnIndex Database.InfluxDB.Decode.ValueParser
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromSeries Database.InfluxDB.Types.Series
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromSeries Database.InfluxDB.Types.SeriesData
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromSeriesData Database.InfluxDB.Types.SeriesData
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue Data.Text.Internal.Lazy.Text
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue Data.Text.Internal.Text
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue Database.InfluxDB.Types.Value
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Base.String
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Int.Int16
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Int.Int32
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Int.Int64
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Int.Int8
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Types.Bool
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Types.Double
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Types.Int
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Word.Word16
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Word.Word32
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue GHC.Word.Word8
- Database.InfluxDB.Decode: instance Database.InfluxDB.Decode.FromValue a => Database.InfluxDB.Decode.FromValue (GHC.Base.Maybe a)
- Database.InfluxDB.Decode: instance GHC.Base.Applicative Database.InfluxDB.Decode.Parser
- Database.InfluxDB.Decode: instance GHC.Base.Applicative Database.InfluxDB.Decode.ValueParser
- Database.InfluxDB.Decode: instance GHC.Base.Functor Database.InfluxDB.Decode.Parser
- Database.InfluxDB.Decode: instance GHC.Base.Functor Database.InfluxDB.Decode.ValueParser
- Database.InfluxDB.Decode: instance GHC.Base.Monad Database.InfluxDB.Decode.Parser
- Database.InfluxDB.Decode: instance GHC.Base.Monad Database.InfluxDB.Decode.ValueParser
- Database.InfluxDB.Decode: parseSeries :: FromSeries a => Series -> Parser a
- Database.InfluxDB.Decode: parseSeriesData :: FromSeriesData a => Vector Column -> Vector Value -> Parser a
- Database.InfluxDB.Decode: parseValue :: FromValue a => Value -> Parser a
- Database.InfluxDB.Decode: typeMismatch :: String -> Value -> Parser a
- Database.InfluxDB.Decode: withValues :: (Vector Value -> ValueParser a) -> Vector Column -> Vector Value -> Parser a
- Database.InfluxDB.Encode: class ToSeries a
- Database.InfluxDB.Encode: class ToSeriesData a
- Database.InfluxDB.Encode: class ToValue a
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue Data.Text.Internal.Lazy.Text
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue Data.Text.Internal.Text
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue Database.InfluxDB.Types.Value
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Base.String
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Int.Int16
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Int.Int32
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Int.Int64
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Int.Int8
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Types.Bool
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Types.Double
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Types.Int
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Word.Word16
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Word.Word32
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue GHC.Word.Word8
- Database.InfluxDB.Encode: instance Database.InfluxDB.Encode.ToValue a => Database.InfluxDB.Encode.ToValue (GHC.Base.Maybe a)
- Database.InfluxDB.Encode: toSeries :: ToSeries a => a -> Series
- Database.InfluxDB.Encode: toSeriesColumns :: ToSeriesData a => Proxy a -> Vector Column
- Database.InfluxDB.Encode: toSeriesData :: forall a. ToSeriesData a => a -> SeriesData
- Database.InfluxDB.Encode: toSeriesPoints :: ToSeriesData a => a -> Vector Value
- Database.InfluxDB.Encode: toValue :: ToValue a => a -> Value
- Database.InfluxDB.Http: Config :: !Credentials -> !(IORef ServerPool) -> !Manager -> Config
- Database.InfluxDB.Http: Credentials :: !Text -> !Text -> Credentials
- Database.InfluxDB.Http: DatabaseRequest :: [ShardSpaceRequest] -> [Text] -> DatabaseRequest
- Database.InfluxDB.Http: Done :: Stream m a
- Database.InfluxDB.Http: MicrosecondsPrecision :: TimePrecision
- Database.InfluxDB.Http: MillisecondsPrecision :: TimePrecision
- Database.InfluxDB.Http: SecondsPrecision :: TimePrecision
- Database.InfluxDB.Http: Server :: !Text -> !Int -> !Bool -> Server
- Database.InfluxDB.Http: ShardSpaceRequest :: Text -> Text -> Text -> Text -> Word32 -> Word32 -> ShardSpaceRequest
- Database.InfluxDB.Http: Yield :: a -> (m (Stream m a)) -> Stream m a
- Database.InfluxDB.Http: [configCreds] :: Config -> !Credentials
- Database.InfluxDB.Http: [configHttpManager] :: Config -> !Manager
- Database.InfluxDB.Http: [configServerPool] :: Config -> !(IORef ServerPool)
- Database.InfluxDB.Http: [credsPassword] :: Credentials -> !Text
- Database.InfluxDB.Http: [credsUser] :: Credentials -> !Text
- Database.InfluxDB.Http: [databaseRequestContinuousQueries] :: DatabaseRequest -> [Text]
- Database.InfluxDB.Http: [databaseRequestSpaces] :: DatabaseRequest -> [ShardSpaceRequest]
- Database.InfluxDB.Http: [serverHost] :: Server -> !Text
- Database.InfluxDB.Http: [serverPort] :: Server -> !Int
- Database.InfluxDB.Http: [serverSsl] :: Server -> !Bool
- Database.InfluxDB.Http: [shardSpaceRequestName] :: ShardSpaceRequest -> Text
- Database.InfluxDB.Http: [shardSpaceRequestRegex] :: ShardSpaceRequest -> Text
- Database.InfluxDB.Http: [shardSpaceRequestReplicationFactor] :: ShardSpaceRequest -> Word32
- Database.InfluxDB.Http: [shardSpaceRequestRetentionPolicy] :: ShardSpaceRequest -> Text
- Database.InfluxDB.Http: [shardSpaceRequestShardDuration] :: ShardSpaceRequest -> Text
- Database.InfluxDB.Http: [shardSpaceRequestSplit] :: ShardSpaceRequest -> Word32
- Database.InfluxDB.Http: addClusterAdmin :: Config -> Text -> Text -> IO Admin
- Database.InfluxDB.Http: addDatabaseUser :: Config -> Text -> Text -> Text -> IO ()
- Database.InfluxDB.Http: authenticateClusterAdmin :: Config -> IO ()
- Database.InfluxDB.Http: authenticateDatabaseUser :: Config -> Text -> IO ()
- Database.InfluxDB.Http: configureDatabase :: Config -> Text -> DatabaseRequest -> IO ()
- Database.InfluxDB.Http: createDatabase :: Config -> Text -> IO ()
- Database.InfluxDB.Http: createShardSpace :: Config -> Text -> ShardSpaceRequest -> IO ()
- Database.InfluxDB.Http: data Config
- Database.InfluxDB.Http: data Credentials
- Database.InfluxDB.Http: data DatabaseRequest
- Database.InfluxDB.Http: data PointT p m a
- Database.InfluxDB.Http: data SeriesT m a
- Database.InfluxDB.Http: data Server
- Database.InfluxDB.Http: data ShardSpaceRequest
- Database.InfluxDB.Http: data Stream m a
- Database.InfluxDB.Http: data TimePrecision
- Database.InfluxDB.Http: deleteClusterAdmin :: Config -> Admin -> IO ()
- Database.InfluxDB.Http: deleteDatabaseUser :: Config -> Text -> Text -> IO ()
- Database.InfluxDB.Http: deleteSeries :: Config -> Text -> Text -> IO ()
- Database.InfluxDB.Http: dropDatabase :: Config -> Text -> IO ()
- Database.InfluxDB.Http: dropShardSpace :: Config -> Text -> Text -> IO ()
- Database.InfluxDB.Http: grantAdminPrivilegeTo :: Config -> Text -> Text -> IO ()
- Database.InfluxDB.Http: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Database.InfluxDB.Http.PointT p m)
- Database.InfluxDB.Http: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Database.InfluxDB.Http.SeriesT m)
- Database.InfluxDB.Http: instance Control.Monad.Trans.Class.MonadTrans (Database.InfluxDB.Http.PointT p)
- Database.InfluxDB.Http: instance Control.Monad.Trans.Class.MonadTrans Database.InfluxDB.Http.SeriesT
- Database.InfluxDB.Http: instance Data.Aeson.Types.Class.ToJSON Database.InfluxDB.Http.DatabaseRequest
- Database.InfluxDB.Http: instance Data.Aeson.Types.Class.ToJSON Database.InfluxDB.Http.ShardSpaceRequest
- Database.InfluxDB.Http: instance GHC.Base.Applicative m => GHC.Base.Applicative (Database.InfluxDB.Http.PointT p m)
- Database.InfluxDB.Http: instance GHC.Base.Applicative m => GHC.Base.Applicative (Database.InfluxDB.Http.SeriesT m)
- Database.InfluxDB.Http: instance GHC.Base.Functor m => GHC.Base.Functor (Database.InfluxDB.Http.PointT p m)
- Database.InfluxDB.Http: instance GHC.Base.Functor m => GHC.Base.Functor (Database.InfluxDB.Http.SeriesT m)
- Database.InfluxDB.Http: instance GHC.Base.Monad m => Control.Monad.Writer.Class.MonadWriter (Data.DList.DList (Data.Vector.Vector Database.InfluxDB.Types.Value)) (Database.InfluxDB.Http.PointT p m)
- Database.InfluxDB.Http: instance GHC.Base.Monad m => Control.Monad.Writer.Class.MonadWriter (Data.DList.DList Database.InfluxDB.Types.Series) (Database.InfluxDB.Http.SeriesT m)
- Database.InfluxDB.Http: instance GHC.Base.Monad m => GHC.Base.Monad (Database.InfluxDB.Http.PointT p m)
- Database.InfluxDB.Http: instance GHC.Base.Monad m => GHC.Base.Monad (Database.InfluxDB.Http.SeriesT m)
- Database.InfluxDB.Http: instance GHC.Show.Show Database.InfluxDB.Http.DatabaseRequest
- Database.InfluxDB.Http: instance GHC.Show.Show Database.InfluxDB.Http.ShardSpaceRequest
- Database.InfluxDB.Http: isInSync :: Config -> IO Bool
- Database.InfluxDB.Http: listClusterAdmins :: Config -> IO [Admin]
- Database.InfluxDB.Http: listDatabaseUsers :: Config -> Text -> IO [User]
- Database.InfluxDB.Http: listDatabases :: Config -> IO [Database]
- Database.InfluxDB.Http: listShardSpaces :: Config -> IO [ShardSpace]
- Database.InfluxDB.Http: localServer :: Server
- Database.InfluxDB.Http: ping :: Config -> IO Ping
- Database.InfluxDB.Http: post :: Config -> Text -> SeriesT IO a -> IO a
- Database.InfluxDB.Http: postWithPrecision :: Config -> Text -> TimePrecision -> SeriesT IO a -> IO a
- Database.InfluxDB.Http: query :: FromSeries a => Config -> Text -> Text -> IO [a]
- Database.InfluxDB.Http: queryChunked :: FromSeries a => Config -> Text -> Text -> (Stream IO a -> IO b) -> IO b
- Database.InfluxDB.Http: revokeAdminPrivilegeFrom :: Config -> Text -> Text -> IO ()
- Database.InfluxDB.Http: rootCreds :: Credentials
- Database.InfluxDB.Http: updateClusterAdminPassword :: Config -> Admin -> Text -> IO ()
- Database.InfluxDB.Http: updateDatabaseUserPassword :: Config -> Text -> Text -> Text -> IO ()
- Database.InfluxDB.Http: withSeries :: forall m a. (Monad m, ToSeriesData a) => Text -> PointT a m () -> SeriesT m ()
- Database.InfluxDB.Http: writePoints :: (Monad m, ToSeriesData a) => a -> PointT a m ()
- Database.InfluxDB.Http: writeSeries :: (Monad m, ToSeriesData a) => Text -> a -> SeriesT m ()
- Database.InfluxDB.Http: writeSeriesData :: Monad m => Text -> SeriesData -> SeriesT m ()
- Database.InfluxDB.Lens: credentials :: Lens' Config Credentials
- Database.InfluxDB.Lens: host :: Lens' Server Text
- Database.InfluxDB.Lens: httpManager :: Lens' Config Manager
- Database.InfluxDB.Lens: password :: Lens' Credentials Text
- Database.InfluxDB.Lens: port :: Lens' Server Int
- Database.InfluxDB.Lens: ssl :: Lens' Server Bool
- Database.InfluxDB.Lens: type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
- Database.InfluxDB.Lens: type Lens' s a = Lens s s a a
- Database.InfluxDB.Lens: user :: Lens' Credentials Text
- Database.InfluxDB.Stream: Done :: Stream m a
- Database.InfluxDB.Stream: Yield :: a -> (m (Stream m a)) -> Stream m a
- Database.InfluxDB.Stream: data Stream m a
- Database.InfluxDB.Stream: fold :: Monad m => (b -> a -> m b) -> b -> Stream m a -> m b
- Database.InfluxDB.Stream: fold' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> m b
- Database.InfluxDB.Stream: mapM :: Monad m => (a -> m b) -> Stream m a -> m (Stream m b)
- Database.InfluxDB.TH: Options :: (String -> String) -> Options
- Database.InfluxDB.TH: [fieldLabelModifier] :: Options -> String -> String
- Database.InfluxDB.TH: data Options
- Database.InfluxDB.TH: defaultOptions :: Options
- Database.InfluxDB.TH: deriveFromSeriesData :: Options -> Name -> Q [Dec]
- Database.InfluxDB.TH: deriveSeriesData :: Options -> Name -> Q [Dec]
- Database.InfluxDB.TH: deriveToSeriesData :: Options -> Name -> Q [Dec]
- Database.InfluxDB.TH: stripPrefixLower :: String -> String -> String
- Database.InfluxDB.TH: stripPrefixSnake :: String -> String -> String
- Database.InfluxDB.Types: Admin :: Text -> Admin
- Database.InfluxDB.Types: Bool :: !Bool -> Value
- Database.InfluxDB.Types: Float :: !Double -> Value
- Database.InfluxDB.Types: Int :: !Int64 -> Value
- Database.InfluxDB.Types: JsonDecodeError :: String -> InfluxException
- Database.InfluxDB.Types: Null :: Value
- Database.InfluxDB.Types: Ping :: Text -> Ping
- Database.InfluxDB.Types: Series :: {-# UNPACK #-} !Text -> {-# UNPACK #-} !SeriesData -> Series
- Database.InfluxDB.Types: SeriesData :: Vector Column -> [Vector Value] -> SeriesData
- Database.InfluxDB.Types: SeriesDecodeError :: String -> InfluxException
- Database.InfluxDB.Types: ShardSpace :: Maybe Text -> Text -> Text -> Text -> Text -> Word32 -> Word32 -> ShardSpace
- Database.InfluxDB.Types: String :: !Text -> Value
- Database.InfluxDB.Types: User :: Text -> Bool -> User
- Database.InfluxDB.Types: [adminName] :: Admin -> Text
- Database.InfluxDB.Types: [credsPassword] :: Credentials -> !Text
- Database.InfluxDB.Types: [credsUser] :: Credentials -> !Text
- Database.InfluxDB.Types: [pingStatus] :: Ping -> Text
- Database.InfluxDB.Types: [seriesDataColumns] :: SeriesData -> Vector Column
- Database.InfluxDB.Types: [seriesDataPoints] :: SeriesData -> [Vector Value]
- Database.InfluxDB.Types: [seriesData] :: Series -> {-# UNPACK #-} !SeriesData
- Database.InfluxDB.Types: [seriesName] :: Series -> {-# UNPACK #-} !Text
- Database.InfluxDB.Types: [serverHost] :: Server -> !Text
- Database.InfluxDB.Types: [serverPort] :: Server -> !Int
- Database.InfluxDB.Types: [serverSsl] :: Server -> !Bool
- Database.InfluxDB.Types: [shardSpaceDatabase] :: ShardSpace -> Maybe Text
- Database.InfluxDB.Types: [shardSpaceName] :: ShardSpace -> Text
- Database.InfluxDB.Types: [shardSpaceRegex] :: ShardSpace -> Text
- Database.InfluxDB.Types: [shardSpaceReplicationFactor] :: ShardSpace -> Word32
- Database.InfluxDB.Types: [shardSpaceRetentionPolicy] :: ShardSpace -> Text
- Database.InfluxDB.Types: [shardSpaceShardDuration] :: ShardSpace -> Text
- Database.InfluxDB.Types: [shardSpaceSplit] :: ShardSpace -> Word32
- Database.InfluxDB.Types: [userIsAdmin] :: User -> Bool
- Database.InfluxDB.Types: [userName] :: User -> Text
- Database.InfluxDB.Types: activeServer :: IORef ServerPool -> IO Server
- Database.InfluxDB.Types: data Series
- Database.InfluxDB.Types: data SeriesData
- Database.InfluxDB.Types: data ServerPool
- Database.InfluxDB.Types: data ShardSpace
- Database.InfluxDB.Types: data User
- Database.InfluxDB.Types: data Value
- Database.InfluxDB.Types: failover :: IORef ServerPool -> IO ()
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.FromJSON Database.InfluxDB.Types.Admin
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.FromJSON Database.InfluxDB.Types.Database
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.FromJSON Database.InfluxDB.Types.Ping
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.FromJSON Database.InfluxDB.Types.Series
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.FromJSON Database.InfluxDB.Types.ShardSpace
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.FromJSON Database.InfluxDB.Types.User
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.FromJSON Database.InfluxDB.Types.Value
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.ToJSON Database.InfluxDB.Types.Series
- Database.InfluxDB.Types: instance Data.Aeson.Types.Class.ToJSON Database.InfluxDB.Types.Value
- Database.InfluxDB.Types: instance Data.Data.Data Database.InfluxDB.Types.Value
- Database.InfluxDB.Types: instance GHC.Classes.Eq Database.InfluxDB.Types.SeriesData
- Database.InfluxDB.Types: instance GHC.Classes.Eq Database.InfluxDB.Types.Value
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.Admin
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.Credentials
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.Database
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.Ping
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.Series
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.SeriesData
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.ShardSpace
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.User
- Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.Value
- Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.Admin
- Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.Credentials
- Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.Ping
- Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.SeriesData
- Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.ShardSpace
- Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.User
- Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.Value
- Database.InfluxDB.Types: jsonDecodeError :: String -> IO a
- Database.InfluxDB.Types: newServerPool :: Server -> [Server] -> IO (IORef ServerPool)
- Database.InfluxDB.Types: newServerPoolWithRetryPolicy :: RetryPolicy -> Server -> [Server] -> IO (IORef ServerPool)
- Database.InfluxDB.Types: newtype Admin
- Database.InfluxDB.Types: newtype Ping
- Database.InfluxDB.Types: seriesColumns :: Series -> Vector Column
- Database.InfluxDB.Types: seriesDecodeError :: String -> IO a
- Database.InfluxDB.Types: seriesPoints :: Series -> [Vector Value]
- Database.InfluxDB.Types: serverRetryPolicy :: ServerPool -> RetryPolicy
- Database.InfluxDB.Types: type Column = Text
- Database.InfluxDB.Types: type Interface = Text
+ Database.InfluxDB: (%) :: Format b c -> Format a b -> Format a c
+ Database.InfluxDB: BadRequest :: String -> Request -> InfluxException
+ Database.InfluxDB: FieldBool :: !Bool -> FieldValue
+ Database.InfluxDB: FieldFloat :: !Double -> FieldValue
+ Database.InfluxDB: FieldInt :: !Int64 -> FieldValue
+ Database.InfluxDB: FieldNull :: FieldValue
+ Database.InfluxDB: FieldString :: !Text -> FieldValue
+ Database.InfluxDB: IllformedJSON :: String -> ByteString -> InfluxException
+ Database.InfluxDB: Line :: !Key -> !(Map Key Text) -> !(Map Key FieldValue) -> !(Maybe time) -> Line time
+ Database.InfluxDB: ServerError :: String -> InfluxException
+ Database.InfluxDB: [Hour] :: Precision ty
+ Database.InfluxDB: [Microsecond] :: Precision ty
+ Database.InfluxDB: [Millisecond] :: Precision ty
+ Database.InfluxDB: [Minute] :: Precision ty
+ Database.InfluxDB: [Nanosecond] :: Precision ty
+ Database.InfluxDB: [RFC3339] :: Precision QueryRequest
+ Database.InfluxDB: [Second] :: Precision ty
+ Database.InfluxDB: authentication :: HasCredentials a => Lens' a (Maybe Credentials)
+ Database.InfluxDB: class HasDatabase a
+ Database.InfluxDB: class HasManager a
+ Database.InfluxDB: class HasPrecision (ty :: RequestType) a | a -> ty
+ Database.InfluxDB: class HasServer a
+ Database.InfluxDB: class QueryResults a
+ Database.InfluxDB: class Timestamp time
+ Database.InfluxDB: data Database
+ Database.InfluxDB: data FieldValue
+ Database.InfluxDB: data Key
+ Database.InfluxDB: data Line time
+ Database.InfluxDB: data Precision (ty :: RequestType)
+ Database.InfluxDB: data Query
+ Database.InfluxDB: data QueryParams
+ Database.InfluxDB: data WriteParams
+ Database.InfluxDB: database :: HasDatabase a => Lens' a Database
+ Database.InfluxDB: fieldSet :: Lens' (Line time) (Map Key FieldValue)
+ Database.InfluxDB: formatQuery :: Format Query r -> r
+ Database.InfluxDB: getField :: Text -> Vector Text -> Array -> Parser Value
+ Database.InfluxDB: getTag :: Monad m => Text -> HashMap Text Text -> m Text
+ Database.InfluxDB: host :: Lens' Server Text
+ Database.InfluxDB: manage :: QueryParams -> Query -> IO ()
+ Database.InfluxDB: manager :: HasManager a => Lens' a (Either ManagerSettings Manager)
+ Database.InfluxDB: measurement :: Lens' (Line time) Key
+ Database.InfluxDB: parseFieldValue :: Value -> Parser FieldValue
+ Database.InfluxDB: parseKey :: Key -> Vector Text -> Array -> Parser Key
+ Database.InfluxDB: parseResults :: QueryResults a => Precision QueryRequest -> Value -> Parser (Vector a)
+ Database.InfluxDB: parseResultsWith :: (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> Parser a) -> Value -> Parser (Vector a)
+ Database.InfluxDB: parseTimestamp :: Precision ty -> Value -> Parser POSIXTime
+ Database.InfluxDB: password :: Lens' Credentials Text
+ Database.InfluxDB: port :: Lens' Server Int
+ Database.InfluxDB: precision :: HasPrecision ty a => Lens' a (Precision ty)
+ Database.InfluxDB: precisionName :: Precision ty -> Text
+ Database.InfluxDB: precisionScale :: Fractional a => Precision ty -> a
+ Database.InfluxDB: queryParams :: Database -> QueryParams
+ Database.InfluxDB: retentionPolicy :: Lens' WriteParams (Maybe Key)
+ Database.InfluxDB: roundTo :: Timestamp time => Precision WriteRequest -> time -> Int64
+ Database.InfluxDB: scaleTo :: Timestamp time => Precision WriteRequest -> time -> Int64
+ Database.InfluxDB: server :: HasServer a => Lens' a Server
+ Database.InfluxDB: ssl :: Lens' Server Bool
+ Database.InfluxDB: tagSet :: Lens' (Line time) (Map Key Text)
+ Database.InfluxDB: timestamp :: Lens' (Line time) (Maybe time)
+ Database.InfluxDB: user :: Lens' Credentials Text
+ Database.InfluxDB: write :: Timestamp time => WriteParams -> Line time -> IO ()
+ Database.InfluxDB: writeBatch :: (Timestamp time, Traversable f) => WriteParams -> f (Line time) -> IO ()
+ Database.InfluxDB: writeByteString :: WriteParams -> ByteString -> IO ()
+ Database.InfluxDB: writeParams :: Database -> WriteParams
+ Database.InfluxDB.Format: (%) :: Format b c -> Format a b -> Format a c
+ Database.InfluxDB.Format: data Format a b
+ Database.InfluxDB.Format: data Query
+ Database.InfluxDB.Format: database :: Format r (Database -> r)
+ Database.InfluxDB.Format: decimal :: Integral a => Format r (a -> r)
+ Database.InfluxDB.Format: fieldVal :: Format r (FieldValue -> r)
+ Database.InfluxDB.Format: formatDatabase :: Format Database r -> r
+ Database.InfluxDB.Format: formatKey :: Format Key r -> r
+ Database.InfluxDB.Format: formatQuery :: Format Query r -> r
+ Database.InfluxDB.Format: fromQuery :: Query -> ByteString
+ Database.InfluxDB.Format: instance Control.Category.Category Database.InfluxDB.Format.Format
+ Database.InfluxDB.Format: instance a ~ b => Data.String.IsString (Database.InfluxDB.Format.Format a b)
+ Database.InfluxDB.Format: key :: Format r (Key -> r)
+ Database.InfluxDB.Format: keys :: Format r ([Key] -> r)
+ Database.InfluxDB.Format: makeFormat :: (a -> Builder) -> Format r (a -> r)
+ Database.InfluxDB.Format: realFloat :: RealFloat a => Format r (a -> r)
+ Database.InfluxDB.Format: text :: Format r (Text -> r)
+ Database.InfluxDB.Format: time :: FormatTime time => Format r (time -> r)
+ Database.InfluxDB.JSON: Decoder :: (Parser a -> Parser b) -> (Parser (Vector b) -> Parser (Vector a)) -> Decoder a
+ Database.InfluxDB.JSON: [decodeEach] :: Decoder a -> Parser a -> Parser b
+ Database.InfluxDB.JSON: [decodeFold] :: Decoder a -> Parser (Vector b) -> Parser (Vector a)
+ Database.InfluxDB.JSON: data Decoder a
+ Database.InfluxDB.JSON: getField :: Text -> Vector Text -> Array -> Parser Value
+ Database.InfluxDB.JSON: getTag :: Monad m => Text -> HashMap Text Text -> m Text
+ Database.InfluxDB.JSON: lenientDecoder :: Decoder a
+ Database.InfluxDB.JSON: parseErrorObject :: Value -> Parser a
+ Database.InfluxDB.JSON: parseFieldValue :: Value -> Parser FieldValue
+ Database.InfluxDB.JSON: parsePOSIXTime :: Precision ty -> Value -> Parser POSIXTime
+ Database.InfluxDB.JSON: parseRFC3339 :: ParseTime time => Value -> Parser time
+ Database.InfluxDB.JSON: parseResultsObject :: Value -> Parser (Vector Value)
+ Database.InfluxDB.JSON: parseResultsWith :: (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> Parser a) -> Value -> Parser (Vector a)
+ Database.InfluxDB.JSON: parseResultsWithDecoder :: Decoder a -> (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> Parser a) -> Value -> Parser (Vector a)
+ Database.InfluxDB.JSON: parseSeriesBody :: Value -> Parser (Maybe Text, HashMap Text Text, Vector Text, Array)
+ Database.InfluxDB.JSON: parseSeriesObject :: Value -> Parser (Vector Value)
+ Database.InfluxDB.JSON: parseTimestamp :: Precision ty -> Value -> Parser POSIXTime
+ Database.InfluxDB.JSON: strictDecoder :: Decoder a
+ Database.InfluxDB.Line: Line :: !Key -> !(Map Key Text) -> !(Map Key FieldValue) -> !(Maybe time) -> Line time
+ Database.InfluxDB.Line: buildLine :: (time -> Int64) -> Line time -> Builder
+ Database.InfluxDB.Line: buildLines :: Traversable f => (time -> Int64) -> f (Line time) -> Builder
+ Database.InfluxDB.Line: data Line time
+ Database.InfluxDB.Line: encodeLine :: (time -> Int64) -> Line time -> ByteString
+ Database.InfluxDB.Line: encodeLines :: Traversable f => (time -> Int64) -> f (Line time) -> ByteString
+ Database.InfluxDB.Line: fieldSet :: Lens' (Line time) (Map Key FieldValue)
+ Database.InfluxDB.Line: measurement :: Lens' (Line time) Key
+ Database.InfluxDB.Line: tagSet :: Lens' (Line time) (Map Key Text)
+ Database.InfluxDB.Line: timestamp :: Lens' (Line time) (Maybe time)
+ Database.InfluxDB.Manage: data ShowQuery
+ Database.InfluxDB.Manage: data ShowSeries
+ Database.InfluxDB.Manage: database :: HasDatabase a => Lens' a Database
+ Database.InfluxDB.Manage: duration :: Lens' ShowQuery NominalDiffTime
+ Database.InfluxDB.Manage: instance Database.InfluxDB.Query.QueryResults Database.InfluxDB.Manage.ShowQuery
+ Database.InfluxDB.Manage: instance Database.InfluxDB.Query.QueryResults Database.InfluxDB.Manage.ShowSeries
+ Database.InfluxDB.Manage: instance Database.InfluxDB.Types.HasDatabase Database.InfluxDB.Manage.ShowQuery
+ Database.InfluxDB.Manage: instance GHC.Show.Show Database.InfluxDB.Manage.ShowQuery
+ Database.InfluxDB.Manage: instance GHC.Show.Show Database.InfluxDB.Manage.ShowSeries
+ Database.InfluxDB.Manage: key :: Lens' ShowSeries Key
+ Database.InfluxDB.Manage: manage :: QueryParams -> Query -> IO ()
+ Database.InfluxDB.Manage: qid :: Lens' ShowQuery Int
+ Database.InfluxDB.Manage: queryText :: Lens' ShowQuery Query
+ Database.InfluxDB.Ping: PingParams :: !Server -> !(Either ManagerSettings Manager) -> !(Maybe Int) -> PingParams
+ Database.InfluxDB.Ping: PingResult :: !TimeSpec -> !ByteString -> PingResult
+ Database.InfluxDB.Ping: [_influxdbVersion] :: PingResult -> !ByteString
+ Database.InfluxDB.Ping: [_manager] :: PingParams -> !(Either ManagerSettings Manager)
+ Database.InfluxDB.Ping: [_roundtripTime] :: PingResult -> !TimeSpec
+ Database.InfluxDB.Ping: [_server] :: PingParams -> !Server
+ Database.InfluxDB.Ping: [_waitForLeader] :: PingParams -> !(Maybe Int)
+ Database.InfluxDB.Ping: data PingParams
+ Database.InfluxDB.Ping: data PingResult
+ Database.InfluxDB.Ping: influxdbVersion :: Lens' PingResult ByteString
+ Database.InfluxDB.Ping: instance Database.InfluxDB.Types.HasManager Database.InfluxDB.Ping.PingParams
+ Database.InfluxDB.Ping: instance Database.InfluxDB.Types.HasServer Database.InfluxDB.Ping.PingParams
+ Database.InfluxDB.Ping: instance GHC.Classes.Eq Database.InfluxDB.Ping.PingResult
+ Database.InfluxDB.Ping: instance GHC.Classes.Ord Database.InfluxDB.Ping.PingResult
+ Database.InfluxDB.Ping: instance GHC.Show.Show Database.InfluxDB.Ping.PingResult
+ Database.InfluxDB.Ping: manager :: HasManager a => Lens' a (Either ManagerSettings Manager)
+ Database.InfluxDB.Ping: ping :: PingParams -> IO PingResult
+ Database.InfluxDB.Ping: pingParams :: PingParams
+ Database.InfluxDB.Ping: roundtripTime :: Lens' PingResult TimeSpec
+ Database.InfluxDB.Ping: server :: HasServer a => Lens' a Server
+ Database.InfluxDB.Ping: waitForLeader :: Lens' PingParams (Maybe Int)
+ Database.InfluxDB.Query: class QueryResults a
+ Database.InfluxDB.Query: data Query
+ Database.InfluxDB.Query: data QueryParams
+ Database.InfluxDB.Query: database :: HasDatabase a => Lens' a Database
+ Database.InfluxDB.Query: instance (a ~ Data.Aeson.Types.Internal.Value, b ~ Data.Aeson.Types.Internal.Value) => Database.InfluxDB.Query.QueryResults (a, b)
+ Database.InfluxDB.Query: instance (a ~ Data.Aeson.Types.Internal.Value, b ~ Data.Aeson.Types.Internal.Value, c ~ Data.Aeson.Types.Internal.Value) => Database.InfluxDB.Query.QueryResults (a, b, c)
+ Database.InfluxDB.Query: instance (a ~ Data.Aeson.Types.Internal.Value, b ~ Data.Aeson.Types.Internal.Value, c ~ Data.Aeson.Types.Internal.Value, d ~ Data.Aeson.Types.Internal.Value) => Database.InfluxDB.Query.QueryResults (a, b, c, d)
+ Database.InfluxDB.Query: instance (a ~ Data.Aeson.Types.Internal.Value, b ~ Data.Aeson.Types.Internal.Value, c ~ Data.Aeson.Types.Internal.Value, d ~ Data.Aeson.Types.Internal.Value, e ~ Data.Aeson.Types.Internal.Value) => Database.InfluxDB.Query.QueryResults (a, b, c, d, e)
+ Database.InfluxDB.Query: instance (a ~ Data.Aeson.Types.Internal.Value, b ~ Data.Aeson.Types.Internal.Value, c ~ Data.Aeson.Types.Internal.Value, d ~ Data.Aeson.Types.Internal.Value, e ~ Data.Aeson.Types.Internal.Value, f ~ Data.Aeson.Types.Internal.Value) => Database.InfluxDB.Query.QueryResults (a, b, c, d, e, f)
+ Database.InfluxDB.Query: instance (a ~ Data.Aeson.Types.Internal.Value, b ~ Data.Aeson.Types.Internal.Value, c ~ Data.Aeson.Types.Internal.Value, d ~ Data.Aeson.Types.Internal.Value, e ~ Data.Aeson.Types.Internal.Value, f ~ Data.Aeson.Types.Internal.Value, g ~ Data.Aeson.Types.Internal.Value) => Database.InfluxDB.Query.QueryResults (a, b, c, d, e, f, g)
+ Database.InfluxDB.Query: instance (a ~ Data.Aeson.Types.Internal.Value, b ~ Data.Aeson.Types.Internal.Value, c ~ Data.Aeson.Types.Internal.Value, d ~ Data.Aeson.Types.Internal.Value, e ~ Data.Aeson.Types.Internal.Value, f ~ Data.Aeson.Types.Internal.Value, g ~ Data.Aeson.Types.Internal.Value, h ~ Data.Aeson.Types.Internal.Value) => Database.InfluxDB.Query.QueryResults (a, b, c, d, e, f, g, h)
+ Database.InfluxDB.Query: instance Database.InfluxDB.Query.QueryResults Data.Void.Void
+ Database.InfluxDB.Query: instance Database.InfluxDB.Types.HasCredentials Database.InfluxDB.Query.QueryParams
+ Database.InfluxDB.Query: instance Database.InfluxDB.Types.HasDatabase Database.InfluxDB.Query.QueryParams
+ Database.InfluxDB.Query: instance Database.InfluxDB.Types.HasManager Database.InfluxDB.Query.QueryParams
+ Database.InfluxDB.Query: instance Database.InfluxDB.Types.HasPrecision 'Database.InfluxDB.Types.QueryRequest Database.InfluxDB.Query.QueryParams
+ Database.InfluxDB.Query: instance Database.InfluxDB.Types.HasServer Database.InfluxDB.Query.QueryParams
+ Database.InfluxDB.Query: manager :: HasManager a => Lens' a (Either ManagerSettings Manager)
+ Database.InfluxDB.Query: parseKey :: Key -> Vector Text -> Array -> Parser Key
+ Database.InfluxDB.Query: parseResults :: QueryResults a => Precision QueryRequest -> Value -> Parser (Vector a)
+ Database.InfluxDB.Query: parseResultsWith :: (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> Parser a) -> Value -> Parser (Vector a)
+ Database.InfluxDB.Query: precision :: HasPrecision ty a => Lens' a (Precision ty)
+ Database.InfluxDB.Query: query :: QueryResults a => QueryParams -> Query -> IO (Vector a)
+ Database.InfluxDB.Query: queryChunked :: QueryResults a => QueryParams -> Optional Int -> Query -> FoldM IO (Vector a) r -> IO r
+ Database.InfluxDB.Query: queryParams :: Database -> QueryParams
+ Database.InfluxDB.Query: server :: HasServer a => Lens' a Server
+ Database.InfluxDB.Query: withQueryResponse :: QueryParams -> Maybe (Optional Int) -> Query -> (Request -> Response BodyReader -> IO r) -> IO r
+ Database.InfluxDB.Types: BadRequest :: String -> Request -> InfluxException
+ Database.InfluxDB.Types: FieldBool :: !Bool -> FieldValue
+ Database.InfluxDB.Types: FieldFloat :: !Double -> FieldValue
+ Database.InfluxDB.Types: FieldInt :: !Int64 -> FieldValue
+ Database.InfluxDB.Types: FieldNull :: FieldValue
+ Database.InfluxDB.Types: FieldString :: !Text -> FieldValue
+ Database.InfluxDB.Types: IllformedJSON :: String -> ByteString -> InfluxException
+ Database.InfluxDB.Types: Key :: Text -> Key
+ Database.InfluxDB.Types: Query :: Text -> Query
+ Database.InfluxDB.Types: QueryRequest :: RequestType
+ Database.InfluxDB.Types: ServerError :: String -> InfluxException
+ Database.InfluxDB.Types: WriteRequest :: RequestType
+ Database.InfluxDB.Types: [Hour] :: Precision ty
+ Database.InfluxDB.Types: [Microsecond] :: Precision ty
+ Database.InfluxDB.Types: [Millisecond] :: Precision ty
+ Database.InfluxDB.Types: [Minute] :: Precision ty
+ Database.InfluxDB.Types: [Nanosecond] :: Precision ty
+ Database.InfluxDB.Types: [RFC3339] :: Precision QueryRequest
+ Database.InfluxDB.Types: [Second] :: Precision ty
+ Database.InfluxDB.Types: [_host] :: Server -> !Text
+ Database.InfluxDB.Types: [_password] :: Credentials -> !Text
+ Database.InfluxDB.Types: [_port] :: Server -> !Int
+ Database.InfluxDB.Types: [_ssl] :: Server -> !Bool
+ Database.InfluxDB.Types: [_user] :: Credentials -> !Text
+ Database.InfluxDB.Types: authentication :: HasCredentials a => Lens' a (Maybe Credentials)
+ Database.InfluxDB.Types: class HasCredentials a
+ Database.InfluxDB.Types: class HasDatabase a
+ Database.InfluxDB.Types: class HasManager a
+ Database.InfluxDB.Types: class HasPrecision (ty :: RequestType) a | a -> ty
+ Database.InfluxDB.Types: class HasServer a
+ Database.InfluxDB.Types: class Timestamp time
+ Database.InfluxDB.Types: data FieldValue
+ Database.InfluxDB.Types: data Precision (ty :: RequestType)
+ Database.InfluxDB.Types: data RequestType
+ Database.InfluxDB.Types: database :: HasDatabase a => Lens' a Database
+ Database.InfluxDB.Types: fromNonEmptyString :: String -> String -> Text
+ Database.InfluxDB.Types: host :: Lens' Server Text
+ Database.InfluxDB.Types: instance Data.Data.Data Database.InfluxDB.Types.FieldValue
+ Database.InfluxDB.Types: instance Data.String.IsString Database.InfluxDB.Types.Database
+ Database.InfluxDB.Types: instance Data.String.IsString Database.InfluxDB.Types.FieldValue
+ Database.InfluxDB.Types: instance Data.String.IsString Database.InfluxDB.Types.Key
+ Database.InfluxDB.Types: instance Data.String.IsString Database.InfluxDB.Types.Query
+ Database.InfluxDB.Types: instance Database.InfluxDB.Types.Timestamp Data.Time.Clock.UTC.NominalDiffTime
+ Database.InfluxDB.Types: instance Database.InfluxDB.Types.Timestamp Data.Time.Clock.UTC.UTCTime
+ Database.InfluxDB.Types: instance GHC.Classes.Eq Database.InfluxDB.Types.Database
+ Database.InfluxDB.Types: instance GHC.Classes.Eq Database.InfluxDB.Types.FieldValue
+ Database.InfluxDB.Types: instance GHC.Classes.Eq Database.InfluxDB.Types.Key
+ Database.InfluxDB.Types: instance GHC.Classes.Eq Database.InfluxDB.Types.Server
+ Database.InfluxDB.Types: instance GHC.Classes.Ord Database.InfluxDB.Types.Database
+ Database.InfluxDB.Types: instance GHC.Classes.Ord Database.InfluxDB.Types.Key
+ Database.InfluxDB.Types: instance GHC.Generics.Generic Database.InfluxDB.Types.FieldValue
+ Database.InfluxDB.Types: instance GHC.Show.Show (Database.InfluxDB.Types.Precision a)
+ Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.FieldValue
+ Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.Key
+ Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.Query
+ Database.InfluxDB.Types: instance GHC.Show.Show Database.InfluxDB.Types.RequestType
+ Database.InfluxDB.Types: localServer :: Server
+ Database.InfluxDB.Types: manager :: HasManager a => Lens' a (Either ManagerSettings Manager)
+ Database.InfluxDB.Types: newtype Key
+ Database.InfluxDB.Types: newtype Query
+ Database.InfluxDB.Types: password :: Lens' Credentials Text
+ Database.InfluxDB.Types: port :: Lens' Server Int
+ Database.InfluxDB.Types: precision :: HasPrecision ty a => Lens' a (Precision ty)
+ Database.InfluxDB.Types: precisionName :: Precision ty -> Text
+ Database.InfluxDB.Types: precisionScale :: Fractional a => Precision ty -> a
+ Database.InfluxDB.Types: roundAt :: RealFrac a => a -> a -> a
+ Database.InfluxDB.Types: roundTo :: Timestamp time => Precision WriteRequest -> time -> Int64
+ Database.InfluxDB.Types: scaleTo :: Timestamp time => Precision WriteRequest -> time -> Int64
+ Database.InfluxDB.Types: server :: HasServer a => Lens' a Server
+ Database.InfluxDB.Types: ssl :: Lens' Server Bool
+ Database.InfluxDB.Types: user :: Lens' Credentials Text
+ Database.InfluxDB.Write: data WriteParams
+ Database.InfluxDB.Write: database :: HasDatabase a => Lens' a Database
+ Database.InfluxDB.Write: instance Database.InfluxDB.Types.HasCredentials Database.InfluxDB.Write.WriteParams
+ Database.InfluxDB.Write: instance Database.InfluxDB.Types.HasDatabase Database.InfluxDB.Write.WriteParams
+ Database.InfluxDB.Write: instance Database.InfluxDB.Types.HasManager Database.InfluxDB.Write.WriteParams
+ Database.InfluxDB.Write: instance Database.InfluxDB.Types.HasPrecision 'Database.InfluxDB.Types.WriteRequest Database.InfluxDB.Write.WriteParams
+ Database.InfluxDB.Write: instance Database.InfluxDB.Types.HasServer Database.InfluxDB.Write.WriteParams
+ Database.InfluxDB.Write: manager :: HasManager a => Lens' a (Either ManagerSettings Manager)
+ Database.InfluxDB.Write: precision :: HasPrecision ty a => Lens' a (Precision ty)
+ Database.InfluxDB.Write: retentionPolicy :: Lens' WriteParams (Maybe Key)
+ Database.InfluxDB.Write: server :: HasServer a => Lens' a Server
+ Database.InfluxDB.Write: write :: Timestamp time => WriteParams -> Line time -> IO ()
+ Database.InfluxDB.Write: writeBatch :: (Timestamp time, Traversable f) => WriteParams -> f (Line time) -> IO ()
+ Database.InfluxDB.Write: writeByteString :: WriteParams -> ByteString -> IO ()
+ Database.InfluxDB.Write: writeParams :: Database -> WriteParams
+ Database.InfluxDB.Write.UDP: data WriteParams
+ Database.InfluxDB.Write.UDP: instance Database.InfluxDB.Types.HasPrecision 'Database.InfluxDB.Types.WriteRequest Database.InfluxDB.Write.UDP.WriteParams
+ Database.InfluxDB.Write.UDP: precision :: HasPrecision ty a => Lens' a (Precision ty)
+ Database.InfluxDB.Write.UDP: sockAddr :: Lens' WriteParams SockAddr
+ Database.InfluxDB.Write.UDP: socket :: Lens' WriteParams Socket
+ Database.InfluxDB.Write.UDP: write :: Timestamp time => WriteParams -> Line time -> IO ()
+ Database.InfluxDB.Write.UDP: writeBatch :: (Timestamp time, Traversable f) => WriteParams -> f (Line time) -> IO ()
+ Database.InfluxDB.Write.UDP: writeByteString :: WriteParams -> ByteString -> IO ()
+ Database.InfluxDB.Write.UDP: writeParams :: Socket -> SockAddr -> WriteParams
- Database.InfluxDB: query :: FromSeries a => Config -> Text -> Text -> IO [a]
+ Database.InfluxDB: query :: QueryResults a => QueryParams -> Query -> IO (Vector a)
- Database.InfluxDB: queryChunked :: FromSeries a => Config -> Text -> Text -> (Stream IO a -> IO b) -> IO b
+ Database.InfluxDB: queryChunked :: QueryResults a => QueryParams -> Optional Int -> Query -> FoldM IO (Vector a) r -> IO r
Files
- CHANGELOG.md +8/−0
- README.md +5/−4
- examples/random-points.hs +61/−91
- examples/write-udp.hs +36/−0
- influxdb.cabal +64/−56
- src/Database/InfluxDB.hs +79/−76
- src/Database/InfluxDB/Decode.hs +0/−232
- src/Database/InfluxDB/Encode.hs +0/−95
- src/Database/InfluxDB/Format.hs +108/−0
- src/Database/InfluxDB/Http.hs +0/−792
- src/Database/InfluxDB/JSON.hs +207/−0
- src/Database/InfluxDB/Lens.hs +0/−66
- src/Database/InfluxDB/Line.hs +124/−0
- src/Database/InfluxDB/Manage.hs +158/−0
- src/Database/InfluxDB/Ping.hs +101/−0
- src/Database/InfluxDB/Query.hs +367/−0
- src/Database/InfluxDB/Stream.hs +0/−41
- src/Database/InfluxDB/TH.hs +0/−141
- src/Database/InfluxDB/Types.hs +177/−254
- src/Database/InfluxDB/Types/Internal.hs +0/−49
- src/Database/InfluxDB/Write.hs +209/−0
- src/Database/InfluxDB/Write/UDP.hs +87/−0
- src/Network/HTTP/Client/Compat.hs +16/−0
- tests/test-suite.hs +0/−461
CHANGELOG.md view
@@ -1,3 +1,11 @@+## TBD++## v1.0.0 - 2017-03-03++The library was completely rewritten and support for older InfluxDB has been dropped.++* Support for InfluxDB 1.2+ ## v0.10.0 - 2016-05-17 * Fix a typo in a Haddock comment (#28)
README.md view
@@ -1,10 +1,11 @@ Haskell client library for InfluxDB ==========-[](https://travis-ci.org/maoe/influxdb-haskell?branch=develop)-[](https://coveralls.io/r/maoe/influxdb-haskell?branch=develop)-[](https://gitter.im/maoe/influxdb-haskell)+[](https://travis-ci.org/maoe/influxdb-haskell)+[](https://hackage.haskell.org/package/influxdb)+[](http://packdeps.haskellers.com/feed?needle=influxdbhttp://packdeps.haskellers.com/feed?needle=influxdb)+[](https://gitter.im/maoe/influxdb-haskell?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -Support for current version of InfluxDB is under development.+Currently this library supports InfluxDB 1.2. Contact information ----------
examples/random-points.hs view
@@ -1,26 +1,30 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE TemplateHaskell #-}-import Control.Applicative-import Control.Exception as E-import Control.Monad-import Control.Monad.Trans-import Data.Function (fix)-import Data.Time.Clock.POSIX+import Data.Foldable+import Data.Traversable import System.Environment import System.IO-import qualified Data.Text as T+import Text.Printf (printf) +import Control.Lens+import Data.Aeson+import Data.Optional (Optional(Default))+import Data.Time.Clock.POSIX import System.Random.MWC (Variate(..))+import qualified Control.Foldl as L+import qualified Data.Map.Strict as Map+import qualified Data.Text as T import qualified Network.HTTP.Client as HC import qualified System.Random.MWC as MWC import Database.InfluxDB-import Database.InfluxDB.TH-import qualified Database.InfluxDB.Stream as S+import qualified Database.InfluxDB.Format as F+import qualified Database.InfluxDB.Manage as M oneWeekInSeconds :: Int oneWeekInSeconds = 7*24*60*60@@ -29,74 +33,60 @@ main = do [read -> (numPoints :: Int), read -> (batches :: Int)] <- getArgs hSetBuffering stdout NoBuffering- HC.withManager managerSettings $ \manager -> do- config <- newConfig manager+ manager' <- HC.newManager managerSettings - let db = "ctx"- dropDatabase config db- `E.catch`- -- Ignore exceptions here- \(_ :: HC.HttpException) -> return ()- createDatabase config "ctx"- gen <- MWC.create- flip fix batches $ \outerLoop !m -> when (m > 0) $ do- postWithPrecision config db SecondsPrecision $ withSeries "ct1" $- flip fix numPoints $ \innerLoop !n -> when (n > 0) $ do- !timestamp <- liftIO $ (-)- <$> getPOSIXTime- <*> (fromIntegral <$> uniformR (0, oneWeekInSeconds) gen)- !value <- liftIO $ uniform gen- writePoints $ Point value (Time timestamp)- innerLoop $ n - 1- outerLoop $ m - 1+ let+ ctx = "ctx"+ ct1 = "ct1"+ qparams = queryParams ctx+ & manager .~ Right manager'+ & precision .~ RFC3339 - result <- query config db "select count(value) from ct1;"- case result of- [] -> putStrLn "Empty series"- series:_ -> do- print $ seriesColumns series- print $ seriesPoints series- -- Streaming output- queryChunked config db "select * from ct1;" $ S.fold step ()- where- step _ series = do- case fromSeriesData series of- Left reason -> hPutStrLn stderr reason- Right points -> mapM_ print (points :: [Point])- putStrLn "--"+ M.manage qparams $ F.formatQuery ("DROP DATABASE "%F.database) ctx+ M.manage qparams $ F.formatQuery ("CREATE DATABASE "%F.database) ctx -newConfig :: HC.Manager -> IO Config-newConfig manager = do- pool <- newServerPool localServer []- return Config- { configCreds = rootCreds- , configServerPool = pool- , configHttpManager = manager- }+ let wparams = writeParams ctx & manager .~ Right manager' + gen <- MWC.create+ for_ [1..batches] $ \_ -> do+ batch <- for [1..numPoints] $ \_ -> do+ !time <- (-)+ <$> getPOSIXTime+ <*> (fromIntegral <$> uniformR (0, oneWeekInSeconds) gen)+ !value <- uniform gen+ return (time, value)+ writeBatch wparams $ flip map batch $ \(time, value) ->+ Line ct1+ (Map.fromList [])+ (Map.fromList [("value", nameToFVal value)])+ (Just time)++ queryChunked qparams Default (F.formatQuery ("SELECT * FROM "%F.key) ct1) $+ L.mapM_ $ traverse_ $ \Row {..} ->+ printf "%s:\t%s\n"+ (show $ posixSecondsToUTCTime rowTime)+ (show rowValue)+ managerSettings :: HC.ManagerSettings managerSettings = HC.defaultManagerSettings- { HC.managerResponseTimeout = Just $ 60*(10 :: Int)^(6 :: Int)- } -data Point = Point- { pointValue :: !Name- , pointTime :: !Time+data Row = Row+ { rowTime :: POSIXTime+ , rowValue :: Name } deriving Show -newtype Time = Time POSIXTime- deriving Show--instance ToValue Time where- toValue (Time epoch) = toValue $ epochInSeconds epoch- where- epochInSeconds :: POSIXTime -> Value- epochInSeconds = Int . floor--instance FromValue Time where- parseValue (Int n) = return $ Time $ fromIntegral n- parseValue (Float d) = return $ Time $ realToFrac d- parseValue v = typeMismatch "Int or Float" v+instance QueryResults Row where+ parseResults prec = parseResultsWith $ \_ _ columns fields -> do+ rowTime <- getField "time" columns fields >>= parseTimestamp prec+ String name <- getField "value" columns fields+ rowValue <- case name of+ "foo" -> return Foo+ "bar" -> return Bar+ "baz" -> return Baz+ "quu" -> return Quu+ "qux" -> return Qux+ _ -> fail $ "unknown name: " ++ show name+ return Row {..} data Name = Foo@@ -106,31 +96,11 @@ | Qux deriving (Enum, Bounded, Show) -instance ToValue Name where- toValue Foo = String "foo"- toValue Bar = String "bar"- toValue Baz = String "baz"- toValue Quu = String "quu"- toValue Qux = String "qux"--instance FromValue Name where- parseValue (String name) = case name of- "foo" -> return Foo- "bar" -> return Bar- "baz" -> return Baz- "quu" -> return Quu- "qux" -> return Qux- _ -> fail $ "Incorrect string: " ++ T.unpack name- parseValue v = typeMismatch "String" v+nameToFVal :: Name -> FieldValue+nameToFVal = FieldString . T.toLower . T.pack . show instance Variate Name where uniform = uniformR (minBound, maxBound) uniformR (lower, upper) g = do name <- uniformR (fromEnum lower, fromEnum upper) g return $! toEnum name---- Instance deriving--deriveSeriesData defaultOptions- { fieldLabelModifier = stripPrefixLower "point" }- ''Point
+ examples/write-udp.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+module Main where+import Control.Exception++import Control.Lens+import Data.Time.Clock+import Network.Socket++import Database.InfluxDB.Types+import Database.InfluxDB.Line+import qualified Database.InfluxDB.Write.UDP as UDP++main :: IO ()+main = bracket (socket AF_INET Datagram defaultProtocol) close $ \sock -> do+ localhost <- inet_addr "127.0.0.1"+ let params = UDP.writeParams sock $ SockAddrInet 8089 localhost+ tags1 =+ [ ("tag1", "A")+ , ("tag2", "B")+ ]+ fields1 =+ [ ("val1", FieldInt 10)+ , ("val2", FieldBool True)+ ]+ fields2 =+ [ ("val1", FieldInt 1)+ , ("val2", FieldBool False)+ ]+ UDP.write params $+ Line "measurement1" tags1 fields1 (Nothing :: Maybe UTCTime)+ now <- getCurrentTime+ UDP.write+ (params & UDP.precision .~ Millisecond)+ (Line "measurement1" tags1 fields2 (Just now))
influxdb.cabal view
@@ -1,5 +1,5 @@ name: influxdb-version: 0.10.0+version: 1.0.0 synopsis: Haskell client library for InfluxDB description: Haskell client library for InfluxDB homepage: https://github.com/maoe/influxdb-haskell@@ -7,10 +7,11 @@ license-file: LICENSE author: Mitsutoshi Aoe maintainer: Mitsutoshi Aoe <maoe@foldr.in>-copyright: Copyright (C) 2014-2015 Mitsutoshi Aoe+copyright: Copyright (C) 2014-2017 Mitsutoshi Aoe category: Database build-type: Simple cabal-version: >= 1.10+tested-with: GHC >= 7.10 && < 8.1 extra-source-files: README.md@@ -21,80 +22,69 @@ default: False manual: True -flag aeson-070- description: Use aeson >= 0.7.0+flag http-client-05 default: True manual: False -flag network-uri- description: Get Network.URI from the network-uri package- default: True- library exposed-modules: Database.InfluxDB- Database.InfluxDB.Decode- Database.InfluxDB.Encode- Database.InfluxDB.Http- Database.InfluxDB.Lens- -- Database.InfluxDB.Protobuf- -- Database.InfluxDB.Query- Database.InfluxDB.Stream+ Database.InfluxDB.Format+ Database.InfluxDB.JSON+ Database.InfluxDB.Line+ Database.InfluxDB.Manage+ Database.InfluxDB.Ping+ Database.InfluxDB.Query Database.InfluxDB.Types- Database.InfluxDB.TH+ Database.InfluxDB.Write+ Database.InfluxDB.Write.UDP other-modules:- Database.InfluxDB.Types.Internal+ Network.HTTP.Client.Compat other-extensions: BangPatterns CPP- ConstraintKinds+ DataKinds DeriveDataTypeable+ DeriveGeneric+ ExistentialQuantification FlexibleInstances+ FunctionalDependencies+ GADTs GeneralizedNewtypeDeriving+ KindSignatures+ LambdaCase+ MultiParamTypeClasses NamedFieldPuns OverloadedStrings- RankNTypes RecordWildCards ScopedTypeVariables+ StandaloneDeriving TemplateHaskell- TypeSynonymInstances ViewPatterns ghc-options: -Wall build-depends:- base >= 4 && < 5.0+ base >= 4 && < 4.10+ , aeson >= 0.7 && < 1.2 , attoparsec < 0.14- , bytestring- , containers- , data-default-class- , dlist- , exceptions >= 0.5 && < 0.9- , http-client < 0.5- , mtl < 2.3- , retry >= 0.7 && < 0.8- , tagged- , template-haskell+ , bytestring >= 0.10 && < 0.11+ , clock >= 0.7 && < 0.8+ , containers >= 0.5 && < 0.6+ , foldl < 1.3+ , http-types >= 0.8.6 && < 0.10+ , lens >= 4.9 && < 4.16+ , network >= 2.6 && < 2.7+ , optional-args >= 1.0 && < 1.1+ , scientific >= 0.3.3 && < 0.4 , text < 1.3- , vector-- if flag(aeson-070)- build-depends:- aeson >= 0.7.0 && < 0.12- , scientific >= 0.2- else- build-depends:- aeson >= 0.6.1.0 && < 0.7.0-- if flag(network-uri)- build-depends:- network-uri >= 2.6+ , time >= 1.5 && < 1.9+ , unordered-containers < 0.3+ , vector >= 0.10 && < 0.13+ if flag(http-client-05)+ build-depends: http-client >= 0.5 && < 0.6 else build-depends:- network < 2.6-- if impl(ghc < 7.6)- build-depends:- ghc-prim-+ data-default-class+ , http-client >= 0.4.10 && < 0.5 hs-source-dirs: src default-language: Haskell2010 @@ -117,24 +107,42 @@ default-language: Haskell2010 executable influx-random-points- if flag(examples)- buildable: True- else+ if !flag(examples) buildable: False hs-source-dirs: examples main-is: random-points.hs ghc-options: -Wall build-depends:- base+ aeson+ , base , bytestring+ , containers+ , foldl >= 1.1.3 , http-client , influxdb- , mtl+ , lens , mwc-random+ , optional-args , text , time+ , vector default-language: Haskell2010 +executable influx-write-udp+ if !flag(examples)+ buildable: False+ hs-source-dirs: examples+ main-is: write-udp.hs+ ghc-options: -Wall+ build-depends:+ base+ , containers+ , influxdb+ , lens+ , network+ , time+ default-language: Haskell2010+ source-repository head type: git branch: develop@@ -142,5 +150,5 @@ source-repository this type: git- tag: v0.10.0+ tag: v1.0.0 location: https://github.com/maoe/influxdb-haskell.git
src/Database/InfluxDB.hs view
@@ -1,94 +1,97 @@+{- |+stability: experimental+portability: GHC+-} module Database.InfluxDB- (- -- * Series data types- Series(..), seriesColumns, seriesPoints- , SeriesData(..)- , Value(..)+ ( -- $intro - -- ** Encoding- , ToSeriesData(..)- , ToValue(..)+ -- * Writing data+ -- $write+ write+ , writeBatch+ , writeByteString - -- ** Decoding- , FromSeries(..), fromSeries- , FromSeriesData(..), fromSeriesData- , FromValue(..), fromValue+ -- ** Write parameters+ , WriteParams+ , writeParams+ , retentionPolicy - , withValues, (.:), (.:?), (.!=)- , typeMismatch+ -- ** The Line protocol+ , Line(Line)+ , measurement+ , tagSet+ , fieldSet+ , timestamp - -- * HTTP API- -- ** Data types- , Config(..)- , Credentials(..), rootCreds- , TimePrecision(..)- , Server(..), localServer- , ServerPool, newServerPool- , newServerPoolWithRetryPolicy- , Database(..)- , User(..)- , Admin(..)- , Ping(..)- , ShardSpace(..)+ , FieldValue(..)+ , Timestamp(..)+ , precisionScale+ , precisionName - -- *** Exception- , InfluxException(..)+ -- * Querying data+ , Query+ , query+ , queryChunked - -- ** Writing Data+ -- * Query constructor+ , formatQuery+ , (%) - -- *** Updating Points- , post, postWithPrecision- , SeriesT, PointT- , writeSeries- , writeSeriesData- , withSeries- , writePoints+ -- ** Query parameters+ , QueryParams+ , queryParams+ , authentication - -- *** Deleting Points- , deleteSeries+ -- ** Parsing results+ , QueryResults(..)+ , parseResultsWith+ , getField+ , getTag+ , parseTimestamp+ , parseFieldValue+ , parseKey - -- ** Querying Data- , query- , Stream(..)- , queryChunked+ -- * Database management+ , manage - -- ** Administration & Security- -- *** Creating and Dropping Databases- , listDatabases- , createDatabase- , dropDatabase+ -- * Common data types and classes+ , Precision(..)+ , Database+ , Key - , DatabaseRequest(..)- , configureDatabase+ , Server+ , host+ , port+ , ssl+ , localServer - -- *** Security- -- **** Shard spaces- , ShardSpaceRequest(..)- , listShardSpaces- , createShardSpace- , dropShardSpace+ , Credentials+ , user+ , password - -- **** Cluster admin- , listClusterAdmins- , authenticateClusterAdmin- , addClusterAdmin- , updateClusterAdminPassword- , deleteClusterAdmin- -- **** Database user- , listDatabaseUsers- , authenticateDatabaseUser- , addDatabaseUser- , updateDatabaseUserPassword- , deleteDatabaseUser- , grantAdminPrivilegeTo- , revokeAdminPrivilegeFrom+ -- * Exception+ , InfluxException(..) - -- *** Other API- , ping- , isInSync+ , HasServer(..)+ , HasDatabase(..)+ , HasPrecision(..)+ , HasManager(..) ) where -import Database.InfluxDB.Decode-import Database.InfluxDB.Encode-import Database.InfluxDB.Http+import Database.InfluxDB.Format ((%), formatQuery)+import Database.InfluxDB.JSON+import Database.InfluxDB.Line+import Database.InfluxDB.Manage (manage)+import Database.InfluxDB.Query import Database.InfluxDB.Types+import Database.InfluxDB.Write++{- $write+InfluxDB has two ways to write data into it, via HTTP and UDP. This module+only exports functions for the HTTP API. For UDP, you can use a qualified+import:++@+import qualified "Database.InfluxDB.Write.UDP" as UDP+@+-}
− src/Database/InfluxDB/Decode.hs
@@ -1,232 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeSynonymInstances #-}-module Database.InfluxDB.Decode- ( FromSeries(..), fromSeries- , FromSeriesData(..), fromSeriesData, fromSeriesData_- , withValues, (.:), (.:?), (.!=)- , FromValue(..), fromValue- , Parser, ValueParser, typeMismatch- ) where-import Control.Applicative-import Control.Monad.Reader-import Data.Either (rights)-import Data.Int-import Data.Word-import Data.Map (Map)-import Data.Maybe (fromMaybe)-import Data.Vector (Vector)-import Data.Tuple (swap)-import qualified Data.Map as Map-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import qualified Data.Vector as V-import Prelude--import Database.InfluxDB.Types---- | A type that can be converted from a 'Series'.-class FromSeries a where- parseSeries :: Series -> Parser a--instance FromSeries Series where- parseSeries = return--instance FromSeries SeriesData where- parseSeries = return . seriesData---- | Converte a value from a 'Series', failing if the types do not match.-fromSeries :: FromSeries a => Series -> Either String a-fromSeries = runParser . parseSeries---- | A type that can be converted from a 'SeriesData'. A typical implementation--- is as follows.------ > import Control.Applicative ((<$>), (<*>))--- > import qualified Data.Vector as V--- >--- > data Event = Event Text EventType--- > data EventType = Login | Logout--- >--- > instance FromSeriesData Event where--- > parseSeriesData = withValues $ \values -> Event--- > <$> values .: "user"--- > <*> values .: "type"--- >--- > instance FromValue EventType-class FromSeriesData a where- parseSeriesData :: Vector Column -> Vector Value -> Parser a--instance FromSeriesData SeriesData where- parseSeriesData columns values = return SeriesData- { seriesDataColumns = columns- , seriesDataPoints = [values]- }---- | Converte a value from a 'SeriesData', failing if the types do not match.-fromSeriesData :: FromSeriesData a => SeriesData -> Either String [a]-fromSeriesData SeriesData {..} = mapM- (runParser . parseSeriesData seriesDataColumns)- seriesDataPoints---- | Same as @fromSeriesData@ but ignores parse errors and returns only--- successful data.-fromSeriesData_ :: FromSeriesData a => SeriesData -> [a]-fromSeriesData_ SeriesData {..} = rights $ map- (runParser . parseSeriesData seriesDataColumns)- seriesDataPoints---- | Helper function to define 'parseSeriesData' from 'ValueParser's.-withValues- :: (Vector Value -> ValueParser a)- -> Vector Column -> Vector Value -> Parser a-withValues f columns values =- runReaderT m $ Map.fromList $ map swap $ V.toList $ V.indexed columns- where- ValueParser m = f values---- | Retrieve the value associated with the given column. The result is 'empty'--- if the column is not present or the value cannot be converted to the desired--- type.-(.:) :: FromValue a => Vector Value -> Column -> ValueParser a-values .: column = do- found <- asks $ Map.lookup column- case found of- Nothing -> liftParser $ parseError $ "No such column: " ++ T.unpack column- Just idx -> do- value <- V.indexM values idx- liftParser $ parseValue value---- | Retrieve the value associated with the given column. The result is--- 'Nothing' if the column is not present or the value cannot be converted to--- the desired type.-(.:?) :: FromValue a => Vector Value -> Column -> ValueParser (Maybe a)-values .:? column = do- found <- asks $ Map.lookup column- case found of- Nothing -> return Nothing- Just idx ->- case values V.!? idx of- Nothing -> return Nothing- Just value -> liftParser $ parseValue value---- | Helper for use in combination with '.:?' to provide default values for--- optional columns.-(.!=) :: Parser (Maybe a) -> a -> Parser a-p .!= def = fromMaybe def <$> p---- | A type that can be converted from a 'Value'.-class FromValue a where- parseValue :: Value -> Parser a---- | Converte a value from a 'Value', failing if the types do not match.-fromValue :: FromValue a => Value -> Either String a-fromValue = runParser . parseValue--instance FromValue Value where- parseValue = return--instance FromValue Bool where- parseValue (Bool b) = return b- parseValue v = typeMismatch "Bool" v--instance FromValue a => FromValue (Maybe a) where- parseValue Null = return Nothing- parseValue v = Just <$> parseValue v--instance FromValue Int where- parseValue (Int n) = return $ fromIntegral n- parseValue v = typeMismatch "Int" v--instance FromValue Int8 where- parseValue (Int n)- | n <= fromIntegral (maxBound :: Int8) = return $ fromIntegral n- | otherwise = parseError $ "Larger than the maximum Int8: " ++ show n- parseValue v = typeMismatch "Int8" v--instance FromValue Int16 where- parseValue (Int n)- | n <= fromIntegral (maxBound :: Int16) = return $ fromIntegral n- | otherwise = parseError $ "Larger than the maximum Int16: " ++ show n- parseValue v = typeMismatch "Int16" v--instance FromValue Int32 where- parseValue (Int n)- | n <= fromIntegral (maxBound :: Int32) = return $ fromIntegral n- | otherwise = parseError $ "Larger than the maximum Int32: " ++ show n- parseValue v = typeMismatch "Int32" v--instance FromValue Int64 where- parseValue (Int n)- | n <= fromIntegral (maxBound :: Int64) = return $ fromIntegral n- | otherwise = parseError $ "Larger than the maximum Int64: " ++ show n- parseValue v = typeMismatch "Int64" v--instance FromValue Word8 where- parseValue (Int n)- | n <= fromIntegral (maxBound :: Word8) = return $ fromIntegral n- | otherwise = parseError $ "Larger than the maximum Word8: " ++ show n- parseValue v = typeMismatch "Word8" v--instance FromValue Word16 where- parseValue (Int n)- | n <= fromIntegral (maxBound :: Word16) = return $ fromIntegral n- | otherwise = parseError $ "Larger than the maximum Word16: " ++ show n- parseValue v = typeMismatch "Word16" v--instance FromValue Word32 where- parseValue (Int n)- | n <= fromIntegral (maxBound :: Word32) = return $ fromIntegral n- | otherwise = parseError $ "Larger than the maximum Word32: " ++ show n- parseValue v = typeMismatch "Word32" v--instance FromValue Double where- parseValue (Float d) = return d- -- If the floating number happens to be a whole number, it must- -- have encoded as an integer. We should decode it back as a floating- -- number here.- parseValue (Int n) = return $ fromIntegral n- parseValue v = typeMismatch "Float" v--instance FromValue T.Text where- parseValue (String xs) = return xs- parseValue v = typeMismatch "Text" v--instance FromValue TL.Text where- parseValue (String xs) = return $ TL.fromStrict xs- parseValue v = typeMismatch "lazy Text" v--instance FromValue String where- parseValue (String xs) = return $ T.unpack xs- parseValue v = typeMismatch "String" v--typeMismatch- :: String- -> Value- -> Parser a-typeMismatch expected actual = parseError $- "when expecting a " ++ expected ++- ", encountered " ++ name ++ " instead"- where- name = case actual of- Int _ -> "Int"- Float _ -> "Float"- String _ -> "String"- Bool _ -> "Bool"- Null -> "Null"--newtype Parser a = Parser- { runParser :: Either String a- } deriving (Functor, Applicative, Monad)--type ColumnIndex = Map Column Int--newtype ValueParser a = ValueParser (ReaderT ColumnIndex Parser a)- deriving (Functor, Applicative, Monad, MonadReader ColumnIndex)--liftParser :: Parser a -> ValueParser a-liftParser = ValueParser . ReaderT . const--parseError :: String -> Parser a-parseError = Parser . Left
− src/Database/InfluxDB/Encode.hs
@@ -1,95 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeSynonymInstances #-}-module Database.InfluxDB.Encode- ( ToSeries(..)- , ToSeriesData(..), toSeriesData- , ToValue(..)- ) where-import Data.Int (Int8, Int16, Int32, Int64)-import Data.Proxy-import Data.Vector (Vector)-import Data.Word (Word8, Word16, Word32)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL--import Database.InfluxDB.Types---- | A type that can be converted to a 'Series'.-class ToSeries a where- toSeries :: a -> Series---- | A type that can be converted to a 'SeriesData'. A typical implementation is--- as follows.------ > import qualified Data.Vector as V--- >--- > data Event = Event Text EventType--- > data EventType = Login | Logout--- >--- > instance ToSeriesData Event where--- > toSeriesColumns _ = V.fromList ["user", "type"]--- > toSeriesPoints (Event user ty) = V.fromList [toValue user, toValue ty]--- >--- > instance ToValue EventType-class ToSeriesData a where- -- | Column names. You can safely ignore the proxy agument.- toSeriesColumns :: Proxy a -> Vector Column- -- | Data points.- toSeriesPoints :: a -> Vector Value--toSeriesData :: forall a. ToSeriesData a => a -> SeriesData-toSeriesData a = SeriesData- { seriesDataColumns = toSeriesColumns (Proxy :: Proxy a)- , seriesDataPoints = [toSeriesPoints a]- }---- | A type that can be stored in InfluxDB.-class ToValue a where- toValue :: a -> Value--instance ToValue Value where- toValue = id--instance ToValue Bool where- toValue = Bool--instance ToValue a => ToValue (Maybe a) where- toValue Nothing = Null- toValue (Just a) = toValue a--instance ToValue Int where- toValue = Int . fromIntegral--instance ToValue Int8 where- toValue = Int . fromIntegral--instance ToValue Int16 where- toValue = Int . fromIntegral--instance ToValue Int32 where- toValue = Int . fromIntegral--instance ToValue Int64 where- toValue = Int--instance ToValue Word8 where- toValue = Int . fromIntegral--instance ToValue Word16 where- toValue = Int . fromIntegral--instance ToValue Word32 where- toValue = Int . fromIntegral--instance ToValue Double where- toValue = Float--instance ToValue T.Text where- toValue = String--instance ToValue TL.Text where- toValue = String . TL.toStrict--instance ToValue String where- toValue = String . T.pack
+ src/Database/InfluxDB/Format.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+module Database.InfluxDB.Format+ ( Query+ , fromQuery++ , Format+ , makeFormat+ , (%)+ , formatQuery+ , formatDatabase+ , formatKey++ , database+ , key+ , keys+ , fieldVal+ , decimal+ , realFloat+ , text+ , time+ ) where+import Control.Category+import Data.Monoid+import Data.String+import Prelude hiding ((.), id)++import Data.Time+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Lazy.Builder as BL+import qualified Data.List as L+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TL+import qualified Data.Text.Lazy.Builder.Int as TL+import qualified Data.Text.Lazy.Builder.RealFloat as TL++import Database.InfluxDB.Types hiding (database)++fromQuery :: Query -> B.ByteString+fromQuery (Query q) =+ BL.toStrict $ BL.toLazyByteString $ T.encodeUtf8Builder q++newtype Format a b = Format { runFormat :: (TL.Builder -> a) -> b }++instance Category Format where+ id = Format (\k -> k "")+ fmt1 . fmt2 = Format $ \k ->+ runFormat fmt1 $ \a ->+ runFormat fmt2 $ \b ->+ k (a <> b)++instance a ~ b => IsString (Format a b) where+ fromString xs = Format $ \k -> k $ fromString xs++(%) :: Format b c -> Format a b -> Format a c+(%) = (.)++formatQuery :: Format Query r -> r+formatQuery fmt = runFormat fmt (Query . TL.toStrict . TL.toLazyText)++formatDatabase :: Format Database r -> r+formatDatabase fmt = runFormat fmt (Database . TL.toStrict . TL.toLazyText)++formatKey :: Format Key r -> r+formatKey fmt = runFormat fmt (Key . TL.toStrict . TL.toLazyText)++makeFormat :: (a -> TL.Builder) -> Format r (a -> r)+makeFormat build = Format $ \k a -> k $ build a++database :: Format r (Database -> r)+database = makeFormat $ \(Database name) -> "\"" <> TL.fromText name <> "\""++keyBuilder :: Key -> TL.Builder+keyBuilder (Key name) = "\"" <> TL.fromText name <> "\""++key :: Format r (Key -> r)+key = makeFormat keyBuilder++keys :: Format r ([Key] -> r)+keys = makeFormat $ mconcat . L.intersperse "," . map keyBuilder++fieldVal :: Format r (FieldValue -> r)+fieldVal = makeFormat $ \case+ FieldInt n -> TL.decimal n+ FieldFloat d -> TL.realFloat d+ FieldString s -> "'" <> TL.fromText s <> "'"+ FieldBool b -> if b then "true" else "false"+ FieldNull -> "null"++decimal :: Integral a => Format r (a -> r)+decimal = makeFormat TL.decimal++realFloat :: RealFloat a => Format r (a -> r)+realFloat = makeFormat TL.realFloat++text :: Format r (T.Text -> r)+text = makeFormat TL.fromText++time :: FormatTime time => Format r (time -> r)+time = makeFormat $ \t ->+ "'" <> TL.fromString (formatTime defaultTimeLocale fmt t) <> "'"+ where+ fmt = "%F %X%Q" -- YYYY-MM-DD HH:MM:SS.nnnnnnnnn
− src/Database/InfluxDB/Http.hs
@@ -1,792 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-module Database.InfluxDB.Http- ( Config(..)- , Credentials(..), rootCreds- , Server(..), localServer- , TimePrecision(..)-- -- * Writing Data-- -- ** Updating Points- , post, postWithPrecision- , SeriesT, PointT- , writeSeries- , writeSeriesData- , withSeries- , writePoints-- -- ** Deleting Points- , deleteSeries-- -- * Querying Data- , query- , Stream(..)- , queryChunked-- -- * Administration & Security- -- ** Creating and Dropping Databases- , listDatabases- , createDatabase- , dropDatabase-- , DatabaseRequest(..)- , configureDatabase-- -- ** Security- -- *** Shard spaces- , ShardSpaceRequest(..)- , listShardSpaces- , createShardSpace- , dropShardSpace-- -- *** Cluster admin- , listClusterAdmins- , authenticateClusterAdmin- , addClusterAdmin- , updateClusterAdminPassword- , deleteClusterAdmin- -- *** Database user- , listDatabaseUsers- , authenticateDatabaseUser- , addDatabaseUser- , updateDatabaseUserPassword- , deleteDatabaseUser- , grantAdminPrivilegeTo- , revokeAdminPrivilegeFrom-- -- ** Other API- , ping- , isInSync- ) where--import Control.Applicative-import Control.Monad.Identity-import Control.Monad.Writer-import Data.DList (DList)-import Data.IORef-import Data.Proxy-import Data.Text (Text)-import Data.Vector (Vector)-import Data.Word (Word32)-import Network.URI (escapeURIString, isAllowedInURI)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Char8 as BS8-import qualified Data.ByteString.Lazy as BL-import qualified Data.DList as DL-import qualified Data.Text as T-import Text.Printf (printf)-import Prelude--import Control.Monad.Catch (Handler(..))-import Control.Retry (recovering)-import Data.Aeson ((.=))-import Data.Aeson.TH (deriveToJSON)-import Data.Default.Class (Default(def))-import qualified Data.Aeson as A-import qualified Data.Aeson.Encode as AE-import qualified Data.Aeson.Parser as AP-import qualified Data.Aeson.Types as AT-import qualified Data.Attoparsec.ByteString as P-import qualified Data.Attoparsec.ByteString.Lazy as PL-import qualified Network.HTTP.Client as HC--import Database.InfluxDB.Decode-import Database.InfluxDB.Encode-import Database.InfluxDB.Types-import Database.InfluxDB.Types.Internal (stripPrefixOptions)-import Database.InfluxDB.Stream (Stream(..))-import qualified Database.InfluxDB.Stream as S---- | Configurations for HTTP API client.-data Config = Config- { configCreds :: !Credentials- , configServerPool :: !(IORef ServerPool)- , configHttpManager :: !HC.Manager- }---- | Default credentials.-rootCreds :: Credentials-rootCreds = Credentials- { credsUser = "root"- , credsPassword = "root"- }---- | Default server location.-localServer :: Server-localServer = Server- { serverHost = "localhost"- , serverPort = 8086- , serverSsl = False- }--data TimePrecision- = SecondsPrecision- | MillisecondsPrecision- | MicrosecondsPrecision--timePrecString :: TimePrecision -> String-timePrecString SecondsPrecision = "s"-timePrecString MillisecondsPrecision = "ms"-timePrecString MicrosecondsPrecision = "u"---------------------------------------------------------------- Writing Data---- | Post a bunch of writes for (possibly multiple) series into a database.-post- :: Config- -> Text- -> SeriesT IO a- -> IO a-post config databaseName =- postGeneric config databaseName Nothing---- | Post a bunch of writes for (possibly multiple) series into a database like--- 'post' but with time precision.-postWithPrecision- :: Config- -> Text -- ^ Database name- -> TimePrecision- -> SeriesT IO a- -> IO a-postWithPrecision config databaseName timePrec =- postGeneric config databaseName (Just timePrec)--postGeneric- :: Config- -> Text -- ^ Database name- -> Maybe TimePrecision- -> SeriesT IO a- -> IO a-postGeneric Config {..} databaseName timePrec write = do- (a, series) <- runSeriesT write- void $ httpLbsWithRetry configServerPool- (makeRequest series)- configHttpManager- return a- where- makeRequest series = def- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode series- , HC.path = escapeString $ printf "/db/%s/series"- (T.unpack databaseName)- , HC.queryString = escapeString $ printf "u=%s&p=%s%s"- (T.unpack credsUser)- (T.unpack credsPassword)- (maybe- ""- (printf "&time_precision=%s" . timePrecString)- timePrec :: String)- }- Credentials {..} = configCreds---- | Monad transformer to batch up multiple writes of series to speed up--- insertions.-newtype SeriesT m a = SeriesT (WriterT (DList Series) m a)- deriving- ( Functor, Applicative, Monad, MonadIO, MonadTrans- , MonadWriter (DList Series)- )---- | Monad transformer to batch up multiple writes of points to speed up--- insertions.-newtype PointT p m a = PointT (WriterT (DList (Vector Value)) m a)- deriving- ( Functor, Applicative, Monad, MonadIO, MonadTrans- , MonadWriter (DList (Vector Value))- )--runSeriesT :: Monad m => SeriesT m a -> m (a, [Series])-runSeriesT (SeriesT w) = do- (a, series) <- runWriterT w- return (a, DL.toList series)---- | Write a single series data.-writeSeries- :: (Monad m, ToSeriesData a)- => Text- -- ^ Series name- -> a- -- ^ Series data- -> SeriesT m ()-writeSeries name = writeSeriesData name . toSeriesData---- | Write a single series data.-writeSeriesData- :: Monad m- => Text- -- ^ Series name- -> SeriesData- -- ^ Series data- -> SeriesT m ()-writeSeriesData name a = tell . DL.singleton $ Series- { seriesName = name- , seriesData = a- }---- | Write a bunch of data for a single series. Columns for the points don't--- need to be specified because they can be inferred from the type of @a@.-withSeries- :: forall m a. (Monad m, ToSeriesData a)- => Text- -- ^ Series name- -> PointT a m ()- -> SeriesT m ()-withSeries name (PointT w) = do- (_, values) <- lift $ runWriterT w- tell $ DL.singleton Series- { seriesName = name- , seriesData = SeriesData- { seriesDataColumns = toSeriesColumns (Proxy :: Proxy a)- , seriesDataPoints = DL.toList values- }- }---- | Write a data into a series.-writePoints- :: (Monad m, ToSeriesData a)- => a- -> PointT a m ()-writePoints = tell . DL.singleton . toSeriesPoints--deleteSeries- :: Config- -> Text -- ^ Database name- -> Text -- ^ Series name- -> IO ()-deleteSeries config databaseName seriesName = runRequest_ config request- where- request = def- { HC.method = "DELETE"- , HC.path = escapeString $ printf "/db/%s/series/%s"- (T.unpack databaseName)- (T.unpack seriesName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---------------------------------------------------------------- Querying Data---- | Query a specified database.------ The query format is specified in the--- <http://influxdb.org/docs/query_language/ InfluxDB Query Language>.-query- :: FromSeries a- => Config- -> Text -- ^ Database name- -> Text -- ^ Query text- -> IO [a]-query config databaseName q = do- xs <- runRequest config request- case mapM fromSeries xs of- Left reason -> seriesDecodeError reason- Right ys -> return ys- where- request = def- { HC.path = escapeString $ printf "/db/%s/series"- (T.unpack databaseName)- , HC.queryString = escapeString $ printf "u=%s&p=%s&q=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- (T.unpack q)- }- Credentials {..} = configCreds config---- | Construct streaming output-responseStream :: A.FromJSON a => HC.BodyReader -> IO (Stream IO a)-responseStream body = demandPayload $ \payload ->- if BS.null payload- then return Done- else decode $ parseAsJson payload- where- demandPayload k = HC.brRead body >>= k- decode (P.Done leftover value) = case A.fromJSON value of- A.Success a -> return $ Yield a $ if BS.null leftover- then responseStream body- else decode $ parseAsJson leftover- A.Error message -> jsonDecodeError message- decode (P.Partial k) = demandPayload (decode . k)- decode (P.Fail _ _ message) = jsonDecodeError message- parseAsJson = P.parse A.json---- | Query a specified database like 'query' but in a streaming fashion.-queryChunked- :: FromSeries a- => Config- -> Text -- ^ Database name- -> Text -- ^ Query text- -> (Stream IO a -> IO b)- -- ^ Action to handle the resulting stream of series- -> IO b-queryChunked Config {..} databaseName q f =- withPool configServerPool request $ \request' ->- HC.withResponse request' configHttpManager $- responseStream . HC.responseBody >=> S.mapM parse >=> f- where- parse series = case fromSeries series of- Left reason -> seriesDecodeError reason- Right a -> return a- request = def- { HC.path = escapeString $ printf "/db/%s/series"- (T.unpack databaseName)- , HC.queryString = escapeString $ printf "u=%s&p=%s&q=%s&chunked=true"- (T.unpack credsUser)- (T.unpack credsPassword)- (T.unpack q)- }- Credentials {..} = configCreds---------------------------------------------------------------- Administration & Security---- | List existing databases.-listDatabases :: Config -> IO [Database]-listDatabases config = runRequest config request- where- request = def- { HC.path = "/db"- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---- | Create a new database. Requires cluster admin privileges.-createDatabase :: Config -> Text -> IO ()-createDatabase config name = runRequest_ config request- where- request = def- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode $ A.object- [ "name" .= name- ]- , HC.path = "/db"- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---- | Drop a database. Requires cluster admin privileges.-dropDatabase- :: Config- -> Text -- ^ Database name- -> IO ()-dropDatabase config databaseName = runRequest_ config request- where- request = def- { HC.method = "DELETE"- , HC.path = escapeString $ printf "/db/%s"- (T.unpack databaseName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---data DatabaseRequest = DatabaseRequest- { databaseRequestSpaces :: [ShardSpaceRequest]- , databaseRequestContinuousQueries :: [Text]- } deriving Show--configureDatabase- :: Config- -> Text -- ^ Database name- -> DatabaseRequest- -> IO ()-configureDatabase config databaseName databaseRequest =- runRequest_ config request- where- request = def- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode databaseRequest- , HC.path = escapeString $ printf "/cluster/database_configs/%s"- (T.unpack databaseName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---- | List shard spaces.-listShardSpaces :: Config -> IO [ShardSpace]-listShardSpaces config = runRequest config request- where- request = def- { HC.path = "/cluster/shard_spaces"- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config--data ShardSpaceRequest = ShardSpaceRequest- { shardSpaceRequestName :: Text- , shardSpaceRequestRegex :: Text- , shardSpaceRequestRetentionPolicy :: Text- , shardSpaceRequestShardDuration :: Text- , shardSpaceRequestReplicationFactor :: Word32- , shardSpaceRequestSplit :: Word32- } deriving Show---- | Create a shard space.-createShardSpace- :: Config- -> Text -- ^ Database- -> ShardSpaceRequest- -> IO ()-createShardSpace config databaseName shardSpace = runRequest_ config request- where- request = def- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode shardSpace- , HC.path = escapeString $ printf "/cluster/shard_spaces/%s"- (T.unpack databaseName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config--dropShardSpace- :: Config- -> Text -- ^ Database name- -> Text -- ^ Shard space name- -> IO ()-dropShardSpace config databaseName shardSpaceName = runRequest_ config request- where- request = def- { HC.method = "DELETE"- , HC.path = escapeString $ printf "/cluster/shard_spaces/%s/%s"- (T.unpack databaseName)- (T.unpack shardSpaceName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---- | List cluster administrators.-listClusterAdmins :: Config -> IO [Admin]-listClusterAdmins config = runRequest config request- where- request = def- { HC.path = "/cluster_admins"- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config--authenticateClusterAdmin :: Config -> IO ()-authenticateClusterAdmin config = runRequest_ config request- where- request = def- { HC.path = "/cluster_admins/authenticate"- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---- | Add a new cluster administrator. Requires cluster admin privilege.-addClusterAdmin- :: Config- -> Text -- ^ Admin name- -> Text -- ^ Password- -> IO Admin-addClusterAdmin config name password = do- runRequest_ config request- return Admin- { adminName = name- }- where- request = def- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode $ A.object- [ "name" .= name- , "password" .= password- ]- , HC.path = "/cluster_admins"- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---- | Update a cluster administrator's password. Requires cluster admin--- privilege.-updateClusterAdminPassword- :: Config- -> Admin- -> Text -- ^ New password- -> IO ()-updateClusterAdminPassword Config {..} admin password =- void $ httpLbsWithRetry configServerPool makeRequest configHttpManager- where- makeRequest = def- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode $ A.object- [ "password" .= password- ]- , HC.path = escapeString $ printf "/cluster_admins/%s"- (T.unpack adminName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Admin {adminName} = admin- Credentials {..} = configCreds---- | Delete a cluster administrator. Requires cluster admin privilege.-deleteClusterAdmin- :: Config- -> Admin- -> IO ()-deleteClusterAdmin Config {..} admin =- void $ httpLbsWithRetry configServerPool makeRequest configHttpManager- where- makeRequest = def- { HC.method = "DELETE"- , HC.path = escapeString $ printf "/cluster_admins/%s"- (T.unpack adminName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Admin {adminName} = admin- Credentials {..} = configCreds---- | List database users.-listDatabaseUsers- :: Config- -> Text- -> IO [User]-listDatabaseUsers config@Config {..} database = runRequest config makeRequest- where- makeRequest = def- { HC.path = escapeString $ printf "/db/%s/users"- (T.unpack database)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds--authenticateDatabaseUser- :: Config- -> Text -- ^ Database name- -> IO ()-authenticateDatabaseUser Config {..} database =- void $ httpLbsWithRetry configServerPool makeRequest configHttpManager- where- makeRequest = def- { HC.path = escapeString $ printf "/db/%s/authenticate"- (T.unpack database)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds---- | Add an user to the database users.-addDatabaseUser- :: Config- -> Text -- ^ Database name- -> Text -- ^ User name- -> Text -- ^ Password- -> IO ()-addDatabaseUser config databaseName name password = runRequest_ config request- where- request = def- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode $ A.object- [ "name" .= name- , "password" .= password- ]- , HC.path = escapeString $ printf "/db/%s/users"- (T.unpack databaseName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds config---- | Delete an user from the database users.-deleteDatabaseUser- :: Config- -> Text -- ^ Database name- -> Text -- ^ User name- -> IO ()-deleteDatabaseUser config databaseName userName = runRequest_ config request- where- request = (makeRequestFromDatabaseUser config databaseName userName)- { HC.method = "DELETE"- }---- | Update password for the database user.-updateDatabaseUserPassword- :: Config- -> Text -- ^ Database name- -> Text -- ^ User name- -> Text -- ^ New password- -> IO ()-updateDatabaseUserPassword config databaseName userName password =- runRequest_ config request- where- request = (makeRequestFromDatabaseUser config databaseName userName)- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode $ A.object- [ "password" .= password- ]- }---- | Give admin privilege to the user.-grantAdminPrivilegeTo- :: Config- -> Text -- ^ Database name- -> Text -- ^ User name- -> IO ()-grantAdminPrivilegeTo config databaseName userName = runRequest_ config request- where- request = (makeRequestFromDatabaseUser config databaseName userName)- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode $ A.object- [ "admin" .= True- ]- }---- | Remove admin privilege from the user.-revokeAdminPrivilegeFrom- :: Config- -> Text -- ^ Database name- -> Text -- ^ User name- -> IO ()-revokeAdminPrivilegeFrom config databaseName userName =- runRequest_ config request- where- request = (makeRequestFromDatabaseUser config databaseName userName)- { HC.method = "POST"- , HC.requestBody = HC.RequestBodyLBS $ AE.encode $ A.object- [ "admin" .= False- ]- }--makeRequestFromDatabaseUser- :: Config- -> Text -- ^ Database name- -> Text -- ^ User name- -> HC.Request-makeRequestFromDatabaseUser Config {..} databaseName userName = def- { HC.path = escapeString $ printf "/db/%s/users/%s"- (T.unpack databaseName)- (T.unpack userName)- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- where- Credentials {..} = configCreds--ping :: Config -> IO Ping-ping config = runRequest config request- where- request = def- { HC.path = "/ping"- }--isInSync :: Config -> IO Bool-isInSync Config {..} = do- response <- httpLbsWithRetry configServerPool makeRequest configHttpManager- case eitherDecodeBool (HC.responseBody response) of- Left reason -> jsonDecodeError reason- Right status -> return status- where- makeRequest = def- { HC.path = "/sync"- , HC.queryString = escapeString $ printf "u=%s&p=%s"- (T.unpack credsUser)- (T.unpack credsPassword)- }- Credentials {..} = configCreds- eitherDecodeBool lbs = do- val <- PL.eitherResult $ PL.parse AP.value lbs- AT.parseEither A.parseJSON val---------------------------------------------------------------httpLbsWithRetry- :: IORef ServerPool- -> HC.Request- -> HC.Manager- -> IO (HC.Response BL.ByteString)-httpLbsWithRetry pool request manager =- withPool pool request $ \request' ->- HC.httpLbs request' manager--withPool- :: IORef ServerPool- -> HC.Request- -> (HC.Request -> IO a)- -> IO a-withPool pool request f = do- retryPolicy <- serverRetryPolicy <$> readIORef pool- recovering retryPolicy handlers $ \_ -> do- server <- activeServer pool- f $ makeRequest server- where- makeRequest Server {..} = request- { HC.host = escapeText serverHost- , HC.port = serverPort- , HC.secure = serverSsl- }- handlers =- [ const $ Handler $ \e -> case e of- HC.FailedConnectionException {} -> retry- HC.FailedConnectionException2 {} -> retry- HC.InternalIOException {} -> retry- HC.ResponseTimeout {} -> retry- _ -> return False- ]- retry = True <$ failover pool--escapeText :: Text -> BS.ByteString-escapeText = escapeString . T.unpack--escapeString :: String -> BS.ByteString-escapeString = BS8.pack . escapeURIString isAllowedInURI--decodeJsonResponse- :: A.FromJSON a- => HC.Response BL.ByteString- -> IO a-decodeJsonResponse response =- case A.eitherDecode (HC.responseBody response) of- Left reason -> jsonDecodeError reason- Right a -> return a--runRequest :: A.FromJSON a => Config -> HC.Request -> IO a-runRequest Config {..} req = do- response <- httpLbsWithRetry configServerPool req configHttpManager- decodeJsonResponse response--runRequest_ :: Config -> HC.Request -> IO ()-runRequest_ Config {..} req =- void $ httpLbsWithRetry configServerPool req configHttpManager---------------------------------------------------------------- Aeson instances--deriveToJSON (stripPrefixOptions "shardSpaceRequest") ''ShardSpaceRequest-deriveToJSON (stripPrefixOptions "databaseRequest") ''DatabaseRequest
+ src/Database/InfluxDB/JSON.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}+module Database.InfluxDB.JSON+ ( parseResultsWith+ , parseResultsWithDecoder+ , Decoder(..)+ , strictDecoder+ , lenientDecoder++ , getField+ , getTag++ , parseTimestamp+ , parsePOSIXTime+ , parseRFC3339+ , parseFieldValue++ , parseResultsObject+ , parseSeriesObject+ , parseSeriesBody+ , parseErrorObject+ ) where+import Control.Applicative+import Control.Exception+import Control.Monad+import Data.Maybe++import Data.Aeson+import Data.HashMap.Strict (HashMap)+import Data.Text (Text)+import Data.Time.Clock.POSIX+import Data.Time.Format+import Data.Vector (Vector)+import qualified Data.Aeson.Types as A+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Scientific as Sci+import qualified Data.Text as T+import qualified Data.Vector as V++import Database.InfluxDB.Types++-- | Parse a JSON response+parseResultsWith+ :: (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> A.Parser a)+ -- ^ A parser that takes+ --+ -- 1. an optional name of the series+ -- 2. a map of tags+ -- 3. an array of field names+ -- 4. an array of values+ --+ -- to construct a value.+ -> Value+ -> A.Parser (Vector a)+parseResultsWith = parseResultsWithDecoder lenientDecoder++-- | Parse a JSON response with specified decoder settings.+parseResultsWithDecoder+ :: Decoder a+ -> (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> A.Parser a)+ -- ^ A parser that takes+ --+ -- 1. an optional name of the series+ -- 2. a map of tags+ -- 3. an array of field names+ -- 4. an array of values+ --+ -- to construct a value.+ -> Value+ -> A.Parser (Vector a)+parseResultsWithDecoder Decoder {..} row val0 = success+ where+ success = do+ results <- parseResultsObject val0++ (join -> series) <- V.forM results $ \val ->+ parseSeriesObject val <|> parseErrorObject val+ values <- V.forM series $ \val -> do+ (name, tags, columns, values) <- parseSeriesBody val+ decodeFold $ V.forM values $ A.withArray "values" $ \fields -> do+ assert (V.length columns == V.length fields) $ return ()+ decodeEach $ row name tags columns fields+ return $! join values++-- | Decoder settings+data Decoder a = forall b. Decoder+ { decodeEach :: A.Parser a -> A.Parser b+ -- ^ How to turn a parser for each element into another. For example, a+ -- failure can be turned into 'Nothing'.+ , decodeFold :: A.Parser (Vector b) -> A.Parser (Vector a)+ -- ^ How to aggregate all results from 'decodeEach' into a vector of results.+ }++-- | Fail immediately if there's any parse failure.+strictDecoder :: Decoder a+strictDecoder = Decoder+ { decodeEach = id+ , decodeFold = id+ }++-- | Ignore parse failures and return successful results.+lenientDecoder :: Decoder a+lenientDecoder = Decoder+ { decodeEach = optional+ , decodeFold = \p -> do+ bs <- p+ return $! V.map fromJust $ V.filter isJust bs+ }++-- | Get a field value from a column name+getField+ :: Text -- ^ Column name+ -> Vector Text -- ^ Columns+ -> Array -- ^ Fields+ -> A.Parser Value+getField column columns fields =+ case V.elemIndex column columns of+ Nothing -> fail $ "getField: no such column " ++ show column+ Just idx -> case V.indexM fields idx of+ Nothing -> fail $ "getField: index out of bound for " ++ show column+ Just field -> return field++-- | Get a tag value from a tag name+getTag+ :: Monad m+ => Text -- ^ Tag name+ -> HashMap Text Text -- ^ Tags+ -> m Text+getTag tag tags = case HashMap.lookup tag tags of+ Nothing -> fail $ "getTag: no such tag " ++ show tag+ Just val -> return val++parseResultsObject :: Value -> A.Parser (Vector A.Value)+parseResultsObject = A.withObject "results" $ \obj -> obj .: "results"++parseSeriesObject :: Value -> A.Parser (Vector A.Value)+parseSeriesObject = A.withObject "series" $ \obj ->+ fromMaybe V.empty <$> obj .:? "series"++parseSeriesBody+ :: Value+ -> A.Parser (Maybe Text, HashMap Text Text, Vector Text, Array)+parseSeriesBody = A.withObject "series" $ \obj -> do+ !name <- obj .:? "name"+ !columns <- obj .: "columns"+ !values <- obj .: "values"+ !tags <- obj .:? "tags" .!= HashMap.empty+ return (name, tags, columns, values)++parseErrorObject :: A.Value -> A.Parser a+parseErrorObject = A.withObject "error" $ \obj -> do+ message <- obj .: "error"+ fail $ T.unpack message++-- | Parse either a POSIX timestamp or RFC3339 formatted timestamp.+parseTimestamp :: Precision ty -> A.Value -> A.Parser POSIXTime+parseTimestamp prec val = case prec of+ RFC3339 -> utcTimeToPOSIXSeconds <$!> parseRFC3339 val+ _ -> parsePOSIXTime prec val++-- | Parse an integer POSIX timestamp in given time precision.+parsePOSIXTime :: Precision ty -> A.Value -> A.Parser POSIXTime+parsePOSIXTime prec val = case prec of+ RFC3339 -> A.typeMismatch err val+ _ -> A.withScientific err+ (\s -> case timestampToUTC s of+ Nothing -> A.typeMismatch err val+ Just !utc -> return utc)+ val+ where+ err = "POSIX timestamp in " ++ T.unpack (precisionName prec)+ timestampToUTC s = do+ n <- Sci.toBoundedInteger s+ return $! fromIntegral (n :: Int) * precisionScale prec++-- | Parse a RFC3339-formatted timestamp.+--+-- Note that this parser is slow as it converts a 'T.Text' input to a 'String'+-- before parsing.+parseRFC3339 :: ParseTime time => A.Value -> A.Parser time+parseRFC3339 val = A.withText err+ (\text -> maybe (A.typeMismatch err val) (return $!) $+ parseTimeM True defaultTimeLocale fmt $ T.unpack text)+ val+ where+ fmt, err :: String+ fmt = "%FT%X%QZ"+ err = "RFC3339-formatted timestamp"++parseFieldValue :: A.Value -> A.Parser FieldValue+parseFieldValue val = case val of+ A.Number sci ->+ return $! either FieldFloat FieldInt $ Sci.floatingOrInteger sci+ A.String txt ->+ return $! FieldString txt+ A.Bool b ->+ return $! FieldBool b+ A.Null ->+ return FieldNull+ _ -> fail "parseFieldValue: expected a flat data structure"
− src/Database/InfluxDB/Lens.hs
@@ -1,66 +0,0 @@-{-# LANGUAGE RankNTypes #-}-module Database.InfluxDB.Lens- ( Lens, Lens'-- -- * Lenses for 'Config'- , credentials- , httpManager-- -- * Lenses for 'Credentials'- , user, password-- -- * Lenses for 'Server'- , host, port, ssl- ) where-import Control.Applicative-import Data.Text (Text)-import Prelude--import Network.HTTP.Client (Manager)--import Database.InfluxDB.Http--type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t-type Lens' s a = Lens s s a a---- | User credentials for authentication-credentials :: Lens' Config Credentials-credentials f r = set <$> f (configCreds r)- where- set c = r { configCreds = c }---- | An instance of 'Manager' from @http-client@ package-httpManager :: Lens' Config Manager-httpManager f c = set <$> f (configHttpManager c)- where- set m = c { configHttpManager = m }---- | User name to be used for authentication-user :: Lens' Credentials Text-user f c = set <$> f (credsUser c)- where- set u = c { credsUser = u }---- | Password to be used for authentication-password :: Lens' Credentials Text-password f s = set <$> f (credsPassword s)- where- set p = s { credsPassword = p }---- | Host name or IP address of an InfluxDB-host :: Lens' Server Text-host f s = set <$> f (serverHost s)- where- set h = s { serverHost = h }---- | Port number to be used to connect to an InfluxDB-port :: Lens' Server Int-port f s = set <$> f (serverPort s)- where- set p = s { serverPort = p }---- | Whether or not to enable SSL connection-ssl :: Lens' Server Bool-ssl f s = set <$> f (serverSsl s)- where- set s' = s { serverSsl = s' }
+ src/Database/InfluxDB/Line.hs view
@@ -0,0 +1,124 @@+{-# 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+ :: Traversable 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+ :: Traversable 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)
+ src/Database/InfluxDB/Manage.hs view
@@ -0,0 +1,158 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Database.InfluxDB.Manage+ ( manage++ , ShowQuery+ , qid+ , queryText+ , Types.database+ , duration++ , ShowSeries+ , key+ ) where+import Control.Applicative+import Control.Exception+import Control.Monad++import Control.Lens+import Data.Aeson+import Data.Scientific (toBoundedInteger)+import Data.Text (Text)+import Data.Time.Clock+import Data.Void+import qualified Data.Aeson.Types as A+import qualified Data.Attoparsec.Combinator as AC+import qualified Data.Attoparsec.Text as AT+import qualified Data.Text.Encoding as TE+import qualified Data.Vector as V+import qualified Network.HTTP.Types as HT++import Database.InfluxDB.Types as Types+import Database.InfluxDB.Query hiding (query)+import qualified Database.InfluxDB.Format as F+import qualified Network.HTTP.Client.Compat as HC++-- | Send a database management query to InfluxDB.+manage :: QueryParams -> Query -> IO ()+manage params q = do+ manager' <- either HC.newManager return $ params^.manager+ response <- HC.httpLbs request manager'+ let body = HC.responseBody response+ case eitherDecode' body of+ Left message -> do+ throwIO $ IllformedJSON message body+ Right val -> case A.parse (parseResults (params^.precision)) val of+ A.Success (_ :: V.Vector Void) -> return ()+ A.Error message -> do+ let status = HC.responseStatus response+ when (HT.statusIsServerError status) $+ throwIO $ ServerError message+ when (HT.statusIsClientError status) $+ throwIO $ BadRequest message request+ fail $ "BUG: " ++ message ++ " in Database.InfluxDB.Manage.manage"+ where+ request = HC.setQueryString qs $ manageRequest params+ qs =+ [ ("q", Just $ F.fromQuery q)+ ]++manageRequest :: QueryParams -> HC.Request+manageRequest params = HC.defaultRequest+ { HC.host = TE.encodeUtf8 $ params^.server.host+ , HC.port = fromIntegral $ params^.server.port+ , HC.secure = params^.server.ssl+ , HC.method = "POST"+ , HC.path = "/query"+ }+ where+ Server {..} = params^.server++data ShowQuery = ShowQuery+ { _qid :: !Int+ , _queryText :: !Query+ , _database :: !Database+ , _duration :: !NominalDiffTime+ } deriving Show++instance QueryResults ShowQuery where+ parseResults _ = parseResultsWith $ \_ _ columns fields ->+ maybe (fail "parseResults: parse error") return $ do+ Number (toBoundedInteger -> Just _qid) <-+ V.elemIndex "qid" columns >>= V.indexM fields+ String (F.formatQuery F.text -> _queryText) <-+ V.elemIndex "query" columns >>= V.indexM fields+ String (F.formatDatabase F.text -> _database) <-+ V.elemIndex "database" columns >>= V.indexM fields+ String (parseDuration -> Right _duration) <-+ V.elemIndex "duration" columns >>= V.indexM fields+ return ShowQuery {..}++parseDuration :: Text -> Either String NominalDiffTime+parseDuration = AT.parseOnly $ sum <$!> durations+ where+ durations = some $ (*)+ <$> fmap fromIntegral int+ <*> unit+ where+ int :: AT.Parser Int+ int = AT.decimal+ unit = AC.choice+ [ 10^^(-6 :: Int) <$ AT.char 'u'+ , 1 <$ AT.char 's'+ , 60 <$ AT.char 'm'+ , 3600 <$ AT.char 'h'+ ]++newtype ShowSeries = ShowSeries+ { _key :: Key+ } deriving Show++instance QueryResults ShowSeries where+ parseResults _ = parseResultsWith $ \_ _ columns fields ->+ ShowSeries <$> parseKey "key" columns fields++makeLensesWith (lensRules & generateSignatures .~ False) ''ShowQuery++-- | Query ID+--+-- >>> v <- query (queryParams "_internal") "SHOW QUERIES" :: IO (V.Vector ShowQuery)+-- >>> v ^.. each.qid+-- [149250]+qid :: Lens' ShowQuery Int++-- | Query text+--+-- >>> v <- query (queryParams "_internal") "SHOW QUERIES" :: IO (V.Vector ShowQuery)+-- >>> v ^.. each.queryText+-- ["SHOW QUERIES"]+queryText :: Lens' ShowQuery Query++database :: Lens' ShowQuery Database++-- |+-- >>> v <- query (queryParams "_internal") "SHOW QUERIES" :: IO (V.Vector ShowQuery)+-- >>> v ^.. each.database+-- ["_internal"]+instance HasDatabase ShowQuery where+ database = Database.InfluxDB.Manage.database++-- | Duration of the query+--+-- >>> v <- query (queryParams "_internal") "SHOW QUERIES" :: IO (V.Vector ShowQuery)+-- >>> v ^.. each.duration+-- [0.06062s]+duration :: Lens' ShowQuery NominalDiffTime++makeLensesWith (lensRules & generateSignatures .~ False) ''ShowSeries++-- | Series name+--+-- >>> v <- query (queryParams "_internal") "SHOW SERIES" :: IO (V.Vector ShowSeries)+-- >>> length $ v ^.. each.key+-- 755+key :: Lens' ShowSeries Key
+ src/Database/InfluxDB/Ping.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}++module Database.InfluxDB.Ping+ (+ -- * Ping interface+ ping++ -- * Ping parameters+ , PingParams(..)+ , pingParams+ , Types.server+ , Types.manager+ , waitForLeader++ -- * Ping result+ , PingResult(..)+ , roundtripTime+ , influxdbVersion+ ) where++import Control.Lens+import qualified Data.ByteString as BS+import qualified Data.Text.Encoding as TE+import qualified Network.HTTP.Client.Compat as HC+import System.Clock++import Database.InfluxDB.Types as Types+++-- Ping requests do not require authentication+-- | The full set of parameters for the ping API+data PingParams = PingParams+ { _server :: !Server+ , _manager :: !(Either HC.ManagerSettings HC.Manager)+ -- ^ HTTP connection manager+ , _waitForLeader :: !(Maybe Int)+ -- ^ the number of seconds to wait+ }++makeLensesWith (lensRules & generateSignatures .~ False) ''PingParams++server :: Lens' PingParams Server++instance HasServer PingParams where+ server = Database.InfluxDB.Ping.server++manager :: Lens' PingParams (Either HC.ManagerSettings HC.Manager)++instance HasManager PingParams where+ manager = Database.InfluxDB.Ping.manager++-- | The number of seconds to wait before returning a response+waitForLeader :: Lens' PingParams (Maybe Int)++pingParams :: PingParams+pingParams =+ PingParams+ { _server = localServer+ , _manager = Left HC.defaultManagerSettings+ , _waitForLeader = Nothing+ }++pingRequest :: PingParams -> HC.Request+pingRequest PingParams {..} = HC.defaultRequest+ { HC.host = TE.encodeUtf8 _host+ , HC.port = fromIntegral _port+ , HC.secure = _ssl+ , HC.method = "GET"+ , HC.path = "/ping"+ }+ where+ Server {..} = _server++data PingResult = PingResult+ { _roundtripTime :: !TimeSpec+ , _influxdbVersion :: !BS.ByteString+ } deriving (Show, Eq, Ord)++makeLensesWith (lensRules & generateSignatures .~ False) ''PingResult++-- | Roundtrip time of the ping+roundtripTime :: Lens' PingResult TimeSpec++-- | Version string returned by the InfluxDB header+influxdbVersion :: Lens' PingResult BS.ByteString++ping :: PingParams -> IO PingResult+ping params = do+ manager' <- either HC.newManager return $ _manager params+ startTime <- getTime'+ HC.withResponse request manager' (\response -> do+ endTime <- getTime'+ let headers = HC.responseHeaders response+ case lookup "X-Influxdb-Version" headers of+ Just version -> pure (PingResult (diffTimeSpec endTime startTime) version)+ Nothing -> error "A response by influxdb should always contain a version header.")+ where+ request = pingRequest params+ getTime' = getTime Monotonic
+ src/Database/InfluxDB/Query.hs view
@@ -0,0 +1,367 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}+module Database.InfluxDB.Query+ (+ -- * Query interface+ Query+ , query+ , queryChunked++ -- * Query parameters+ , QueryParams+ , queryParams+ , Types.server+ , Types.database+ , Types.precision+ , Types.manager++ -- * Parsing results+ , QueryResults(..)+ , parseResultsWith+ , parseKey++ -- * Low-level functions+ , withQueryResponse+ ) where+import Control.Exception+import Control.Monad+import Text.Printf++import Control.Lens+import Data.Aeson+import Data.Optional (Optional(..), optional)+import Data.Text (Text)+import Data.Vector (Vector)+import Data.Void+import qualified Control.Foldl as L+import qualified Data.Aeson.Parser as A+import qualified Data.Aeson.Types as A+import qualified Data.Attoparsec.ByteString as AB+import qualified Data.ByteString as B+import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Lazy as BL+import qualified Data.Text.Encoding as TE+import qualified Data.Text as T+import qualified Data.Vector as V+import qualified Network.HTTP.Types as HT++import Database.InfluxDB.JSON+import Database.InfluxDB.Types as Types+import qualified Database.InfluxDB.Format as F+import qualified Network.HTTP.Client.Compat as HC++class QueryResults a where+ parseResults+ :: Precision 'QueryRequest+ -> Value+ -> A.Parser (Vector a)++instance QueryResults Void where+ parseResults _ = A.withObject "error" $ \obj -> obj .:? "error"+ >>= maybe (pure V.empty) (withText "error" $ fail . T.unpack)++instance (a ~ Value, b ~ Value) => QueryResults (a, b) where+ parseResults _ = parseResultsWith $ \_ _ _ fields ->+ maybe (fail $ "invalid fields: " ++ show fields) return $ do+ a <- fields V.!? 0+ b <- fields V.!? 1+ return (a, b)++instance (a ~ Value, b ~ Value, c ~ Value)+ => QueryResults (a, b, c) where+ parseResults _ = parseResultsWith $ \_ _ _ fields ->+ maybe (fail $ "invalid fields: " ++ show fields) return $ do+ a <- fields V.!? 0+ b <- fields V.!? 1+ c <- fields V.!? 2+ return (a, b, c)++instance (a ~ Value, b ~ Value, c ~ Value, d ~ Value)+ => QueryResults (a, b, c, d) where+ parseResults _ = parseResultsWith $ \_ _ _ fields ->+ maybe (fail $ "invalid fields: " ++ show fields) return $ do+ a <- fields V.!? 0+ b <- fields V.!? 1+ c <- fields V.!? 2+ d <- fields V.!? 3+ return (a, b, c, d)++instance (a ~ Value, b ~ Value, c ~ Value, d ~ Value, e ~ Value)+ => QueryResults (a, b, c, d, e) where+ parseResults _ = parseResultsWith $ \_ _ _ fields ->+ maybe (fail $ "invalid fields: " ++ show fields) return $ do+ a <- fields V.!? 0+ b <- fields V.!? 1+ c <- fields V.!? 2+ d <- fields V.!? 3+ e <- fields V.!? 4+ return (a, b, c, d, e)++instance (a ~ Value, b ~ Value, c ~ Value, d ~ Value, e ~ Value, f ~ Value)+ => QueryResults (a, b, c, d, e, f) where+ parseResults _ = parseResultsWith $ \_ _ _ fields ->+ maybe (fail $ "invalid fields: " ++ show fields) return $ do+ a <- fields V.!? 0+ b <- fields V.!? 1+ c <- fields V.!? 2+ d <- fields V.!? 3+ e <- fields V.!? 4+ f <- fields V.!? 5+ return (a, b, c, d, e, f)++instance+ ( a ~ Value, b ~ Value, c ~ Value, d ~ Value, e ~ Value, f ~ Value+ , g ~ Value )+ => QueryResults (a, b, c, d, e, f, g) where+ parseResults _ = parseResultsWith $ \_ _ _ fields ->+ maybe (fail $ "invalid fields: " ++ show fields) return $ do+ a <- fields V.!? 0+ b <- fields V.!? 1+ c <- fields V.!? 2+ d <- fields V.!? 3+ e <- fields V.!? 4+ f <- fields V.!? 5+ g <- fields V.!? 6+ return (a, b, c, d, e, f, g)++instance+ ( a ~ Value, b ~ Value, c ~ Value, d ~ Value, e ~ Value, f ~ Value+ , g ~ Value, h ~ Value )+ => QueryResults (a, b, c, d, e, f, g, h) where+ parseResults _ = parseResultsWith $ \_ _ _ fields ->+ maybe (fail $ "invalid fields: " ++ show fields) return $ do+ a <- fields V.!? 0+ b <- fields V.!? 1+ c <- fields V.!? 2+ d <- fields V.!? 3+ e <- fields V.!? 4+ f <- fields V.!? 5+ g <- fields V.!? 6+ h <- fields V.!? 7+ return (a, b, c, d, e, f, g, h)++parseKey :: Key -> Vector Text -> Array -> A.Parser Key+parseKey (Key name) columns fields = do+ case V.elemIndex name columns >>= V.indexM fields of+ Just (String (F.formatKey F.text -> key)) -> return key+ _ -> fail $ printf "parseKey: %s not found in columns" $ show name++-- | The full set of parameters for the query API+data QueryParams = QueryParams+ { _server :: !Server+ , _database :: !Database+ , _precision :: !(Precision 'QueryRequest)+ -- ^ Timestamp precision+ --+ -- InfluxDB uses nanosecond precision if nothing is specified.+ , _authentication :: !(Maybe Credentials)+ -- ^ No authentication by default+ , _manager :: !(Either HC.ManagerSettings HC.Manager)+ -- ^ HTTP connection manager+ }++-- | Smart constructor for 'QueryParams'+--+-- Default parameters:+--+-- ['L.server'] 'localServer'+-- ['L.precision'] 'RFC3339'+-- ['authentication'] 'Nothing'+-- ['L.manager'] @'Left' 'HC.defaultManagerSettings'@+queryParams :: Database -> QueryParams+queryParams _database = QueryParams+ { _server = localServer+ , _precision = RFC3339+ , _authentication = Nothing+ , _manager = Left HC.defaultManagerSettings+ , ..+ }++-- | Query data from InfluxDB.+--+-- It may throw 'InfluxException'.+query :: QueryResults a => QueryParams -> Query -> IO (Vector a)+query params q = withQueryResponse params Nothing q go+ where+ go request response = do+ chunks <- HC.brConsume $ HC.responseBody response+ let body = BL.fromChunks chunks+ case eitherDecode' body of+ Left message -> throwIO $ IllformedJSON message body+ Right val -> case A.parse (parseResults (_precision params)) val of+ A.Success vec -> return vec+ A.Error message ->+ errorQuery request response message++setPrecision+ :: Precision 'QueryRequest+ -> [(B.ByteString, Maybe B.ByteString)]+ -> [(B.ByteString, Maybe B.ByteString)]+setPrecision prec qs = maybe qs (\p -> ("epoch", Just p):qs) $+ precisionParam prec++precisionParam :: Precision 'QueryRequest -> Maybe B.ByteString+precisionParam = \case+ Nanosecond -> return "ns"+ Microsecond -> return "u"+ Millisecond -> return "ms"+ Second -> return "s"+ Minute -> return "m"+ Hour -> return "h"+ RFC3339 -> Nothing++-- | Same as 'query' but it instructs InfluxDB to stream chunked responses+-- rather than returning a huge JSON object. This can be lot more efficient than+-- 'query' if the result is huge.+--+-- It may throw 'InfluxException'.+queryChunked+ :: QueryResults a+ => QueryParams+ -> Optional Int+ -- ^ Chunk size+ --+ -- By 'Default', InfluxDB chunks responses by series or by every 10,000+ -- points, whichever occurs first. If it set to a 'Specific' value, InfluxDB+ -- chunks responses by series or by that number of points.+ -> Query+ -> L.FoldM IO (Vector a) r+ -> IO r+queryChunked params chunkSize q (L.FoldM step initialize extract) =+ withQueryResponse params (Just chunkSize) q go+ where+ go request response = do+ x0 <- initialize+ chunk0 <- HC.responseBody response+ x <- loop x0 k0 chunk0+ extract x+ where+ k0 = AB.parse A.json+ loop x k chunk+ | B.null chunk = return x+ | otherwise = case k chunk of+ AB.Fail unconsumed _contexts message ->+ throwIO $ IllformedJSON message $ BL.fromStrict unconsumed+ AB.Partial k' -> do+ chunk' <- HC.responseBody response+ loop x k' chunk'+ AB.Done leftover val ->+ case A.parse (parseResults (_precision params)) val of+ A.Success vec -> do+ x' <- step x vec+ loop x' k0 leftover+ A.Error message ->+ errorQuery request response message++withQueryResponse+ :: QueryParams+ -> Maybe (Optional Int)+ -- ^ Chunk size+ --+ -- By 'Nothing', InfluxDB returns all matching data points at once.+ -- By @'Just' 'Default'@, InfluxDB chunks responses by series or by every+ -- 10,000 points, whichever occurs first. If it set to a 'Specific' value,+ -- InfluxDB chunks responses by series or by that number of points.+ -> Query+ -> (HC.Request -> HC.Response HC.BodyReader -> IO r)+ -> IO r+withQueryResponse params chunkSize q f = do+ manager' <- either HC.newManager return $ _manager params+ HC.withResponse request manager' (f request)+ where+ request =+ HC.setQueryString (setPrecision (_precision params) queryString) $+ queryRequest params+ queryString = addChunkedParam+ [ ("q", Just $ F.fromQuery q)+ , ("db", Just db)+ ]+ where+ !db = TE.encodeUtf8 $ databaseName $ _database params+ addChunkedParam ps = case chunkSize of+ Nothing -> ps+ Just size ->+ let !chunked = optional "true" (decodeChunkSize . max 1) size+ in ("chunked", Just chunked) : ps+ where+ decodeChunkSize = BL.toStrict . BB.toLazyByteString . BB.intDec+++queryRequest :: QueryParams -> HC.Request+queryRequest QueryParams {..} = HC.defaultRequest+ { HC.host = TE.encodeUtf8 _host+ , HC.port = fromIntegral _port+ , HC.secure = _ssl+ , HC.method = "GET"+ , HC.path = "/query"+ }+ where+ Server {..} = _server++errorQuery :: HC.Request -> HC.Response body -> String -> IO a+errorQuery request response message = do+ let status = HC.responseStatus response+ when (HT.statusIsServerError status) $+ throwIO $ ServerError message+ when (HT.statusIsClientError status) $+ throwIO $ BadRequest message request+ fail $ "BUG: " ++ message ++ " in Database.InfluxDB.Query.query - "+ ++ show request++makeLensesWith (lensRules & generateSignatures .~ False) ''QueryParams++server :: Lens' QueryParams Server++-- |+-- >>> let p = queryParams "foo"+-- >>> p ^. server.host+-- "localhost"+instance HasServer QueryParams where+ server = Database.InfluxDB.Query.server++database :: Lens' QueryParams Database++-- |+-- >>> let p = queryParams "foo"+-- >>> p ^. database+-- "foo"+instance HasDatabase QueryParams where+ database = Database.InfluxDB.Query.database++precision :: Lens' QueryParams (Precision 'QueryRequest)++-- | Returning JSON responses contain timestamps in the specified+-- precision/format.+--+-- >>> let p = queryParams "foo"+-- >>> p ^. precision+-- Nanosecond+instance HasPrecision 'QueryRequest QueryParams where+ precision = Database.InfluxDB.Query.precision++manager :: Lens' QueryParams (Either HC.ManagerSettings HC.Manager)++-- |+-- >>> let p = queryParams "foo"+-- >>> p & manager .~ Left HC.defaultManagerSettings+instance HasManager QueryParams where+ manager = Database.InfluxDB.Query.manager++-- | Authentication info for the query+--+-- >>> let p = queryParams "foo"+-- >>> p ^. authentication+-- Nothing+authentication :: Lens' QueryParams (Maybe Credentials)++instance HasCredentials QueryParams where+ authentication = Database.InfluxDB.Query.authentication
− src/Database/InfluxDB/Stream.hs
@@ -1,41 +0,0 @@-{-# LANGUAGE BangPatterns #-}-module Database.InfluxDB.Stream where-import Prelude hiding (mapM)---- | Effectful stream-data Stream m a- = Yield a (m (Stream m a))- -- ^ Yield a value. The stream will be continued.- | Done- -- ^ The end of the stream.---- | Map each element of a stream to a monadic action, evaluate these actions--- from left to right, and collect the results as a stream.-mapM :: Monad m => (a -> m b) -> Stream m a -> m (Stream m b)-mapM _ Done = return Done-mapM f (Yield a mb) = do- a' <- f a- b <- mb- return $ Yield a' (mapM f b)---- | Monadic left fold for 'Stream'.-fold :: Monad m => (b -> a -> m b) -> b -> Stream m a -> m b-fold f = loop- where- loop z stream = case stream of- Done -> return z- Yield a nextStream -> do- b <- f z a- stream' <- nextStream- loop b stream'---- | Strict version of 'fold'.-fold' :: Monad m => (b -> a -> m b) -> b -> Stream m a -> m b-fold' f = loop- where- loop z stream = case stream of- Done -> return z- Yield a nextStream -> do- !b <- f z a- stream' <- nextStream- loop b stream'
− src/Database/InfluxDB/TH.hs
@@ -1,141 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE TemplateHaskell #-}--#if __GLASGOW_HASKELL__ == 704-{-# LANGUAGE ConstraintKinds #-}-#endif--module Database.InfluxDB.TH- ( Options(..), defaultOptions- , deriveSeriesData- , deriveToSeriesData- , deriveFromSeriesData-- , stripPrefixLower- , stripPrefixSnake- ) where-import Control.Applicative-import Language.Haskell.TH-import Language.Haskell.TH.Syntax (VarStrictType)-import Prelude--import qualified Data.Vector as V--import Database.InfluxDB.Decode-import Database.InfluxDB.Encode-import Database.InfluxDB.Types.Internal--data Options = Options- { fieldLabelModifier :: String -> String- }--defaultOptions :: Options-defaultOptions = Options- { fieldLabelModifier = id- }--deriveSeriesData :: Options -> Name -> Q [Dec]-deriveSeriesData opts name = (++)- <$> deriveToSeriesData opts name- <*> deriveFromSeriesData opts name--deriveToSeriesData :: Options -> Name -> Q [Dec]-deriveToSeriesData opts name = do- info <- reify name- case info of- TyConI dec -> pure <$> deriveWith toSeriesDataBody opts dec- _ -> fail $ "Expected a type constructor, but got " ++ show info--deriveFromSeriesData :: Options -> Name -> Q [Dec]-deriveFromSeriesData opts name = do- info <- reify name- case info of- TyConI dec -> pure <$> deriveWith fromSeriesDataBody opts dec- _ -> fail $ "Expected a type constructor, but got " ++ show info--deriveWith- :: (Options -> Name -> [TyVarBndr] -> Con -> Q Dec)- -> Options -> Dec -> Q Dec-deriveWith f opts dec = case dec of-#if MIN_VERSION_template_haskell(2, 11, 0)- DataD _ tyName tyVars _ [con] _ -> f opts tyName tyVars con- NewtypeD _ tyName tyVars _ con _ -> f opts tyName tyVars con-#else- DataD _ tyName tyVars [con] _ -> f opts tyName tyVars con- NewtypeD _ tyName tyVars con _ -> f opts tyName tyVars con-#endif- _ -> fail $ "Expected a data or newtype declaration, but got " ++ show dec--toSeriesDataBody :: Options -> Name -> [TyVarBndr] -> Con -> Q Dec-toSeriesDataBody opts tyName tyVars con = do- case con of- RecC conName vars -> InstanceD-#if MIN_VERSION_template_haskell(2, 11, 0)- Nothing-#endif- <$> mapM tyVarToPred tyVars- <*> [t| ToSeriesData $(conT tyName) |]- <*> deriveDecs conName vars- _ -> fail $ "Expected a record, but got " ++ show con- where- tyVarToPred tv = case tv of-#if MIN_VERSION_template_haskell(2, 10, 0)- PlainTV name -> conT ''FromValue `appT` varT name- KindedTV name _ -> conT ''FromValue `appT` varT name-#else- PlainTV name -> classP ''FromValue [varT name]- KindedTV name _ -> classP ''FromValue [varT name]-#endif- deriveDecs _conName vars = do- a <- newName "a"- sequence- [ funD 'toSeriesColumns- [ clause [wildP]- (normalB [| V.fromList $(listE columns) |]) []- ]- , funD 'toSeriesPoints- [ clause [varP a]- (normalB [| V.fromList $(listE $ map (applyToValue a) vars) |]) []- ]- ]- where- applyToValue a (name, _, _) = [| toValue ($(varE name) $(varE a)) |]- columns = map (varStrictTypeToColumn opts) vars--fromSeriesDataBody :: Options -> Name -> [TyVarBndr] -> Con -> Q Dec-fromSeriesDataBody opts tyName tyVars con = do- case con of- RecC conName vars -> instanceD- (mapM tyVarToPred tyVars)- [t| FromSeriesData $(conT tyName) |]- [deriveDec conName vars]- _ -> fail $ "Expected a record, but got " ++ show con- where- tyVarToPred tv = case tv of-#if MIN_VERSION_template_haskell(2, 10, 0)- PlainTV name -> conT ''FromValue `appT` varT name- KindedTV name _ -> conT ''FromValue `appT` varT name-#else- PlainTV name -> classP ''FromValue [varT name]- KindedTV name _ -> classP ''FromValue [varT name]-#endif- deriveDec conName vars = funD 'parseSeriesData- [ clause [] (normalB deriveBody) []- ]- where- deriveBody = do- values <- newName "values"- appE (varE 'withValues) $ lamE [varP values] $- foldl (go values) [| pure $(conE conName) |] columns- where- go :: Name -> Q Exp -> Q Exp -> Q Exp- go values expQ col = [| $expQ <*> $(varE values) .: $col |]- columns = map (varStrictTypeToColumn opts) vars--varStrictTypeToColumn :: Options -> VarStrictType -> Q Exp-varStrictTypeToColumn opts = column opts . f- where- f (var, _, _) = var--column :: Options -> Name -> Q Exp-column opts = litE . stringL . fieldLabelModifier opts . nameBase
src/Database/InfluxDB/Types.hs view
@@ -1,302 +1,225 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-}-module Database.InfluxDB.Types- ( -- * Series, columns and data points- Series(..)- , seriesColumns- , seriesPoints- , SeriesData(..)- , Column- , Value(..)-- -- * Data types for HTTP API- , Credentials(..)- , Server(..)- , Database(..)- , User(..)- , Admin(..)- , Ping(..)- , Interface- , ShardSpace(..)-- -- * Server pool- , ServerPool- , serverRetryPolicy- , newServerPool- , newServerPoolWithRetryPolicy- , activeServer- , failover-- -- * Exceptions- , InfluxException(..)- , jsonDecodeError- , seriesDecodeError- ) where--import Control.Applicative (empty)-import Control.Exception (Exception, throwIO)+module Database.InfluxDB.Types where+import Control.Exception import Data.Data (Data)-import Data.IORef import Data.Int (Int64)-import Data.Monoid ((<>))-import Data.Sequence (Seq, ViewL(..), (|>))-import Data.Text (Text)+import Data.String import Data.Typeable (Typeable)-import Data.Vector (Vector)-import Data.Word (Word32) import GHC.Generics (Generic)-import qualified Data.Sequence as Seq -import Control.Retry (RetryPolicy, limitRetries, exponentialBackoff)-import Data.Aeson ((.=), (.:))-import Data.Aeson.TH-import qualified Data.Aeson as A--import Database.InfluxDB.Types.Internal (stripPrefixOptions)+import Control.Lens+import Data.Text (Text)+import Data.Time.Clock+import Data.Time.Clock.POSIX+import Network.HTTP.Client (Manager, ManagerSettings, Request)+import qualified Data.ByteString.Lazy as BL+import qualified Data.Text as T -#if MIN_VERSION_aeson(0, 7, 0)-import Data.Scientific-#else-import Data.Attoparsec.Number-#endif+newtype Query = Query T.Text deriving IsString --------------------------------------------------------------- Compatibility for older GHC+instance Show Query where+ show (Query q) = show q -#if __GLASGOW_HASKELL__ < 706-import Control.Exception (evaluate)+data Server = Server+ { _host :: !Text+ , _port :: !Int+ , _ssl :: !Bool+ } deriving (Show, Generic, Eq) -atomicModifyIORef' :: IORef a -> (a -> (a, b)) -> IO b-atomicModifyIORef' ref f = do- b <- atomicModifyIORef ref $ \x ->- let (a, b) = f x- in (a, a `seq` b)- evaluate b-#endif------------------------------------------------------------+-- | Default server settings.+--+-- Default parameters:+--+-- * 'host': @"localhost"@+-- * 'port': @8086@+-- * 'ssl': 'False'+localServer :: Server+localServer = Server+ { _host = "localhost"+ , _port = 8086+ , _ssl = False+ } --- | A series consists of name, columns and points. The columns and points are--- expressed in a separate type 'SeriesData'.-data Series = Series- { seriesName :: {-# UNPACK #-} !Text- -- ^ Series name- , seriesData :: {-# UNPACK #-} !SeriesData- -- ^ Columns and data points in the series- } deriving (Typeable, Generic)+makeLensesWith (lensRules & generateSignatures .~ False) ''Server --- | Convenient accessor for columns.-seriesColumns :: Series -> Vector Column-seriesColumns = seriesDataColumns . seriesData+-- | Host name of the server+host :: Lens' Server Text --- | Convenient accessor for points.-seriesPoints :: Series -> [Vector Value]-seriesPoints = seriesDataPoints . seriesData+-- | Port number of the server+port :: Lens' Server Int -instance A.ToJSON Series where- toJSON Series {..} = A.object- [ "name" .= seriesName- , "columns" .= seriesDataColumns- , "points" .= seriesDataPoints- ]- where- SeriesData {..} = seriesData+-- | If SSL is enabled+ssl :: Lens' Server Bool -instance A.FromJSON Series where- parseJSON (A.Object v) = do- name <- v .: "name"- columns <- v .: "columns"- points <- v .: "points"- return Series- { seriesName = name- , seriesData = SeriesData- { seriesDataColumns = columns- , seriesDataPoints = points- }- }- parseJSON _ = empty+-- | User credentials+data Credentials = Credentials+ { _user :: !Text+ , _password :: !Text+ } --- | 'SeriesData' consists of columns and points.-data SeriesData = SeriesData- { seriesDataColumns :: Vector Column- , seriesDataPoints :: [Vector Value]- } deriving (Eq, Show, Typeable, Generic)+makeLensesWith (lensRules & generateSignatures .~ False) ''Credentials -type Column = Text+-- | User name to access InfluxDB+user :: Lens' Credentials Text --- | An InfluxDB value represented as a Haskell value.-data Value- = Int !Int64- | Float !Double- | String !Text- | Bool !Bool- | Null- deriving (Eq, Show, Data, Typeable, Generic)+-- | Password to access InfluxDB+password :: Lens' Credentials Text -instance A.ToJSON Value where- toJSON (Int n) = A.toJSON n- toJSON (Float d) = A.toJSON d- toJSON (String xs) = A.toJSON xs- toJSON (Bool b) = A.toJSON b- toJSON Null = A.Null+-- | Database name+newtype Database = Database { databaseName :: Text } deriving (Eq, Ord) -instance A.FromJSON Value where- parseJSON (A.Object o) = fail $ "Unexpected object: " ++ show o- parseJSON (A.Array a) = fail $ "Unexpected array: " ++ show a- parseJSON (A.String xs) = return $ String xs- parseJSON (A.Bool b) = return $ Bool b- parseJSON A.Null = return Null- parseJSON (A.Number n) = return $! numberToValue- where-#if MIN_VERSION_aeson(0, 7, 0)- numberToValue- -- If the number is larger than Int64, it must be- -- a float64 (Double in Haskell).- | n > maxInt = Float $ toRealFloat n- | e < 0 = Float $ realToFrac n- | otherwise = Int $ fromIntegral $ coefficient n * 10 ^ e- where- e = base10Exponent n-#if !MIN_VERSION_scientific(0, 3, 0)- toRealFloat = realToFrac--- scientific-#endif-#else- numberToValue = case n of- I i- -- If the number is larger than Int64, it must be- -- a float64 (Double in Haskell).- | i > maxInt -> Float $ fromIntegral i- | otherwise -> Int $ fromIntegral i- D d -> Float d--- aeson-#endif- maxInt = fromIntegral (maxBound :: Int64)+-- | String type that is used for measurements, tag keys and field keys.+newtype Key = Key Text deriving (Eq, Ord) ------------------------------------------------------------+instance IsString Database where+ fromString xs = Database $ fromNonEmptyString "Database" xs --- | User credentials.-data Credentials = Credentials- { credsUser :: !Text- , credsPassword :: !Text- } deriving (Show, Typeable, Generic)+instance IsString Key where+ fromString xs = Key $ fromNonEmptyString "Key" xs --- | Server location.-data Server = Server- { serverHost :: !Text- -- ^ Hostname or IP address- , serverPort :: !Int- , serverSsl :: !Bool- -- ^ SSL is enabled or not in the server side- } deriving (Show, Typeable, Generic)+fromNonEmptyString :: String -> String -> Text+fromNonEmptyString ty xs+ | null xs = error $ ty ++ " should never be empty"+ | otherwise = fromString xs --- | Non-empty set of server locations. The active server will always be used--- until any HTTP communications fail.-data ServerPool = ServerPool- { serverActive :: !Server- -- ^ Current active server- , serverBackup :: !(Seq Server)- -- ^ The rest of the servers in the pool.- , serverRetryPolicy :: !RetryPolicy- }+instance Show Database where+ show (Database name) = show name -newtype Database = Database- { databaseName :: Text- } deriving (Show, Typeable, Generic)+instance Show Key where+ show (Key name) = show name --- | User-data User = User- { userName :: Text- , userIsAdmin :: Bool- } deriving (Show, Typeable, Generic)+data FieldValue+ = FieldInt !Int64+ | FieldFloat !Double+ | FieldString !Text+ | FieldBool !Bool+ | FieldNull+ deriving (Eq, Show, Data, Typeable, Generic) --- | Administrator-newtype Admin = Admin- { adminName :: Text- } deriving (Show, Typeable, Generic)+instance IsString FieldValue where+ fromString = FieldString . T.pack -newtype Ping = Ping- { pingStatus :: Text- } deriving (Show, Typeable, Generic)+-- | Type of a request+data RequestType+ = QueryRequest+ -- ^ Request for @/query@+ | WriteRequest+ -- ^ Request for @/write@+ deriving Show -type Interface = Text+-- | Predefined set of time precision.+--+-- 'RFC3339' is only available for 'QueryRequest's.+data Precision (ty :: RequestType) where+ -- | POSIX time in ns+ Nanosecond :: Precision ty+ -- | POSIX time in μs+ Microsecond :: Precision ty+ -- | POSIX time in ms+ Millisecond :: Precision ty+ -- | POSIX time in s+ Second :: Precision ty+ -- | POSIX time in minutes+ Minute :: Precision ty+ -- | POSIX time in hours+ Hour :: Precision ty+ -- | Nanosecond precision time in a human readable format, like+ -- @2016-01-04T00:00:23.135623Z@. This is the default format for @/query@.+ RFC3339 :: Precision 'QueryRequest -data ShardSpace = ShardSpace- { shardSpaceDatabase :: Maybe Text- , shardSpaceName :: Text- , shardSpaceRegex :: Text- , shardSpaceRetentionPolicy :: Text- , shardSpaceShardDuration :: Text- , shardSpaceReplicationFactor :: Word32- , shardSpaceSplit :: Word32- } deriving (Show, Typeable, Generic)+deriving instance Show (Precision a) --------------------------------------------------------------- Server pool manipulation+precisionName :: Precision ty -> Text+precisionName = \case+ Nanosecond -> "n"+ Microsecond -> "u"+ Millisecond -> "ms"+ Second -> "s"+ Minute -> "m"+ Hour -> "h"+ RFC3339 -> "rfc3339" --- | Create a non-empty server pool. You must specify at least one server--- location to create a pool.-newServerPool :: Server -> [Server] -> IO (IORef ServerPool)-newServerPool = newServerPoolWithRetryPolicy defaultRetryPolicy- where- defaultRetryPolicy :: RetryPolicy- defaultRetryPolicy = limitRetries 5 <> exponentialBackoff 50+-- | A 'Timestamp' is something that can be converted to a valid+-- InfluxDB timestamp, which is represented as a 64-bit integer.+class Timestamp time where+ -- | Round a time to the given precision and scale it to nanoseconds+ roundTo :: Precision 'WriteRequest -> time -> Int64+ -- | Scale a time to the given precision+ scaleTo :: Precision 'WriteRequest -> time -> Int64 -newServerPoolWithRetryPolicy- :: RetryPolicy -> Server -> [Server] -> IO (IORef ServerPool)-newServerPoolWithRetryPolicy retryPolicy active backups =- newIORef ServerPool- { serverActive = active- , serverBackup = Seq.fromList backups- , serverRetryPolicy = retryPolicy- }+roundAt :: RealFrac a => a -> a -> a+roundAt scale x = fromIntegral (round (x / scale) :: Int) * scale --- | Get a server from the pool.-activeServer :: IORef ServerPool -> IO Server-activeServer ref = do- ServerPool { serverActive } <- readIORef ref- return serverActive+precisionScale :: Fractional a => Precision ty -> a+precisionScale = \case+ RFC3339 -> 10^^(-9 :: Int)+ Nanosecond -> 10^^(-9 :: Int)+ Microsecond -> 10^^(-6 :: Int)+ Millisecond -> 10^^(-3 :: Int)+ Second -> 1+ Minute -> 60+ Hour -> 60 * 60 --- | Move the current server to the backup pool and pick one of the backup--- server as the new active server. Currently the scheduler works in--- round-robin fashion.-failover :: IORef ServerPool -> IO ()-failover ref = atomicModifyIORef' ref $ \pool@ServerPool {..} ->- case Seq.viewl serverBackup of- EmptyL -> (pool, ())- active :< rest -> (newPool, ())- where- newPool = pool- { serverActive = active- , serverBackup = rest |> serverActive- }+instance Timestamp UTCTime where+ roundTo prec = roundTo prec . utcTimeToPOSIXSeconds+ scaleTo prec = scaleTo prec . utcTimeToPOSIXSeconds --------------------------------------------------------------- Exceptions+instance Timestamp NominalDiffTime where+ roundTo prec time =+ round $ 10^(9 :: Int) * roundAt (precisionScale prec) time+ scaleTo prec time = round $ time / precisionScale prec +-- | Exceptions used in this library.+--+-- In general, the library tries to convert exceptions from the dependent+-- libraries to the following types of errors. data InfluxException- = JsonDecodeError String- | SeriesDecodeError String+ = ServerError String+ -- ^ Server side error.+ --+ -- You can expect to get a successful response once the issue is resolved on+ -- the server side.+ | BadRequest String Request+ -- ^ Client side error.+ --+ -- You need to fix your query to get a successful response.+ | IllformedJSON String BL.ByteString+ -- ^ Unexpected JSON response.+ --+ -- This can happen e.g. when the response from InfluxDB is incompatible with+ -- what this library expects due to an upstream format change etc. deriving (Show, Typeable) instance Exception InfluxException -jsonDecodeError :: String -> IO a-jsonDecodeError = throwIO . JsonDecodeError+class HasServer a where+ server :: Lens' a Server -seriesDecodeError :: String -> IO a-seriesDecodeError = throwIO . SeriesDecodeError+class HasDatabase a where+ database :: Lens' a Database --------------------------------------------------------------- Aeson instances+class HasPrecision (ty :: RequestType) a | a -> ty where+ -- Time precision parameter+ precision :: Lens' a (Precision ty) -deriveFromJSON (stripPrefixOptions "database") ''Database-deriveFromJSON (stripPrefixOptions "admin") ''Admin-deriveFromJSON (stripPrefixOptions "user") ''User-deriveFromJSON (stripPrefixOptions "ping") ''Ping-deriveFromJSON (stripPrefixOptions "shardSpace") ''ShardSpace+class HasManager a where+ -- | HTTP manager settings or a manager itself.+ --+ -- If it's set to 'ManagerSettings', the library will create a 'Manager' from+ -- the settings for you.+ manager :: Lens' a (Either ManagerSettings Manager)++class HasCredentials a where+ authentication :: Lens' a (Maybe Credentials)
− src/Database/InfluxDB/Types/Internal.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE CPP #-}-module Database.InfluxDB.Types.Internal- ( stripPrefixOptions- , stripPrefixLower- , stripPrefixSnake- ) where-import Data.Char (isUpper, toLower)------------------------------------------------------ Conditional imports--#if MIN_VERSION_aeson(0, 6, 2)-import Data.Aeson.TH (Options(..), defaultOptions)-#endif-----------------------------------------------------#if MIN_VERSION_aeson(0, 6, 2)-stripPrefixOptions :: String -> Options-stripPrefixOptions name = defaultOptions- { fieldLabelModifier = stripPrefixLower name- }-#else-stripPrefixOptions :: String -> String -> String-stripPrefixOptions = stripPrefixLower-#endif---- | Strip the prefix then convert to 'lowerCamelCase'.-stripPrefixLower- :: String -- ^ Prefix to be stripped- -> String -- ^ Input string- -> String-stripPrefixLower prefix xs = case drop (length prefix) xs of- [] -> error "Insufficient length of field name"- c:cs -> toLower c : cs---- | Strip the prefix then convert to 'snake_case'.-stripPrefixSnake- :: String -- ^ Prefix to be stripped- -> String -- ^ Input string- -> String-stripPrefixSnake prefix xs = case drop (length prefix) xs of- [] -> error "Insufficient length of field name"- cs -> toSnake cs- where- toSnake = dropWhile (== '_') . foldr f []- f c cs- | isUpper c = '_':toLower c:cs- | otherwise = c:cs
+ src/Database/InfluxDB/Write.hs view
@@ -0,0 +1,209 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}+module Database.InfluxDB.Write+ ( -- * Writers+ write+ , writeBatch+ , writeByteString++ -- * Writer parameters+ , WriteParams+ , writeParams+ , Types.server+ , Types.database+ , retentionPolicy+ , Types.precision+ , Types.manager+) where+import Control.Exception+import Control.Monad+import Data.Maybe++import Control.Lens+import qualified Data.Aeson as A+import qualified Data.Aeson.Types as A+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Lazy as BL+import qualified Data.Text.Encoding as TE+import qualified Network.HTTP.Types as HT++import Database.InfluxDB.Line+import Database.InfluxDB.Types as Types+import Database.InfluxDB.JSON+import qualified Network.HTTP.Client.Compat as HC++-- | The full set of parameters for the HTTP writer.+data WriteParams = WriteParams+ { _server :: !Server+ , _database :: !Database+ -- ^ Database to be written+ , _retentionPolicy :: !(Maybe Key)+ -- ^ 'Nothing' means the default retention policy for the database.+ , _precision :: !(Precision 'WriteRequest)+ -- ^ Timestamp precision+ --+ -- In the HTTP API, timestamps are scaled by the given precision.+ , _authentication :: !(Maybe Credentials)+ -- ^ No authentication by default+ , _manager :: !(Either HC.ManagerSettings HC.Manager)+ -- ^ HTTP connection manager+ }++-- | Smart constructor for 'WriteParams'+--+-- Default parameters:+--+-- ['L.server'] 'localServer'+-- ['L.precision'] 'Nanosecond'+-- ['retentionPolicy'] 'Nothing'+-- ['L.manager'] @'Left' 'HC.defaultManagerSettings'@+writeParams :: Database -> WriteParams+writeParams _database = WriteParams+ { _server = localServer+ , _precision = Nanosecond+ , _retentionPolicy = Nothing+ , _authentication = Nothing+ , _manager = Left HC.defaultManagerSettings+ , ..+ }++-- | Write a 'Line'+write+ :: Timestamp time+ => WriteParams+ -> Line time+ -> IO ()+write p@WriteParams {_precision} =+ writeByteString p . encodeLine (scaleTo _precision)++-- | Write 'Line's in a batch+--+-- This is more efficient than 'write'.+writeBatch+ :: (Timestamp time, Traversable f)+ => WriteParams+ -> f (Line time)+ -> IO ()+writeBatch p@WriteParams {_precision} =+ writeByteString p . encodeLines (scaleTo _precision)++-- | Write a raw 'BL.ByteString'+writeByteString :: WriteParams -> BL.ByteString -> IO ()+writeByteString params payload = do+ manager' <- either HC.newManager return $ _manager params+ response <- HC.httpLbs request manager'+ let body = HC.responseBody response+ status = HC.responseStatus response+ if BL.null body+ then do+ let message = B8.unpack $ HT.statusMessage status+ when (HT.statusIsServerError status) $+ throwIO $ ServerError message+ when (HT.statusIsClientError status) $+ throwIO $ BadRequest message request+ else case A.eitherDecode' body of+ Left message ->+ throwIO $ IllformedJSON message body+ Right val -> case A.parse parseErrorObject val of+ A.Success _ ->+ fail $ "BUG: impossible code path in Database.InfluxDB.Write.writeByteString"+ A.Error message -> do+ when (HT.statusIsServerError status) $+ throwIO $ ServerError message+ when (HT.statusIsClientError status) $+ throwIO $ BadRequest message request+ fail $ "BUG: " ++ message+ ++ " in Database.InfluxDB.Write.writeByteString"++ where+ request = (writeRequest params)+ { HC.requestBody = HC.RequestBodyLBS payload+ }++writeRequest :: WriteParams -> HC.Request+writeRequest WriteParams {..} =+ HC.setQueryString qs HC.defaultRequest+ { HC.host = TE.encodeUtf8 _host+ , HC.port = fromIntegral _port+ , HC.secure = _ssl+ , HC.method = "POST"+ , HC.path = "/write"+ }+ where+ Server {..} = _server+ qs = concat+ [ [("db", Just $ TE.encodeUtf8 $ databaseName _database)]+ , fromMaybe [] $ do+ Key name <- _retentionPolicy+ return [("rp", Just (TE.encodeUtf8 name))]+ , fromMaybe [] $ do+ Credentials { _user = u, _password = p } <- _authentication+ return+ [ ("u", Just (TE.encodeUtf8 u))+ , ("p", Just (TE.encodeUtf8 p))+ ]+ ]++makeLensesWith (lensRules & generateSignatures .~ False) ''WriteParams++server :: Lens' WriteParams Server++-- |+-- >>> let p = writeParams "foo"+-- >>> p ^. server.host+-- "localhost"+instance HasServer WriteParams where+ server = Database.InfluxDB.Write.server++database :: Lens' WriteParams Database++-- |+-- >>> let p = writeParams "foo"+-- >>> p ^. database+-- "foo"+instance HasDatabase WriteParams where+ database = Database.InfluxDB.Write.database++-- | Target retention policy for the write.+--+-- InfluxDB writes to the @default@ retention policy if this parameter is set+-- to 'Nothing'.+--+-- >>> let p = writeParams "foo"+-- >>> let p' = p & retentionPolicy .~ Just "two_hours"+-- >>> p' ^. retentionPolicy+-- Just "two_hours"+retentionPolicy :: Lens' WriteParams (Maybe Key)++precision :: Lens' WriteParams (Precision 'WriteRequest)++-- |+-- >>> let p = writeParams "foo"+-- >>> p ^. precision+-- Nanosecond+instance HasPrecision 'WriteRequest WriteParams where+ precision = Database.InfluxDB.Write.precision++manager :: Lens' WriteParams (Either HC.ManagerSettings HC.Manager)++-- |+-- >>> let p = writeParams "foo"+-- >>> p & manager .~ Left HC.defaultManagerSettings+instance HasManager WriteParams where+ manager = Database.InfluxDB.Write.manager++-- | Authentication info for the write+--+-- >>> let p = writeParams "foo"+-- >>> p ^. authentication+-- Nothing+authentication :: Lens' WriteParams (Maybe Credentials)++instance HasCredentials WriteParams where+ authentication = Database.InfluxDB.Write.authentication
+ src/Database/InfluxDB/Write/UDP.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+module Database.InfluxDB.Write.UDP+ ( -- * 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++-- | The full set of parameters for the UDP writer.+data WriteParams = WriteParams+ { _socket :: !Socket+ , _sockAddr :: !SockAddr+ , _precision :: !(Precision 'WriteRequest)+ }++-- | Smart constructor for 'WriteParams'+--+-- Default parameters:+--+-- ['L.precision'] 'Nanosecond'+writeParams :: Socket -> SockAddr -> WriteParams+writeParams _socket _sockAddr = WriteParams+ { _precision = Nanosecond+ , ..+ }++-- | Write a 'Line'+write+ :: Timestamp time+ => WriteParams+ -> Line time+ -> IO ()+write p@WriteParams {_precision} =+ writeByteString p . encodeLine (roundTo _precision)++-- | Write 'Line's in a batch+--+-- This is more efficient than 'write'.+writeBatch+ :: (Timestamp time, Traversable f)+ => WriteParams+ -> f (Line time)+ -> IO ()+writeBatch p@WriteParams {_precision} =+ writeByteString p . encodeLines (roundTo _precision)++-- | Write a raw 'L.ByteString'+writeByteString :: WriteParams -> BL.ByteString -> IO ()+writeByteString WriteParams {..} payload =+ sendManyTo _socket (BL.toChunks payload) _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 = Database.InfluxDB.Write.UDP.precision
+ src/Network/HTTP/Client/Compat.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE CPP #-}+module Network.HTTP.Client.Compat+ ( defaultRequest+ , module X+ ) where++#if MIN_VERSION_http_client(0, 5, 0)+import Network.HTTP.Client (defaultRequest)+import Network.HTTP.Client as X hiding (defaultRequest)+#else+import Data.Default.Class (def)+import Network.HTTP.Client as X++defaultRequest :: Request+defaultRequest = def+#endif
tests/test-suite.hs view
@@ -16,469 +16,8 @@ import Test.Tasty.HUnit import Test.Tasty.TH-import Test.Tasty.QuickCheck hiding (reason)-import qualified Network.HTTP.Client as HC import Database.InfluxDB-import Database.InfluxDB.TH-import qualified Database.InfluxDB.Stream as S -prop_fromValue_toValue_identity_Value :: Value -> Bool-prop_fromValue_toValue_identity_Value = fromValueToValueIdentity--prop_fromValue_toValue_identity_Bool :: Bool -> Bool-prop_fromValue_toValue_identity_Bool = fromValueToValueIdentity--prop_fromValue_toValue_identity_Int :: Int -> Bool-prop_fromValue_toValue_identity_Int = fromValueToValueIdentity--prop_fromValue_toValue_identity_Int8 :: Int8 -> Bool-prop_fromValue_toValue_identity_Int8 = fromValueToValueIdentity--prop_fromValue_toValue_identity_Int16 :: Int16 -> Bool-prop_fromValue_toValue_identity_Int16 = fromValueToValueIdentity--prop_fromValue_toValue_identity_Int32 :: Int32 -> Bool-prop_fromValue_toValue_identity_Int32 = fromValueToValueIdentity--prop_fromValue_toValue_identity_Int64 :: Int64 -> Bool-prop_fromValue_toValue_identity_Int64 = fromValueToValueIdentity--prop_fromValue_toValue_identity_Word8 :: Word8 -> Bool-prop_fromValue_toValue_identity_Word8 = fromValueToValueIdentity--prop_fromValue_toValue_identity_Word16 :: Word16 -> Bool-prop_fromValue_toValue_identity_Word16 = fromValueToValueIdentity--prop_fromValue_toValue_identity_Word32 :: Word32 -> Bool-prop_fromValue_toValue_identity_Word32 = fromValueToValueIdentity--prop_fromValue_toValue_identity_Double :: Double -> Bool-prop_fromValue_toValue_identity_Double = fromValueToValueIdentity--prop_fromValue_toValue_identity_Text :: T.Text -> Bool-prop_fromValue_toValue_identity_Text = fromValueToValueIdentity--prop_fromValue_toValue_identity_LazyText :: TL.Text -> Bool-prop_fromValue_toValue_identity_LazyText = fromValueToValueIdentity--prop_fromValue_toValue_identity_String :: String -> Bool-prop_fromValue_toValue_identity_String = fromValueToValueIdentity--prop_fromValue_toValue_identity_Maybe_Int :: Maybe Int -> Bool-prop_fromValue_toValue_identity_Maybe_Int = fromValueToValueIdentity-----------------------------------------------------instance Arbitrary Value where- arbitrary = oneof- [ Int <$> arbitrary- , Float <$> arbitrary- , String <$> arbitrary- , Bool <$> arbitrary- , pure Null- ]--instance Arbitrary T.Text where- arbitrary = T.pack <$> arbitrary--instance Arbitrary TL.Text where- arbitrary = TL.pack <$> arbitrary--fromValueToValueIdentity :: (Eq a, FromValue a, ToValue a) => a -> Bool-fromValueToValueIdentity a = fromValue (toValue a) == Right a-----------------------------------------------------case_ping :: Assertion-case_ping = runTest $ \config -> do- Ping status <- ping config- status @?= "ok"--case_isInSync :: Assertion-case_isInSync = runTest $ \config -> do- inSync <- isInSync config- assertBool "The database is not in sync." inSync--case_post :: Assertion-case_post = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- post config database $- writeSeries name $ Val 42- ss <- query config database $ "select value from " <> name- case ss of- [series] -> fromSeriesData series @?= Right [Val 42]- _ -> assertFailure $ "Expect one series, but got: " ++ show ss--case_post_multi_series :: Assertion-case_post_multi_series = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- post config database $ do- writeSeries name $ Val 42- writeSeries name $ Val 42- writeSeries name $ Val 42- ss <- query config database $ "select value from " <> name- case ss of- [series] -> fromSeriesData series @?= Right [Val 42, Val 42, Val 42]- _ -> assertFailure $ "Expect one series, but got: " ++ show ss--case_post_multi_points :: Assertion-case_post_multi_points = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- post config database $ withSeries name $ do- writePoints $ Val 42- writePoints $ Val 42- writePoints $ Val 42- ss <- query config database $ "select value from " <> name- case ss of- [series] -> fromSeriesData series @?= Right [Val 42, Val 42, Val 42]- _ -> assertFailure $ "Expect one series, but got: " ++ show ss--case_query_nonexistent_series :: Assertion-case_query_nonexistent_series = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- assertStatusCodeException- (query config database $ "select * from " <> name :: IO [SeriesData])--case_query_empty_series :: Assertion-case_query_empty_series = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- post config database $- writeSeries name $ Val 42- ss1 <- query config database $ "delete from " <> name- ss1 @?= ([] :: [SeriesData])- ss2 <- query config database $ "select * from " <> name- ss2 @?= ([] :: [SeriesData])--case_queryChunked :: Assertion-case_queryChunked = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- post config database $ withSeries name $ do- writePoints $ Val 42- writePoints $ Val 42- writePoints $ Val 42- ss <- queryChunked config database ("select value from " <> name) $- S.fold step []- mapM fromSeriesData ss @?= Right [[Val 42], [Val 42], [Val 42]]- where- step xs series = case fromSeriesData series of- Left reason -> throwIO $ HUnitFailure reason- Right values -> return $ xs ++ values--case_post_with_precision :: Assertion-case_post_with_precision = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- postWithPrecision config database SecondsPrecision $- writeSeries name $ Val 42- ss <- query config database $ "select value from " <> name- case ss of- [series] -> fromSeriesData series @?= Right [Val 42]- _ -> assertFailure $ "Expect one series, but got: " ++ show ss--case_delete_series :: Assertion-case_delete_series = runTest $ \config ->- withTestDatabase config $ \database -> do- name <- liftIO newName- post config database $- writeSeries name $ Val 42- ss <- query config database $ "select value from " <> name- case ss of- [series] -> fromSeriesData series @?= Right [Val 42]- _ -> assertFailure $ "Expect one series, but got: " ++ show ss- deleteSeries config database name- assertStatusCodeException- (query config database $ "select value from " <> name :: IO [SeriesData])--case_listDatabases :: Assertion-case_listDatabases = runTest $ \config ->- withTestDatabase config $ \name -> do- databases <- listDatabases config- assertBool ("No such database: " ++ T.unpack name) $- any ((name ==) . databaseName) databases--case_configureDatabase :: Assertion-case_configureDatabase = runTest $ \config -> do- dbName <- newName- do- configureDatabase config dbName $ DatabaseRequest shardSpaces contQueries- listDatabases config >>= \databases ->- assertBool ("No such database: " ++ T.unpack dbName) $- any ((dbName ==) . databaseName) databases- listShardSpaces config >>= \spaces ->- assertBool "Missing shard space(s)" $- any ((`elem` spaceNames) . shardSpaceName) spaces- `finally`- dropDatabase config dbName- where- spaceNames = map shardSpaceRequestName shardSpaces- shardSpaces =- [ ShardSpaceRequest- { shardSpaceRequestName = "everything_30d"- , shardSpaceRequestRetentionPolicy = "30d"- , shardSpaceRequestShardDuration = "7d"- , shardSpaceRequestRegex = "/.*/"- , shardSpaceRequestReplicationFactor = 1- , shardSpaceRequestSplit = 1- }- , ShardSpaceRequest- { shardSpaceRequestName = "forever"- , shardSpaceRequestRetentionPolicy = "inf"- , shardSpaceRequestShardDuration = "7d"- , shardSpaceRequestRegex = "/^_.*/"- , shardSpaceRequestReplicationFactor = 1- , shardSpaceRequestSplit = 1- }- , ShardSpaceRequest- { shardSpaceRequestName = "rollups"- , shardSpaceRequestRetentionPolicy = "365d"- , shardSpaceRequestShardDuration = "30d"- , shardSpaceRequestRegex = "/^\\d+.*/"- , shardSpaceRequestReplicationFactor = 1- , shardSpaceRequestSplit = 1- }- ]- contQueries =- [ "select * from events into events.[id]"- , "select count(value) from events group by time(5m) into 5m.count.events"- ]--case_shardSpaces :: Assertion-case_shardSpaces = runTest $ \config ->- withTestDatabase config $ \name -> do- spaceName <- newName- createShardSpace config name $ ShardSpaceRequest- { shardSpaceRequestName = spaceName- , shardSpaceRequestRegex = "^[a-z].*"- , shardSpaceRequestRetentionPolicy = "7d"- , shardSpaceRequestShardDuration = "1d"- , shardSpaceRequestReplicationFactor = 1- , shardSpaceRequestSplit = 1- }- listShardSpaces config >>= \spaces ->- assertBool ("No such shard space: " ++ T.unpack spaceName) $- any ((spaceName ==) . shardSpaceName) spaces- dropShardSpace config name spaceName- listShardSpaces config >>= \spaces ->- assertBool ("Found a dropped shard space: " ++ T.unpack spaceName) $- all ((spaceName /=) . shardSpaceName) spaces--case_create_then_drop_database :: Assertion-case_create_then_drop_database = runTest $ \config -> do- name <- newName- dropDatabaseIfExists config name- createDatabase config name- listDatabases config >>= \databases ->- assertBool ("No such database: " ++ T.unpack name) $- any ((name ==) . databaseName) databases- dropDatabase config name- listDatabases config >>= \databases ->- assertBool ("Found a dropped database: " ++ T.unpack name) $- all ((name /=) . databaseName) databases--case_list_cluster_admins :: Assertion-case_list_cluster_admins = runTest $ \config -> do- admins <- listClusterAdmins config- assertBool "No root admin" $- any (("root" ==) . adminName) admins--case_authenticate_cluster_admin :: Assertion-case_authenticate_cluster_admin = runTest authenticateClusterAdmin--case_add_then_delete_cluster_admin :: Assertion-case_add_then_delete_cluster_admin = runTest $ \config -> do- name <- newName- admin <- addClusterAdmin config name "somePassword"- listClusterAdmins config >>= \admins ->- assertBool ("No such admin: " ++ T.unpack name) $- any ((name ==) . adminName) admins- deleteClusterAdmin config admin- listClusterAdmins config >>= \admins ->- assertBool ("Found a deleted admin: " ++ T.unpack name) $- all ((name /=) . adminName) admins--case_update_cluster_admin_password :: Assertion-case_update_cluster_admin_password = runTest $ \config -> do- let curPassword = "somePassword"- newPassword = "otherPassword"- name <- newName- deleteClusterAdminIfExists config name- admin <- addClusterAdmin config name curPassword- updateClusterAdminPassword config admin newPassword- let newCreds = Credentials name newPassword- newConfig = config { configCreds = newCreds }- name' <- newName- dropDatabaseIfExists config name'- createDatabase newConfig name'- listDatabases newConfig >>= \databases ->- assertBool ("No such database: " ++ T.unpack name') $- any ((name' ==) . databaseName) databases- dropDatabase newConfig name'- listDatabases newConfig >>= \databases ->- assertBool ("Found a dropped database: " ++ T.unpack name') $- all ((name' /=) . databaseName) databases- deleteClusterAdmin config admin--case_add_then_delete_database_users :: Assertion-case_add_then_delete_database_users = runTest $ \config ->- withTestDatabase config $ \name -> do- listDatabaseUsers config name >>= \users ->- assertBool "There shouldn't be any users" $ null users- newUserName <- newName- addDatabaseUser config name newUserName "somePassword"- let newCreds = rootCreds- { credsUser = newUserName- , credsPassword = "somePassword" }- newConfig = config { configCreds = newCreds }- authenticateDatabaseUser newConfig name- listDatabaseUsers config name >>= \users ->- assertBool ("No such user: " <> T.unpack newUserName) $- any ((newUserName ==) . userName) users- deleteDatabaseUser config name newUserName- listDatabaseUsers config name >>= \users ->- assertBool ("Found a deleted user: " <> T.unpack newUserName) $- all ((newUserName /=) . userName) users--case_update_database_user_password :: Assertion-case_update_database_user_password = runTest $ \config ->- withTestDatabase config $ \name -> do- newUserName <- newName- addDatabaseUser config name newUserName "somePassword"- listDatabaseUsers config name >>= \users ->- assertBool ("No such user: " <> T.unpack newUserName) $- any ((newUserName ==) . userName) users- updateDatabaseUserPassword config name newUserName "otherPassword"- deleteDatabaseUser config name newUserName--case_grant_revoke_database_user :: Assertion-case_grant_revoke_database_user = runTest $ \config ->- withTestDatabase config $ \name -> do- newUserName <- newName- addDatabaseUser config name newUserName "somePassword"- listDatabaseUsers config name >>= \users ->- assertBool ("No such user: " <> T.unpack newUserName) $- any ((newUserName ==) . userName) users- grantAdminPrivilegeTo config name newUserName- listDatabaseUsers config name >>= \users ->- case find ((newUserName ==) . userName) users of- Nothing -> assertFailure $ "No such user: " <> T.unpack newUserName- Just user -> assertBool- ("User is not privileged: " <> T.unpack newUserName)- (userIsAdmin user)- revokeAdminPrivilegeFrom config name newUserName- listDatabaseUsers config name >>= \users ->- case find ((newUserName ==) . userName) users of- Nothing -> assertFailure $ "No such user: " <> T.unpack newUserName- Just user -> assertBool- ("User is still privileged: " <> T.unpack newUserName)- (not $ userIsAdmin user)- deleteDatabaseUser config name newUserName------------------------------------------------------ Regressions--newtype WholeFloat = WholeFloat- { wholeFloatValue :: Double- } deriving (Eq, Show)---- #14: InfluxDB may return Int instead of Float when--- the WholeFloat value happens to be a whole number.-case_regression_whole_Float_number :: Assertion-case_regression_whole_Float_number = runTest $ \config ->- withTestDatabase config $ \database -> do- series <- newName- post config database $- writeSeries series $ WholeFloat 42.0- ss <- query config database $ "select value from " <> series- case ss of- [sd] -> fromSeriesData sd @?= Right [WholeFloat 42]- _ -> assertFailure $ "Expect one series, but got: " ++ show ss--case_regression_really_big_Float_number :: Assertion-case_regression_really_big_Float_number = runTest $ \config ->- withTestDatabase config $ \database -> do- series <- newName- post config database $- writeSeries series $ WholeFloat 42e100- ss <- query config database $ "select value from " <> series- case ss of- [sd] -> fromSeriesData sd @?= Right [WholeFloat 42e100]- _ -> assertFailure $ "Expect one series, but got: " ++ show ss-----------------------------------------------------data Val = Val Int deriving (Eq, Show)--instance ToSeriesData Val where- toSeriesColumns _ = V.fromList ["value"]- toSeriesPoints (Val n) = V.fromList [toValue n]--instance FromSeriesData Val where- parseSeriesData = withValues $ \values -> Val <$> values .: "value"-----------------------------------------------------dropDatabaseIfExists :: Config -> Text -> IO ()-dropDatabaseIfExists config name =- dropDatabase config name- `catchAll` \_ -> return ()--deleteClusterAdminIfExists :: Config -> Text -> IO ()-deleteClusterAdminIfExists config name =- deleteClusterAdmin config (Admin name)- `catchAll` \_ -> return ()-----------------------------------------------------runTest :: (Config -> IO a) -> IO a-runTest f = do- pool <- newServerPool localServer []- HC.withManager settings (f . Config rootCreds pool)- where- settings = HC.defaultManagerSettings--newName :: IO Text-newName = do- uniq <- newUnique- return $ T.pack $ "test_" ++ show (hashUnique uniq)--withTestDatabase :: Config -> (Text -> IO a) -> IO a-withTestDatabase config = bracket acquire release- where- acquire = do- name <- newName- dropDatabaseIfExists config name- createDatabase config name- return name- release = dropDatabase config--catchAll :: IO a -> (SomeException -> IO a) -> IO a-catchAll = E.catch--assertStatusCodeException :: Show a => IO a -> IO ()-assertStatusCodeException io = do- r <- try io- case r of- Left e -> case fromException e of- Just HC.StatusCodeException {} -> return ()- _ ->- assertFailure $ "Expect a StatusCodeException, but got " ++ show e- Right ss -> assertFailure $ "Expect an exception, but got " ++ show ss---------------------------------------------------- main :: IO () main = $defaultMainGenerator------------------------------------------------------ Instance deriving--deriveSeriesData defaultOptions- { fieldLabelModifier = stripPrefixLower "wholeFloat" }- ''WholeFloat