-- | The calls the integration tests read rows with. Every suite that reads
-- rows takes a 'QueryApi' and runs once for each of 'queryApis', so the old
-- 'MySQLValue' functions and each path of "Database.MySQL.Decoder" go through
-- the very same cases, and a test differs between them only in the API.
module QueryApi
( QueryApi (..)
, ColumnCount (..)
, queryApis
, mysqlValueApis
) where
import Control.Exception (throwIO)
import Control.Monad (replicateM)
import qualified Data.Vector as V
import Database.MySQL.Base
import qualified Database.MySQL.Decoder as Decode
import System.IO.Streams (InputStream)
import TypedValue (typedDecoder)
import qualified System.IO.Streams as Stream
-- | How many columns a query returns. Only the 'Decode.RowDecoder' path uses
-- it: a row decoder states its columns before the query runs, where the other
-- paths learn them from the result set.
newtype ColumnCount = ColumnCount Int
data QueryApi = QueryApi
{ apiName :: String
, apiQuery_ :: ColumnCount -> MySQLConn -> Query
-> IO ([ColumnDef], InputStream [MySQLValue])
, apiQuery :: ColumnCount -> MySQLConn -> Query -> [Param]
-> IO ([ColumnDef], InputStream [MySQLValue])
, apiQueryVector_ :: ColumnCount -> MySQLConn -> Query
-> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
, apiQueryStmt :: ColumnCount -> MySQLConn -> StmtID -> [MySQLValue]
-> IO ([ColumnDef], InputStream [MySQLValue])
, apiQueryStmtVector :: ColumnCount -> MySQLConn -> StmtID -> [MySQLValue]
-> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
}
queryApis :: [QueryApi]
queryApis = mysqlValueApis ++ [typedRawRowApi]
-- | The APIs that give every field as the 'MySQLValue' the old API does, also
-- the dates a 'Data.Time.Calendar.Day' cannot hold.
mysqlValueApis :: [QueryApi]
mysqlValueApis = [valueApi, rowDecoderApi, rawRowApi]
-- | The API the tests were written for.
valueApi :: QueryApi
valueApi = QueryApi
{ apiName = "MySQLValue API"
, apiQuery_ = const query_
, apiQuery = const query
, apiQueryVector_ = const queryVector_
, apiQueryStmt = const queryStmt
, apiQueryStmtVector = const queryStmtVector
}
-- | 'queryRows_', 'queryRows' and 'queryStmtRows' with 'Decode.mysqlValue' for
-- every column.
rowDecoderApi :: QueryApi
rowDecoderApi = QueryApi
{ apiName = "RowDecoder API"
, apiQuery_ = queryRows_ . everyColumn
, apiQuery = queryRows . everyColumn
, apiQueryVector_ = rowDecoderQueryVector_
, apiQueryStmt = queryStmtRows . everyColumn
, apiQueryStmtVector = rowDecoderQueryStmtVector
}
everyColumn :: ColumnCount -> Decode.RowDecoder [MySQLValue]
everyColumn (ColumnCount count) = replicateM count (Decode.field Decode.mysqlValue)
rowDecoderQueryVector_ :: ColumnCount -> MySQLConn -> Query
-> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
rowDecoderQueryVector_ count conn qry = toVectors =<< queryRows_ (everyColumn count) conn qry
rowDecoderQueryStmtVector :: ColumnCount -> MySQLConn -> StmtID -> [MySQLValue]
-> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
rowDecoderQueryStmtVector count conn stmt params =
toVectors =<< queryStmtRows (everyColumn count) conn stmt params
-- | 'queryRawRows_' and 'queryStmtRawRows', each column read by its number with
-- 'Decode.mysqlValue', as a library decoding by column number would.
rawRowApi :: QueryApi
rawRowApi = rawRowApiWith "RawRow API" valueDecoder
-- | 'queryRawRows_' and 'queryStmtRawRows', each column read with the typed
-- decoder for its type, the value put back into the 'MySQLValue' constructor
-- the old API returns, so the same expectations hold.
typedRawRowApi :: QueryApi
typedRawRowApi = rawRowApiWith "typed decoders" typedDecoder
rawRowApiWith :: String -> (ColumnKind -> Decode.FieldDecoder MySQLValue) -> QueryApi
rawRowApiWith name decoderFor = QueryApi
{ apiName = name
, apiQuery_ = \_ conn qry -> rawValues decoderFor =<< queryRawRows_ conn qry
, apiQuery = \_ conn qry params ->
rawValues decoderFor =<< queryRawRows_ conn (renderParams qry params)
, apiQueryVector_ = \_ conn qry -> toVectors =<< rawValues decoderFor =<< queryRawRows_ conn qry
, apiQueryStmt = \_ conn stmt params -> rawValues decoderFor =<< queryStmtRawRows conn stmt params
, apiQueryStmtVector = \_ conn stmt params ->
toVectors =<< rawValues decoderFor =<< queryStmtRawRows conn stmt params
}
valueDecoder :: ColumnKind -> Decode.FieldDecoder MySQLValue
valueDecoder _ = Decode.mysqlValue
-- | Each raw row as the list of its columns' values, every column with the
-- parser 'prepareColumn' picks for it.
rawValues :: Decode.RowProtocol protocol
=> (ColumnKind -> Decode.FieldDecoder MySQLValue)
-> ([ColumnDef], InputStream (Decode.RawRow protocol))
-> IO ([ColumnDef], InputStream [MySQLValue])
rawValues decoderFor (columns, rows) = do
parsers <- either throwIO pure
(traverse (prepareColumn decoderFor) (zip (map Decode.ColumnNumber [0 ..]) columns))
(,) columns <$> Stream.mapM (rowValues parsers) rows
prepareColumn :: Decode.RowProtocol protocol
=> (ColumnKind -> Decode.FieldDecoder MySQLValue) -> (Decode.ColumnNumber, ColumnDef)
-> Either Decode.ColumnMismatch (Decode.ColumnNumber, Decode.FieldParser protocol MySQLValue)
prepareColumn decoderFor (column, definition) =
(,) column <$> Decode.prepareFieldParser (decoderFor (columnKind definition)) column definition
rowValues :: [(Decode.ColumnNumber, Decode.FieldParser protocol MySQLValue)] -> Decode.RawRow protocol
-> IO [MySQLValue]
rowValues parsers row = traverse (fieldValue row) parsers
fieldValue :: Decode.RawRow protocol -> (Decode.ColumnNumber, Decode.FieldParser protocol MySQLValue)
-> IO MySQLValue
fieldValue row (column, parser) = Decode.runFieldParser parser row column throwIO pure
toVectors :: ([ColumnDef], InputStream [MySQLValue])
-> IO (V.Vector ColumnDef, InputStream (V.Vector MySQLValue))
toVectors (columns, rows) = (,) (V.fromList columns) <$> Stream.map V.fromList rows