# 概述

easysql是一个使用Scala3编写的sql构造器,主要特点有:

1. 其充分利用了Scala3优秀的类型系统,可以在编译期解决掉大多数的错误sql;
2. 得益于Scala3强大的表达能力,使用方式与原生sql非常相似,学习成本低;
3. 可以使用Scala3本身的封装能力,动态构建上千行的复杂sql,而无需拼接字符串,也无需使用者学习sql语法树知识。

虽然easysql是为了动态构建报表的复杂查询等场景而生的,但也可以当做orm类框架使用,比如执行查询,并把结果映射到对象(支持多表结果映射),或是使用case class直接生成insert、update等sql语句,避免编写样板代码,这些功能使用Scala3的内联函数和宏等功能,在编译期生成代码,并无运行期反射开销。

我们可以使用原生sql风格的api来构造一个查询模板:

val userName: Option[String] = ???

val s = (
    select (user, post)
    from user
    leftJoin post on user.id === post.userId
    orderBy user.id.asc
    limit 10 offset 10
).where(userName.nonEmpty, user.name === userName)

库内置了一个sql的语法树,sql的任何部分都可以转化为对象或者方法调用,可以灵活地动态构造查询,这在某些应用(比如报表平台)中会非常有价值:

val s = select(user)

val newQuery = if (true) {
    val condition = user.id === 1
    s.from(user).where(condition).orderBy(user.id.asc)
} else {
    s.from(user).leftJoin(post).on(user.id === post.userId).select(post)
}

相比使用字符串等方式,不必费尽心思的处理字符串拼接,并且,调用各种子句的顺序可以不囿于sql语句的顺序,也可以在生成sql时处理成正确的情况。

并且,查询可以结构化创建,比如下面的嵌套子查询:

val q1 = select (user.id, count() + sum(user.id) as "c1") from user as "q1"
    
val q2 = select (q1.id, q1.c1) from q1 as "q2"

val q3 = select (q2.id) from q2 as "q3"

val q4 = select (q3.id as "c1") from q3 as "q4"

val q5 = select (q4.c1) from q4

我们可以分步构建复杂查询,而无需像原生sql那样嵌套,在最终使用时,也能正确生成查询。

而且easysql提供的api是类型安全的,比如下面的错误查询,会得到编译错误,而不是运行期异常:

val s1 = select (user.id, user.name) from user
val s2 = select (user.id) from user

val u = s1 union s2

easysql还提供除此之外的更多编译期正确性检查,帮助用户构建安全的查询。

不过,easysql并不是时时刻刻都能判定错误查询并返回编译错误,这并非是Scala3的类型系统不支持,而是与动态构建查询的易用性进行权衡之后的结果,某些编译期校验会让动态查询构建变得十分复杂(比如group by字段非主键的情况下,查询列表中出现了别的字段)。

easysql在易用性与安全性之间有比较好的平衡,比起毫无编译期保障的sql字符串来说,更具安全性,并且还能得到IDE的提示。

支持MySQL、PostgreSQL、Oracle、SQLserver、DB2、SQLite等数据库的方言生成。同一个查询对象,可以方便地生成不同数据库的方言:

val s = select (user) from user limit 10 offset 10

val sql1 = s.sql(DB.MYSQL)
val sql2 = s.sql(DB.PGSQL)
...... 

其中PostgreSQL和MySQL方言是第一优先级支持,dsl设计以PostgreSQL语法为蓝本。