persistent-postgresql: Implement Postgres interval support#1053
Conversation
|
Can someone help me getting unstuck with this? |
parsonsmatt
left a comment
There was a problem hiding this comment.
well done! I think I've noticed the problem. The call to convertPV is telling postgresql-simple to use the FromField instance for ByteString, but we need to have it pick a FromField instance for PgInterval (which doesn't currently exist). Then, rather unfortunately, we need to convert it back to a ByteString to stuff it in the PersistDbSpecific.
| , (k PS.time, convertPV PersistTimeOfDay) | ||
| , (k PS.timestamp, convertPV (PersistUTCTime. localTimeToUTC utc)) | ||
| , (k PS.timestamptz, convertPV PersistUTCTime) | ||
| , (k PS.interval, convertPV PersistDbSpecific) |
There was a problem hiding this comment.
I think this line is wrong.
convertPV is defined as:
convertPV :: PGFF.FromField a => (a -> b) -> Getter b
convertPV f = (fmap f .) . PGFF.fromFieldThis means that it will pick a ByteString instance of FromField. But that's not what we want - we want an instance of FromField that picks NominalDiffTime, and then converts it to ByteString so it can be wrapped in PersistDbSpecific.
The current code path picks this instance when parsing:
-- | bytea, name, text, \"char\", bpchar, varchar, unknown
instance FromField SB.ByteString where
fromField f dat = if typeOid f == TI.byteaOid
then unBinary <$> fromField f dat
else doFromField f okText' pure datdoFromField is defined here:
doFromField :: forall a . (Typeable a)
=> Field -> Compat -> (ByteString -> Conversion a)
-> Maybe ByteString -> Conversion a
doFromField f isCompat cvt (Just bs)
| isCompat (typeOid f) = cvt bs
| otherwise = returnError Incompatible f "types incompatible"
doFromField f _ _ _ = returnError UnexpectedNull f ""There's our "types incompatible" error message. The isCompat function passed in is this thing, which appears to just check if the type is one of a few compatbiel text-like things:
okText' = eq TI.nameOid \/ eq TI.textOid \/ eq TI.charOid
\/ eq TI.bpcharOid \/ eq TI.varcharOid \/ eq TI.unknownOidNow... postgresql-simple doesn't have an instance of FromField NominalDiffTime. It does have an instance of ToField NominalDiffTime. So we need to provide an instance of PGTF.FromField PgInterval.
The instance of FromField UTCTime is here:
-- | timestamptz
instance FromField UTCTime where
fromField = ff TI.timestamptzOid "UTCTime" parseUTCTimeand parseUTCTime is a function ByteString -> Either String UTCTime. I think you could write a parseNominalDiffTime that worked here, and then use that, and we'd be set.
| , (1183, listOf PersistTimeOfDay) | ||
| , (1115, listOf PersistUTCTime) | ||
| , (1185, listOf PersistUTCTime) | ||
| , (1187, listOf PersistDbSpecific) |
There was a problem hiding this comment.
I think this would also need to be updated to pick the right instance.
29b3567 to
3b81a37
Compare
|
@parsonsmatt This is what I have at the moment. Am confused about the negative decimal interval tests. Specifying a negative decimal value returns me a positive one. Also refering 8.5.4 Interval Input and mixed intervals seemed problematic. Using Debug.Trace gives me this. The After some more debugging, I found something wierd. The input to N.B. In the commit, for now the test is rigged to purposely pass. |
parsonsmatt
left a comment
There was a problem hiding this comment.
Great progress! That's some odd behavior though.
| Nothing -> PGFF.returnError PGFF.UnexpectedNull f "" | ||
| Just dat -> case P.parseOnly (nominalDiffTime <* P.endOfInput) dat of | ||
| Left msg -> PGFF.returnError PGFF.ConversionFailed f msg | ||
| Right t -> return $ PgInterval t |
There was a problem hiding this comment.
nice, this looks good!
| case P.parseOnly (P.signed P.rational <* P.char 's' <* P.endOfInput) bs of | ||
| Left _ -> Left $ fromPersistValueError "PgInterval" "Interval" x | ||
| Right i -> Right $ PgInterval i | ||
| fromPersistValue x = Left $ fromPersistValueError "PgInterval" "Interval" x |
There was a problem hiding this comment.
This also looks good to me.
| , (k PS.time, convertPV PersistTimeOfDay) | ||
| , (k PS.timestamp, convertPV (PersistUTCTime. localTimeToUTC utc)) | ||
| , (k PS.timestamptz, convertPV PersistUTCTime) | ||
| , (k PS.interval, convertPV (PersistDbSpecific . pgIntervalToBs)) |
| , (1183, listOf PersistTimeOfDay) | ||
| , (1115, listOf PersistUTCTime) | ||
| , (1185, listOf PersistUTCTime) | ||
| , (1187, listOf (PersistDbSpecific . pgIntervalToBs)) |
| _ <- runMigrationSilent pgIntervalMigrate | ||
| rid <- insert $ PgIntervalDb $ PgInterval (-0.01) | ||
| r <- getJust rid | ||
| liftIO $ r @?= (PgIntervalDb $ PgInterval 0.01) |
There was a problem hiding this comment.
So this is the test that is "wrong", right? That's very odd.
| specs runDb = do | ||
| describe "Postgres Interval tests" $ do | ||
| it "test for insert and read of negative intervals" $ runDb $ do | ||
| _ <- runMigrationSilent pgIntervalMigrate |
There was a problem hiding this comment.
The migrations are usually stored higher up in the test tree so that individual cases don't have to deal with them.
There was a problem hiding this comment.
eg in persistent-postgresql, migrations are done in test/Main.hs at line ~100-120
|
|
||
| specs :: (MonadUnliftIO m, MonadFail m) => RunDb SqlBackend m -> Spec | ||
| specs runDb = do | ||
| describe "Postgres Interval tests" $ do |
There was a problem hiding this comment.
These round-trip tests are a great candidate for QuickCheck. We can write:
describe "pg interval" $ do
prop "round trips" $ \time -> runDb $ do
let example = PgIntervalDb $ PgInterval time
rid <- insert example
r <- getJust rid
liftIO $ r `shouldBe` exampleand it should catch edge casees.
|
In postgres, I get: So, let's look at the parser. We pick up the sign in the hour bit. Then, we calculate the time in seconds using this line. But suppose that we have an (s = 0.01) + 60 * (fromIntegral (m = 0)) + 60 * 60 * (fromIntegral (h = -0))
0.01 + 60 * 0 + 60 * 60 * -0
0.01 + 0 + 0
0.01So I think we need to multiply the absolute value of the signum h * (s + 60 * fromIntegral m + 60 * 60 * fromIntegral (abs h)) |
|
Thanks for the detailed help. Tracking the sign on Hour seems to be not enough. I think I would have to explicitly detect the sign during running of Parser and setup the sign myself. Edit: Got it working now. Will look into cleaning up the test based on points you mentioned earlier and also rebase. |
|
Hmm...The property seems to have caught the edge cases I guess due to rounding errors. |
|
|
3b81a37 to
e2c766a
Compare
|
Fixed the test and rebased. |
b183b15 to
a02a4e8
Compare
parsonsmatt
left a comment
There was a problem hiding this comment.
awesome! Just needs the changelog entry moved to be under the 2.11.0.0 :)
persistent-postgresql/ChangeLog.md
Outdated
|
|
||
| ## 2.11.1 | ||
|
|
||
| * Implement interval support. #1053 |
There was a problem hiding this comment.
| * Implement interval support. #1053 | |
| * Implement interval support. [#1053](https://github.com/yesodweb/persistent/pull/1053) |
This will get batched in with persistent-2.11.0.0, since that is not yet released. Could you move the bullet down there?
a02a4e8 to
dc1c784
Compare
Implements yesodweb#638. Signed-off-by: Sanchayan Maity <[email protected]>
dc1c784 to
5b8ada2
Compare
|
Thanks so much @SanchayanMaity , this is great 😄 |
For issue #638
Signed-off-by: Sanchayan Maity [email protected]
Before submitting your PR, check that you've:
@sincedeclarations to the HaddockAfter submitting your PR: