2021-08-04 14:42:24 +03:00
|
|
|
module Hasura.Backends.MySQL.Connection
|
2021-09-24 01:56:37 +03:00
|
|
|
( runJSONPathQuery,
|
|
|
|
resolveSourceConfig,
|
|
|
|
resolveDatabaseMetadata,
|
|
|
|
fetchAllRows,
|
2021-10-12 14:33:33 +03:00
|
|
|
runQueryYieldingRows,
|
2021-10-23 14:42:20 +03:00
|
|
|
withMySQLPool,
|
|
|
|
parseTextRows,
|
2021-08-04 14:42:24 +03:00
|
|
|
)
|
|
|
|
where
|
2021-07-13 16:32:15 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
import Data.Aeson hiding (Result)
|
|
|
|
import Data.Aeson qualified as J
|
|
|
|
import Data.Aeson.Text (encodeToTextBuilder)
|
|
|
|
import Data.ByteString (ByteString)
|
|
|
|
import Data.Environment qualified as Env
|
|
|
|
import Data.HashMap.Strict qualified as HM
|
2021-10-12 14:33:33 +03:00
|
|
|
import Data.HashMap.Strict.InsOrd qualified as OMap
|
2021-09-24 01:56:37 +03:00
|
|
|
import Data.Pool
|
|
|
|
import Data.Scientific (fromFloatDigits)
|
|
|
|
import Data.Text qualified as T
|
|
|
|
import Data.Text.Encoding (decodeUtf8)
|
|
|
|
import Data.Text.Lazy (toStrict)
|
|
|
|
import Data.Text.Lazy.Builder (toLazyText)
|
2021-10-12 14:33:33 +03:00
|
|
|
import Data.Vector (Vector)
|
|
|
|
import Data.Vector qualified as V
|
2021-09-24 01:56:37 +03:00
|
|
|
import Database.MySQL.Base
|
|
|
|
import Database.MySQL.Base.Types (Field (..))
|
|
|
|
import Database.MySQL.Simple.Result qualified as MySQL
|
2021-10-12 14:33:33 +03:00
|
|
|
import Hasura.Backends.MySQL.DataLoader.Plan qualified as DataLoaderPlan
|
2021-09-24 01:56:37 +03:00
|
|
|
import Hasura.Backends.MySQL.Meta (getMetadata)
|
|
|
|
import Hasura.Backends.MySQL.ToQuery (Query (..))
|
|
|
|
import Hasura.Backends.MySQL.Types
|
|
|
|
import Hasura.Base.Error
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.Types.Common
|
|
|
|
import Hasura.RQL.Types.Source
|
2021-10-29 17:42:07 +03:00
|
|
|
import Hasura.RQL.Types.SourceCustomization
|
2021-09-24 01:56:37 +03:00
|
|
|
import Hasura.SQL.Backend
|
2021-07-23 15:25:16 +03:00
|
|
|
|
2021-10-12 14:33:33 +03:00
|
|
|
resolveSourceConfig :: (MonadIO m) => SourceName -> ConnSourceConfig -> Env.Environment -> m (Either QErr SourceConfig)
|
2021-09-24 01:56:37 +03:00
|
|
|
resolveSourceConfig _name csc@ConnSourceConfig {_cscPoolSettings = ConnPoolSettings {..}, ..} _env = do
|
2021-07-13 16:32:15 +03:00
|
|
|
let connectInfo =
|
|
|
|
defaultConnectInfo
|
2021-09-24 01:56:37 +03:00
|
|
|
{ connectHost = T.unpack _cscHost,
|
|
|
|
connectPort = _cscPort,
|
|
|
|
connectUser = T.unpack _cscUser,
|
|
|
|
connectPassword = T.unpack _cscPassword,
|
|
|
|
connectDatabase = T.unpack _cscDatabase
|
2021-07-13 16:32:15 +03:00
|
|
|
}
|
2021-07-23 15:25:16 +03:00
|
|
|
runExceptT $
|
2021-09-24 01:56:37 +03:00
|
|
|
SourceConfig csc
|
|
|
|
<$> liftIO
|
|
|
|
( createPool
|
|
|
|
(connect connectInfo)
|
|
|
|
close
|
|
|
|
1
|
|
|
|
(fromIntegral _cscIdleTimeout)
|
|
|
|
(fromIntegral _cscMaxConnections)
|
|
|
|
)
|
2021-08-04 14:42:24 +03:00
|
|
|
|
2021-10-29 17:42:07 +03:00
|
|
|
resolveDatabaseMetadata :: (MonadIO m) => SourceConfig -> SourceTypeCustomization -> m (Either QErr (ResolvedSource 'MySQL))
|
|
|
|
resolveDatabaseMetadata sc@SourceConfig {..} sourceCustomization =
|
2021-07-15 15:44:26 +03:00
|
|
|
runExceptT $ do
|
|
|
|
metadata <- liftIO $ withResource scConnectionPool (getMetadata scConfig)
|
2021-10-29 17:42:07 +03:00
|
|
|
pure $ ResolvedSource sc sourceCustomization metadata mempty mempty
|
2021-08-04 14:42:24 +03:00
|
|
|
|
|
|
|
parseFieldResult :: Field -> Maybe ByteString -> Value
|
2021-09-24 01:56:37 +03:00
|
|
|
parseFieldResult f@Field {..} mBs =
|
2021-08-04 14:42:24 +03:00
|
|
|
case fieldType of
|
|
|
|
Long ->
|
|
|
|
let fvalue :: Double = MySQL.convert f mBs
|
2021-09-24 01:56:37 +03:00
|
|
|
in Number $ fromFloatDigits fvalue
|
2021-08-04 14:42:24 +03:00
|
|
|
VarString ->
|
|
|
|
let fvalue :: Text = MySQL.convert f mBs
|
2021-09-24 01:56:37 +03:00
|
|
|
in J.String fvalue
|
2021-10-22 02:50:18 +03:00
|
|
|
Blob ->
|
|
|
|
let fvalue :: Text = MySQL.convert f mBs
|
|
|
|
in J.String fvalue
|
2021-08-04 14:42:24 +03:00
|
|
|
DateTime -> maybe J.Null (J.String . decodeUtf8) mBs
|
2021-09-24 01:56:37 +03:00
|
|
|
_ -> error $ "parseResult: not implemented yet " <> show f <> " " <> show mBs
|
2021-08-04 14:42:24 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
-- TODO: handle remaining cases
|
2021-08-04 14:42:24 +03:00
|
|
|
|
|
|
|
fieldsToAeson :: [Field] -> [[Maybe ByteString]] -> [Value]
|
|
|
|
fieldsToAeson column rows =
|
2021-09-24 01:56:37 +03:00
|
|
|
[ Object $
|
|
|
|
HM.fromList $
|
2021-08-04 14:42:24 +03:00
|
|
|
[ (decodeUtf8 (fieldName c)) .= (parseFieldResult c r)
|
2021-09-24 01:56:37 +03:00
|
|
|
| (c, r) <- (zip column row :: [(Field, Maybe ByteString)])
|
|
|
|
]
|
|
|
|
| row <- (rows :: [[Maybe ByteString]])
|
|
|
|
]
|
2021-08-04 14:42:24 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
runJSONPathQuery ::
|
|
|
|
(MonadError QErr m, MonadIO m) =>
|
|
|
|
(Pool Connection) ->
|
|
|
|
Query ->
|
|
|
|
m Text
|
2021-08-04 14:42:24 +03:00
|
|
|
runJSONPathQuery pool (Query querySql) = do
|
|
|
|
result <- liftIO $
|
|
|
|
withResource pool $ \conn -> do
|
|
|
|
query conn querySql
|
|
|
|
result <- storeResult conn
|
|
|
|
fields <- fetchFields result
|
|
|
|
rows <- fetchAllRows result
|
|
|
|
pure $ fieldsToAeson fields rows
|
|
|
|
pure $ toStrict $ toLazyText $ encodeToTextBuilder $ toJSON result
|
|
|
|
|
2021-10-12 14:33:33 +03:00
|
|
|
-- | Used by the dataloader to produce rows of records. Those rows of
|
|
|
|
-- records are then manipulated by the dataloader to do Haskell-side
|
|
|
|
-- joins. Is a Vector of HashMaps the most efficient choice? A
|
|
|
|
-- pandas-style data frame could also be more efficient,
|
|
|
|
-- dependingly. However, this is a legible approach; efficiency
|
|
|
|
-- improvements can be added later.
|
|
|
|
parseAndCollectRows ::
|
|
|
|
[Field] ->
|
|
|
|
[[Maybe ByteString]] ->
|
|
|
|
Vector (InsOrdHashMap DataLoaderPlan.FieldName J.Value)
|
|
|
|
parseAndCollectRows columns rows =
|
|
|
|
V.fromList
|
|
|
|
[ OMap.fromList
|
|
|
|
[ (DataLoaderPlan.FieldName . decodeUtf8 . fieldName $ column, parseFieldResult column value)
|
|
|
|
| (column, value) <- zip columns row :: [(Field, Maybe ByteString)]
|
|
|
|
]
|
|
|
|
| row <- rows :: [[Maybe ByteString]]
|
|
|
|
]
|
|
|
|
|
|
|
|
-- | Run a query immediately and parse up the results into a vector.
|
|
|
|
runQueryYieldingRows ::
|
|
|
|
(MonadIO m) =>
|
|
|
|
Pool Connection ->
|
|
|
|
Query ->
|
|
|
|
m (Vector (InsOrdHashMap DataLoaderPlan.FieldName J.Value))
|
|
|
|
runQueryYieldingRows pool (Query querySql) = do
|
|
|
|
liftIO $
|
|
|
|
withResource pool $ \conn -> do
|
|
|
|
query conn querySql
|
|
|
|
result <- storeResult conn
|
|
|
|
fields <- fetchFields result
|
|
|
|
rows <- fetchAllRows result
|
|
|
|
pure (parseAndCollectRows fields rows)
|
|
|
|
|
2021-08-04 14:42:24 +03:00
|
|
|
fetchAllRows :: Result -> IO [[Maybe ByteString]]
|
|
|
|
fetchAllRows r = reverse <$> go [] r
|
|
|
|
where
|
|
|
|
go acc res =
|
|
|
|
fetchRow res >>= \case
|
|
|
|
[] -> pure acc
|
|
|
|
r' -> go (r' : acc) res
|
2021-10-23 14:42:20 +03:00
|
|
|
|
|
|
|
parseTextRows :: [Field] -> [[Maybe ByteString]] -> [[Text]]
|
|
|
|
parseTextRows columns rows = map (\(column, row) -> map (MySQL.convert column) row) (zip columns rows)
|
|
|
|
|
|
|
|
withMySQLPool :: (MonadIO m) => Pool Connection -> (Connection -> IO a) -> m a
|
|
|
|
withMySQLPool pool = liftIO . withResource pool
|