From ab396b9db6ff78462436924c3c2641d71ea22d7d Mon Sep 17 00:00:00 2001 From: Paul Brinkmeier Date: Sat, 16 Sep 2023 04:58:23 +0200 Subject: [PATCH] Make getColumnTable report missing columns --- lib/Database/PostgreSQL/Opium.hs | 26 +++++++++---------- lib/Database/PostgreSQL/Opium/FromField.hs | 1 + test/Database/PostgreSQL/OpiumSpec.hs | 29 +++++++++++----------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/Database/PostgreSQL/Opium.hs b/lib/Database/PostgreSQL/Opium.hs index 71fcd2d..abaab2e 100644 --- a/lib/Database/PostgreSQL/Opium.hs +++ b/lib/Database/PostgreSQL/Opium.hs @@ -62,16 +62,16 @@ fetchResult result = do runExceptT $ mapM (ExceptT . flip fromRow result) [0..nRows - 1] class FromRow a where - getColumnTable :: Proxy a -> Result -> IO [LibPQ.Column] - default getColumnTable :: (Generic a, GetColumnTable' (Rep a)) => Proxy a -> Result -> IO [LibPQ.Column] - getColumnTable Proxy = getColumnTable' @(Rep a) Proxy + getColumnTable :: Proxy a -> Result -> IO (Either Error [Column]) + default getColumnTable :: (Generic a, GetColumnTable' (Rep a)) => Proxy a -> Result -> IO (Either Error [Column]) + getColumnTable Proxy = runExceptT . getColumnTable' @(Rep a) Proxy fromRow :: Row -> Result -> IO (Either Error a) default fromRow :: (Generic a, FromRow' (Rep a)) => Row -> Result -> IO (Either Error a) fromRow row result = fmap to <$> fromRow' row result class GetColumnTable' f where - getColumnTable' :: Proxy (f p) -> Result -> IO [LibPQ.Column] + getColumnTable' :: Proxy (f p) -> Result -> ExceptT Error IO [Column] instance GetColumnTable' f => GetColumnTable' (M1 D c f) where getColumnTable' Proxy = getColumnTable' @f Proxy @@ -83,17 +83,15 @@ instance (GetColumnTable' f, GetColumnTable' g) => GetColumnTable' (f :*: g) whe getColumnTable' Proxy result = (++) <$> getColumnTable' @f Proxy result <*> getColumnTable' @g Proxy result -checkColumn :: FromField f => Proxy f -> String -> Result -> IO [Column] -checkColumn Proxy nameStr result = LibPQ.fnumber result name >>= \case - Just column -> do - -- TODO: Rewrite FromField to check whether oid works for decoding t - _oid <- LibPQ.ftype result column - pure [column] - Nothing -> do - -- TODO: Return ErrorMissingColumn - undefined +checkColumn :: FromField f => Proxy f -> String -> Result -> ExceptT Error IO [Column] +checkColumn Proxy nameStr result = do + column <- ExceptT $ maybe (Left $ ErrorMissingColumn nameText) Right <$> LibPQ.fnumber result name + -- TODO: Rewrite FromField to check whether oid works for decoding t + _oid <- ExceptT $ Right <$> LibPQ.ftype result column + pure [column] where - name = Encoding.encodeUtf8 $ Text.pack nameStr + nameText = Text.pack nameStr + name = Encoding.encodeUtf8 nameText instance {-# OVERLAPPABLE #-} (FromField t, KnownSymbol nameSym) => GetColumnTable' (M1 S ('MetaSel ('Just nameSym) nu ns dl) (Rec0 t)) where getColumnTable' Proxy = checkColumn @t Proxy $ symbolVal @nameSym Proxy diff --git a/lib/Database/PostgreSQL/Opium/FromField.hs b/lib/Database/PostgreSQL/Opium/FromField.hs index 4b0e621..a407ffb 100644 --- a/lib/Database/PostgreSQL/Opium/FromField.hs +++ b/lib/Database/PostgreSQL/Opium/FromField.hs @@ -29,6 +29,7 @@ p \/ q = \x -> p x || q x data FieldError = FieldErrorUnexpectedNull + -- TODO: Move this to the normal Error | FieldErrorInvalidOid Oid | FieldErrorInvalidField Oid Text String deriving (Eq, Show) diff --git a/test/Database/PostgreSQL/OpiumSpec.hs b/test/Database/PostgreSQL/OpiumSpec.hs index 4f5e24e..86c8a12 100644 --- a/test/Database/PostgreSQL/OpiumSpec.hs +++ b/test/Database/PostgreSQL/OpiumSpec.hs @@ -36,19 +36,24 @@ spec = do it "Gets the column table for a result" $ \conn -> do Just result <- LibPQ.execParams conn "SELECT name, age FROM person" [] LibPQ.Text columnTable <- Opium.getColumnTable @Person Proxy result - columnTable `shouldBe` [0, 1] + columnTable `shouldBe` Right [0, 1] it "Gets the numbers right for funky configurations" $ \conn -> do Just result0 <- LibPQ.execParams conn "SELECT age, name FROM person" [] LibPQ.Text columnTable0 <- Opium.getColumnTable @Person Proxy result0 - columnTable0 `shouldBe` [1, 0] + columnTable0 `shouldBe` Right [1, 0] Just result1 <- LibPQ.execParams conn "SELECT 0 a, 1 b, 2 c, age, 4 d, name FROM person" [] LibPQ.Text columnTable1 <- Opium.getColumnTable @Person Proxy result1 - columnTable1 `shouldBe` [5, 3] + columnTable1 `shouldBe` Right [5, 3] + + it "Fails for missing columns" $ \conn -> do + Just result <- LibPQ.execParams conn "SELECT 0 a FROM person" [] LibPQ.Text + columnTable <- Opium.getColumnTable @Person Proxy result + columnTable `shouldBe` Left (Opium.ErrorMissingColumn "name") describe "fromRow" $ do - it "decodes rows in a Result" $ \conn -> do + it "Decodes rows in a Result" $ \conn -> do Just result <- LibPQ.execParams conn "SELECT * FROM person" [] LibPQ.Text row0 <- Opium.fromRow @Person (LibPQ.Row 0) result @@ -57,35 +62,31 @@ spec = do row1 <- Opium.fromRow @Person (LibPQ.Row 1) result row1 `shouldBe` Right (Person "albus" 103) - it "decodes NULL into Nothing for Maybes" $ \conn -> do + it "Decodes NULL into Nothing for Maybes" $ \conn -> do Just result <- LibPQ.execParams conn "SELECT NULL AS a" [] LibPQ.Text row <- Opium.fromRow (LibPQ.Row 0) result row `shouldBe` Right (MaybeTest Nothing) - it "decodes values into Just for Maybes" $ \conn -> do + it "Decodes values into Just for Maybes" $ \conn -> do Just result <- LibPQ.execParams conn "SELECT 'abc' AS a" [] LibPQ.Text row <- Opium.fromRow (LibPQ.Row 0) result row `shouldBe` Right (MaybeTest $ Just "abc") describe "fetch_" $ do - it "retrieves a list of rows" $ \conn -> do + it "Retrieves a list of rows" $ \conn -> do rows <- Opium.fetch_ conn "SELECT * FROM person" rows `shouldBe` Right [Person "paul" 25, Person "albus" 103] - it "fails for invalid queries" $ \conn -> do + it "Fails for invalid queries" $ \conn -> do rows <- Opium.fetch_ @Person conn "MRTLBRNFT" rows `shouldSatisfy` isLeft - it "fails for missing columns" $ \conn -> do - rows <- Opium.fetch_ @Person conn "SELECT name FROM person" - rows `shouldBe` Left (Opium.ErrorMissingColumn "age") - - it "fails for unexpected NULLs" $ \conn -> do + it "Fails for unexpected NULLs" $ \conn -> do rows <- Opium.fetch_ @Person conn "SELECT NULL AS name, 0 AS age" rows `shouldBe` Left (Opium.ErrorUnexpectedNull (LibPQ.Row 0) "name") - it "fails for the wrong column type" $ \conn -> do + it "Fails for the wrong column type" $ \conn -> do rows <- Opium.fetch_ @Person conn "SELECT 'quby' AS name, 'indeterminate' AS age" rows `shouldBe` Left (Opium.ErrorDecode (LibPQ.Row 0) "age" $ Opium.FieldErrorInvalidOid $ LibPQ.Oid 25)