groundhog-mysql 0.3.0.1 → 0.4.0
raw patch · 2 files changed
+79/−68 lines, 2 filesdep +monad-loggerdep ~groundhogdep ~monad-controldep ~resource-poolPVP ok
version bump matches the API change (PVP)
Dependencies added: monad-logger
Dependency ranges changed: groundhog, monad-control, resource-pool, text, transformers
API changes (from Hackage documentation)
- Database.Groundhog.MySQL: instance (MonadBaseControl IO m, MonadIO m) => PersistBackend (DbPersist MySQL m)
- Database.Groundhog.MySQL: instance (MonadBaseControl IO m, MonadIO m) => SchemaAnalyzer (DbPersist MySQL m)
+ Database.Groundhog.MySQL: instance (MonadBaseControl IO m, MonadIO m, MonadLogger m) => PersistBackend (DbPersist MySQL m)
+ Database.Groundhog.MySQL: instance (MonadBaseControl IO m, MonadIO m, MonadLogger m) => SchemaAnalyzer (DbPersist MySQL m)
- Database.Groundhog.MySQL: runDbConn :: (MonadBaseControl IO m, MonadIO m, ConnectionManager cm conn) => DbPersist conn m a -> cm -> m a
+ Database.Groundhog.MySQL: runDbConn :: (MonadBaseControl IO m, MonadIO m, ConnectionManager cm conn) => DbPersist conn (NoLoggingT m) a -> cm -> m a
Files
- Database/Groundhog/MySQL.hs +72/−62
- groundhog-mysql.cabal +7/−6
Database/Groundhog/MySQL.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, FlexibleContexts, OverloadedStrings, TypeFamilies, MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, FlexibleContexts, OverloadedStrings, TypeFamilies, MultiParamTypeClasses, TemplateHaskell #-} module Database.Groundhog.MySQL ( withMySQLPool , withMySQLConn@@ -31,17 +31,18 @@ import qualified Database.MySQL.Base.Types as MySQLBase import Control.Arrow ((***))-import Control.Monad (liftM, liftM2)+import Control.Monad (liftM, liftM2, (>=>)) import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Logger (MonadLogger, logDebugS) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Control (MonadBaseControl) import Control.Monad.Trans.Reader (ask) import Data.ByteString.Char8 (ByteString) import Data.Char (toUpper)-import Data.Either (partitionEithers) import Data.Function (on) import Data.Int (Int64)-import Data.List (groupBy, intercalate, partition)+import Data.IORef (newIORef, readIORef, writeIORef)+import Data.List (groupBy, intercalate, intersect, partition, stripPrefix) import Data.Maybe (fromJust, fromMaybe) import Data.Pool @@ -55,8 +56,7 @@ instance SqlDb MySQL where append a b = Expr $ function "concat" [toExpr a, toExpr b] -instance (MonadBaseControl IO m, MonadIO m) => PersistBackend (DbPersist MySQL m) where- {-# SPECIALIZE instance PersistBackend (DbPersist MySQL IO) #-}+instance (MonadBaseControl IO m, MonadIO m, MonadLogger m) => PersistBackend (DbPersist MySQL m) where type PhantomDb (DbPersist MySQL m) = MySQL insert v = insert' v insert_ v = insert_' v@@ -81,7 +81,7 @@ insertList l = insertList' l getList k = getList' k -instance (MonadBaseControl IO m, MonadIO m) => SchemaAnalyzer (DbPersist MySQL m) where+instance (MonadBaseControl IO m, MonadIO m, MonadLogger m) => SchemaAnalyzer (DbPersist MySQL m) where listTables schema = queryRaw' "SELECT table_name FROM information_schema.tables WHERE table_schema=coalesce(?,database())" [toPrimitivePersistValue proxy schema] (mapAllRows $ return . fst . fromPurePersistValues proxy) listTableTriggers schema name = queryRaw' "SELECT trigger_name FROM information_schema.triggers WHERE event_object_schema=coalesce(?,database()) AND event_object_table=?" [toPrimitivePersistValue proxy schema, toPrimitivePersistValue proxy name] (mapAllRows $ return . fst . fromPurePersistValues proxy) analyzeTable = analyzeTable'@@ -140,8 +140,7 @@ close' :: MySQL -> IO () close' (MySQL conn) = MySQL.close conn -{-# SPECIALIZE insert' :: PersistEntity v => v -> DbPersist MySQL IO (AutoKey v) #-}-insert' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m) => v -> DbPersist MySQL m (AutoKey v)+insert' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m, MonadLogger m) => v -> DbPersist MySQL m (AutoKey v) insert' v = do -- constructor number and the rest of the field values vals <- toEntityPersistValues' v@@ -165,8 +164,7 @@ executeRaw' cQuery (vals' []) pureFromPersistValue [rowid] -{-# SPECIALIZE insert_' :: PersistEntity v => v -> DbPersist MySQL IO () #-}-insert_' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m) => v -> DbPersist MySQL m ()+insert_' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m, MonadLogger m) => v -> DbPersist MySQL m () insert_' v = do -- constructor number and the rest of the field values vals <- toEntityPersistValues' v@@ -186,7 +184,6 @@ let RenderS cQuery vals' = insertIntoConstructorTable True (tableName escapeS e constr) constr (rowid:tail vals) executeRaw' cQuery (vals' []) --- TODO: In Sqlite we can insert null to the id column. If so, id will be generated automatically. Check performance change from this. insertIntoConstructorTable :: Bool -> Utf8 -> ConstructorDef -> [PersistValue] -> RenderS db r insertIntoConstructorTable withId tName c vals = RenderS query vals' where query = "INSERT INTO " <> tName <> "(" <> fieldNames <> ")VALUES(" <> placeholders <> ")"@@ -196,7 +193,7 @@ fieldNames = renderFields escapeS fields RenderS placeholders vals' = commasJoin $ map renderPersistValue vals -insertList' :: forall m a.(MonadBaseControl IO m, MonadIO m, PersistField a) => [a] -> DbPersist MySQL m Int64+insertList' :: forall m a.(MonadBaseControl IO m, MonadIO m, MonadLogger m, PersistField a) => [a] -> DbPersist MySQL m Int64 insertList' (l :: [a]) = do let mainName = "List" <> delim' <> delim' <> fromString (persistName (undefined :: a)) executeRaw' ("INSERT INTO " <> escapeS mainName <> "()VALUES()") []@@ -213,7 +210,7 @@ go 0 l return $ fromPrimitivePersistValue proxy k -getList' :: forall m a.(MonadBaseControl IO m, MonadIO m, PersistField a) => Int64 -> DbPersist MySQL m [a]+getList' :: forall m a.(MonadBaseControl IO m, MonadIO m, MonadLogger m, PersistField a) => Int64 -> DbPersist MySQL m [a] getList' k = do let mainName = "List" <> delim' <> delim' <> fromString (persistName (undefined :: a)) let valuesName = mainName <> delim' <> "values"@@ -221,17 +218,16 @@ let query = "SELECT " <> renderFields escapeS [value] <> " FROM " <> escapeS valuesName <> " WHERE id=? ORDER BY ord" queryRaw' query [toPrimitivePersistValue proxy k] $ mapAllRows (liftM fst . fromPersistValues) -{-# SPECIALIZE getLastInsertId :: DbPersist MySQL IO PersistValue #-}-getLastInsertId :: (MonadBaseControl IO m, MonadIO m) => DbPersist MySQL m PersistValue+getLastInsertId :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => DbPersist MySQL m PersistValue getLastInsertId = do x <- queryRaw' "SELECT last_insert_id()" [] id return $ maybe (error "getLastInsertId: Nothing") head x ---------- -executeRaw' :: MonadIO m => Utf8 -> [PersistValue] -> DbPersist MySQL m ()+executeRaw' :: (MonadIO m, MonadLogger m) => Utf8 -> [PersistValue] -> DbPersist MySQL m () executeRaw' query vals = do- --liftIO $ print $ fromUtf8 query ""+ $logDebugS "SQL" $ fromString $ show (fromUtf8 query) ++ " " ++ show vals MySQL conn <- DbPersist ask let stmt = getStatement query liftIO $ do@@ -249,34 +245,34 @@ delim' :: Utf8 delim' = fromChar delim -toEntityPersistValues' :: (MonadBaseControl IO m, MonadIO m, PersistEntity v) => v -> DbPersist MySQL m [PersistValue]+toEntityPersistValues' :: (MonadBaseControl IO m, MonadIO m, MonadLogger m, PersistEntity v) => v -> DbPersist MySQL m [PersistValue] toEntityPersistValues' = liftM ($ []) . toEntityPersistValues --- MIGRATION -migrate' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m) => v -> Migration (DbPersist MySQL m)+migrate' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m, MonadLogger m) => v -> Migration (DbPersist MySQL m) migrate' v = do x <- lift $ queryRaw' "SELECT database()" [] id let schema = fst $ fromPurePersistValues proxy $ fromJust x migrateRecursively (migrateEntity $ migrationPack schema) (migrateList $ migrationPack schema) v -migrationPack :: (MonadBaseControl IO m, MonadIO m) => String -> GM.MigrationPack (DbPersist MySQL m)+migrationPack :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => String -> GM.MigrationPack (DbPersist MySQL m) migrationPack currentSchema = GM.MigrationPack compareTypes (compareRefs currentSchema) compareUniqs+ compareDefaults migTriggerOnDelete migTriggerOnUpdate GM.defaultMigConstr escape- DbInt64 "BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY"- "BIGINT" mainTableId defaultPriority (\uniques refs -> ([], map AddUnique uniques ++ map AddReference refs)) showColumn (showAlterDb currentSchema)+ Restrict showColumn :: Column -> String showColumn (Column n nu t def) = concat@@ -290,7 +286,7 @@ Just s -> " DEFAULT " ++ s ] -migTriggerOnDelete :: (MonadBaseControl IO m, MonadIO m) => Maybe String -> String -> [(String, String)] -> DbPersist MySQL m (Bool, [AlterDB])+migTriggerOnDelete :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Maybe String -> String -> [(String, String)] -> DbPersist MySQL m (Bool, [AlterDB]) migTriggerOnDelete schema name deletes = do let addTrigger = AddTriggerOnDelete schema name schema name (concatMap snd deletes) x <- analyzeTrigger schema name@@ -306,7 +302,7 @@ -- | Schema name, table name and a list of field names and according delete statements -- assume that this function is called only for ephemeral fields-migTriggerOnUpdate :: (MonadBaseControl IO m, MonadIO m) => Maybe String -> String -> [(String, String)] -> DbPersist MySQL m [(Bool, [AlterDB])]+migTriggerOnUpdate :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Maybe String -> String -> [(String, String)] -> DbPersist MySQL m [(Bool, [AlterDB])] migTriggerOnUpdate schema name deletes = do let trigName = name ++ "_ON_UPDATE" f (fieldName, del) = "IF NOT (NEW." ++ escape fieldName ++ " <=> OLD." ++ escape fieldName ++ ") THEN " ++ del ++ " END IF;"@@ -323,7 +319,7 @@ -- this can happen when an ephemeral field was added or removed. else [DropTrigger schema trigName schema name, addTrigger]) -analyzeTable' :: (MonadBaseControl IO m, MonadIO m) => Maybe String -> String -> DbPersist MySQL m (Either [String] (Maybe TableInfo))+analyzeTable' :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Maybe String -> String -> DbPersist MySQL m (Maybe TableInfo) analyzeTable' schema name = do table <- queryRaw' "SELECT * FROM information_schema.tables WHERE table_schema = coalesce(?, database()) AND table_name = ?" [toPrimitivePersistValue proxy schema, toPrimitivePersistValue proxy name] id case table of@@ -335,7 +331,6 @@ \ ORDER BY c.ordinal_position" cols <- queryRaw' colQuery [toPrimitivePersistValue proxy schema, toPrimitivePersistValue proxy name] (mapAllRows $ return . getColumn name . fst . fromPurePersistValues proxy)- let (col_errs, cols') = partitionEithers cols -- MySQL has no difference between unique keys and indexes let constraintQuery = "SELECT u.constraint_name, u.column_name FROM information_schema.table_constraints tc INNER JOIN information_schema.key_column_usage u USING (constraint_catalog, constraint_schema, constraint_name, table_schema, table_name) WHERE tc.constraint_type=? AND tc.table_schema=coalesce(?,database()) AND u.table_name=? ORDER BY u.constraint_name, u.column_name" uniqConstraints <- queryRaw' constraintQuery [toPrimitivePersistValue proxy ("UNIQUE" :: String), toPrimitivePersistValue proxy schema, toPrimitivePersistValue proxy name] (mapAllRows $ return . fst . fromPurePersistValues proxy)@@ -343,16 +338,14 @@ let mkUniqs typ = map (\us -> UniqueDef' (fst $ head us) typ (map snd us)) . groupBy ((==) `on` fst) let uniqs = mkUniqs UniqueConstraint uniqConstraints ++ mkUniqs UniquePrimary uniqPrimary references <- analyzeTableReferences schema name- return $ case col_errs of- [] -> Right $ Just $ TableInfo cols' uniqs references- errs -> Left errs- Nothing -> return $ Right Nothing+ return $ Just $ TableInfo cols uniqs references+ Nothing -> return Nothing -getColumn :: String -> ((String, String, String, String, Maybe String), (Maybe Int, Maybe Int, Maybe Int)) -> Either String Column-getColumn _ ((column_name, is_nullable, data_type, column_type, d), modifiers) = Right $ Column column_name (is_nullable == "YES") t d where+getColumn :: String -> ((String, String, String, String, Maybe String), (Maybe Int, Maybe Int, Maybe Int)) -> Column+getColumn _ ((column_name, is_nullable, data_type, column_type, d), modifiers) = Column column_name (is_nullable == "YES") t d where t = readSqlType data_type column_type modifiers -analyzeTableReferences :: (MonadBaseControl IO m, MonadIO m) => Maybe String -> String -> DbPersist MySQL m [(Maybe String, Reference)]+analyzeTableReferences :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Maybe String -> String -> DbPersist MySQL m [(Maybe String, Reference)] analyzeTableReferences schema tName = do let sql = "SELECT tc.constraint_name, u.referenced_table_schema, u.referenced_table_name, rc.delete_rule, rc.update_rule, u.column_name, u.referenced_column_name FROM information_schema.table_constraints tc\ \ INNER JOIN information_schema.key_column_usage u USING (constraint_catalog, constraint_schema, constraint_name, table_schema, table_name)\@@ -468,7 +461,7 @@ showAlterTable _ table (DropReference name) = [(False, defaultPriority, "ALTER TABLE " ++ table ++ " DROP CONSTRAINT " ++ name)] -readSqlType :: String -> String -> (Maybe Int, Maybe Int, Maybe Int) -> DbType+readSqlType :: String -> String -> (Maybe Int, Maybe Int, Maybe Int) -> DbTypePrimitive readSqlType typ colTyp (_, numeric_precision, numeric_scale) = (case typ of _ | typ `elem` ["int", "short", "mediumint"] -> DbInt32 _ | typ `elem` ["long", "longlong", "bigint"] -> DbInt64@@ -487,22 +480,20 @@ ) where numAttrs = (numeric_precision, numeric_scale) -showSqlType :: DbType -> String-showSqlType DbString = "TEXT CHARACTER SET utf8"-showSqlType DbInt32 = "INT"-showSqlType DbInt64 = "BIGINT"-showSqlType DbReal = "DOUBLE PRECISION"-showSqlType DbBool = "TINYINT(1)"-showSqlType DbDay = "DATE"-showSqlType DbTime = "TIME"-showSqlType DbDayTime = "DATETIME"-showSqlType DbDayTimeZoned = "VARCHAR(50) CHARACTER SET utf8"-showSqlType DbBlob = "BLOB"-showSqlType (DbOther (OtherTypeDef f)) = f showSqlType-showSqlType (DbMaybe t) = showSqlType t-showSqlType (DbList _ _) = showSqlType DbInt64-showSqlType (DbEntity Nothing _ _ _) = showSqlType DbInt64-showSqlType t = error $ "showSqlType: DbType does not have corresponding database type: " ++ show t+showSqlType :: DbTypePrimitive -> String+showSqlType t = case t of+ DbString -> "TEXT CHARACTER SET utf8"+ DbInt32 -> "INT"+ DbInt64 -> "BIGINT"+ DbReal -> "DOUBLE PRECISION"+ DbBool -> "TINYINT(1)"+ DbDay -> "DATE"+ DbTime -> "TIME"+ DbDayTime -> "DATETIME"+ DbDayTimeZoned -> "VARCHAR(50) CHARACTER SET utf8"+ DbBlob -> "BLOB"+ DbOther (OtherTypeDef f) -> f showSqlType+ DbAutoKey -> showSqlType DbInt64 compareUniqs :: UniqueDef' -> UniqueDef' -> Bool compareUniqs (UniqueDef' _ UniquePrimary cols1) (UniqueDef' _ UniquePrimary cols2) = haveSameElems (==) cols1 cols2@@ -519,12 +510,17 @@ && fromMaybe Restrict onUpd1 == fromMaybe Restrict onUpd2 where unescape name = if head name == '"' && last name == '"' then tail $ init name else name -compareTypes :: DbType -> DbType -> Bool+compareTypes :: DbTypePrimitive -> DbTypePrimitive -> Bool compareTypes type1 type2 = f type1 == f type2 where f = map toUpper . showSqlType . hack hack DbDayTimeZoned = DbOther $ OtherTypeDef $ const "VARCHAR(50)" hack t = t +compareDefaults :: String -> String -> Bool+compareDefaults def1 def2 = not . null $ f def1 `intersect` f def2 where+ f def = [Just def, stripQuotes def]+ stripQuotes = stripPrefix "'" >=> fmap reverse . stripPrefix "'" . reverse+ defaultPriority, referencePriority, functionPriority, triggerPriority :: Int defaultPriority = 0 referencePriority = 1@@ -543,24 +539,38 @@ getStatement :: Utf8 -> MySQL.Query getStatement sql = MySQL.Query $ fromUtf8 sql -queryRawTyped' :: (MonadBaseControl IO m, MonadIO m) => Utf8 -> [DbType] -> [PersistValue] -> (RowPopper (DbPersist MySQL m) -> DbPersist MySQL m a) -> DbPersist MySQL m a+queryRawTyped' :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Utf8 -> [DbType] -> [PersistValue] -> (RowPopper (DbPersist MySQL m) -> DbPersist MySQL m a) -> DbPersist MySQL m a queryRawTyped' query _ vals f = queryRaw' query vals f -queryRaw' :: (MonadBaseControl IO m, MonadIO m) => Utf8 -> [PersistValue] -> (RowPopper (DbPersist MySQL m) -> DbPersist MySQL m a) -> DbPersist MySQL m a+queryRaw' :: (MonadBaseControl IO m, MonadIO m, MonadLogger m) => Utf8 -> [PersistValue] -> (RowPopper (DbPersist MySQL m) -> DbPersist MySQL m a) -> DbPersist MySQL m a queryRaw' query vals func = do+ $logDebugS "SQL" $ fromString $ show (fromUtf8 query) ++ " " ++ show vals MySQL conn <- DbPersist ask liftIO $ MySQL.formatQuery conn (getStatement query) (map P vals) >>= MySQLBase.query conn result <- liftIO $ MySQLBase.storeResult conn -- Find out the type of the columns fields <- liftIO $ MySQLBase.fetchFields result- let getters = [ maybe PersistNull (getGetter (MySQLBase.fieldType f) f . Just) | f <- fields]- x <- func $ do- row <- liftIO $ MySQLBase.fetchRow result- case row of- [] -> return Nothing -- Do not free the result per sourceIO's docs.- _ -> return $ Just $ zipWith ($) getters row- liftIO $ MySQLBase.freeResult result- return x+ let getters = [maybe PersistNull (getGetter (MySQLBase.fieldType f) f . Just) | f <- fields]+ convert = use getters where+ use (g:gs) (col:cols) = v `seq` vs `seq` (v:vs) where+ v = g col+ vs = use gs cols+ use _ _ = []+ let go acc = do+ row <- MySQLBase.fetchRow result+ case row of+ [] -> return (acc [])+ _ -> let converted = convert row+ in converted `seq` go (acc . (converted:))+ -- TODO: this variable is ugly. Switching to pipes or conduit might help+ rowsVar <- liftIO $ flip finally (MySQLBase.freeResult result) (go id) >>= newIORef+ func $ do+ rows <- liftIO $ readIORef rowsVar+ case rows of+ [] -> return Nothing+ (x:xs) -> do+ liftIO $ writeIORef rowsVar xs+ return $ Just x -- | Avoid orphan instances. newtype P = P PersistValue
groundhog-mysql.cabal view
@@ -1,5 +1,5 @@ name: groundhog-mysql-version: 0.3.0.1+version: 0.4.0 license: BSD3 license-file: LICENSE author: Boris Lykah <lykahb@gmail.com>@@ -16,12 +16,13 @@ , mysql-simple >= 0.2.2.3 && < 0.3 , mysql >= 0.1.1.3 && < 0.2 , bytestring >= 0.9- , transformers >= 0.2.1 && < 0.4- , groundhog >= 0.3.0 && < 0.4.0- , monad-control >= 0.3 && < 0.4+ , transformers >= 0.2.1+ , groundhog >= 0.4.0 && < 0.5.0+ , monad-control >= 0.3+ , monad-logger >= 0.3 , containers >= 0.2- , text >= 0.8 && < 0.12- , resource-pool >= 0.2.1 && < 0.3+ , text >= 0.8+ , resource-pool >= 0.2.1 , time >= 1.1 exposed-modules: Database.Groundhog.MySQL ghc-options: -Wall -fno-warn-unused-do-bind