En este tema, aprenderemos cómo interactuar con bases de datos desde Haskell. Cubriremos los conceptos básicos de la conexión a una base de datos, la ejecución de consultas y la manipulación de resultados. Utilizaremos la biblioteca persistent
para facilitar estas tareas.
Contenido
Introducción a Persistent
persistent
es una biblioteca de Haskell que proporciona una forma sencilla y segura de interactuar con bases de datos. Soporta varios sistemas de bases de datos, incluyendo SQLite, PostgreSQL y MySQL.
Ventajas de usar Persistent
- Seguridad de tipos: Las consultas y operaciones están fuertemente tipadas.
- Abstracción: Proporciona una capa de abstracción sobre SQL.
- Facilidad de uso: Simplifica la definición de esquemas y la ejecución de consultas.
Configuración del Entorno
Para comenzar, necesitamos instalar las bibliotecas necesarias. Asegúrate de tener cabal
o stack
instalado.
Instalación con Cabal
Instalación con Stack
Añade las siguientes dependencias a tu archivo stack.yaml
:
Y luego ejecuta:
Definición de Esquemas
En persistent
, definimos esquemas utilizando una sintaxis especial en un archivo de plantilla. Aquí hay un ejemplo de cómo definir un esquema para una tabla de usuarios:
{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE OverloadedStrings #-} import Database.Persist.TH share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| User name String age Int deriving Show |]
Explicación del Código
- GADTs, GeneralizedNewtypeDeriving, etc.: Extensiones de Haskell necesarias para
persistent
. - share: Función que genera el código necesario para interactuar con la base de datos.
- mkPersist, mkMigrate: Funciones que crean las instancias necesarias para el esquema.
- persistLowerCase: Quasi-quote que define el esquema.
Operaciones Básicas de CRUD
Conexión a la Base de Datos
Primero, necesitamos establecer una conexión a la base de datos. Aquí hay un ejemplo de cómo conectarse a una base de datos SQLite:
import Database.Persist.Sqlite import Control.Monad.IO.Class (liftIO) import Control.Monad.Logger (runStdoutLoggingT) main :: IO () main = runStdoutLoggingT $ withSqliteConn "test.db" $ \backend -> liftIO $ do runSqlConn (runMigration migrateAll) backend
Crear (Create)
Para insertar un nuevo usuario en la base de datos:
import Database.Persist main :: IO () main = runStdoutLoggingT $ withSqliteConn "test.db" $ \backend -> liftIO $ do runSqlConn (runMigration migrateAll) backend runSqlConn (insert $ User "Alice" 30) backend
Leer (Read)
Para leer usuarios de la base de datos:
main :: IO () main = runStdoutLoggingT $ withSqliteConn "test.db" $ \backend -> liftIO $ do runSqlConn (runMigration migrateAll) backend users <- runSqlConn (selectList [] []) backend liftIO $ print (users :: [Entity User])
Actualizar (Update)
Para actualizar un usuario existente:
main :: IO () main = runStdoutLoggingT $ withSqliteConn "test.db" $ \backend -> liftIO $ do runSqlConn (runMigration migrateAll) backend userId <- runSqlConn (insert $ User "Alice" 30) backend runSqlConn (update userId [UserAge =. 31]) backend
Eliminar (Delete)
Para eliminar un usuario:
main :: IO () main = runStdoutLoggingT $ withSqliteConn "test.db" $ \backend -> liftIO $ do runSqlConn (runMigration migrateAll) backend userId <- runSqlConn (insert $ User "Alice" 30) backend runSqlConn (delete userId) backend
Ejemplo Práctico
Vamos a crear un pequeño programa que permita agregar, listar y eliminar usuarios desde la línea de comandos.
Código Completo
{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE OverloadedStrings #-} import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH import Control.Monad.IO.Class (liftIO) import Control.Monad.Logger (runStdoutLoggingT) import System.Environment (getArgs) share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| User name String age Int deriving Show |] main :: IO () main = do args <- getArgs case args of ("add":name:age:_) -> runDb $ insert_ (User name (read age)) ("list":_) -> runDb $ selectList [] [] >>= liftIO . print ("delete":name:_) -> runDb $ deleteWhere [UserName ==. name] _ -> putStrLn "Usage: add <name> <age> | list | delete <name>" runDb :: SqlPersistT (LoggingT IO) a -> IO a runDb query = runStdoutLoggingT $ withSqliteConn "test.db" $ \backend -> runSqlConn query backend
Explicación del Código
- main: Lee los argumentos de la línea de comandos y ejecuta la operación correspondiente.
- runDb: Función auxiliar para ejecutar consultas en la base de datos.
Ejercicios Prácticos
- Ejercicio 1: Modifica el programa para que también permita actualizar la edad de un usuario.
- Ejercicio 2: Añade una nueva tabla
Post
con los campostitle
ycontent
, y crea relaciones entreUser
yPost
.
Soluciones
Solución Ejercicio 1
main :: IO () main = do args <- getArgs case args of ("add":name:age:_) -> runDb $ insert_ (User name (read age)) ("list":_) -> runDb $ selectList [] [] >>= liftIO . print ("delete":name:_) -> runDb $ deleteWhere [UserName ==. name] ("update":name:age:_) -> runDb $ updateWhere [UserName ==. name] [UserAge =. read age] _ -> putStrLn "Usage: add <name> <age> | list | delete <name> | update <name> <age>"
Solución Ejercicio 2
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| User name String age Int deriving Show Post title String content String userId UserId deriving Show |]
Conclusión
En esta sección, hemos aprendido cómo interactuar con bases de datos en Haskell utilizando la biblioteca persistent
. Hemos cubierto la configuración del entorno, la definición de esquemas y las operaciones básicas de CRUD. Además, hemos implementado un ejemplo práctico y proporcionado ejercicios para reforzar los conceptos aprendidos.
En el próximo tema, exploraremos cómo realizar pruebas y depuración en Haskell para asegurar la calidad y el correcto funcionamiento de nuestras aplicaciones.
Curso de Programación en Haskell
Módulo 1: Introducción a Haskell
- ¿Qué es Haskell?
- Configuración del Entorno de Haskell
- Sintaxis Básica y Hola Mundo
- Haskell REPL (GHCi)