mysql-haskell 1.2.0 → 1.2.1
raw patch · 9 files changed
+171/−9 lines, 9 filesdep ~cryptondep ~directoryPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: crypton, directory
API changes (from Hackage documentation)
+ Database.MySQL.Base: connectUnixSocket :: FilePath -> ConnectInfo -> IO MySQLConn
+ Database.MySQL.Base: connectUnixSocketDetail :: FilePath -> ConnectInfo -> IO (Greeting, MySQLConn)
+ Database.MySQL.Connection: connectUnixSocket :: FilePath -> ConnectInfo -> IO MySQLConn
+ Database.MySQL.Connection: connectUnixSocketDetail :: FilePath -> ConnectInfo -> IO (Greeting, MySQLConn)
Files
- ChangeLog.md +7/−0
- mysql-haskell.cabal +16/−4
- src/Data/TLSSetting.hs +3/−3
- src/Database/MySQL/Base.hs +2/−0
- src/Database/MySQL/Connection.hs +41/−0
- test/Aeson.hs +1/−0
- test/AesonBP.hs +1/−0
- test/Integration.hs +7/−2
- test/UnixSocket.hs +93/−0
ChangeLog.md view
@@ -1,5 +1,12 @@ # Revision history for mysql-haskell +## 1.2.1 -- 2026.03.23++ Export `connectUnixSocket` function.++ Tweak cabal file to better handle crypton vs cryptonite.++ Fix version cut off for tls compatibility.++ Fix missing `liftA2` import when running tests on GHC 9.4.++ Add integration test for Unix socket.+ ## 1.2.0 -- 2026.03.07 + Add support for caching_sha2_password authentiation.
mysql-haskell.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: mysql-haskell-version: 1.2.0+version: 1.2.1 synopsis: pure haskell MySQL driver description: pure haskell MySQL driver. license: BSD-3-Clause@@ -42,6 +42,10 @@ type: git location: https://github.com/winterland1989/mysql-haskell +flag crypton-1-1+ description: Build with crypton >= 1.1.0 (uses ram instead of memory for ByteArrayAccess)+ default: True+ manual: False common common-options ghc-options:@@ -90,16 +94,13 @@ blaze-textual >=0.2 && <0.3, bytestring >=0.10.2.0 && <0.12 || ^>=0.12.0, bytestring-lexing >=0.5 && <0.6,- crypton >=0.31 && <0.40 || ^>=1.0.0 || ^>=1.1.0, crypton-x509 >=1.5 && <2.0, crypton-x509-store >=1.5 && <2.0, crypton-x509-system >=1.5 && <2.0, data-default-class >=0.1.2 && <0.2 || ^>=0.2.0, deepseq >=1.4.6 && <1.5 || ^>=1.5.0, io-streams >=1.2 && <2.0,- memory >=0.14.4 && <0.19, monad-loops >=0.4 && <0.5,- ram >=0.20 && <0.23, network >=2.3 && <4.0, pem >=0.2.4 && <0.3, scientific >=0.3 && <0.4,@@ -109,6 +110,15 @@ vector >=0.8 && <0.13 || ^>=0.13.0, word-compat >=0.0 && <0.1 + if flag(crypton-1-1)+ build-depends:+ crypton ^>=1.1.0,+ ram >=0.20 && <0.23+ else+ build-depends:+ crypton >=0.31 && <0.40 || ^>=1.0.0,+ memory >=0.14.4 && <0.19+ default-extensions: DeriveDataTypeable DeriveGeneric@@ -177,11 +187,13 @@ SelectOne TextRow TextRowNew+ UnixSocket hs-source-dirs: test build-depends: base, bytestring >=0.10,+ directory, io-streams, mysql-haskell, tasty >=0.11 && <2.0,
src/Data/TLSSetting.hs view
@@ -26,7 +26,7 @@ import Paths_mysql_haskell (getDataFileName) import qualified System.X509 as X509 -#if !MIN_VERSION_tls(2,0,0)+#if !MIN_VERSION_tls(2,1,4) import Data.Default.Class (def) #endif @@ -144,8 +144,8 @@ } } --- Compatibility shims for tls < 2.0 vs >= 2.0-#if MIN_VERSION_tls(2,0,0)+-- Compatibility shims for tls < 2.1.4 vs >= 2.1.4+#if MIN_VERSION_tls(2,1,4) defaultServerParams' :: TLS.ServerParams defaultServerParams' = TLS.defaultParamsServer
src/Database/MySQL/Base.hs view
@@ -32,6 +32,8 @@ , defaultConnectInfoMB4 , connect , connectDetail+ , connectUnixSocket+ , connectUnixSocketDetail , close , ping -- * Direct query
src/Database/MySQL/Connection.hs view
@@ -42,6 +42,7 @@ import Database.MySQL.Protocol.Command import Database.MySQL.Protocol.Packet import Network.Socket (HostName, PortNumber)+import qualified Network.Socket as N import System.IO.Streams (InputStream) import qualified System.IO.Streams as Stream import qualified System.IO.Streams.TCP as TCP@@ -138,6 +139,46 @@ return (greet, conn) connectWithBufferSize h p bs = TCP.connectSocket h p >>= TCP.socketToConnection bs+ write c a = TCP.send c $ Binary.runPut . Binary.put $ a++-- | Connect to MySQL via a Unix domain socket.+--+connectUnixSocket :: FilePath -> ConnectInfo -> IO MySQLConn+connectUnixSocket socketPath ci = fmap snd (connectUnixSocketDetail socketPath ci)++-- | Connect to MySQL via a Unix domain socket with 'Greeting' back,+-- so you can find server's version .etc.+--+connectUnixSocketDetail :: FilePath -> ConnectInfo -> IO (Greeting, MySQLConn)+connectUnixSocketDetail socketPath (ConnectInfo _host _port db user pass charset)+ = bracketOnError open TCP.close go+ where+ open = bracketOnError+ (N.socket N.AF_UNIX N.Stream 0)+ N.close+ (\sock -> do+ N.connect sock (N.SockAddrUnix socketPath)+ TCP.socketToConnection bUFSIZE (sock, N.SockAddrUnix socketPath)+ )+ go c = do+ let is = TCP.source c+ is' <- decodeInputStream is+ p <- readPacket is'+ greet <- decodeFromPacket p+ let auth = mkAuth db user pass charset greet+ write c $ encodeToPacket 1 auth+ q <- readPacket is'+ completeAuth is' (write c) pass q plainFullAuth+ consumed <- newIORef True+ let waitNotMandatoryOK = catch+ (void (waitCommandReply is')) -- server will either reply an OK packet+ ((\_ -> return ()) :: SomeException -> IO ()) -- or directy close the connection+ conn = MySQLConn is'+ (write c)+ (writeCommand COM_QUIT (write c) >> waitNotMandatoryOK >> TCP.close c)+ consumed+ return (greet, conn)+ write c a = TCP.send c $ Binary.runPut . Binary.put $ a mkAuth :: ByteString -> ByteString -> ByteString -> Word8 -> Greeting -> Auth
test/Aeson.hs view
@@ -6,6 +6,7 @@ import Data.ByteString.Builder (Builder, byteString, toLazyByteString, charUtf8, word8) +import Control.Applicative (liftA2) import Control.DeepSeq (NFData(..)) import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific, skipSpace, string)
test/AesonBP.hs view
@@ -6,6 +6,7 @@ import Data.ByteString.Builder (Builder, byteString, toLazyByteString, charUtf8, word8) +import Control.Applicative (liftA2) import Data.Bits ((.|.), shiftL) import Data.ByteString (ByteString) import Data.Char (chr)
test/Integration.hs view
@@ -8,17 +8,20 @@ import qualified RoundtripBit import qualified RoundtripYear import qualified SelectOne+import qualified UnixSocket main :: IO () main = do- -- Connect briefly to detect the server version, so we can conditionally- -- include tests that only apply to certain servers (e.g. caching_sha2 on MySQL 8.0+). (greet, c) <- connectDetail defaultConnectInfo {ciUser = "testMySQLHaskell", ciDatabase = "testMySQLHaskell"} close c let ver = greetingVersion greet isMySql80 = "8." `B.isPrefixOf` ver || "9." `B.isPrefixOf` ver++ -- Probe for a Unix domain socket (MYSQL_UNIX_SOCKET env var, then common paths).+ mSockPath <- UnixSocket.findSocketPath+ defaultMain $ testGroup "mysql-integration" $ [ SelectOne.tests , RoundtripBit.tests@@ -28,3 +31,5 @@ -- caching_sha2_password is MySQL 8.0+ only (MariaDB does not support it). -- The sha2 test users are created by the nix CI config for the MySQL 8.0 VM. ++ [ CachingSha2.tests | isMySql80 ]+ -- Unix socket tests are included only when a socket file is found.+ ++ [ UnixSocket.tests p | Just p <- [mSockPath] ]
+ test/UnixSocket.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE ScopedTypeVariables #-}++module UnixSocket (tests, findSocketPath) where++import qualified Data.ByteString as B+import Database.MySQL.Base+import qualified System.IO.Streams as Stream+import System.Directory (doesFileExist)+import System.Environment (lookupEnv)+import Test.Tasty+import Test.Tasty.HUnit++-- | Common Unix socket paths for MySQL/MariaDB.+defaultSocketPaths :: [FilePath]+defaultSocketPaths =+ [ "/tmp/mysql.sock" -- macOS Homebrew+ , "/var/run/mysqld/mysqld.sock" -- Debian/Ubuntu+ , "/run/mysqld/mysqld.sock" -- NixOS, Arch+ , "/var/lib/mysql/mysql.sock" -- RHEL/CentOS+ , "/tmp/mysqld.sock" -- some macOS installs+ ]++-- | Find the MySQL Unix socket path. Checks the @MYSQL_UNIX_SOCKET@+-- environment variable first, then probes common default locations.+findSocketPath :: IO (Maybe FilePath)+findSocketPath = do+ envPath <- lookupEnv "MYSQL_UNIX_SOCKET"+ case envPath of+ Just p -> do+ exists <- doesFileExist p+ return $ if exists then Just p else Nothing+ Nothing -> firstExisting defaultSocketPaths+ where+ firstExisting [] = return Nothing+ firstExisting (p:ps) = do+ exists <- doesFileExist p+ if exists then return (Just p) else firstExisting ps++tests :: FilePath -> TestTree+tests socketPath = testGroup "unix-socket"+ [ testCaseSteps "connectUnixSocket: SELECT 1" $ \step -> do+ step "connecting via unix socket..."+ c <- connectUnixSocket socketPath defaultConnectInfo+ { ciUser = "testMySQLHaskell"+ , ciDatabase = "testMySQLHaskell"+ }++ step "executing SELECT 1..."+ (_, is) <- query_ c "SELECT 1"+ Just row <- Stream.read is+ assertBool "SELECT 1 returns 1"+ (row == [MySQLInt32 1] || row == [MySQLInt64 1])+ Stream.skipToEof is++ close c++ , testCaseSteps "connectUnixSocketDetail: returns greeting" $ \step -> do+ step "connecting via unix socket with detail..."+ (greet, c) <- connectUnixSocketDetail socketPath defaultConnectInfo+ { ciUser = "testMySQLHaskell"+ , ciDatabase = "testMySQLHaskell"+ }++ step "checking greeting version..."+ let ver = greetingVersion greet+ assertBool "greeting version is not empty" (not $ B.null ver)++ step "executing query to verify connection..."+ (_, is) <- query_ c "SELECT 1 + 1"+ Just row <- Stream.read is+ assertBool "SELECT 1+1 returns 2"+ (row == [MySQLInt32 2] || row == [MySQLInt64 2])+ Stream.skipToEof is++ close c++ , testCaseSteps "unix socket: prepared statement roundtrip" $ \step -> do+ step "connecting via unix socket..."+ c <- connectUnixSocket socketPath defaultConnectInfo+ { ciUser = "testMySQLHaskell"+ , ciDatabase = "testMySQLHaskell"+ }++ step "executing prepared statement..."+ stmt <- prepareStmt c "SELECT ? + 1"+ (_, is) <- queryStmt c stmt [MySQLInt32 41]+ Just row <- Stream.read is+ assertBool "41 + 1 = 42"+ (row == [MySQLInt32 42] || row == [MySQLInt64 42])+ Stream.skipToEof is++ close c+ ]