用scala实现一个sql执行引擎-(上)

前言

在实时计算中,通常是从队列中收集原始数据,这种原始数据在内存中通常是一个java bean,把数据收集过来以后,通常会把数据落地到数据库,供后面的ETL使用。举个一个简单的例子,对一个游戏来说,为了统计某个游戏,某个服务器的登陆注册

等事件,原始数据对应的java bean可能会是这样:

public class Event {
    private String userName;
    private String game;
    private String server;
    private String event;
}

Event

当数据量过大的时候,通常没有办法实时的去做一个些统计操作,例如统计按照游戏和服务器分组统计出登陆的人次是多少,对应的SQL大致如下:

select count(user_name) from event group by name,sever where event = ‘login‘

当有一个sql执行引擎,可以在内存中对于一批收集过来的数据执行sql计算的时候,无疑能够实时的计算出结果,另外由于sql是实时输入的,程序也可以比较灵活。

例如,收集过来的一批数据,可以换成成一个List<Map<String,Object>>形式的数据结构,通过sql执行引擎,执行某个特定的sql,得到结果(也是一个List<Map<String,Object>>形式的数据结构),demo如下

----------------
[username:user1,game:lol,server:s1,event:login]
[username:user2,game:dota2,server:s2,event:register]
[username:user3,game:lol,server:s2,event:login]
[username:user4,game:dota2,server:s3,event:register]
[username:user5,game:lol,server:s10,event:login]
[username:user6,game:dota2,server:s1,event:login]
[username:user7,game:lol,server:s1,event:login]
[username:user8,game:lol,server:s1,event:login]
[username:user9,game:lol,server:s1,event:login]
----------------
 select count(*) as loginNum, game,server from event group by game,server where event=‘login‘
----------------
[loginNum:1,game:lol,server:s2]
[loginNum:4,game:lol,server:s1]
[loginNum:1,game:lol,server:s10]
[loginNum:1,game:dota2,server:s1]
----------------

解析

此sql执行引擎只支持的sql语法中的一个很小的子集,所以我更加偏向称其为sql-like DSL(Domain Specific Language-特定领域语言),关于DSL的论述很多,我推荐两本书,一本是Martin大叔的Domain Specific Language,另外一个本是DSL in

action。之所以选择scala来实现,是因为scala语言中内置了对DSL的支持,可以很方便的实现一个自己的Parser,通过此Parser,可以解析你的DSL脚本(此处就是sql语句),得到你想要的中间结果,通常我们将中间结果称为AST(Abstract syntax tree),类似于

select {...} from {...} group by {...}  where {...}order by{...} limit {...}形式的sql语句,我将它转化成如下类型的AST。

解析器的入口为

def select: Parser[SelectStmt] = "select" ~> projectionStatements ~ fromStatements ~ opt(groupStatements) ~ opt(whereExpr) ~ opt(orderByExpr) ~ opt(limit) ~ opt(";") ^^ {
    case p ~ f ~ g ~ w ~ o ~ l ~ end => SelectStmt(p, f, w, g, o, l)
  }

其中,fromStatements,groupStatements,whereExpr等有是一个单独的解析器,通过scala中已经提供的parser combinators(解析器组合子),例如(~>,~,opt()...)等,将单独的解析器组合起来,可以得到更复杂的解析器,类似于lego积木,你编写一个解析

器,parserA, 只能解析某段特殊的文本,这个段文本的模式我们用patternA来表示。通过组合子 rep1sep(“,”,parserA),你就得到了一个新的解析器,这个解析器能解析的partern = patternA[,patternA][,patternA][,patternA]...

例如sql语句中的group by子句,不考虑having语法的话,大致格式是这样的 group by [tableName.]coulumn1,[tableName.]coulumn1,[tableName.]coulumn1 可见[tableName.]coulumn1这种格式的文本,可以是基本的pattern,于是可以写出一个解析器来解析这种格式的文本:

def selectIdent: Parser[SqlProj] = {
    ident ~ opt("." ~> ident) ^^ {
      case table ~ Some(b: String) => FieldIdent(Option(table), b)
      case column ~ None => FieldIdent(None, column)
    }
  }

这个函数中ident值得的标示符,opt()表示的是可以有也可以没有,那么这个解析器解析的文本就可以有如下形式:标示符.标示符|标示符,那么通过rep1sep的组合子就能得到解析group by字句的解析器:

def groupStatements: Parser[SqlGroupBy] = "group" ~> "by" ~> rep1sep(selectIdent, ",") ^^ {
    case keys => SqlGroupBy(keys)
  }

其他部分的sql字句的解析大抵如此,整个项目的代码,在github上。下一篇讲拿到AST之后,怎么执行,得到想要的结果。

时间: 2024-10-08 04:56:43

用scala实现一个sql执行引擎-(上)的相关文章

用scala实现一个sql执行引擎-(下)

执行 上一篇讲述了如何通过scala提供的内置DSL支持,实现一个可以解析sql的解析器,这篇讲如何拿到了解析结果-AST以后,如何在数据上进行操作,得到我们想要的结果.之前说到,为什么选择scala作为这个引擎的实现,之一是scala提供了方便的DSL实现支持,其二是因为作为一门函数式编程语言,scala提供了丰富对于集合操作的函数.此外,函数在scala中是一个独立的类型,所以能够把现有的函数进行组合,得到更为强大的函数(和上一篇提到的用解析组合子组合已有的解析器得到更强大的解析器一样).

自己实现一个SQL解析引擎

自己实现一个SQL解析引擎 功能:将用户输入的SQL语句序列转换为一个可执行的操作序列,并返回查询的结果集. SQL的解析引擎包括查询编译与查询优化和查询的运行,主要包括3个步骤: 查询分析: 制定逻辑查询计划(优化相关) 制定物理查询计划(优化相关) 查询分析: 将SQL语句表示成某种有用的语法树. 制定逻辑查询计划: 把语法树转换成一个关系代数表达式或者类似的结构,这个结构通常称作逻辑计划. 制定物理查询计划:把逻辑计划转换成物理查询计划,要求指定操作执行的顺序,每一步使用的算法,操作之间的

SQL执行过程中的性能负载点

一.SQL执行过程 1.用户连接数据库,执行SQL语句: 2.先在内存进行内存读,找到了所需数据就直接交给用户工作空间: 3.内存读失败,也就说在内存中没找到支持SQL所需数据,就进行物理读,也就是到磁盘中查找: 4.找到的数据放到内存中,在内存进行数据过滤再放到会话工作空间. 5.假设会话工作空间需要暂存结果集进行排序,但空间不足的话,就会借用磁盘tmpdir,最后再将结果返回给用户. 注: 用户会话空间是内存中分配出来的一个工作空间,而innodb_buffer_pool是innodb存储引

Oracle数据库该如何着手优化一个SQL

这是个终极问题,因为优化本身的复杂性实在是难以总结的,很多时候优化的方法并不是用到了什么高深莫测的技术,而只是一个思想意识层面的差异,而这些都很可能连带导致性能表现上的巨大差异.所以有时候我们应该先搞清楚需求到底是什么,SQL本身是否合理,这些思考很可能会使优化工作事半功倍.而本文是假设SQL本身合理,从Oracle提供给我们的一些技术手段来简单介绍下Oracle数据库,该如何使用一些现有的技术来优化一个SQL执行的性能. 确定需要优化的SQL文本及当前SQL执行计划 确定SQL涉及的所有表及其

Farseer.net轻量级开源框架 中级篇:SQL执行报告

导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 中级篇: 数据库切换 下一篇:Farseer.net轻量级开源框架 中级篇: 探究ORM(Mapping) 很多时候,我们希望能知道我们写的项目在运行过程中到底执行了哪些SQL语句,或者说一个页面中访问了几次数据库.好让我们在优化的时候提供一些方向. 举个例子,作者在有时候,会自信的认为这个页面很简单,估计也就顶多5条SQL语句就可以了.可是当打开SQL报告之后才吓傻眼,几十条SQL,看的

SQL Server中的执行引擎入门

简介 当查询优化器(Query Optimizer)将T-SQL语句解析后并从执行计划中选择最低消耗的执行计划后,具体的执行就会交由执行引擎(Execution Engine)来进行执行.本文旨在分类讲述执行计划中每一种操作的相关信息. 数据访问操作 首先最基本的操作就是访问数据.这既可以通过直接访问表,也可以通过访问索引来进行.表内数据的组织方式分为堆(Heap)和B树,其中表中没有建立聚集索引时数据是通过堆进行组织的,这个是无序的,表中建立聚集索引后和非聚集索引的数据都是以B树方式进行组织,

MySQL执行sql查询并上传至远程服务器

最近项目中有需要做一个shell脚本,可以对一个数据库执行sql操作,并将结果转为txt,筛选结果用tab隔开,保存至一个远程服务器上,以供其他人用Excel读取用txt中的内容. MySQL中将结果保存下来,有两种方案,一种是在sql语句中增加INTO OUTFILE语句,并且可以定制化输出的格式.但是这种方法留下的文件在数据库所在的服务器上,而期望的是将文件放在执行脚本的机器上. 后来实现是不改变sql语句的内容,在脚本中将结果保存到本地/tmp目录下,再用curl上传到远程服务器上,下面抽

DocCms存储型XSS+后台任意文件下载上传+目录删除+sql执行(有条件可getshell)

下载链接 https://share.weiyun.com/46ebceb4fe91da144ad2661522a941e1 留言处存储型XSS 漏洞在content/guestbook/index.php function create() { echo 123; global $db,$request; if ($_SESSION['verifycode'] != $request['checkcode']) { echo '<script>alert("请正确填写验证码!&qu

SqlServer 中如何查看某一个Sql语句是复用了执行计划,还是重新生成了执行计划

我们知道SqlServer的查询优化器会将所执行的Sql语句的执行计划作缓存,如果后续查询可以复用缓存中的执行计划,那么SqlServer就会为后续查询复用执行计划而不是重新生成一个新的执行计划,因为复用执行计划的性能比生成执行计划的性能要高很多,所以SqlServer的这一特性可以大大提高Sql语句的执行效率.特别是对于存储过程,因为存储过程的执行计划是在存储过程第一次执行的时候生成的,存储过程的执行计划生成后就会被缓存到SqlServer的执行计划列表中,如果以后存储过程再被执行,那么存储过