zoukankan      html  css  js  c++  java
  • 用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之后,怎么执行,得到想要的结果。

  • 相关阅读:
    【UOJ #46】 【清华集训2014】玄学
    【BZOJ3626】 [LNOI2014]LCA
    THUSC 2017 游记
    Cqoi2017试题泛做
    全国高校程序设计大赛 评定奖学金
    全国高校程序设计大赛 素数和偶数
    全国高校程序设计大赛 八进制数中含7的总个数
    全国高校程序设计大赛 电商促销
    PTA 02-线性结构1 两个有序链表序列的合并
    PTA 02-线性结构2 一元多项式的乘法与加法运算
  • 原文地址:https://www.cnblogs.com/javanerd/p/scala_sql.html
Copyright © 2011-2022 走看看