En aquest tema, aprendrem com interactuar amb bases de dades des de Haskell. Utilitzarem la biblioteca persistent
per gestionar les connexions i operacions amb bases de dades. Aquest mòdul inclou la configuració de l'entorn, la definició de models, la realització d'operacions bàsiques (CRUD) i exemples pràctics.
Contingut
Introducció a Persistent
persistent
és una biblioteca de Haskell que proporciona una interfície per treballar amb bases de dades relacionals i no relacionals. Ofereix una manera senzilla de definir models de dades i realitzar operacions com insercions, actualitzacions, eliminacions i consultes.
Característiques Clau
- Definició de Models: Utilitza plantilles de Haskell per definir models de dades.
- Operacions CRUD: Proporciona funcions per a la creació, lectura, actualització i eliminació de registres.
- Suport per a Diverses Bases de Dades: Compatible amb SQLite, PostgreSQL, MySQL, entre d'altres.
Configuració de l'Entorn
Instal·lació de les Dependències
Per començar, necessitem instal·lar les biblioteques necessàries. Afegiu les següents dependències al vostre fitxer cabal
o stack
:
-- file: package.yaml (per a Stack) dependencies: - persistent - persistent-sqlite - persistent-template - persistent-postgresql - persistent-mysql - persistent-mongoDB - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached - persistent-redis - persistent-memcached
-- file: package.yaml (per a Stack) dependencies:
- persistent
- persistent-sqlite
- persistent-template
### Configuració de la Base de Dades Per aquest exemple, utilitzarem SQLite. Creeu un fitxer `config/settings.yml` amb la configuració de la base de dades:
sqlite: database: "database.sqlite3" poolsize: 10
## Definició de Models Els models es defineixen en un fitxer separat utilitzant la sintaxi de plantilles de Haskell. Creeu un fitxer `models/Models.hs` amb el següent contingut:
{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}
module Models where
import Database.Persist.TH
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Person name String age Int Maybe deriving Show |]
Aquest codi defineix un model `Person` amb dos camps: `name` (String) i `age` (Int, opcional). ## Operacions Bàsiques (CRUD) ### Connexió a la Base de Dades Primer, configurem la connexió a la base de dades. Creeu un fitxer `Main.hs` amb el següent contingut:
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}
import Control.Monad.IO.Class (liftIO) import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH import Models
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
-- Inserir un nou registre
personId <- insert $ Person "John Doe" (Just 30)
liftIO $ putStrLn $ "Inserted person with ID: " ++ show personId
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
-- Inserir un nou registre
personId <- insert $ Person "John Doe" (Just 30)
liftIO $ putStrLn $ "Inserted person with ID: " ++ show personId
-- Llegir registres
people <- selectList [] [Asc PersonName]
liftIO $ print people
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
-- Inserir un nou registre
personId <- insert $ Person "John Doe" (Just 30)
liftIO $ putStrLn $ "Inserted person with ID: " ++ show personId
-- Llegir registres
people <- selectList [] [Asc PersonName]
liftIO $ print people
-- Actualitzar un registre
update personId [PersonAge =. Just 31]
liftIO $ putStrLn "Updated person's age."
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
-- Inserir un nou registre
personId <- insert $ Person "John Doe" (Just 30)
liftIO $ putStrLn $ "Inserted person with ID: " ++ show personId
-- Llegir registres
people <- selectList [] [Asc PersonName]
liftIO $ print people
-- Actualitzar un registre
update personId [PersonAge =. Just 31]
liftIO $ putStrLn "Updated person's age."
-- Eliminar un registre
delete personId
liftIO $ putStrLn "Deleted person."
## Exemple Pràctic Ara que hem vist les operacions bàsiques, creem un exemple pràctic que combina totes aquestes operacions. El nostre objectiu serà gestionar una llista de persones, permetent afegir, llegir, actualitzar i eliminar registres. ### Codi Complet
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}
import Control.Monad.IO.Class (liftIO) import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH import Models
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
-- Inserir un nou registre
personId <- insert $ Person "John Doe" (Just 30)
liftIO $ putStrLn $ "Inserted person with ID: " ++ show personId
-- Llegir registres
people <- selectList [] [Asc PersonName]
liftIO $ print people
-- Actualitzar un registre
update personId [PersonAge =. Just 31]
liftIO $ putStrLn "Updated person's age."
-- Eliminar un registre
delete personId
liftIO $ putStrLn "Deleted person."
## Exercicis Pràctics 1. **Afegir un Nou Model**: Afegiu un nou model `Car` amb els camps `make` (String) i `year` (Int). Inseriu, llegiu, actualitzeu i elimineu registres de `Car`. 2. **Consultes Avançades**: Creeu una funció que llegeixi totes les persones majors de 25 anys. 3. **Relacions entre Models**: Definiu una relació entre `Person` i `Car` on una persona pot tenir múltiples cotxes. Inseriu registres i realitzeu consultes que mostrin les persones amb els seus cotxes. ### Solucions 1. **Afegir un Nou Model**
-- models/Models.hs {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}
module Models where
import Database.Persist.TH
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Person name String age Int Maybe deriving Show
Car make String year Int deriving Show |]
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
-- Inserir registres
_ <- insert $ Person "John Doe" (Just 30)
_ <- insert $ Person "Jane Doe" (Just 20)
-- Llegir persones majors de 25 anys
people <- selectList [PersonAge >. Just 25] [Asc PersonName]
liftIO $ print people
-- models/Models.hs {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}
module Models where
import Database.Persist.TH
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| Person name String age Int Maybe deriving Show
Car make String year Int ownerId PersonId deriving Show |]
main :: IO () main = runSqlite "database.sqlite3" $ do runMigration migrateAll liftIO $ putStrLn "Database migrated."
-- Inserir registres
personId <- insert $ Person "John Doe" (Just 30)
_ <- insert $ Car "Toyota" 2010 personId
_ <- insert $ Car "Honda" 2015 personId
-- Llegir persones amb els seus cotxes
people <- selectList [] [Asc PersonName]
forM_ people $ \\(Entity personId person) -> do
liftIO $ print person
cars <- selectList [CarOwnerId ==. personId] [Asc CarMake]
liftIO $ print cars
## Conclusió En aquest tema, hem après a configurar l'entorn per treballar amb bases de dades en Haskell utilitzant la biblioteca `persistent`. Hem vist com definir models, realitzar operacions bàsiques (CRUD) i hem creat un exemple pràctic per gestionar una llista de persones. Els exercicis pràctics proporcionats us ajudaran a consolidar els coneixements adquirits i a explorar funcionalitats més avançades. En el següent tema, explorarem com realitzar proves i depuració en aplicacions Haskell.