packages feed

groundhog-sqlite 0.2.0 → 0.3.0

raw patch · 3 files changed

+186/−208 lines, 3 filesdep +resource-pooldep −blaze-builderdep −pool-conduitdep ~groundhogPVP ok

version bump matches the API change (PVP)

Dependencies added: resource-pool

Dependencies removed: blaze-builder, pool-conduit

Dependency ranges changed: groundhog

API changes (from Hackage documentation)

- Database.Groundhog.Sqlite: runSqliteConn :: (MonadBaseControl IO m, MonadIO m) => DbPersist Sqlite m a -> Sqlite -> m a
- Database.Groundhog.Sqlite: runSqlitePool :: (MonadBaseControl IO m, MonadIO m) => DbPersist Sqlite m a -> Pool Sqlite -> m a
+ Database.Groundhog.Sqlite: instance (MonadBaseControl IO m, MonadIO m) => SchemaAnalyzer (DbPersist Sqlite m)
+ Database.Groundhog.Sqlite: instance ConnectionManager (Pool Sqlite) Sqlite
+ Database.Groundhog.Sqlite: instance ConnectionManager Sqlite Sqlite
+ Database.Groundhog.Sqlite: instance Savepoint Sqlite
+ Database.Groundhog.Sqlite: instance SingleConnectionManager Sqlite Sqlite
+ Database.Groundhog.Sqlite: instance SqlDb Sqlite
+ Database.Groundhog.Sqlite: runDbConn :: (MonadBaseControl IO m, MonadIO m, ConnectionManager cm conn) => DbPersist conn m a -> cm -> m a

Files

− Database/Groundhog/Generic/Sql/Utf8.hs
@@ -1,47 +0,0 @@-module Database.Groundhog.Generic.Sql.Utf8-    ( module Database.Groundhog.Generic.Sql-    , Utf8 (..)-    , fromUtf8-    ) where--import Database.Groundhog.Core-import Database.Groundhog.Generic.Sql-import Blaze.ByteString.Builder-import qualified Blaze.ByteString.Builder.Char.Utf8 as B-import Data.ByteString-import Data.Monoid-import Data.String--newtype Utf8 = Utf8 Builder--fromUtf8 :: Utf8 -> ByteString-fromUtf8 (Utf8 a) = toByteString a--instance Monoid Utf8 where-  mempty = Utf8 mempty-  mappend (Utf8 a) (Utf8 b) = Utf8 (mappend a b)--instance IsString Utf8 where-  fromString = Utf8 . B.fromString--instance StringLike Utf8 where-  fromChar = Utf8 . B.fromChar--{-# SPECIALIZE (<>) :: RenderS Utf8 -> RenderS Utf8 -> RenderS Utf8 #-}--{-# SPECIALIZE renderArith :: (PersistEntity v, Constructor c, DbDescriptor db) => Proxy db -> (Utf8 -> Utf8) -> Arith v c a -> RenderS Utf8 #-}--{-# SPECIALIZE renderCond :: (PersistEntity v, Constructor c, DbDescriptor db)-  => Proxy db-  -> (Utf8 -> Utf8)-  -> (Utf8 -> Utf8 -> Utf8)-  -> (Utf8 -> Utf8 -> Utf8)-  -> Cond v c -> Maybe (RenderS Utf8) #-}--{-# SPECIALIZE renderOrders :: (PersistEntity v, Constructor c) => (Utf8 -> Utf8) -> [Order v c] -> Utf8 #-}--{-# SPECIALIZE renderUpdates :: (PersistEntity v, Constructor c, DbDescriptor db) => Proxy db -> (Utf8 -> Utf8) -> [Update v c] -> Maybe (RenderS Utf8) #-}--{-# SPECIALIZE renderFields :: (Utf8 -> Utf8) -> [(String, DbType)] -> Utf8 #-}--{-# SPECIALIZE renderChain :: (Utf8 -> Utf8) -> FieldChain -> [Utf8] -> [Utf8] #-}
Database/Groundhog/Sqlite.hs view
@@ -1,11 +1,11 @@-{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, FlexibleContexts, OverloadedStrings, TypeFamilies, RecordWildCards, Rank2Types #-}+{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, FlexibleContexts, OverloadedStrings, TypeFamilies, RecordWildCards, Rank2Types, MultiParamTypeClasses #-} module Database.Groundhog.Sqlite     ( withSqlitePool     , withSqliteConn-    , runSqlitePool-    , runSqliteConn+    , runDbConn     , Sqlite     , module Database.Groundhog+    , module Database.Groundhog.Generic.Sql.Functions     ) where  import Database.Groundhog@@ -13,7 +13,8 @@ import Database.Groundhog.Generic import Database.Groundhog.Generic.Migration hiding (MigrationPack(..)) import qualified Database.Groundhog.Generic.Migration as GM-import Database.Groundhog.Generic.Sql.Utf8+import Database.Groundhog.Generic.Sql+import Database.Groundhog.Generic.Sql.Functions import qualified Database.Groundhog.Generic.PersistBackendHelpers as H  import qualified Database.Sqlite as S@@ -23,15 +24,15 @@ import Control.Monad.Trans.Control (MonadBaseControl) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Trans.Reader (ask)-import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS import Data.Char (toUpper) import Data.Function (on) import Data.Int (Int64) import Data.List (groupBy, intercalate, isInfixOf, partition, sort) import Data.IORef import qualified Data.HashMap.Strict as Map-import Data.Maybe (maybeToList)-import Data.Conduit.Pool+import Data.Maybe (fromMaybe)+import Data.Pool import qualified Data.Text as T  import GHC.Exts (inline)@@ -41,11 +42,17 @@  instance DbDescriptor Sqlite where   type AutoKeyType Sqlite = Int64+  type QueryRaw Sqlite = Snippet Sqlite+  backendName _ = "sqlite" +instance SqlDb Sqlite where+  append a b = Expr $ operator 50 "||" a b+ instance (MonadBaseControl IO m, MonadIO m) => PersistBackend (DbPersist Sqlite m) where   {-# SPECIALIZE instance PersistBackend (DbPersist Sqlite IO) #-}   type PhantomDb (DbPersist Sqlite m) = Sqlite   insert v = insert' v+  insert_ v = insert_' v   insertBy u v = H.insertBy escapeS queryRawTyped u v   insertByAll v = H.insertByAll escapeS queryRawTyped v   replace k v = H.replace escapeS queryRawTyped executeRawCached' insertIntoConstructorTable k v@@ -69,9 +76,17 @@   insertList l = insertList' l   getList k = getList' k ---{-# SPECIALIZE INLINE H.get :: (MonadBaseControl IO m, MonadIO m, PersistEntity v, PrimitivePersistField (Key v BackendSpecific)) => (Utf8 -> Utf8) -> (forall a . Utf8 -> [DbType] -> [PersistValue] -> (RowPopper (DbPersist Sqlite m) -> DbPersist Sqlite m a) -> DbPersist Sqlite m a) -> Key v BackendSpecific -> DbPersist Sqlite m (Maybe v) #-}+instance (MonadBaseControl IO m, MonadIO m) => SchemaAnalyzer (DbPersist Sqlite m) where+  listTables _ = queryRaw' "SELECT name FROM sqlite_master WHERE type='table'" [] (mapAllRows $ return . fst . fromPurePersistValues proxy)+  listTableTriggers _ name = queryRaw' "SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name=?" [toPrimitivePersistValue proxy name] (mapAllRows $ return . fst . fromPurePersistValues proxy)+  analyzeTable = analyzeTable'+  analyzeTrigger _ name = do+    x <- queryRaw' "SELECT sql FROM sqlite_master WHERE type='trigger' AND name=?" [toPrimitivePersistValue proxy name] id+    case x of+      Nothing  -> return Nothing+      Just src -> return (fst $ fromPurePersistValues proxy src)+  analyzeFunction = error "analyzeFunction: is not supported by Sqlite" ---{-# SPECIALIZE withSqlitePool :: String -> Int -> (Pool Sqlite -> IO a) -> IO a #-} withSqlitePool :: (MonadBaseControl IO m, MonadIO m)                => String                -> Int -- ^ number of connections to open@@ -87,20 +102,30 @@                -> m a withSqliteConn s = bracket (liftIO $ open' s) (liftIO . close') -{-# SPECIALIZE runSqlitePool :: DbPersist Sqlite IO a -> Pool Sqlite -> IO a #-}-runSqlitePool :: (MonadBaseControl IO m, MonadIO m) => DbPersist Sqlite m a -> Pool Sqlite -> m a-runSqlitePool f pconn = withResource pconn $ runSqliteConn f+instance Savepoint Sqlite where+  withConnSavepoint name m (Sqlite c _) = do+    let runStmt query = S.prepare c query >>= \stmt -> S.step stmt >> S.finalize stmt+    let name' = fromString name+    liftIO $ runStmt $ "SAVEPOINT " <> name'+    x <- onException m (liftIO $ runStmt $ "ROLLBACK TO " <> name')+    liftIO $ runStmt $ "RELEASE " <> name'+    return x -{-# SPECIALIZE runSqliteConn :: DbPersist Sqlite IO a -> Sqlite -> IO a #-}-{-# INLINE runSqliteConn #-}-runSqliteConn :: (MonadBaseControl IO m, MonadIO m) => DbPersist Sqlite m a -> Sqlite -> m a-runSqliteConn f conn@(Sqlite c _) = do-  let runStmt query = S.prepare c query >>= \stmt -> S.step stmt >> S.finalize stmt-  liftIO $ runStmt "BEGIN"-  x <- onException (runDbPersist f conn) (liftIO $ runStmt "ROLLBACK")-  liftIO $ runStmt "COMMIT"-  return x+instance ConnectionManager Sqlite Sqlite where+  withConn f conn@(Sqlite c _) = do+    let runStmt query = S.prepare c query >>= \stmt -> S.step stmt >> S.finalize stmt+    liftIO $ runStmt "BEGIN"+    x <- onException (f conn) (liftIO $ runStmt "ROLLBACK")+    liftIO $ runStmt "COMMIT"+    return x+  withConnNoTransaction f conn = f conn +instance ConnectionManager (Pool Sqlite) Sqlite where+  withConn f pconn = withResource pconn (withConn f)+  withConnNoTransaction f pconn = withResource pconn (withConnNoTransaction f)++instance SingleConnectionManager Sqlite Sqlite+ open' :: String -> IO Sqlite open' s = do   conn <- S.open s@@ -121,12 +146,12 @@   compareTypes   compareRefs   compareUniqs-  checkTable   migTriggerOnDelete   migTriggerOnUpdate   GM.defaultMigConstr   escape-  "INTEGER PRIMARY KEY"+  DbInt64+  "INTEGER PRIMARY KEY NOT NULL"   "INTEGER"   mainTableId   defaultPriority@@ -136,87 +161,75 @@  addUniquesReferences :: [UniqueDef'] -> [Reference] -> ([String], [AlterTable]) addUniquesReferences uniques refs = (map sqlUnique constraints ++ map sqlReference refs, map AddUnique indexes) where-  (constraints, indexes) = partition (\(UniqueDef' _ uType _) -> uType == UniqueConstraint) uniques+  (constraints, indexes) = partition ((/= UniqueIndex) . uniqueDefType) uniques --- it handles only delete operations. So far when list replace is not allowed, it is ok-migTriggerOnDelete :: (MonadBaseControl IO m, MonadIO m) => String -> [(String, String)] -> DbPersist Sqlite m (Bool, [AlterDB])-migTriggerOnDelete name deletes = do-  let addTrigger = AddTriggerOnDelete name name (concatMap snd deletes)-  x <- checkTrigger name+migTriggerOnDelete :: (MonadBaseControl IO m, MonadIO m) => Maybe String -> String -> [(String, String)] -> DbPersist Sqlite m (Bool, [AlterDB])+migTriggerOnDelete schema name deletes = do+  let addTrigger = AddTriggerOnDelete Nothing name Nothing name (concatMap snd deletes)+  x <- analyzeTrigger schema name   return $ case x of     Nothing | null deletes -> (False, [])     Nothing -> (False, [addTrigger])     Just sql -> (True, if null deletes -- remove old trigger if a datatype earlier had fields of ephemeral types-      then [DropTrigger name name]+      then [DropTrigger Nothing name Nothing name]       else if Right [(False, triggerPriority, sql)] == showAlterDb addTrigger         then []         -- this can happen when an ephemeral field was added or removed.-        else [DropTrigger name name, addTrigger])+        else [DropTrigger Nothing name Nothing name, addTrigger])         --- | Table name and a  list of field names and according delete statements+-- | 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) => String -> String -> String -> DbPersist Sqlite m (Bool, [AlterDB])-migTriggerOnUpdate name fieldName del = do+migTriggerOnUpdate :: (MonadBaseControl IO m, MonadIO m) => Maybe String -> String -> [(String, String)] -> DbPersist Sqlite m [(Bool, [AlterDB])]+migTriggerOnUpdate schema name dels = forM dels $ \(fieldName, del) -> do   let trigName = name ++ delim : fieldName-  let addTrigger = AddTriggerOnUpdate trigName name fieldName del-  x <- checkTrigger trigName+  let addTrigger = AddTriggerOnUpdate Nothing trigName Nothing name (Just fieldName) del+  x <- analyzeTrigger schema trigName   return $ case x of     Nothing -> (False, [addTrigger])     Just sql -> (True, if Right [(False, triggerPriority, sql)] == showAlterDb addTrigger         then []-        else [DropTrigger trigName name, addTrigger])-  -checkTrigger :: (MonadBaseControl IO m, MonadIO m) => String -> DbPersist Sqlite m (Maybe String)-checkTrigger = checkSqliteMaster "trigger"--checkSqliteMaster :: (MonadBaseControl IO m, MonadIO m) => String -> String -> DbPersist Sqlite m (Maybe String)-checkSqliteMaster vtype name = do-  let query = "SELECT sql FROM sqlite_master WHERE type = ? AND name = ?"-  x <- queryRawTyped query [DbString] [toPrimitivePersistValue proxy vtype, toPrimitivePersistValue proxy name] id-  let throwErr = error . ("Unexpected result from sqlite_master: " ++)-  case x of-    Nothing -> return Nothing-    Just [hsql] -> case hsql of-      PersistString sql -> return $ Just sql-      err               -> throwErr $ "column sql is not string: " ++ show err-    Just xs -> throwErr $ "requested 1 column, returned " ++ show xs+        else [DropTrigger Nothing trigName Nothing name, addTrigger]) -checkTable :: (MonadBaseControl IO m, MonadIO m) => String -> DbPersist Sqlite m (Maybe (Either [String] TableInfo))-checkTable tableName = do+analyzeTable' :: (MonadBaseControl IO m, MonadIO m) => Maybe String -> String -> DbPersist Sqlite m (Either [String] (Maybe TableInfo))+analyzeTable' _ tName = do   let fromName = escapeS . fromString-  tableInfo <- queryRaw' ("pragma table_info(" <> fromName tableName <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)+  tableInfo <- queryRaw' ("pragma table_info(" <> fromName tName <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)   case tableInfo of-    [] -> return Nothing-    xs -> liftM Just $ do-      let rawColumns = filter (\(_ , (_, _, _, _, isPrimary)) -> isPrimary == 0) xs+    [] -> return $ Right Nothing+    rawColumns -> do       let mkColumn :: (Int, (String, String, Int, Maybe String, Int)) -> Column           mkColumn (_, (name, typ, isNotNull, defaultValue, _)) = Column name (isNotNull == 0) (readSqlType typ) defaultValue+      let primaryKeyColumnNames = foldr (\(_ , (name, _, _, _, isPrimary)) xs -> if isPrimary == 1 then name:xs else xs) [] rawColumns       let columns = map mkColumn rawColumns-      indexList <- queryRaw' ("pragma index_list(" <> fromName tableName <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)+      indexList <- queryRaw' ("pragma index_list(" <> fromName tName <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)       let uniqueNames = map (\(_ :: Int, name, _) -> name) $ filter (\(_, _, isUnique) -> isUnique) indexList       uniques <- forM uniqueNames $ \name -> do         uFields <- queryRaw' ("pragma index_info(" <> fromName name <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)         sql <- queryRaw' ("select sql from sqlite_master where type = 'index' and name = ?") [toPrimitivePersistValue proxy name] id-        let uType = if sql == Just [PersistNull] then UniqueConstraint else UniqueIndex-        return $ UniqueDef' name uType $ map (\(_, _, columnName) -> columnName) (uFields :: [(Int, Int, String)])-      foreignKeyList <- queryRaw' ("pragma foreign_key_list(" <> fromName tableName <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)+        let columnNames = map (\(_, _, columnName) -> columnName) (uFields :: [(Int, Int, String)])+        let uType = if sql == Just [PersistNull]+              then if sort columnNames == sort primaryKeyColumnNames then UniquePrimary else UniqueConstraint+              else UniqueIndex+        return $ UniqueDef' (Just name) uType columnNames+      foreignKeyList <- queryRaw' ("pragma foreign_key_list(" <> fromName tName <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)       (foreigns :: [(Maybe String, Reference)]) <- do           let foreigns :: [[(Int, (Int, String, (String, Maybe String), (String, String, String)))]]               foreigns = groupBy ((==) `on` fst) . sort $ foreignKeyList -- sort by foreign key number and column number inside key (first and second integers)+              mkAction c = Just $ fromMaybe (error $ "unknown reference action type: " ++ c) $ readReferenceAction c           forM foreigns $ \rows -> do -            let (_, (_, foreignTable, _, _)) = head rows+            let (_, (_, foreignTable, _, (onUpdate, onDelete, _))) = head rows             refs <- forM rows $ \(_, (_, _, (child, parent), _)) -> case parent of-              Nothing -> checkPrimaryKey foreignTable >>= \x -> case x of+              Nothing -> analyzePrimaryKey foreignTable >>= \x -> case x of                 Just primaryKeyName -> return (child, primaryKeyName)-                Nothing -> error $ "checkTable: cannot find primary key for table " ++ foreignTable ++ " which is referenced without specifying column names"+                Nothing -> error $ "analyzeTable: cannot find primary key for table " ++ foreignTable ++ " which is referenced without specifying column names"               Just columnName -> return (child, columnName)-            return (Nothing, (foreignTable, refs))-      primaryKey <- checkPrimaryKey tableName-      return $ Right $ TableInfo primaryKey columns uniques foreigns+            return (Nothing, Reference Nothing foreignTable refs (mkAction onDelete) (mkAction onUpdate))+      let uniques' = uniques ++ if length primaryKeyColumnNames == 1 then [UniqueDef' Nothing UniquePrimary primaryKeyColumnNames] else []+      return $ Right $ Just $ TableInfo columns uniques' foreigns -checkPrimaryKey :: (MonadBaseControl IO m, MonadIO m) => String -> DbPersist Sqlite m (Maybe String)-checkPrimaryKey tableName = do-  tableInfo <- queryRaw' ("pragma table_info(" <> escapeS (fromString tableName) <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)+analyzePrimaryKey :: (MonadBaseControl IO m, MonadIO m) => String -> DbPersist Sqlite m (Maybe String)+analyzePrimaryKey tName = do+  tableInfo <- queryRaw' ("pragma table_info(" <> escapeS (fromString tName) <> ")") [] $ mapAllRows (return . fst . fromPurePersistValues proxy)   let rawColumns :: [(Int, (String, String, Int, Maybe String, Int))]       rawColumns = filter (\(_ , (_, _, _, _, isPrimary)) -> isPrimary == 1) tableInfo   return $ case rawColumns of@@ -254,10 +267,10 @@ showSqlType DbDayTime = "TIMESTAMP" showSqlType DbDayTimeZoned = "TIMESTAMP WITH TIME ZONE" showSqlType DbBlob = "BLOB"-showSqlType (DbOther name) = name+showSqlType (DbOther (OtherTypeDef f)) = f showSqlType showSqlType (DbMaybe t) = showSqlType t showSqlType (DbList _ _) = showSqlType DbInt64-showSqlType (DbEntity Nothing _) = showSqlType DbInt64+showSqlType (DbEntity Nothing _ _ _) = showSqlType DbInt64 showSqlType t = error $ "showSqlType: DbType does not have corresponding database type: " ++ show t  readSqlType :: String -> DbType@@ -270,26 +283,12 @@ readSqlType "TIMESTAMP" = DbDayTime readSqlType "TIMESTAMP WITH TIME ZONE" = DbDayTimeZoned readSqlType "BLOB" = DbBlob-readSqlType typ = DbOther typ+readSqlType typ = DbOther $ OtherTypeDef $ const typ  data Affinity = TEXT | NUMERIC | INTEGER | REAL | NONE deriving (Eq, Show)  dbTypeAffinity :: DbType -> Affinity-dbTypeAffinity DbString = TEXT-dbTypeAffinity DbInt32 = INTEGER-dbTypeAffinity DbInt64 = INTEGER-dbTypeAffinity DbReal = REAL-dbTypeAffinity DbBool = NUMERIC-dbTypeAffinity DbDay = NUMERIC-dbTypeAffinity DbTime = NUMERIC-dbTypeAffinity DbDayTime = NUMERIC-dbTypeAffinity DbDayTimeZoned = NUMERIC-dbTypeAffinity DbBlob = NONE-dbTypeAffinity (DbOther name) = readSqlTypeAffinity name-dbTypeAffinity (DbMaybe t) = dbTypeAffinity t-dbTypeAffinity (DbList _ _) = INTEGER-dbTypeAffinity (DbEntity Nothing _) = INTEGER-dbTypeAffinity t = error $ "showSqlType: DbType does not have corresponding database type: " ++ show t+dbTypeAffinity = readSqlTypeAffinity . showSqlType  readSqlTypeAffinity :: String -> Affinity readSqlTypeAffinity typ = affinity where@@ -308,18 +307,23 @@            else ""  sqlReference :: Reference -> String-sqlReference (tname, columns) = "FOREIGN KEY(" ++ our ++ ") REFERENCES " ++ escape tname ++ "(" ++ foreign ++ ")" where-  (our, foreign) = f *** f $ unzip columns+sqlReference Reference{..} = "FOREIGN KEY(" ++ our ++ ") REFERENCES " ++ escape referencedTableName ++ "(" ++ foreign ++ ")" ++ actions where+  actions = maybe "" ((" ON DELETE " ++) . showReferenceAction) referenceOnDelete+         ++ maybe "" ((" ON UPDATE " ++) . showReferenceAction) referenceOnUpdate+  (our, foreign) = f *** f $ unzip referencedColumns   f = intercalate ", " . map escape  sqlUnique :: UniqueDef' -> String-sqlUnique (UniqueDef' name _ cols) = concat-    [ "CONSTRAINT "-    , escape name-    , " UNIQUE ("-    , intercalate "," $ map escape cols-    , ")"-    ]+sqlUnique (UniqueDef' name typ cols) = concat [+    maybe "" ((" CONSTRAINT " ++) . escape) name+  , constraintType+  , intercalate "," $ map escape cols+  , ")"+  ] where+    constraintType = case typ of+      UniquePrimary -> " PRIMARY KEY("+      UniqueConstraint -> " UNIQUE("+      UniqueIndex -> error "sqlUnique: does not handle indexes"  {-# SPECIALIZE insert' :: PersistEntity v => v -> DbPersist Sqlite IO (AutoKey v) #-} insert' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m) => v -> DbPersist Sqlite m (AutoKey v)@@ -327,35 +331,55 @@   -- constructor number and the rest of the field values   vals <- toEntityPersistValues' v   let e = entityDef v-  let name = persistName v   let constructorNum = fromPrimitivePersistValue proxy (head vals)    liftM fst $ if isSimple (constructors e)     then do       let constr = head $ constructors e-      let query = insertIntoConstructorTable False name constr-      executeRawCached' query (tail vals)+      let RenderS query vals' = insertIntoConstructorTable False (tableName escapeS e constr) constr (tail vals)+      executeRawCached' query (vals' [])       case constrAutoKeyName constr of         Nothing -> pureFromPersistValue []         Just _  -> getLastInsertRowId >>= \rowid -> pureFromPersistValue [rowid]     else do       let constr = constructors e !! constructorNum-      let cName = name ++ [delim] ++ constrName constr-      let query = "INSERT INTO " <> escapeS (fromString name) <> "(discr)VALUES(?)"+      let query = "INSERT INTO " <> mainTableName escapeS e <> "(discr)VALUES(?)"       executeRawCached' query $ take 1 vals       rowid <- getLastInsertRowId-      let cQuery = insertIntoConstructorTable True cName constr-      executeRawCached' cQuery $ rowid:(tail vals)+      let RenderS cQuery vals' = insertIntoConstructorTable True (tableName escapeS e constr) constr (rowid:tail vals)+      executeRawCached' cQuery (vals' [])       pureFromPersistValue [rowid] +{-# SPECIALIZE insert_' :: PersistEntity v => v -> DbPersist Sqlite IO () #-}+insert_' :: (PersistEntity v, MonadBaseControl IO m, MonadIO m) => v -> DbPersist Sqlite m ()+insert_' v = do+  -- constructor number and the rest of the field values+  vals <- toEntityPersistValues' v+  let e = entityDef v+  let constructorNum = fromPrimitivePersistValue proxy (head vals)++  if isSimple (constructors e)+    then do+      let constr = head $ constructors e+      let RenderS query vals' = insertIntoConstructorTable False (tableName escapeS e constr) constr (tail vals)+      executeRawCached' query (vals' [])+    else do+      let constr = constructors e !! constructorNum+      let query = "INSERT INTO " <> mainTableName escapeS e <> "(discr)VALUES(?)"+      executeRawCached' query $ take 1 vals+      rowid <- getLastInsertRowId+      let RenderS cQuery vals' = insertIntoConstructorTable True (tableName escapeS e constr) constr (rowid:tail vals)+      executeRawCached' 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 -> String -> ConstructorDef -> Utf8-insertIntoConstructorTable withId tName c = "INSERT INTO " <> escapeS (fromString tName) <> "(" <> fieldNames <> ")VALUES(" <> placeholders <> ")" where+insertIntoConstructorTable :: Bool -> Utf8 -> ConstructorDef -> [PersistValue] -> RenderS db r+insertIntoConstructorTable withId tName c vals = RenderS query vals' where+  query = "INSERT INTO " <> tName <> "(" <> fieldNames <> ")VALUES(" <> placeholders <> ")"   fields = case constrAutoKeyName c of     Just idName | withId -> (idName, dbType (0 :: Int64)):constrParams c     _                    -> constrParams c   fieldNames   = renderFields escapeS fields-  placeholders = renderFields (const $ fromChar '?') fields+  RenderS placeholders vals' = commasJoin $ map renderPersistValue vals  insertList' :: forall m a.(MonadBaseControl IO m, MonadIO m, PersistField a) => [a] -> DbPersist Sqlite m Int64 insertList' l = do@@ -398,16 +422,17 @@   go _ [] = return ()   go i (x:xs) = do     case x of-      PersistInt64 int64     -> S.bindInt64 stmt i int64-      PersistString text     -> S.bindText stmt i $ T.pack text-      PersistDouble double   -> S.bindDouble stmt i double-      PersistBool b          -> S.bindInt64 stmt i $ if b then 1 else 0-      PersistByteString blob -> S.bindBlob stmt i blob-      PersistNull            -> S.bindNull stmt i-      PersistDay d           -> S.bindText stmt i $ T.pack $ show d-      PersistTimeOfDay d     -> S.bindText stmt i $ T.pack $ show d-      PersistUTCTime d       -> S.bindText stmt i $ T.pack $ show d-      PersistZonedTime (ZT d)-> S.bindText stmt i $ T.pack $ show d+      PersistInt64 int64      -> S.bindInt64 stmt i int64+      PersistString text      -> S.bindText stmt i $ T.pack text+      PersistDouble double    -> S.bindDouble stmt i double+      PersistBool b           -> S.bindInt64 stmt i $ if b then 1 else 0+      PersistByteString blob  -> S.bindBlob stmt i blob+      PersistNull             -> S.bindNull stmt i+      PersistDay d            -> S.bindText stmt i $ T.pack $ show d+      PersistTimeOfDay d      -> S.bindText stmt i $ T.pack $ show d+      PersistUTCTime d        -> S.bindText stmt i $ T.pack $ show d+      PersistZonedTime (ZT d) -> S.bindText stmt i $ T.pack $ show d+      PersistCustom _ _       -> error "bind: unexpected PersistCustom"     go (i + 1) xs  executeRaw' :: (MonadBaseControl IO m, MonadIO m) => Utf8 -> [PersistValue] -> DbPersist Sqlite m ()@@ -475,13 +500,13 @@ typeToSqlite (DbOther _) = Nothing typeToSqlite (DbMaybe _) = Nothing typeToSqlite (DbList _ _) = Just S.IntegerColumn-typeToSqlite (DbEntity Nothing _) = Just S.IntegerColumn+typeToSqlite (DbEntity Nothing _ _ _) = Just S.IntegerColumn typeToSqlite t = error $ "typeToSqlite: DbType does not have corresponding database type: " ++ show t  getDbTypes :: DbType -> [DbType] -> [DbType] getDbTypes typ acc = case typ of   DbEmbedded (EmbeddedDef _ ts) -> foldr (getDbTypes . snd) acc ts-  DbEntity (Just (EmbeddedDef _ ts, _)) _ -> foldr (getDbTypes . snd) acc ts+  DbEntity (Just (EmbeddedDef _ ts, _)) _ _ _ -> foldr (getDbTypes . snd) acc ts   t               -> t:acc  pFromSql :: S.SQLData -> PersistValue@@ -498,15 +523,13 @@ escapeS :: Utf8 -> Utf8 escapeS a = let q = fromChar '"' in q <> a <> q -renderCond' :: (PersistEntity v, Constructor c) => Cond v c -> Maybe (RenderS Utf8)-renderCond' = renderCond proxy escapeS renderEquals renderNotEquals where+renderCond' :: Cond Sqlite r -> Maybe (RenderS Sqlite r)+renderCond' = renderCond escapeS renderEquals renderNotEquals where   renderEquals a b = a <> " IS " <> b   renderNotEquals a b = a <> " IS NOT " <> b -defaultPriority :: Int+defaultPriority, triggerPriority :: Int defaultPriority = 0--triggerPriority :: Int triggerPriority = 1  proxy :: Proxy Sqlite@@ -519,10 +542,17 @@ toEntityPersistValues' = liftM ($ []) . toEntityPersistValues  compareUniqs :: UniqueDef' -> UniqueDef' -> Bool+compareUniqs (UniqueDef' _ UniquePrimary cols1) (UniqueDef' _ UniquePrimary cols2) = haveSameElems (==) cols1 cols2+-- only one of the uniques is primary+compareUniqs (UniqueDef' _ type1 _) (UniqueDef' _ type2 _) | UniquePrimary `elem` [type1, type2] = False compareUniqs (UniqueDef' _ type1 cols1) (UniqueDef' _ type2 cols2) = haveSameElems (==) cols1 cols2 && type1 == type2  compareRefs :: (Maybe String, Reference) -> (Maybe String, Reference) -> Bool-compareRefs (_, (tbl1, pairs1)) (_, (tbl2, pairs2)) = unescape tbl1 == unescape tbl2 && haveSameElems (==) pairs1 pairs2 where+compareRefs (_, Reference _ tbl1 pairs1 onDel1 onUpd1) (_, Reference _ tbl2 pairs2 onDel2 onUpd2) =+     unescape tbl1 == unescape tbl2+  && haveSameElems (==) pairs1 pairs2+  && fromMaybe NoAction onDel1 == fromMaybe NoAction onDel2+  && fromMaybe NoAction onUpd1 == fromMaybe NoAction onUpd2 where   unescape name = if head name == '"' && last name == '"' then tail $ init name else name  compareTypes :: DbType -> DbType -> Bool@@ -534,7 +564,7 @@  showAlterDb :: AlterDB -> SingleMigration showAlterDb (AddTable s) = Right [(False, defaultPriority, s)]-showAlterDb (AlterTable table createTable (TableInfo oldId oldCols _ _) (TableInfo newId newCols _ _) alts) = case mapM (showAlterTable table) alts of+showAlterDb (AlterTable _ table createTable (TableInfo oldCols _ _) (TableInfo newCols _ _) alts) = case mapM (showAlterTable table) alts of   Just alts' -> Right alts'   Nothing -> (Right     [ (False, defaultPriority, "CREATE TEMP TABLE " ++ escape tableTmp ++ "(" ++ columnsTmp ++ ")")@@ -547,20 +577,37 @@       tableTmp = table ++ "_backup"       copy (from, fromCols) (to, toCols) = "INSERT INTO " ++ escape to ++ "(" ++ toCols ++ ") SELECT " ++ fromCols ++ " FROM " ++ escape from       (oldOnlyColumns, _, commonColumns) = matchElements ((==) `on` colName) oldCols newCols-      columnsTmp = intercalate "," $ map escape $ maybeToList (newId >> oldId) ++ map (colName . snd) commonColumns-      columnsNew = intercalate "," $ map escape $ maybeToList (oldId >> newId) ++ map (colName . snd) commonColumns-showAlterDb (DropTrigger name _) = Right [(False, triggerPriority, "DROP TRIGGER " ++ escape name)]-showAlterDb (AddTriggerOnDelete trigName tableName body) = Right [(False, triggerPriority,-  "CREATE TRIGGER " ++ escape trigName ++ " DELETE ON " ++ escape tableName ++ " BEGIN " ++ body ++ "END")]-showAlterDb (AddTriggerOnUpdate trigName tableName fieldName body) = Right [(False, triggerPriority,-  "CREATE TRIGGER " ++ escape trigName ++ " UPDATE OF " ++ escape fieldName ++ " ON " ++ escape tableName ++ " BEGIN " ++ body ++ "END")]+      columnsTmp = intercalate "," $ map (escape . colName . snd) commonColumns+      columnsNew = intercalate "," $ map (escape . colName . snd) commonColumns+showAlterDb (DropTrigger _ name _ _) = Right [(False, triggerPriority, "DROP TRIGGER " ++ escape name)]+showAlterDb (AddTriggerOnDelete _ trigName _ tName body) = Right [(False, triggerPriority,+  "CREATE TRIGGER " ++ escape trigName ++ " DELETE ON " ++ escape tName ++ " BEGIN " ++ body ++ "END")]+showAlterDb (AddTriggerOnUpdate _ trigName _ tName fieldName body) = Right [(False, triggerPriority,+  "CREATE TRIGGER " ++ escape trigName ++ " UPDATE OF " ++ fieldName' ++ " ON " ++ escape tName ++ " BEGIN " ++ body ++ "END")] where+    fieldName' = maybe (error $ "showAlterDb: AddTriggerOnUpdate does not have fieldName for trigger " ++ trigName) escape fieldName showAlterDb alt = error $ "showAlterDb: does not support " ++ show alt  showAlterTable :: String -> AlterTable -> Maybe (Bool, Int, String)-showAlterTable table (AlterColumn alt) = showAlterColumn table alt+showAlterTable table (AddColumn col) = Just (False, defaultPriority, concat+  [ "ALTER TABLE "+  , escape table+  , " ADD COLUMN "+  , showColumn col+  ])+showAlterTable table (AlterColumn col [UpdateValue s]) = Just (False, defaultPriority, concat+  [ "UPDATE "+  , escape table+  , " SET "+  , escape (colName col)+  , "="+  , s+  , " WHERE "+  , escape (colName col)+  , " IS NULL"+  ]) showAlterTable table (AddUnique (UniqueDef' uName UniqueIndex cols)) = Just (False, defaultPriority, concat   [ "CREATE UNIQUE INDEX "-  , escape uName+  , maybe (error $ "showAlterTable: index for table " ++ table ++ " does not have a name") escape uName   , " ON "   , escape table   , "("@@ -572,23 +619,3 @@   , escape uName   ]) showAlterTable _ _ = Nothing--showAlterColumn :: String -> AlterColumn' -> Maybe (Bool, Int, String)-showAlterColumn table (_, Add col) = Just (False, defaultPriority, concat-    [ "ALTER TABLE "-    , escape table-    , " ADD COLUMN "-    , showColumn col-    ])-showAlterColumn table (n, UpdateValue s) = Just (False, defaultPriority, concat-    [ "UPDATE "-    , escape table-    , " SET "-    , escape n-    , "="-    , s-    , " WHERE "-    , escape n-    , " IS NULL"-    ])-showAlterColumn _ _ = Nothing
groundhog-sqlite.cabal view
@@ -1,5 +1,5 @@ name:            groundhog-sqlite-version:         0.2.0+version:         0.3.0 license:         BSD3 license-file:    LICENSE author:          Boris Lykah <lykahb@gmail.com>@@ -19,16 +19,14 @@     build-depends:   base                    >= 4         && < 5                    , bytestring              >= 0.9                    , transformers            >= 0.2.1     && < 0.4-                   , groundhog               >= 0.2.0     && < 0.3.0+                   , groundhog               >= 0.3.0     && < 0.4.0                    , monad-control           >= 0.3       && < 0.4                    , containers              >= 0.2                    , text                    >= 0.8       && < 0.12-                   , blaze-builder           >= 0.3.0.0   && < 0.4-                   , pool-conduit            >= 0.1       && < 0.2+                   , resource-pool           >= 0.2.1     && < 0.3                    , unordered-containers     exposed-modules: Database.Sqlite                      Database.Groundhog.Sqlite-    other-modules:   Database.Groundhog.Generic.Sql.Utf8     ghc-options:     -Wall -fno-warn-unused-do-bind     if flag(systemlib)         extra-libraries: sqlite3