quokka 0.1.1 → 0.1.2
raw patch · 5 files changed
+199/−27 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Quokka.Functions: build1With1CustomRel :: ToRow q => Connection -> Relation -> ChildTable -> q -> IO (Maybe Id)
+ Quokka.Functions: build1WithManyCustomRels :: ToRow q => Connection -> [Relation] -> ChildTable -> q -> IO (Maybe Id)
+ Quokka.Functions: buildWith1CustomRel :: ToRow q => Connection -> Relation -> ChildTable -> [q] -> IO [Id]
+ Quokka.Functions: buildWithManyCustomRels :: ToRow q => Connection -> [Relation] -> ChildTable -> [q] -> IO [Id]
+ Quokka.Functions: insertStatementWith1CustomRel :: Relation -> ChildTable -> Query
+ Quokka.Functions: insertStatementWithManyCustomRels :: [Relation] -> ChildTable -> Query
+ Quokka.Types: FK :: Text -> FK
+ Quokka.Types: Relation :: ParentTable -> FK -> Relation
+ Quokka.Types: data Relation
+ Quokka.Types: newtype FK
Files
- CHANGELOG.md +9/−1
- quokka.cabal +1/−1
- src/Quokka/Functions.hs +119/−22
- src/Quokka/Types.hs +22/−1
- test/Quokka/CreateSpec.hs +48/−2
CHANGELOG.md view
@@ -1,5 +1,13 @@ # Revision history for quokka -## 0.1.0.0 -- 2019-10-30 +## 0.1.0 -- 2019-10-30 * First version. Released on an unsuspecting world.++## 0.1.1 -- 2019-10-30++* Minor build update to fix a release issue with verion 0.1.0.++## 0.1.2++* Added support for building insert statements for child tables that have foreign key columns that do not follow a standard naming convention which Quokka understands.
quokka.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: quokka-version: 0.1.1+version: 0.1.2 license: MIT license-file: LICENSE author: Shirren Premaratne
src/Quokka/Functions.hs view
@@ -15,32 +15,41 @@ build , build1 , buildWith1Rel+, buildWith1CustomRel , build1With1Rel+, build1With1CustomRel , buildWithManyRels+, buildWithManyCustomRels , build1WithManyRels+, build1WithManyCustomRels , delete , deleteStatement , id' , insertStatement , insertStatementWith1Rel+, insertStatementWith1CustomRel , insertStatementWithManyRels+, insertStatementWithManyCustomRels , mapFromIdToResult ) where +import Data.Functor (void) import Data.Int (Int64) import Data.Text (intercalate) import Data.Text.Encoding (encodeUtf8) import Database.PostgreSQL.Simple (Connection, ToRow, execute_, returning, query) import Database.PostgreSQL.Simple.Types (Query (Query)) import Quokka.Types (ChildTable (ChildTable)+ , FK (FK) , Id (getId) , ParentTable (ParentTable) , Table (Table)+ , Relation (Relation) , Result (SingleResult)) import Quokka.Text.Countable (singularize) --- Build a prepared statement to insert data into the database`+-- | Build a prepared statement to insert data into the database` build :: (ToRow q) => Connection@@ -54,7 +63,7 @@ returning conn qry --- Similar to the build function but we only ever return+-- | Similar to the build function but we only ever return -- a single optional result, and only take 1 value. build1 :: (ToRow q)@@ -69,8 +78,8 @@ fmap build1Helper . query conn qry --- Build a prepared statement for a child table with a single foreign--- key table+-- | Build a prepared statement for a child table with a single foreign+-- key to the nominated parent table. buildWith1Rel :: (ToRow q) => Connection@@ -85,7 +94,23 @@ returning conn qry --- Build a prepared statement for a child table with a single foreign+-- | Build a prepared statement for a child table with a single foreign+-- key to the nominated parent table through a custom relation.+buildWith1CustomRel+ :: (ToRow q)+ => Connection+ -> Relation + -> ChildTable+ -> [q]+ -> IO [Id]+buildWith1CustomRel conn relation child =+ let+ qry = insertStatementWith1CustomRel relation child+ in+ returning conn qry+++-- | Build a prepared statement for a child table with a single foreign -- key table build1With1Rel :: (ToRow q)@@ -101,7 +126,23 @@ fmap build1Helper . query conn qry --- Build a prepared statement for a child table with more than 1 parent+-- | Build a prepared statement for a child table with a single foreign+-- key table mapped through a custom relation.+build1With1CustomRel+ :: (ToRow q)+ => Connection+ -> Relation + -> ChildTable+ -> q+ -> IO (Maybe Id)+build1With1CustomRel conn relation child =+ let+ qry = insertStatementWith1CustomRel relation child+ in+ fmap build1Helper . query conn qry+++-- | Build a prepared statement for a child table with more than 1 parent buildWithManyRels :: (ToRow q) => Connection@@ -116,7 +157,23 @@ returning conn qry --- Build a prepared statement for a child table with more than 1 parent+-- | Build a prepared statement for a child table with more than 1 parent+-- mapped through a custom relation.+buildWithManyCustomRels+ :: (ToRow q)+ => Connection+ -> [Relation]+ -> ChildTable+ -> [q]+ -> IO [Id]+buildWithManyCustomRels conn relations child =+ let+ qry = insertStatementWithManyCustomRels relations child+ in+ returning conn qry+++-- | Build a prepared statement for a child table with more than 1 parent build1WithManyRels :: (ToRow q) => Connection@@ -131,7 +188,23 @@ fmap build1Helper . query conn qry --- Perform a truncate with cascade action on the Table+-- | Build a prepared statement for a child table with more than 1 parent mapped+-- through a custom relation.+build1WithManyCustomRels+ :: (ToRow q)+ => Connection+ -> [Relation]+ -> ChildTable+ -> q+ -> IO (Maybe Id)+build1WithManyCustomRels conn relations child =+ let+ qry = insertStatementWithManyCustomRels relations child+ in+ fmap build1Helper . query conn qry+++-- | Perform a truncate with cascade action on the Table delete :: Connection -> Table@@ -140,11 +213,11 @@ let alter = alterSequenceStatement tbl qry = deleteStatement tbl- _ <- execute_ conn alter+ void $ execute_ conn alter execute_ conn qry --- Create an insert statement for a table+-- | Create an insert statement for a table insertStatement :: ParentTable -> Query@@ -157,7 +230,7 @@ Query (encodeUtf8 $ baseInsert <> " values (" <> valuesAsText <> ") returning id;") --- Creates an insert statement for a table, and uses the parent table to also incude+-- | Creates an insert statement for a table, and uses the parent table to also incude -- a foreign key in the generation of the statement. insertStatementWith1Rel :: ParentTable@@ -167,23 +240,47 @@ insertStatementWithManyRels [parent] --- Creates an insert statement for a table, and uses multiple parent tables to also incude+-- | Creates an insert statement for a table, and uses the parent table custom relation+-- to build an insert statement for a child table using the relation+insertStatementWith1CustomRel+ :: Relation+ -> ChildTable+ -> Query+insertStatementWith1CustomRel relation =+ insertStatementWithManyCustomRels [relation]+++-- | Creates an insert statement for a table, and uses multiple parent tables to also include -- foreign keys in the generation of the statement. insertStatementWithManyRels :: [ParentTable] -> ChildTable -> Query-insertStatementWithManyRels parents (ChildTable name columns) =+insertStatementWithManyRels parents child = let- updatedColumns = columns ++ map (\(ParentTable parentName _) -> singularize parentName <> "_id") parents- columnsAsText = intercalate "," updatedColumns- valuesAsText = intercalate "," (map (const "?") updatedColumns)- baseInsert = "insert into " <> name <> " (" <> columnsAsText <> ")"+ buildFK name = FK (singularize name <> "_id")+ relations = map (\p@(ParentTable name _) -> Relation p (buildFK name)) parents in+ insertStatementWithManyCustomRels relations child+++-- | Creates an insert statement for a table where the relationship between the parent and child+-- is modelled using a custom key.+insertStatementWithManyCustomRels+ :: [Relation]+ -> ChildTable+ -> Query+insertStatementWithManyCustomRels relations (ChildTable name columns) =+ let+ updatedColumns = columns ++ map (\(Relation _ (FK fkName)) -> fkName) relations+ columnsAsText = intercalate "," updatedColumns+ valuesAsText = intercalate "," (map (const "?") updatedColumns)+ baseInsert = "insert into " <> name <> " (" <> columnsAsText <> ")"+ in Query (encodeUtf8 $ baseInsert <> " values (" <> valuesAsText <> ") returning id;") --- Generate an alter sequence statement for a table+-- | Generate an alter sequence statement for a table alterSequenceStatement :: Table -> Query@@ -191,7 +288,7 @@ Query (encodeUtf8 $ "alter sequence " <> name <> "_id_seq restart;") --- Generate a delete statement for a table+-- | Generate a delete statement for a table deleteStatement :: Table -> Query@@ -199,7 +296,7 @@ Query (encodeUtf8 $ "truncate table " <> name <> " cascade;") --- Helper function to extract the underlying Int value from+-- | Helper function to extract the underlying Int value from -- the first value in the list id' :: [Id] -> Int id' (x:_) =@@ -207,7 +304,7 @@ id' [] = -1 --- Postgres Simple does not have a function which maps from [r] -> Maybe r+-- | Postgres Simple does not have a function which maps from [r] -> Maybe r -- so we've written one that takes the head or returns Nothing in a safe -- manner. build1Helper@@ -219,7 +316,7 @@ Nothing --- Function to map from IO [Id] -> IO [Result]+-- | Function to map from IO [Id] -> IO [Result] mapFromIdToResult :: ParentTable -> [Id]
src/Quokka/Types.hs view
@@ -13,9 +13,11 @@ module Quokka.Types ( ChildTable (..) , Data+, FK (..) , Id (..) , ParentTable (..) , Table (..)+, Relation (..) , Result (..) , Row (..) ) where@@ -27,10 +29,11 @@ import Database.PostgreSQL.Simple.ToField (toField, ToField) +-- | Alias for a list of values. type Data = [Text] --- Represents the identity column of a row in a table. I.e. the+-- | Represents the identity column of a row in a table. I.e. the -- primary key of a table which is limited to integers. newtype Id = Id { getId :: Int }@@ -45,21 +48,39 @@ toField val +-- | A child table represents a relation in Postgres with a foreign key+-- to a parent table. data ChildTable = ChildTable Text [Text] +-- | A parent table represents a relation in Postgres with no foreign keys. data ParentTable = ParentTable Text [Text] +-- | This type is used to model and create deletes. newtype Table = Table Text ++-- | A relation is defined as a parent table that is related+-- through a custom foreign key.+data Relation+ = Relation ParentTable FK+++-- | Column that represents a foreign key in a database.+newtype FK+ = FK Text+++-- | Represents a result retrieved by Quokka via Postgres-simple. data Result = SingleResult ParentTable Id +-- | A row represents a Postgres row retrieved for a relation. newtype Row a = Row [a]
test/Quokka/CreateSpec.hs view
@@ -8,13 +8,16 @@ import Data.Text (Text) import Data.Text.Encoding (encodeUtf8) import Database.PostgreSQL.Simple.Types (Query (Query))-import Quokka.Types (ChildTable (..), Id (..), ParentTable (..))+import Quokka.Types (ChildTable (..), FK (..), Id (..), ParentTable (..), Relation (..)) import Quokka.Functions (build1 , build1With1Rel+ , build1With1CustomRel , id' , insertStatement , insertStatementWith1Rel- , insertStatementWithManyRels)+ , insertStatementWith1CustomRel+ , insertStatementWithManyRels+ , insertStatementWithManyCustomRels) import Quokka.Helper (setupDb, withDatabaseConnection) import Quokka.Tables (accountTableAsChild , insertAccounts@@ -50,7 +53,38 @@ stmt `shouldBe` encodeUtf8 "insert into accounts (name,user_id) values (?,?) returning id;" + + describe "insertStatementWith1CustomRel" $+ it "should return an insert statement with the custom FK set" $ do+ let+ relation = Relation (ParentTable "users" ["firstname", "lastname", "age"]) (FK "user")+ childTable = ChildTable "accounts" ["name"]+ Query stmt = insertStatementWith1CustomRel relation childTable + stmt `shouldBe` encodeUtf8 "insert into accounts (name,user) values (?,?) returning id;"+++ describe "insertStatementWithManyCustomRels" $ do+ context "for 2 parent tables" $+ it "should return an insert statement with 2 custom FKs set" $ do+ let+ parentTable1 = ParentTable "users" ["firstname", "lastname", "age"]+ parentTable2 = ParentTable "accounts" ["name"]+ childTable = ChildTable "profiles" ["active"]+ Query stmt = insertStatementWithManyCustomRels + [Relation parentTable1 (FK "userfk"), Relation parentTable2 (FK "accountfk")]+ childTable++ stmt `shouldBe` encodeUtf8 "insert into profiles (active,userfk,accountfk) values (?,?,?) returning id;"+ + context "for no parents" $+ it "should return an insert statement with no FKs set" $ do+ let+ childTable = ChildTable "profiles" ["active"]+ Query stmt = insertStatementWithManyCustomRels [] childTable++ stmt `shouldBe` encodeUtf8 "insert into profiles (active) values (?) returning id;"+ describe "insertStatementWithManyRels" $ do context "for 2 parent tables" $ it "should return an insert statement with 2 FKs set" $ do@@ -93,6 +127,18 @@ accountId `shouldBe` Just (Id 1) ++ context "for a table with a single custom relationship" $+ it "should insert parent and child into the database" $ \conn -> do+ let+ insertUser = build1 conn userTable+ customRel = Relation userTable (FK "user_id")+ insertAccount = build1With1CustomRel conn customRel accountTableAsChild+ userId <- insertUser ("John" :: Text, "Doe" :: Text, 1 :: Int)+ accountId <- insertAccount ("Account-1" :: Text, "Description" :: Text, fromJust userId)++ accountId `shouldBe` Just (Id 1)+ describe "insert" $ do context "for a table with no relationships" $