在Kotlin上输入Safe SQL

表现力是编程语言的一个有趣特性。 通过简单地组合表达式,您可以获得令人印象深刻的结果。 有些语言故意拒绝表达性的想法,但是Kotlin绝对不是这样的语言。


使用基本的语言构造和少量的糖,我们将尝试以尽可能接近Kotlin语法的方式重新创建SQL。


与


不耐烦的GitHub链接


我们的目标是帮助程序员在编译阶段捕获特定的错误子集。 Kotlin是一种强类型语言,将帮助我们摆脱SQL查询结构中的无效表达式。 作为奖励,我们将在IDE编写请求中获得更多的错字保护和帮助。 不可能完全修复SQL缺陷,但是有可能修复某些问题区域。


本文将向您介绍Kotlin库,该库允许您以Kotlin语法编写SQL查询。 另外,我们看一下库的内部以了解其工作原理。


一点理论


SQL代表结构化查询语言,即 尽管语法很差,但仍存在查询的结构-语言的创建是为了使甚至没有编程技能的任何用户都可以使用它。


但是,在SQL下,以关系数据库理论的形式奠定了相当强大的基础-那里的一切都很合乎逻辑。 为了理解查询的结构,我们转向一个简单的选择:


SELECT id, name --  (projection), π(id, name) FROM employees --  (table) WHERE organization_id = 1 --    (predicate), σ(organization_id = 1) 

重要的是要理解:请求包含三个连续的部分。 这些部分中的每个部分首先取决于前一个部分,其次意味着用于继续请求的一组有限的表达式。 实际上,事实并非如此:FROM表达式显然相对于SELECT是主要的,因为 我们可以选择哪些字段集取决于进行选择的表,反之则不然。


的SQL


移植到科特林


因此,相对于任何其他查询语言构造,FROM是主要的。 正是从此表达式中得出了继续查询的所有可能选项。 在Kotlin中,我们通过from(T)函数反映了这一点,该函数将采用一个输入对象,该对象是具有一组列的表。


 object Employees : Table("employees") { val id = Column("id") val name = Column("name") val organizationId = Column("organization_id") } 

该函数将返回一个对象,该对象包含反映请求可能继续进行的方法。 from构造始终在所有其他表达式之前排在最前面,因此它涉及大量扩展,包括最终的SELECT(与SQL相对,其中SELECT总是在FROM之前)。 与上述SQL查询等效的代码如下所示:


 from(Employees) .where { e -> e.organizationId eq 1 } .select { e -> e.id .. e.name } 

有趣的是,通过这种方式,即使在编译时,我们也可以防止无效的SQL。 链中的每个表达式,每个方法调用都包含有限数量的扩展。 我们可以使用Kotlin语言控制请求的有效性。 例如,where表达式并不意味着以另一个where and形式继续,但是,groupBy,orderBy,limit,offset和final select都是有效的。


库里


将lambda作为参数传递给where和select语句的目的是分别构造谓词和投影(我们前面已经提到过)。 将一个表传递给lambda输入,以便您可以访问列。 同样重要的是,类型安全性也必须保持在此级别上-在运算符重载的帮助下,我们可以确保谓词最终是伪布尔表达式,如果存在语法错误或与类型相关的错误,则无法编译该谓词。 投影也是如此。


 fun where(predicate: (T) -> Predicate): WhereClause<T> fun select(projection: (T) -> Iterable<Projection>): SelectStatement<T> 

加盟


关系数据库使您可以处理许多表及其之间的关系。 让开发人员有机会与我们的库中的JOIN一起工作会很好。 幸运的是,关系模型非常适合前面描述的所有内容-您只需要添加join方法,这将向我们的表达式添加第二个表。


 fun <T2: Table> join(table2: T2): JoinClause<T, T2> 

在这种情况下,JOIN将具有与FROM表达式提供的方法类似的方法,唯一的区别在于,投影和谓词lambda将采用两个参数,每个参数都能够访问两个表的列。


 from(Employees) .join(Organizations).on { e, o -> o.id eq e.organizationId } .where { e, o -> e.organizationId eq 1 } .select { e, o -> e.id .. e.name .. o.name } 

资料管理


数据操作语言是一种SQL语言工具,除查询表外,还允许您插入,修改和删除数据。 这些设计非常适合我们的模型。 为了支持更新和删除,我们只需要在调用from和where表达式时对变量进行补充就可以调用相应的方法。 为了支持插入,我们在功能中引入了附加功能。


 from(Employees) .where { e -> e.id eq 1 } .update { e -> e.name("John Doe") } from(Employees) .where { e -> e.id eq 0 } .delete() into(Employees) .insert { e -> e.name("John Doe") .. e.organizationId(1) } 

资料说明


SQL使用表形式的结构化数据。 在使用表之前,需要先进行描述。 该语言的这一部分称为数据定义语言。


CREATE TABLE和DROP TABLE语句的实现方式类似-over函数将作为起点。


 over(Employees) .create { integer(it.id).primaryKey(autoIncrement = true).. text(it.name).unique().notNull().. integer(it.organizationId).foreignKey(references = Organizations.id) } 

 over(Employees).drop() 

Source: https://habr.com/ru/post/zh-CN414483/


All Articles