Beam:没有模板Haskell的数据库功能!

2023-10-26

作为Haskell Web系列的一部分,我们检查了PersistentEsqueleto库。 这些中的第一个允许您使用特殊语法创建数据库模式。 然后,您可以使用Template Haskell生成所有必要的Haskell数据类型和类型的实例。 更好的是,您可以编写Haskell代码以查询类似于SQL的代码。 这些查询是类型安全的,这非常好。 但是,需要使用模板Haskell指定我们的架构存在一些缺点。 例如,代码花费更长的时间进行编译,而对于初学者而言则较难获得。

本周在博客上,我们将探索另一个名为Beam的数据库库。 该库使我们无需使用Template Haskell即可指定数据库架构。 涉及到一些样板,但这还不错! 与Persistent一样,Beam也支持许多后端,例如SQLite和PostgresQL。 与Persistent不同,Beam还支持将联接查询作为其系统的内置部分。

有关高级库的更多想法,请务必查看我们的生产清单 ! 它包括几个其他数据库选项供您查看。

指定我们的类型

首先,虽然Beam不需要模板Haskell,但确实需要很多其他编译器扩展。 您可以查看下面附录中的内容,也可以查看Github上的示例代码 。 现在让我们回想一下在使用Persistent时如何指定架构:

import qualified Database.Persist.TH as PTH
PTH.share [PTH.mkPersist PTH.sqlSettings, PTH.mkMigrate "migrateAll"] [PTH.persistLowerCase|
User sql=users
name Text
email Text
age Int
occupation Text
UniqueEmail email
deriving Show Read Eq
Article sql=articles
title Text
body Text
publishedTime UTCTime
authorId UserId
UniqueTitle title
deriving Show Read Eq

使用Beam,我们不会使用Template Haskell,因此实际上将创建普通的Haskell数据类型。 但是仍然会有一些奇怪之处。 首先,按照惯例,我们将在类型的最后加上额外的字符T 这是不必要的,但是约定可以帮助我们记住与表相关的类型。 我们还必须提供一个额外的类型参数f ,稍后我们将进一步介绍它:

data UserT f =
data ArticleT f =
...

我们的下一个约定是在字段名称前使用下划线。 与持久性不同,我们还将在字段名称中指定类型名称。 遵循这些约定,我遵循图书馆创建者Travis的建议。

data UserT f =
{ _userId :: ...
, _userName :: …
, _userEmail :: …
, _userAge :: …
, _userOccupation :: …
}
data ArticleT f =
{ _articleId :: …
, _articleTitle :: …
, _articleBody :: …
, _articlePublishedTime :: …
}

因此,当我们指定每个字段的实际类型时,我们只需放入相关的数据类型,例如IntText或其他,对吗? 好吧,不完全是。 为了完成我们的类型,我们将用所需的类型填充每个字段,除非通过Columnar f指定。 同样,我们将在这两种类型上派生Generic ,这将使Beam发挥其魔力:

data UserT f =
{ _userId :: Columnar f Int64
, _userName :: Columnar f Text
, _userEmail :: Columnar f Text
, _userAge :: Columnar f Int
, _userOccupation :: Columnar f Text
} deriving (Generic)
data ArticleT f =
{ _articleId :: Columnar f Int64
, _articleTitle :: Columnar f Text
, _articleBody :: Columnar f Text
, _articlePublishedTime :: Columnar f Int64 -- Unix Epoch
} deriving (Generic)

现在,此模式与我们之前的模式之间存在一些小差异。 首先,我们将主键作为类型的显式字段。 对于持久性,我们使用Entity抽象将其分离。 我们将在下面看到如何处理未知密钥的情况。 第二个区别是(目前),我们在文章上省略了userId字段。 我们在处理主键时会添加它。

柱状

那么,这种Columnar业务到底是什么呢? 在大多数情况下,我们希望使用原始字段类型指定一个User 。 但是在某些情况下,我们必须为SQL表达式使用更复杂的类型。 让我们先从简单的案例开始。

幸运的是, Columnar工作方式是,如果我们将Identity用作f ,则可以使用原始类型来填充字段值。 我们将专门为此身份案例创建类型同义词。 然后我们可以举一些例子:

type User = UserT Identity
type Article = ArticleT Identity
user1 :: User
user1 = User 1 "James" "james@example.com" 25 "programmer"
user2 :: User
user2 = User 2 "Katie" "katie@example.com " 25 "engineer"
users :: [User]
users = [ user1, user2 ]

请注意,如果您发现重复Columnar关键字比较麻烦,可以将其缩短为C

data UserT f =
{ _userId :: C f Int64
, _userName :: C f Text
, _userEmail :: C f Text
, _userAge :: C f Int
, _userOccupation :: C f Text
} deriving (Generic)

现在,我们的初始示例将为我们的所有字段分配原始值。 因此,除了Identity之外,我们最初不需要为f参数使用任何东西。 不过,接下来,我们将讨论自动递增主键的情况。 在这种情况下,我们将使用default_函数,该函数的类型实际上是SQL表达式的Beam形式。 在这种情况下,我们将为f使用其他类型,但是灵活性将使我们能够继续使用User构造函数!

我们的类型的实例

既然我们已经指定了类型,我们就可以使用BeamableTable类型类来告诉Beam有关我们类型的更多信息。 在将任何这些类型设置为Table ,我们需要分配其主键类型。 因此,让我们再加上几个类型同义词来表示这些:

type UserId = PrimaryKey UserT Identity
type ArticleId = PrimaryKey ArticleT Identity

在此过程中,让我们将外键添加到Article类型中:

data ArticleT f =
{ _articleId :: Columnar f Int64
, _articleTitle :: Columnar f Text
, _articleBody :: Columnar f Text
, _articlePublishedTime :: Columnar f Int64
, _articleUserId :: PrimaryKey UserT f
} deriving (Generic)

现在,我们可以在主要类型和主键类型上为Beamable生成实例。 我们还将导出ShowEq实例:

data UserT f =
deriving instance Show User
deriving instance Eq User
instance Beamable UserT
instance Beamable (PrimaryKey UserT)
data ArticleT f =
deriving instance Show Article
deriving instance Eq Article
instance Beamable ArticleT
instance Beamable (PrimaryKey ArticleT)

现在,我们将为Table类创建一个实例。 这将涉及一些类型族语法。 我们将指定UserIdArticleId作为我们的主键数据类型。 然后,我们可以填写primaryKey函数以匹配右边的字段。

instance Table UserT where
data PrimaryKey UserT f = UserId (Columnar f Int64) deriving Generic
primaryKey = UserId . _userId
instance Table ArticleT where
data PrimaryKey ArticleT f = ArticleId (Columnar f Int64) deriving Generic
primaryKey = ArticleId . _articleId

隐形眼镜

我们将做另一件事来模仿持久性。 模板Haskell为我们自动生成了镜头。 我们可以在进行数据库查询时使用它们。 下面,我们将使用类似的方法。 但是,我们将使用特殊功能tableLenses而不是Template Haskell来制作这些。 如果您还记得我们如何使用Servant Client库,我们可以通过使用client并将其与模式匹配来创建client函数。 我们将使用tableLenses做类似的tableLenses 。 我们将在表的每个字段上使用LensFor ,并创建构造项目的模式。

User
(LensFor userId)
(LensFor userName)
(LensFor userEmail)
(LensFor userAge)
(LensFor userOccupation) = tableLenses
Article
(LensFor articleId)
(LensFor articleTitle)
(LensFor articleBody)
(LensFor articlePublishedTime)
(UserId (LensFor articuleUserId)) = tableLenses

注意,我们必须将外键镜头包装在UserId

创建我们的数据库

现在,与持久性不同,我们将创建一个额外的类型来表示数据库。 我们的两个表中的每个表在此数据库中都有一个字段:

data BlogDB f = BlogDB
{ _blogUsers :: f (TableEntity UserT)
, _blogArticles :: f (TableEntity ArticleT)
} deriving (Generic)

我们需要使数据库类型成为Database类的实例。 我们还将指定一组可在数据库上使用的默认设置。 这两项都将包含参数be ,代表后端(例如,SQLite,Postgres)。 我们暂时保留此参数的通用性。

instance Database be BlogDB
blogDb :: DatabaseSettings be BlogDB
blogDb = defaultDbSettings

插入我们的数据库

现在,使用Beam迁移我们的数据库要比使用Persistent迁移更为复杂。 我们可能会在以后的文章中介绍。 现在,我们将使事情变得简单,并使用SQLite数据库并自己进行迁移。 因此,让我们首先创建表。 我们必须在这里遵循Beam的约定,特别是在外键的user_id__id字段上:

CREATE TABLE users \
( id INTEGER PRIMARY KEY AUTOINCREMENT\
, name VARCHAR NOT NULL \
, email VARCHAR NOT NULL \
, age INTEGER NOT NULL \
, occupation VARCHAR NOT NULL \
);
CREATE TABLE articles \
( id INTEGER PRIMARY KEY AUTOINCREMENT \
, title VARCHAR NOT NULL \
, body VARCHAR NOT NULL \
, published_time INTEGER NOT NULL \
, user_id__id INTEGER NOT NULL \
);

现在,我们要编写一些可以与数据库交互的查询。 让我们从插入原始用户开始。 我们首先打开一个SQLite连接,然后编写一个使用该连接的函数:

import Database.SQLite.Simple (open, Connection)
main :: IO ()
main = do
conn <- open "blogdb1.db"
insertUsers conn
insertUsers :: Connection -> IO ()
insertUsers = ...

我们通过使用runBeamSqlite并传递连接来开始表达式。 然后,我们使用runInsert将希望创建插入语句的内容指定给Beam。

import Database.Beam
import Database.Beam.SQLite
insertUsers :: Connection -> IO ()
insertUsers conn = runBeamSqlite conn $ runInsert $
...

现在,我们将使用insert函数,并从数据库中发出想要从哪个表中发出信号的信号:

insertUsers :: Connection -> IO ()
insertUsers conn = runBeamSqlite conn $ runInsert $
insert (_blogUsers blogDb) $ ...

最后,由于我们要插入原始值( UserT Identity ),因此我们使用insertValues函数来完成此调用:

insertUsers :: Connection -> IO ()
insertUsers conn = runBeamSqlite conn $ runInsert $
insert (_blogUsers blogDb) $ insertValues users

现在,我们可以检查并验证我们的用户是否存在!

SELECT * FROM users;
1|James|james@example.com|25|programmer
2|Katie|katie@example.com|25|engineer

让我们对文章做同样的事情。 我们将使用pk函数来访问特定User的主键:

article1 :: Article
article1 = Article 1 "First article"
"A great article" 1531193221 (pk user1)
article2 :: Article
article2 = Article 2 "Second article"
"A better article" 1531199221 (pk user2)
article3 :: Article
article3 = Article 3 "Third article"
"The best article" 1531200221 (pk user1)
articles :: [Article]
articles = [ article1, article2, article3]
insertArticles :: Connection -> IO ()
insertArticles conn = runBeamSqlite conn $ runInsert $
insert (_blogArticles blogDb) $ insertValues articles

选择查询

现在我们已经插入了几个元素,让我们运行一些基本的select语句。 通常,对于select,我们需要runSelectReturningList函数。 如果需要,我们还可以查询具有不同功能的单个元素:

findUsers :: Connection -> IO ()
findUsers conn = runBeamSqlite conn $ do
users <- runSelectReturningList $ ...

现在,我们将使用select而不是从上一个查询insert 。 我们还将在数据库的用户字段中使用all_函数,以表示我们希望他们全部获得。 这就是我们所需要的!:

findUsers :: Connection -> IO ()
findUsers conn = runBeamSqlite conn $ do
users <- runSelectReturningList $ select (all_ (_blogUsers blogDb))
mapM_ (liftIO . putStrLn . show) users

为了进行过滤查询,我们将从相同的框架开始。 但是现在我们需要将select语句增强为单子表达式。 我们将从所有用户中选择一个user开始:

findUsers :: Connection -> IO ()
findUsers conn = runBeamSqlite conn $ do
users <- runSelectReturningList $ select $ do
user <- (all_ (_blogUsers blogDb))
...
mapM_ (liftIO . putStrLn . show) users

现在,我们将使用guard_并应用其中一个镜头来guard_进行过滤。 我们使用==. 像“持久性”中的平等运算符。 我们还必须用val包装我们的原始比较值:

findUsers :: Connection -> IO ()
findUsers conn = runBeamSqlite conn $ do
users <- runSelectReturningList $ select $ do
user <- (all_ (_blogUsers blogDb))
guard_ (user ^. userName ==. (val_ "James"))
return user
mapM_ (liftIO . putStrLn . show) users

这就是我们所需要的! Beam将为我们生成SQL! 现在让我们尝试加入。 在Beam中,这实际上比使用Persistent / Esqueleto简单得多。 我们需要的是在文章的“选择”部分添加更多声明。 我们将仅通过用户ID对其进行过滤!

findUsersAndArticles :: Connection -> IO ()
findUsersAndArticles conn = runBeamSqlite conn $ do
users <- runSelectReturningList $ select $ do
user <- (all_ (_blogUsers blogDb))
guard_ (user ^. userName ==. (val_ "James"))
articles <- (all_ (_blogArticles blogDb))
guard_ (article ^. articleUserId ==. user ^. userId)
return user
mapM_ (liftIO . putStrLn . show) users

这里的所有都是它的!

自动递增主键

在上面的示例中,我们对所有ID进行了硬编码。 但这通常不是您想要的。 我们应该让数据库通过某些规则分配ID,在本例中为自动递增。 在这种情况下,我们将创建一个“表达式”,而不是创建一个User “值”。 这可以通过我们类型中的多态f参数来实现。 我们将取消类型签名,因为它有点混乱。 但是,我们将创建以下表达式:

user1' = User
default_
(val_ "James")
(val_ "james@example.com")
(val_ 25)
(val_ "programmer")

我们使用default_表示将告诉SQL使用默认值的表达式。 然后,我们使用val_提升所有其他值。 最后,我们将在Haskell表达式中使用insertExpressions而不是insertValues

insertUsers :: Connection -> IO ()
insertUsers conn = runBeamSqlite conn $ runInsert $
insert (_blogUsers blogDb) $ insertExpressions [ user1' ]

然后,我们将获得我们的自动递增密钥!

结论

到此结束我们对Beam库的介绍。 如我们所见,Beam是一个很棒的库,可让您无需使用任何模板Haskell即可指定数据库架构。 有关更多详细信息,请确保签出文档

要更深入地了解使用Haskell库制作Web应用程序,请务必阅读我们的Haskell Web系列 。 它介绍了一些数据库机制,以及创建API和测试。 另一个挑战是,尝试重新编写该系列中的代码以使用Beam而不是Persistent。 查看需要更改多少Servant代码以适应这种情况。

有关酷库的更多示例,请下载我们的生产清单 ! 您还可以查看更多数据库和API库!

附录:编译器扩展

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE NoMonoMorphismRestriction #-}

From: https://hackernoon.com/beam-database-power-without-template-haskell-77a2df12fa24

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Beam:没有模板Haskell的数据库功能! 的相关文章

随机推荐

  • Python代码实现“FlappyBird”小游戏

    开发工具 Python版本 3 6 4 相关模块 pygame模块 以及一些Python自带的模块 相关文件 关注公众号 Python学习指南 回复 FlappyBird 获取 环境搭建 安装Python并添加到环境变量 pip安装需要的相
  • SpringBoot admin 2.0 详解

    一 什么是Spring Boot Admin Spring Boot Admin是一个开源社区项目 用于管理和监控SpringBoot应用程序 应用程序作为Spring Boot Admin Client向为Spring Boot Admi
  • vue项目中使用echarts和china.js实现中国地图

    在echarts最新的5 4 0版本中 已不能直接引用china js来绘制中国地图 需要我们自己下载china js包 在网上查找资料 大部分是在index html文件中直接引入echarts和china js文件 但我使用这种方法在v
  • 平均池化和最大池化区别

    pooling的结果是使得特征减少 参数减少 但pooling的目的并不仅在于此 pooling目的是为了保持某种不变性 旋转 平移 伸缩等 常用的有mean pooling max pooling和Stochastic pooling三种
  • @RequestBody 500 的原因

    因为 RequestBody是调用目标类的无参构造器 若有有参构造就会报错 因此一般实用RequestBody的类 和 domain不同 应该重新配置一个包来存放此类 类 且之赋予他们get set方法
  • VTK教程1--------VTK在win10下的安装

    VTK的安装 本文在win10操作系统下 安装了VTK8 1 2 下文是安装顺序 事先准备三个软件 1 Visual Studio2017 community 该版本可以免费使用 2 CMake 本文使用的版本是cmake 3 13 1 w
  • XREAL 联合创始人吴克艰谈AR:下一代计算平台及其关键技术

    编者按 一种行业观点是 AR或是未来十年 三十年的革命性技术 是下一代计算平台 近半个世纪 我们总能听到苹果在AR行业的创新动作 开辟了新的硬件范式 AR VR行业为苹果不断欢呼的同时 激发了人们的好奇心 究竟 人类在戴上AR眼镜的那一瞬间
  • 【C++】内存分区&引用

    内存分区 首先我们要了解 内存区域大概分为四个区域 1 代码区 这里主要存放我们写的代码的二进表达式 即CPU可以看懂的机械指令 这个区域有两个特征 只读和共享 前者可以保证代码的不会被随意修改 后者可以保证相同代码多次阅读不需要创建多个副
  • linux-kali 2020.3.3 虚拟机 环境 下载安装

    一 所需环境配置文件下载 1 虚拟机 这次配置环境使用的vmware版本为15 5 0 虚拟机大家可以自行在相关微信公众号上搜索破解版 按照其上进行安装 如下图 如果需要也可以vm官方网站上进行下载相关软件 直接下载对应版本即可 vm官网链
  • python argument 1 must be 2-item sequence, not int

    在继续python学习的时候 发现报错了 出现错误argument 1 must be 2 item sequence not int 明明我是照着书打的 为什么会出现错误呢 import pygame import sys from se
  • 《软件测试》第十三章 软件安全测试

    软件测试 第十三章 软件安全测试 13 0 前言 13 1 战争游戏 电影 13 2 了解动机 13 3 威胁模式分析 13 4 软件安全是一项功能吗 软件漏洞是一个缺陷吗 13 5 了解缓冲区溢出 13 6 使用安全的字符串函数 13 7
  • Microsoft visual C++ 2013 redistributable (x86) setup failed

    Microsoft visual C 2013 redistributable x86 0x80070005 setup failed log 截图 下载SubInACL工具 链接 https blogs msdn microsoft co
  • css flex shrink,CSS3 flex-shrink属性用法详解

    下面本文章来给各位介绍一下CSS3 flex shrink使用方法 希望例子能帮助到各位 flex grow控制flex container有多余空间的时候怎么分配 默认值为0 即所有的flex items都不分配 flex shrink1
  • C#里面SQLite读取数据的操作

    挂载表格时候用 public static DataSet Query string SQLString using SQLiteConnection connection new SQLiteConnection connectionSt
  • BottomNavigationView+ViewPager实现页面滑动

    如图所示 在androidstudio中新建一个Bottom Navigation Activity 修改布局中的内容
  • import无法定位到输入点

    torch geometric安装 运行环境需要torch geometric 下载安装完之后 再 import torch geometric时 一直出错 安装不上 找了很多解决方法 终于找到可以解决的办法了 亲测有效 原因 最终发现因为
  • 最小重量机器设计问题

    相关问题 工作分配问题 设某一机器由n个部件组成 每一种部件都可以从m个不同的供应商处购得 设 wij 是从供应商j 处购得的部件i的重量 cij 是相应的价格 试设计一个回溯算法 给出总价格不超过d的最小重量机器设计 对于给定的机器部件重
  • .net core 并发下的线程安全问题

    抱歉 其实内容并不如题 背景 写测试demo所出现的异常 供大家学习与拍砖 net core webapi项目 做了一个授权的filter 真正的生产项目的话 JWT很棒 单个接口测试没有问题 当用前端在同一个页面调用多个接口的时候 运行服
  • Python语言学习实战-内置函数filter()的使用(附源码和实现效果)

    实现功能 filter 函数是Python的内置函数之一 用于过滤序列中的元素 它接受两个参数 一个是函数 用于判断每个元素是否符合条件 另一个是可迭代对象 包含要过滤的元素 filter 函数返回一个迭代器 其中包含所有符合条件的元素 f
  • Beam:没有模板Haskell的数据库功能!

    作为Haskell Web系列的一部分 我们检查了Persistent和Esqueleto库 这些中的第一个允许您使用特殊语法创建数据库模式 然后 您可以使用Template Haskell生成所有必要的Haskell数据类型和类型的实例