Add top-level re-exports module and passing acceptance tests

This commit is contained in:
2026-05-29 23:23:15 -04:00
parent 694448888f
commit c2cd26694b
4 changed files with 135 additions and 20 deletions
+3
View File
@@ -15,6 +15,7 @@ build-type: Simple
library library
exposed-modules: exposed-modules:
Orville.SQLite
Orville.SQLite.AutoMigration Orville.SQLite.AutoMigration
Orville.SQLite.Execution Orville.SQLite.Execution
Orville.SQLite.FieldDefinition Orville.SQLite.FieldDefinition
@@ -39,6 +40,8 @@ test-suite spec
hs-source-dirs: test hs-source-dirs: test
build-depends: build-depends:
base >=4.17 && <5 base >=4.17 && <5
, mtl
, orville-sqlite , orville-sqlite
, hspec , hspec
, text
default-language: Haskell2010 default-language: Haskell2010
+24
View File
@@ -0,0 +1,24 @@
module Orville.SQLite
( -- * Monad
module Orville.SQLite.Monad
-- * SqlType
, module Orville.SQLite.SqlType
-- * FieldDefinition
, module Orville.SQLite.FieldDefinition
-- * SqlMarshaller
, module Orville.SQLite.SqlMarshaller
-- * TableDefinition
, module Orville.SQLite.TableDefinition
-- * AutoMigration
, module Orville.SQLite.AutoMigration
-- * Execution
, module Orville.SQLite.Execution
) where
import Orville.SQLite.AutoMigration
import Orville.SQLite.Execution
import Orville.SQLite.FieldDefinition
import Orville.SQLite.Monad
import Orville.SQLite.SqlMarshaller
import Orville.SQLite.SqlType
import Orville.SQLite.TableDefinition
+16 -20
View File
@@ -18,7 +18,7 @@ module Orville.SQLite.AutoMigration
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (ask) import Control.Monad.Reader (ask)
import Data.List (find, intercalate) import Data.List (intercalate)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Database.SQLite3 as SQLite3 import qualified Database.SQLite3 as SQLite3
import Orville.SQLite.FieldDefinition (fieldColumnName) import Orville.SQLite.FieldDefinition (fieldColumnName)
@@ -87,7 +87,7 @@ planItem :: SchemaItem -> OrvilleM [MigrationStep]
planItem (SchemaItem (SchemaTableItem tableName' marshaller pkName dropColsList)) = do planItem (SchemaItem (SchemaTableItem tableName' marshaller pkName dropColsList)) = do
existingCols <- getExistingColumns tableName' existingCols <- getExistingColumns tableName'
let expectedCols = marshallerExpectedColumns marshaller pkName let expectedCols = marshallerExpectedColumns marshaller pkName
pure $ planTableChanges tableName' expectedCols existingCols dropColsList pure $ planTableChanges tableName' expectedCols existingCols pkName dropColsList
getExistingColumns :: String -> OrvilleM [ExistingColumn] getExistingColumns :: String -> OrvilleM [ExistingColumn]
getExistingColumns tableName' = do getExistingColumns tableName' = do
@@ -140,10 +140,11 @@ planTableChanges ::
String -> String ->
[(String, String, Bool)] -> [(String, String, Bool)] ->
[ExistingColumn] -> [ExistingColumn] ->
String ->
[String] -> [String] ->
[MigrationStep] [MigrationStep]
planTableChanges tableName' expected existing dropColsList planTableChanges tableName' expected existing pkName dropColsList
| null existing = [CreateTable tableName' expected (findPk expected)] | null existing = [CreateTable tableName' expected pkName]
| otherwise = addColSteps ++ dropColSteps | otherwise = addColSteps ++ dropColSteps
where where
existingNames = map existingName existing existingNames = map existingName existing
@@ -160,13 +161,6 @@ planTableChanges tableName' expected existing dropColsList
, name `elem` existingNames , name `elem` existingNames
] ]
findPk cols =
case find (\(_, _, isNull) -> not isNull) cols of
Just (name, _, _) -> name
Nothing -> case cols of
((name, _, _) : _) -> name
[] -> ""
autoMigrateSchema :: MigrationOptions -> [SchemaItem] -> OrvilleM () autoMigrateSchema :: MigrationOptions -> [SchemaItem] -> OrvilleM ()
autoMigrateSchema opts items = do autoMigrateSchema opts items = do
plan <- generateMigrationPlan items plan <- generateMigrationPlan items
@@ -179,10 +173,10 @@ executeMigrationPlan = mapM_ executeStep
executeStep step = do executeStep step = do
db <- ask db <- ask
case step of case step of
CreateTable name cols _pkName -> CreateTable name cols pkName ->
liftIO $ liftIO $
SQLite3.exec db $ SQLite3.exec db $
mkCreateTable name cols mkCreateTable name cols pkName
AddColumn name colName colType -> AddColumn name colName colType ->
liftIO $ liftIO $
SQLite3.exec db $ SQLite3.exec db $
@@ -202,8 +196,8 @@ executeMigrationPlan = mapM_ executeStep
<> " DROP COLUMN " <> " DROP COLUMN "
<> colName <> colName
mkCreateTable :: String -> [(String, String, Bool)] -> T.Text mkCreateTable :: String -> [(String, String, Bool)] -> String -> T.Text
mkCreateTable name cols = mkCreateTable name cols pkName =
T.pack $ T.pack $
"CREATE TABLE IF NOT EXISTS " "CREATE TABLE IF NOT EXISTS "
<> name <> name
@@ -211,8 +205,10 @@ mkCreateTable name cols =
<> intercalate ",\n " (map mkColumnDef cols) <> intercalate ",\n " (map mkColumnDef cols)
<> "\n)" <> "\n)"
where where
mkColumnDef (colName, colType, notNullFlag) = mkColumnDef (colName, colType, notNullFlag)
colName | colName == pkName =
<> " " colName <> " " <> colType <> " PRIMARY KEY"
<> colType | notNullFlag =
<> if notNullFlag then " NOT NULL" else "" colName <> " " <> colType <> " NOT NULL"
| otherwise =
colName <> " " <> colType
+92
View File
@@ -0,0 +1,92 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad.IO.Class (liftIO)
import Data.Int (Int64)
import Data.Text (Text)
import Test.Hspec
import Orville.SQLite
data Person = Person
{ personId :: Int64
, firstName :: Text
, lastName :: Text
, age :: Int64
}
deriving (Show, Eq)
personIdField :: FieldDefinition 'NotNull Int64
personIdField = integerField "id"
firstNameField :: FieldDefinition 'NotNull Text
firstNameField = textField "first_name"
lastNameField :: FieldDefinition 'NotNull Text
lastNameField = textField "last_name"
ageField :: FieldDefinition 'NotNull Int64
ageField = integerField "age"
personMarshaller :: SqlMarshaller Person Person
personMarshaller =
Person
<$> marshallReadOnlyField personIdField
<*> marshallField firstName firstNameField
<*> marshallField lastName lastNameField
<*> marshallField age ageField
personTable :: TableDefinition Int64 Person Person
personTable =
mkTableDefinition "person" (primaryKey personId personIdField) personMarshaller
main :: IO ()
main = hspec $ do
describe "Orville.SQLite" $ do
it "creates a table and inserts a row" $ do
db <- openConnection ":memory:"
withConnection db $ do
autoMigrateSchema defaultOptions [schemaTable personTable []]
insertEntity personTable (Person 0 "Alice" "Smith" 30)
mAlice <- findEntity personTable 1
liftIO $ mAlice `shouldBe` Just (Person 1 "Alice" "Smith" 30)
closeConnection db
it "finds all rows" $ do
db <- openConnection ":memory:"
withConnection db $ do
autoMigrateSchema defaultOptions [schemaTable personTable []]
insertEntity personTable (Person 0 "Alice" "Smith" 30)
insertEntity personTable (Person 0 "Bob" "Jones" 25)
results <- findAll personTable
liftIO $ length results `shouldBe` 2
closeConnection db
it "updates a row" $ do
db <- openConnection ":memory:"
withConnection db $ do
autoMigrateSchema defaultOptions [schemaTable personTable []]
insertEntity personTable (Person 0 "Alice" "Smith" 30)
updateEntity personTable (Person 1 "Alice" "Jones" 31)
mAlice <- findEntity personTable 1
liftIO $ mAlice `shouldBe` Just (Person 1 "Alice" "Jones" 31)
closeConnection db
it "deletes a row" $ do
db <- openConnection ":memory:"
withConnection db $ do
autoMigrateSchema defaultOptions [schemaTable personTable []]
insertEntity personTable (Person 0 "Alice" "Smith" 30)
deleteEntity personTable 1
mAlice <- findEntity personTable 1
liftIO $ mAlice `shouldBe` Nothing
closeConnection db
it "returns Nothing for missing row" $ do
db <- openConnection ":memory:"
withConnection db $ do
autoMigrateSchema defaultOptions [schemaTable personTable []]
mAlice <- findEntity personTable 999
liftIO $ mAlice `shouldBe` Nothing
closeConnection db