2009-09-08 398 views
38

我想在非关系数据存储上创建一个SQL接口。非关系数据存储,但以关系方式访问数据是有意义的。用Python解析SQL

我正在研究使用ANTLR来产生一个表示SQL作为关系代数表达式的AST。然后通过评估/行走树来返回数据。

我从来没有实现过一个解析器,因此我想就如何最好地实现SQL解析器和评估器提供一些建议。

  • 上面描述的方法听起来是对的吗?
  • 我应该看看有其他的工具/库吗?像PLYPyparsing
  • 指向将帮助我的文章,书籍或源代码的赞赏。

更新:

我使用pyparsing实现了一个简单的SQL语法分析程序。结合对我的数据存储实现关系操作的Python代码,这非常简单。

正如我在其中一条评论中所说的,练习的要点是使数据可用于报告引擎。为此,我可能需要实现一个ODBC驱动程序。这可能是很多工作。

+2

为什么对对象施加SQL限制?会得到什么? OQL有什么问题? http://en.wikipedia.org/wiki/Object_Query_Language – 2009-09-08 20:00:16

+7

待获得:大量报告工具可以使用的查询界面。我打算在客户端上实现一个ODBC驱动程序。因此,业务用户可以使用Crystal Reports,Excel等从数据存储中获取数据。 OQL虽然可能是一种很好的查询语言(我从来没有用过),但它并不像SQL那样广泛。 – codeape 2009-09-09 11:29:19

+1

+1两者:OO数据库最大的问题之一就是缺乏报表引擎:( – van 2009-09-19 11:43:27

回答

33

我已经很深入地研究了这个问题。 Python-sqlparse是一个非验证解析器,它不是您真正需要的。 antlr中的例子需要很多工作才能转换为python中的一个很好的ast。 sql标准语法是here,但它将是一个全职工作来自己转换它们,并且很可能您只需要它们的一个子集,即没有联接。你也可以试试看gadfly(一个python sql数据库),但是我避免了它,因为他们使用了他们自己的解析工具。

对我而言,我只需要一个where子句。我尝试用pyparsing编写booleneo(一个布尔表达式解析器),但最终从头开始使用pyparsing。 Mark Rushakoff的reddit文章中的第一个链接给出了一个使用它的sql示例。 Whoosh全文搜索引擎也使用它,但我没有看过源,看看如何。

Pyparsing非常易于使用,您可以非常容易地将其定制为与sql(大多数不需要的语法)完全相同。我不喜欢它,因为它使用一些使用命名约定的魔法。

简而言之,pyparsing一个尝试,它很可能足够强大,可以完成你所需要的任务,并且简单地与python进行集成(使用简单的回调和错误处理)将使得体验变得非常轻松。

+1

感谢您分享您的体验。初始的,非常有限的python-sqlparse测试,似乎我可能会使用它,我将尝试使用python-sqlparse中''parse''函数的返回值,但是我会研究pyparsing in任何情况下。 – codeape 2009-09-09 11:25:01

+1

Pyparsing是一个很好的工具,有很多解析sql的例子。 – 2009-09-09 21:30:59

+2

pyparsing wiki上的海报(http://pyparsing.wikispaces.com/message/view/home/14105203)刚刚报告完成一个SQL SELECT语法分析器 - 也许你可以联系他/她寻求帮助,建议,甚至是代码 – PaulMcG 2009-09-11 02:51:25

9

This reddit post建议Python-sqlparse作为一个现有的实现,在其他一些链接中。

+0

感谢您的建议。Python-sqlparse看起来很有趣,我会试试看。 – codeape 2009-09-09 11:19:52

2

TwoLaid的Python的SQL语法分析器都非常好,我的目的。它用C编写,需要编译。它很健壮。它解析每个子句的各个元素。

https://github.com/TwoLaid/python-sqlparser

我用它来解析出查询列名在报告标题中使用。这是一个例子。

import sqlparser 

def get_query_columns(sql): 
    '''Return a list of column headers from given sqls select clause''' 

    columns = [] 

    parser = sqlparser.Parser() 

    # Parser does not like new lines 
    sql2 = sql.replace('\n', ' ') 

    # Check for syntax errors 
    if parser.check_syntax(sql2) != 0: 
     raise Exception('get_query_columns: SQL invalid.') 

    stmt = parser.get_statement(0) 
    root = stmt.get_root() 
    qcolumns = root.__dict__['resultColumnList'] 
    for qcolumn in qcolumns.list: 
     if qcolumn.aliasClause: 
     alias = qcolumn.aliasClause.get_text() 
     columns.append(alias) 
     else: 
     name = qcolumn.get_text() 
     name = name.split('.')[-1] # remove table alias 
     columns.append(name) 

    return columns 

sql = ''' 
SELECT 
    a.a, 
    replace(coalesce(a.b, 'x'), 'x', 'y') as jim, 
    a.bla as sally -- some comment 
FROM 
    table_a as a 
WHERE 
    c > 20 
''' 

print get_query_columns(sql) 

# output: ['a', 'jim', 'sally']