packages feed

mysql-simple 0.4.7.1 → 0.4.7.2

raw patch · 6 files changed

+113/−38 lines, 6 filesdep ~bytestring

Dependency ranges changed: bytestring

Files

ChangeLog.md view
@@ -1,3 +1,7 @@+## 0.4.7.2++* Fix question marks in string literals causing substitution errors (https://github.com/paul-rouse/mysql-simple/pull/54)+ ## 0.4.7.1  * Use parseTimeM from Data.Time.Format instead of parseTime (https://github.com/paul-rouse/mysql-simple/pull/53)
Database/MySQL/Simple.hs view
@@ -80,6 +80,7 @@     -- * Helper functions     , formatMany     , formatQuery+    , splitQuery     ) where  import Blaze.ByteString.Builder (Builder, fromByteString, toByteString)@@ -88,6 +89,7 @@ import Control.Exception (Exception, bracket, onException, throw, throwIO) import Control.Monad.Fix (fix) import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as BS import Data.Int (Int64) import Data.List (intersperse) import Data.Monoid (mappend, mconcat)@@ -169,18 +171,47 @@         [caseless]  buildQuery :: Connection -> Query -> ByteString -> [Action] -> IO Builder-buildQuery conn q template xs = zipParams (split template) <$> mapM sub xs+buildQuery conn q template xs = zipParams queryFragments <$> mapM sub xs   where sub (Plain b)  = pure b         sub (Escape s) = (inQuotes . fromByteString) <$> Base.escape conn s         sub (Many ys)  = mconcat <$> mapM sub ys-        split s = fromByteString h : if B.null t then [] else split (B.tail t)-            where (h,t) = B.break (=='?') s         zipParams (t:ts) (p:ps) = t `mappend` p `mappend` zipParams ts ps         zipParams [t] []        = t-        zipParams _ _ = fmtError (show (B.count '?' template) +++        zipParams _ _ = fmtError (show fragmentCount ++                                   " '?' characters, but " ++                                   show (length xs) ++ " parameters") q xs+        fragmentCount = length queryFragments - 1+        queryFragments = splitQuery template +-- | Split a query into fragments separated by @?@ characters. Does not+-- break a fragment if the question mark is in a string literal.+splitQuery :: ByteString -> [Builder]+splitQuery s =+  reverse $ fmap (fromByteString . BS.pack . reverse) $+    begin [] (BS.unpack s)+  where+  begin = normal []++  normal ret acc [] =+    acc : ret+  normal ret acc (c : cs) =+    case c of+      '?' ->+        normal (acc : ret) [] cs+      '\'' ->+        quotes ret (c : acc) cs+      _ ->+        normal ret (c : acc) cs++  quotes ret acc [] =+    acc : ret+  quotes ret acc (c : cs) =+    case c of+      '\'' ->+        normal ret (c : acc) cs+      _ ->+        quotes ret (c : acc) cs+ -- | Execute an @INSERT@, @UPDATE@, or other SQL query that is not -- expected to return results. --@@ -373,7 +404,7 @@ -- facility to address both ease of use and security.  -- $querytype--- +-- -- A 'Query' is a @newtype@-wrapped 'ByteString'. It intentionally -- exposes a tiny API that is not compatible with the 'ByteString' -- API; this makes it difficult to construct queries from fragments of
Database/MySQL/Simple/Types.hs view
@@ -31,26 +31,12 @@ import qualified Data.ByteString as B  -- | A placeholder for the SQL @NULL@ value.------ Prior to @mysql-simple-0.5@, an 'Eq' instance was provided for 'Null'.--- In an attempt to mirror SQL semantics, it unconditionally produced--- 'False' for both '==' and '/='.  However, this is not correct SQL semantics,--- and in any case it is unclear why those semantics in Haskell would be--- caried over to a Haskell program manipulating a database.  A proposal has--- been accepted to remove '/=' from the 'Eq' class, so this non-law-abiding--- instance will become impossible.  Therefore, the 'Eq' instance will simply--- be removed.------ Currently a WARNING is produced for uses of the 'Null' type since it is--- not possible to attach a DEPRECATED pragma to an instance.--- data Null = Null           deriving (Read, Show, Typeable)  instance Eq Null where     _ == _ = False     _ /= _ = False-{-# WARNING Null "The Eq instance of Null is deprecated: see the documentation for Null in Database.MySQL.Simple.Types" #-}  -- | A query string. This type is intended to make it difficult to -- construct a SQL query by concatenating string fragments, as that is
mysql-simple.cabal view
@@ -1,5 +1,5 @@ name:           mysql-simple-version:        0.4.7.1+version:        0.4.7.2 homepage:       https://github.com/paul-rouse/mysql-simple bug-reports:    https://github.com/paul-rouse/mysql-simple/issues synopsis:       A mid-level MySQL client library.@@ -84,6 +84,7 @@     ghc-options:     -Wall     default-language: Haskell2010     build-depends:   base                    >= 4 && < 5+                   , bytestring                    , blaze-builder                    , hspec                    , mysql-simple
stack.yaml view
@@ -4,4 +4,4 @@ packages: - '.' extra-deps: []-resolver: lts-18.6+resolver: lts-18.28
test/main.hs view
@@ -1,31 +1,42 @@ {-# LANGUAGE CPP, OverloadedStrings #-} ++{-# options_ghc -fno-warn-orphans #-}++import Data.ByteString.Builder as BS+import Control.Applicative   ((<|>)) import Control.Exception (bracket) import Data.Text (Text) import Database.MySQL.Simple import Database.MySQL.Simple.Param+import System.Environment    (getEnvironment) import Test.Hspec import Blaze.ByteString.Builder (toByteString)-#if MIN_VERSION_base(4,8,2)-#else-import Control.Applicative-import Data.Monoid-#endif +isCI :: IO Bool+isCI = do+    env <- getEnvironment+    return $ case lookup "TRAVIS" env <|> lookup "CI" env of+               Just "true" -> True+               _ -> False+ -- This is how to connect to our test database-testConn :: ConnectInfo-testConn = defaultConnectInfo {-               connectHost     = "127.0.0.1",-               connectUser     = "test",-               connectDatabase = "test"-           }+testConn :: Bool -> ConnectInfo+testConn ci = defaultConnectInfo {+                connectHost     = "127.0.0.1"+              , connectUser     = "test"+              , connectPassword = "test"+              , connectDatabase = "test"+              , connectPort     = if ci then 33306 else 3306+              }  main :: IO ()-main =-  bracket (connect testConn) close $ \conn ->-    hspec $ do-      unitSpec-      integrationSpec conn+main = do+    ci <- isCI+    bracket (connect $ testConn ci) close $ \conn ->+      hspec $ do+        describe "Database.MySQL.Simple.unitSpec" unitSpec+        describe "Database.MySQL.Simple.integrationSpec" $ integrationSpec conn  unitSpec :: Spec unitSpec = do@@ -46,9 +57,51 @@         Many [Plain _, Escape "foo", Plain _, Escape "bar", Plain _] -> pure ()         _ -> expectationFailure "expected a Many with specific contents" +  describe "splitQuery" $ do+    it "works for a single question mark" $ do+      splitQuery "select * from foo where name = ?"+        `shouldBe`+          ["select * from foo where name = ", ""]+    it "works with a question mark in a string literal" $ do+      splitQuery "select 'hello?'"+        `shouldBe`+          ["select 'hello?'"]+    it "works with many question marks" $ do+      splitQuery "select ? + ? + what from foo where bar = ?"+        `shouldBe`+          ["select ", " + ", " + what from foo where bar = ", ""]++instance Show BS.Builder where+  show = show . BS.toLazyByteString++instance Eq BS.Builder where+  a == b = BS.toLazyByteString a == BS.toLazyByteString b+ integrationSpec :: Connection -> Spec integrationSpec conn = do-  describe "the library" $ do+  describe "query_" $ do     it "can connect to a database" $ do       result <- query_ conn "select 1 + 1"       result `shouldBe` [Only (2::Int)]+    it "can have question marks in string literals" $ do+      result <- query_ conn "select 'hello?'"+      result `shouldBe` [Only ("hello?" :: Text)]+  describe "query" $ do+    it "can have question marks in string literals" $ do+      result <- query conn "select 'hello?'" ()+      result `shouldBe` [Only ("hello?" :: Text)]+    describe "with too many query params" $ do+      it "should have the right message" $ do+        (query conn "select 'hello?'" (Only ['a']) :: IO [Only Text])+          `shouldThrow`+            (\e -> fmtMessage e == "0 '?' characters, but 1 parameters")+    describe "with too few query params" $ do+      it "should have the right message" $ do+        (query conn "select 'hello?' = ?" () :: IO [Only Text])+          `shouldThrow`+            (\e -> fmtMessage e == "1 '?' characters, but 0 parameters")+  describe "formatQuery" $ do+    it "should not blow up on a question mark in string literal" $ do+      formatQuery conn "select 'hello?'" ()+        `shouldReturn`+          "select 'hello?'"