packages feed

postgresql-simple-interval 0.2025.7.11 → 0.2025.7.12

raw patch · 3 files changed

+64/−10 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

postgresql-simple-interval.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: postgresql-simple-interval-version: 0.2025.7.11+version: 0.2025.7.12 synopsis: A simple interval type for PostgreSQL. description:   This library provides a simple interval type for PostgreSQL. It supports@@ -27,6 +27,8 @@     attoparsec ^>=0.14.4,     base >=4.13 && <4.22,     bytestring >=0.10.10 && <0.13,+    postgresql-simple ^>=0.7,+    text >=1.2.4 && <1.3 || >=2.0 && <2.2,    default-language: Haskell2010   ghc-options:@@ -61,9 +63,7 @@   import: library   build-depends:     persistent ^>=2.17,-    postgresql-simple ^>=0.7,     scientific ^>=0.3.8,-    text >=1.2.4 && <1.3 || >=2.0 && <2.2,    -- cabal-gild: discover source/library   exposed-modules:
source/library/Database/PostgreSQL/Simple/Interval/Unstable.hs view
@@ -35,6 +35,12 @@ -- - 'Data.Time.CalendarDiffTime': Does not handle days. Embeds a --   @NominalDiffTime@. Is not bounded. -- - 'Data.Time.CalendarDiffDays': Does not handle seconds. Is not bounded.+--+-- WARNING: The PostgreSQL interval parser is broken in versions prior to 15.+-- It is not possible to round trip all intervals through PostgreSQL on those+-- versions. You should upgrade to at least PostgreSQL version 15. For more+-- information, see this patch:+-- <https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=e39f99046> data Interval = MkInterval   { months :: !Int.Int32,     days :: !Int.Int32,@@ -47,7 +53,7 @@   fromField = Postgres.attoFieldParser (== Postgres.intervalOid) parse  -- | Uses 'render'. Always includes an @interval@ prefix, like--- @interval '\@ 0 mon -1 day +2 us'@.+-- @interval '...'@. instance Postgres.ToField Interval where   toField = Postgres.Plain . ("interval '" <>) . (<> "'") . render @@ -204,24 +210,34 @@         <*> Function.on safeAdd microseconds x y  -- | Renders an interval to a 'Builder'. This always has the same format:--- @"\@ X mon Y day Z us"@, where @X@, @Y@, and @Z@ are signed integers.+-- @"\@ A mon B day C hour D min E sec F us"@, where @A@, @B@, @C@, @D@, @E@,+-- and @F@ are signed integers. -- -- This is not the most compact format, but it is very easy to interpret and -- does not require dealing with decimals (which could introduce precision -- problems). -- -- >>> render MkInterval { months = 0, days = -1, microseconds = 2 }--- "@ 0 mon -1 day +2 us"+-- "@ 0 mon -1 day 0 hour 0 min 0 sec +2 us" render :: Interval -> Builder.Builder render x =   let signed :: (Num a, Ord a) => (a -> Builder.Builder) -> a -> Builder.Builder       signed f n = (if n > 0 then "+" else "") <> f n+      (t1, u) = quotRem (microseconds x) 1000000+      (t2, s) = quotRem t1 60+      (h, m) = quotRem t2 60    in "@ "         <> signed Builder.int32Dec (months x)         <> " mon "         <> signed Builder.int32Dec (days x)         <> " day "-        <> signed Builder.int64Dec (microseconds x)+        <> signed Builder.int64Dec h+        <> " hour "+        <> signed Builder.int64Dec m+        <> " min "+        <> signed Builder.int64Dec s+        <> " sec "+        <> signed Builder.int64Dec u         <> " us"  -- | Parses an interval. This is not a general purpose parser. It only supports
source/test-suite/Main.hs view
@@ -1,14 +1,19 @@ {-# LANGUAGE NumDecimals #-} {-# LANGUAGE OverloadedStrings #-} +import qualified Control.Exception as Exception import qualified Control.Monad as Monad import qualified Data.Attoparsec.ByteString.Char8 as Attoparsec import qualified Data.ByteString as ByteString import qualified Data.ByteString.Builder as Builder import qualified Data.ByteString.Lazy as LazyByteString import qualified Data.Int as Int+import qualified Data.Text as Text+import qualified Database.PostgreSQL.Simple as Postgres import qualified Database.PostgreSQL.Simple.Interval.Unstable as I+import qualified Database.PostgreSQL.Simple.ToField as Postgres import qualified Test.Hspec as H+import qualified Text.Read as Read  main :: IO () main = H.hspec spec@@ -105,16 +110,20 @@   H.describe "render" $ do     H.it "works with zero" $ do       let actual = Builder.toLazyByteString $ I.render I.zero-      actual `H.shouldBe` "@ 0 mon 0 day 0 us"+      actual `H.shouldBe` "@ 0 mon 0 day 0 hour 0 min 0 sec 0 us"      H.it "works with positive components" $ do       let actual = Builder.toLazyByteString . I.render $ I.MkInterval 1 2 3-      actual `H.shouldBe` "@ +1 mon +2 day +3 us"+      actual `H.shouldBe` "@ +1 mon +2 day 0 hour 0 min 0 sec +3 us"      H.it "works with negative components" $ do       let actual = Builder.toLazyByteString . I.render $ I.MkInterval (-3) (-2) (-1)-      actual `H.shouldBe` "@ -3 mon -2 day -1 us"+      actual `H.shouldBe` "@ -3 mon -2 day 0 hour 0 min 0 sec -1 us" +    H.it "works with time components" $ do+      let actual = Builder.toLazyByteString . I.render $ I.MkInterval 0 0 3723000004+      actual `H.shouldBe` "@ 0 mon 0 day +1 hour +2 min +3 sec +4 us"+   H.describe "parse" $ do     H.it "fails with invalid input" $ do       let actual = Attoparsec.parseOnly I.parse "invalid"@@ -143,12 +152,41 @@         let actual = Attoparsec.parseOnly I.parse input         actual `H.shouldBe` Right interval +  H.describe "integration" $ do+    Monad.forM_ intervalStyles $ \(style, field) -> do+      H.describe ("with style " <> show style) $ do+        Monad.forM_ examples $ \example -> do+          H.it ("round trips " <> show (field example)) $ do+            Postgres.withConnect Postgres.defaultConnectInfo $ \connection -> do+              let interval = exampleInterval example+              result <- Exception.try . Postgres.withTransaction connection $ do+                Monad.void $ Postgres.execute connection "set local intervalstyle = ?" [style]+                Postgres.query connection "select ?" [interval]+              case result of+                Right actual -> actual `H.shouldBe` [Postgres.Only interval]+                Left somePostgresqlException -> do+                  rows <- Postgres.query_ connection "select version()"+                  case rows of+                    Postgres.Only text : _+                      | _ : rawVersion : _ <- Text.words text,+                        Just version <- Read.readMaybe (Text.unpack rawVersion),+                        version < (15 :: Double) ->+                          H.pendingWith $ "interval parsing broken with PostgreSQL version " <> show version+                    _ -> Exception.throwIO (somePostgresqlException :: Postgres.SomePostgreSqlException)+ data IntervalStyle   = Iso8601   | Postgres   | PostgresVerbose   | SqlStandard   deriving (Eq, Show)++instance Postgres.ToField IntervalStyle where+  toField style = Postgres.Plain $ case style of+    Iso8601 -> "iso_8601"+    Postgres -> "postgres"+    PostgresVerbose -> "postgres_verbose"+    SqlStandard -> "sql_standard"  data Example = MkExample   { exampleInterval :: I.Interval,