packages feed

postgresql-typed 0.4.2.1 → 0.4.2.2

raw patch · 5 files changed

+170/−22 lines, 5 filesdep +QuickCheckdep ~postgresql-binaryPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: QuickCheck

Dependency ranges changed: postgresql-binary

API changes (from Hackage documentation)

+ Database.PostgreSQL.Typed.Inet: bton32 :: (Word8, Word8, Word8, Word8) -> Word32
+ Database.PostgreSQL.Typed.Inet: instance Database.PostgreSQL.Typed.Types.PGColumn "cidr" Database.PostgreSQL.Typed.Inet.PGInet
+ Database.PostgreSQL.Typed.Inet: instance Database.PostgreSQL.Typed.Types.PGColumn "inet" Database.PostgreSQL.Typed.Inet.PGInet
+ Database.PostgreSQL.Typed.Inet: instance GHC.Classes.Eq Database.PostgreSQL.Typed.Inet.PGInet
+ Database.PostgreSQL.Typed.Inet: instance GHC.Read.Read Database.PostgreSQL.Typed.Inet.PGInet
+ Database.PostgreSQL.Typed.Range: instance GHC.Classes.Ord a => GHC.Classes.Ord (Database.PostgreSQL.Typed.Range.Range a)
- Database.PostgreSQL.Typed.Inet: PGInet :: !HostAddress -> !Int -> PGInet
+ Database.PostgreSQL.Typed.Inet: PGInet :: !HostAddress -> !Word8 -> PGInet
- Database.PostgreSQL.Typed.Inet: PGInet6 :: !HostAddress6 -> !Int -> PGInet
+ Database.PostgreSQL.Typed.Inet: PGInet6 :: !HostAddress6 -> !Word8 -> PGInet
- Database.PostgreSQL.Typed.Inet: [pgInetMask] :: PGInet -> !Int
+ Database.PostgreSQL.Typed.Inet: [pgInetMask] :: PGInet -> !Word8

Files

Database/PostgreSQL/Typed/Inet.hs view
@@ -9,28 +9,45 @@  module Database.PostgreSQL.Typed.Inet where +import Control.Monad (void, guard, liftM2) import qualified Data.ByteString.Char8 as BSC+import Data.Bits (shiftL, (.|.)) import Data.Maybe (fromJust)+import Data.Word (Word8, Word16, Word32)+import Foreign.Marshal.Array (withArray)+import Foreign.Ptr (castPtr)+import Foreign.Storable (peek) import qualified Network.Socket as Net+import Numeric (readDec, readHex) import System.IO.Unsafe (unsafeDupablePerformIO)+import qualified Text.ParserCombinators.ReadP as RP+import qualified Text.ParserCombinators.ReadPrec as RP (lift)+import Text.Read (Read(readPrec))  import Database.PostgreSQL.Typed.Types  data PGInet    = PGInet     { pgInetAddr :: !Net.HostAddress-    , pgInetMask :: !Int+    , pgInetMask :: !Word8     }   | PGInet6     { pgInetAddr6 :: !Net.HostAddress6-    , pgInetMask :: !Int+    , pgInetMask :: !Word8     }+  deriving (Eq)  sockAddrPGInet :: Net.SockAddr -> Maybe PGInet sockAddrPGInet (Net.SockAddrInet _ a) = Just $ PGInet a 32 sockAddrPGInet (Net.SockAddrInet6 _ _ a _) = Just $ PGInet6 a 128 sockAddrPGInet _ = Nothing +-- |Convert four bytes to network byte order, using unsafe casting.+-- 'Data.Word.byteSwap32' would be better, but I couldn't find a good way to determine host byte order.+bton32 :: (Word8, Word8, Word8, Word8) -> Word32+bton32 (b1, b2, b3, b4) = unsafeDupablePerformIO $+  withArray [b1, b2, b3, b4] (peek . castPtr)+ instance Show PGInet where   -- This is how Network.Socket's Show SockAddr does it:   show (PGInet a 32) = unsafeDupablePerformIO $ Net.inet_ntoa a@@ -39,9 +56,77 @@     Net.getNameInfo [Net.NI_NUMERICHOST] True False (Net.SockAddrInet6 0 0 a 0)   show (PGInet6 a m) = show (PGInet6 a 128) ++ '/' : show m +instance Read PGInet where+  -- This is even less pleasant, but we only have to deal with representations pg generates+  -- Not at all efficient, since in ReadP, but should get us by+  readPrec = RP.lift $ r4 RP.+++ r6 where+    r4i = do+      o1 <- rdec+      _ <- RP.char '.'+      o2 <- rdec+      _ <- RP.char '.'+      o3 <- rdec+      _ <- RP.char '.'+      o4 <- rdec+      return (o1, o2, o3, o4)+    -- ipv4+    r4 = do+      q <- r4i+      m <- mask 32+      return $ PGInet (bton32 q) m++    -- trailing ipv4 in ipv6+    r64 = do+      (b1, b2, b3, b4) <- r4i+      return [jb b1 b2, jb b3 b4]+    -- ipv6 pre-double-colon+    r6l 0 = return []+    r6l 2 = colon >> r6lc 2 RP.+++ r64+    r6l n = colon >> r6lc n+    r6lc n = r6lp n RP.+++ r6b n+    r6lp n = r6w (r6l (pred n))+    -- ipv6 double-colon+    r6b n = do+      colon+      r <- r6rp (pred n) RP.<++ return []+      let l = length r+      return $ replicate (n - l) 0 ++ r+    -- ipv6 post-double-colon+    r6r 0 = return []+    r6r n = (colon >> r6rp n) RP.<++ return []+    r6rp n+      | n >= 2 = r6rc n RP.+++ r64+      | otherwise = r6rc n+    r6rc n = r6w (r6r (pred n))+    r6w = liftM2 (:) rhex+    -- ipv6+    r6 = do+      [w1, w2, w3, w4, w5, w6, w7, w8] <- r6lp 8 RP.<++ (colon >> r6b 8)+      m <- mask 128+      return $ PGInet6 (jw w1 w2, jw w3 w4, jw w5 w6, jw w7 w8) m++    colon = void $ RP.char ':'+    mask m = RP.option m $ do+      _ <- RP.char '/'+      n <- rdec+      guard (n <= m)+      return n+    rdec :: RP.ReadP Word8+    rdec = RP.readS_to_P readDec+    rhex :: RP.ReadP Word16+    rhex = RP.readS_to_P readHex+    jw :: Word16 -> Word16 -> Word32+    jw x y = fromIntegral x `shiftL` 16 .|. fromIntegral y+    jb :: Word8 -> Word8 -> Word16+    jb x y = fromIntegral x `shiftL` 8 .|. fromIntegral y+ instance PGType "inet" instance PGType "cidr" instance PGParameter "inet" PGInet where   pgEncode _ = BSC.pack . show instance PGParameter "cidr" PGInet where   pgEncode _ = BSC.pack . show+instance PGColumn "inet" PGInet where+  pgDecode _ = read . BSC.unpack+instance PGColumn "cidr" PGInet where+  pgDecode _ = read . BSC.unpack
Database/PostgreSQL/Typed/Range.hs view
@@ -79,7 +79,7 @@     { lower :: LowerBound a     , upper :: UpperBound a     }-  deriving (Eq)+  deriving (Eq, Ord)  instance Functor Range where   fmap _ Empty = Empty@@ -181,7 +181,7 @@     _ -> l   u' = case u of     Bounded True b -> Bounded False (succ b)-    _ -> l+    _ -> u  -- |Contains range (@>), (<@) :: Ord a => Range a -> Range a -> Bool
postgresql-typed.cabal view
@@ -1,5 +1,5 @@ Name:          postgresql-typed-Version:       0.4.2.1+Version:       0.4.2.2 Cabal-Version: >= 1.8 License:       BSD3 License-File:  COPYING@@ -97,9 +97,10 @@     CPP-options: -DUSE_AESON  test-suite test-  build-depends: base, network, time, bytestring, postgresql-typed+  build-depends: base, network, time, bytestring, postgresql-typed, QuickCheck   type: exitcode-stdio-1.0   main-is: Main.hs+  Other-Modules: Connect   buildable: True   hs-source-dirs: test   Extensions: TemplateHaskell, QuasiQuotes
+ test/Connect.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE OverloadedStrings #-}+module Connect where++import Database.PostgreSQL.Typed (PGDatabase(..), defaultPGDatabase)+import Network (PortID(UnixSocket))++db :: PGDatabase+db = defaultPGDatabase+  { pgDBPort = UnixSocket "/tmp/.s.PGSQL.5432"+  , pgDBName = "templatepg"+  , pgDBUser = "templatepg"+  , pgDBDebug = True+  }+
test/Main.hs view
@@ -2,16 +2,20 @@ -- {-# OPTIONS_GHC -ddump-splices #-} module Main (main) where -import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BSC import Data.Int (Int32) import qualified Data.Time as Time import System.Exit (exitSuccess, exitFailure)+import qualified Test.QuickCheck as Q+import Test.QuickCheck.Test (isSuccess)  import Database.PostgreSQL.Typed import Database.PostgreSQL.Typed.Types (OID) import Database.PostgreSQL.Typed.Array () import qualified Database.PostgreSQL.Typed.Range as Range import Database.PostgreSQL.Typed.Enum+import Database.PostgreSQL.Typed.Inet  import Connect @@ -26,6 +30,42 @@  makePGEnum "myenum" "MyEnum" ("MyEnum_" ++) +instance Q.Arbitrary MyEnum where+  arbitrary = Q.arbitraryBoundedEnum++instance Q.Arbitrary Time.Day where+  arbitrary = Time.ModifiedJulianDay <$> Q.arbitrary+instance Q.Arbitrary Time.DiffTime where+  arbitrary = Time.picosecondsToDiffTime . (1000000 *) <$> Q.arbitrary+instance Q.Arbitrary Time.UTCTime where+  arbitrary = Time.UTCTime <$> Q.arbitrary <*> ((Time.picosecondsToDiffTime . (1000000 *)) <$> Q.choose (0,86399999999))+instance Q.Arbitrary Time.LocalTime where+  arbitrary = Time.utcToLocalTime Time.utc <$> Q.arbitrary++instance Q.Arbitrary a => Q.Arbitrary (Range.Bound a) where+  arbitrary = do+    u <- Q.arbitrary+    if u+      then return $ Range.Unbounded+      else Range.Bounded <$> Q.arbitrary <*> Q.arbitrary+instance (Ord a, Q.Arbitrary a) => Q.Arbitrary (Range.Range a) where+  arbitrary = Range.range <$> Q.arbitrary <*> Q.arbitrary++instance Q.Arbitrary PGInet where+  arbitrary = do+    v6 <- Q.arbitrary+    if v6+      then PGInet6 <$> Q.arbitrary <*> ((`mod` 129) <$> Q.arbitrary)+      else PGInet  <$> Q.arbitrary <*> ((`mod`  33) <$> Q.arbitrary)++newtype Str = Str { strString :: [Char] } deriving (Eq, Show)+strByte :: Str -> BS.ByteString+strByte = BSC.pack . strString+byteStr :: BS.ByteString -> Str+byteStr = Str . BSC.unpack+instance Q.Arbitrary Str where+  arbitrary = Str <$> Q.listOf (Q.choose (' ', '~'))+ simple :: PGConnection -> OID -> IO [String] simple c t = pgQuery c [pgSQL|SELECT typname FROM pg_catalog.pg_type WHERE oid = ${t} AND oid = $1|] simpleApply :: PGConnection -> OID -> IO [Maybe String]@@ -35,24 +75,32 @@ preparedApply :: PGConnection -> Int32 -> IO [String] preparedApply c = pgQuery c . [pgSQL|$(integer)SELECT typname FROM pg_catalog.pg_type WHERE oid = $1|] +selectProp :: PGConnection -> Bool -> Int32 -> Float -> Time.LocalTime -> Time.UTCTime -> Time.Day -> Time.DiffTime -> Str -> [Maybe Str] -> Range.Range Int32 -> MyEnum -> PGInet -> Q.Property+selectProp c b i f t z d p s l r e a = Q.ioProperty $ do+  [(Just b', Just i', Just f', Just s', Just d', Just t', Just z', Just p', Just l', Just r', Just e', Just a')] <- pgQuery c+    [pgSQL|$SELECT ${b}::bool, ${Just i}::int, ${f}::float4, ${strString s}::varchar, ${Just d}::date, ${t}::timestamp, ${z}::timestamptz, ${p}::interval, ${map (fmap strByte) l}::text[], ${r}::int4range, ${e}::myenum, ${a}::inet|]+  return $ Q.conjoin +    [ i Q.=== i'+    , b Q.=== b'+    , strString s Q.=== s'+    , f Q.=== f'+    , d Q.=== d'+    , t Q.=== t'+    , z Q.=== z'+    , p Q.=== p'+    , l Q.=== map (fmap byteStr) l'+    , Range.normalize' r Q.=== r'+    , e Q.=== e'+    , a Q.=== a'+    ]+ main :: IO () main = do   c <- pgConnect db-  z <- Time.getZonedTime-  let i = 1 :: Int32-      b = True-      f = 3.14 :: Float-      t = Time.zonedTimeToLocalTime z-      d = Time.localDay t-      p = -34881559 :: Time.DiffTime-      s = "\"hel\\o'" :: String-      l = [Just "a\\\"b,c", Nothing, Just "null", Just "nullish" :: Maybe ByteString]-      r = Range.normal (Just (-2 :: Int32)) Nothing-      e = MyEnum_XX_ye-  [(Just b', Just i', Just f', Just s', Just d', Just t', Just z', Just p', Just l', Just r', Just e')] <- pgQuery c-    [pgSQL|$SELECT ${b}::bool, ${Just i}::int, ${f}::float4, ${s}::varchar(10), ${Just d}::date, ${t}::timestamp, ${Time.zonedTimeToUTC z}::timestamptz, ${p}::interval, ${l}::text[], ${r}::int4range, ${e}::myenum|]-  assert $ i == i' && b == b' && s == s' && f == f' && d == d' && t == t' && Time.zonedTimeToUTC z == z' && p == p' && l == l' && r == r' && e == e' +  r <- Q.quickCheckResult $ selectProp c+  assert $ isSuccess r+   ["box"] <- simple c 603   [Just "box"] <- simpleApply c 603   [Just "box"] <- prepared c 603 "box"@@ -60,7 +108,7 @@   [Just "line"] <- prepared c 628 "line"   ["line"] <- preparedApply c 628 -  assert $ [pgSQL|#abc${f}def|] == "abc3.14::realdef"+  assert $ [pgSQL|#abc${3.14 :: Float}def|] == "abc3.14::realdef"    assert $ pgEnumValues == [(MyEnum_abc, "abc"), (MyEnum_DEF, "DEF"), (MyEnum_XX_ye, "XX_ye")]