编写特定于域的语言,用于从表中选择行

时间:2008-09-26 14:56:05

标签: python database algorithm dsl

我正在编写一个服务器,我期望由许多不同的人管理,而不是所有与我直接联系的人。服务器将在群集中相互通信。服务器功能的一部分涉及从可能非常大的表中选择一小部分行。选择哪些行的确切选择将需要一些调整,并且重要的是运行集群的人(例如,我自己)可以更新选择标准而不让每个服务器管理员部署新版本的服务器

简单地用Python编写函数实际上并不是一个选项,因为没有人会想要安装一个在运行时下载并执行任意Python代码的服务器。

我需要的是关于实现域特定语言以实现此目标的最简单方法的建议。该语言需要能够进行简单的表达式评估,以及查询表索引和迭代返回的行。易于编写和阅读语言是易于实现的第二步。我也不想编写整个查询优化器,所以明确指定要查询的索引是理想的。

这将需要编译的接口与App Engine数据存储区导出的功能类似:您可以查询表中任何索引的顺序范围(例如,小于,大于,范围和等式查询),然后通过任何布尔表达式过滤返回的行。您还可以将多个独立的结果集连接在一起。

我意识到这个问题听起来很像我要求SQL。但是,我不希望要求支持此数据的数据存储区是关系数据库,并且我不希望尝试自己重新实现SQL的开销。我也只处理一个具有已知模式的表。最后,不需要加入。更简单的东西是更可取的。

编辑:扩展说明以消除一些误解。

9 个答案:

答案 0 :(得分:4)

构建一个由Python解释的DSL。

步骤1.构建运行时类和对象。这些类将包含所有游标循环和SQL语句,并且所有这些算法处理都隐藏在它们的方法中。您将大量使用CommandStrategy设计模式来构建这些类。大多数事情都是命令,选项和选择都是插件策略。看看Apache Ant的Task API的设计 - 这是一个很好的例子。

步骤2.验证此对象系统是否真正有效。确保设计简单而完整。您将测试将构造Command和Strategy对象,然后执行顶级Command对象。 Command对象将完成工作。

此时你基本完成了。您的运行时只是从上述域创建的对象的配置。 [这并不像听起来那么容易。需要注意定义一组可以实例化的类,然后“在它们之间进行交谈”以完成应用程序的工作。]

请注意,您所拥有的只需要声明。程序有什么问题?你开始用程序元素编写一个DSL,你会发现在用不同的语法编写Python之前,你需要越来越多的功能。不好。

此外,程序语言解释器很难写。执行状态和参考范围很难管理。

你可以使用原生Python - 并且不再担心“走出沙箱”。实际上,这就是你如何使用简短的Python脚本来创建对象来对所有内容进行单元测试。 Python将是DSL。

[“但是等等”,你说,“如果我只是使用Python作为DSL,人们可以执行任意事情。”取决于PYTHONPATH和sys.path上的内容。查看site模块,了解控制可用内容的方法。]

声明性DSL最简单。这完全是一种代表性的练习。仅仅设置某些变量值的Python块很不错。这就是Django使用的。

您可以使用ConfigParser作为语言来表示对象的运行时配置。

您可以使用JSONYAML作为表示对象运行时配置的语言。现成的解析器完全可用。

您也可以使用XML。设计和解析起来比较困难,但效果很好。人们喜欢它。这就是Ant和Maven(以及许多其他工具)使用声明性语法来描述过程的方式。我不推荐它,因为它是颈部的罗嗦疼痛。我建议只使用Python。

或者,您可以远离深层并发明自己的语法并编写自己的解析器。

答案 1 :(得分:1)

我想我们在这里需要更多信息。如果以下任何内容基于错误的假设,请告诉我。

首先,正如您自己指出的那样,已经存在用于从任意表中选择行的DSL--它被称为“SQL”。由于您不想重新发明SQL,我假设您只需要从具有固定格式的单个表中进行查询。

如果是这种情况,您可能不需要实施DSL(虽然这当然是一种方法);如果您习惯于面向对象,则可能更容易创建Filter对象。

更具体地说,是一个“过滤器”集合,它将包含一个或多个SelectionCriterion对象。您可以实现这些来继承一个或多个表示选择类型的基类(Range,LessThan,ExactMatch,Like等)。一旦这些基类到位,您就可以创建适合该列的特定于列的继承版本。最后,根据您想要支持的查询的复杂性,您需要实现某种连接粘合来处理各种标准之间的AND和OR和NOT链接。

如果您愿意,可以创建一个简单的GUI来加载集合;如果您没有其他任何想法,我会将Excel中的过滤视为模型。

最后,将此Collection的内容转换为相应的SQL并将其传递给数据库应该是微不足道的。

但是:如果你所追求的是简单性,并且你的用户理解SQL,你可以简单地要求他们输入WHERE子句的内容,并以编程方式构建查询的其余部分。从安全角度来看,如果您的代码可以控制所选列和FROM子句,并且您的数据库权限设置正确,并且您对来自用户的字符串进行了一些完整性检查,那么这将是一个相对安全的选项。

答案 2 :(得分:1)

“实施领域特定语言”

“没有人会想要安装在运行时下载并执行任意Python代码的服务器”

我想要DSL,但我不希望Python成为DSL。好的。你将如何执行这个DSL?如果不是Python,那么什么运行时 可以接受?

如果我有一个碰巧嵌入Python解释器的C程序怎么办?那可以接受吗?

并且 - 如果Python不是可接受的运行时 - 为什么这有Python标签?

答案 3 :(得分:0)

为什么不创建一种语言,当它“编译”它会生成SQL或您的数据存储需要的任何查询语言?

您基本上会在持久层上创建抽象。

答案 4 :(得分:0)

你提到了Python。为什么不使用Python?如果有人可以在DSL中“输入”表达式,他们可以输入Python。

你需要一些关于表达式结构的规则,但这比实现新的东西容易得多。

答案 5 :(得分:0)

你说没有人会想要安装一个在运行时下载并执行任意代码的服务器。然而,这正是你的DSL将做的(最终),所以可能没有那么大的区别。除非你正在做一些非常具体的数据,否则我认为DSL不会给你带来那么多,它会让那些已经熟悉SQL的用户感到沮丧。不要低估你将要承担的任务的大小。

然而,要回答你的问题,你需要为你的语言提出一个语法,解析文本,走树,发代码或调用你写的API(这就是为什么我的评论你仍然需要发布一些代码)。

有许多关于数学表达式的语法教育文本,你可以在网上引用,这是相当直接的。您可能有一个像ANTLR或Yacc这样的解析器生成器工具,可以帮助您生成解析器(或者使用像Lisp / Scheme这样的语言并将它们结合起来)。提出合理的SQL语法并不容易。但谷歌'BNF SQL',看看你想出了什么。

祝你好运。

答案 6 :(得分:0)

这听起来像是SQL,但如果你想保持简单,也许尝试使用SQLite是值得的?

答案 7 :(得分:0)

听起来你想创建一种语法而不是DSL。我将研究ANTLR,它将允许您创建一个特定的解析器,它将解释文本并转换为特定的命令。 ANTLR为Python,SQL,Java,C ++,C,C#等提供库。

此外,这是在C#

中创建的ANTLR calculation engine的一个很好的例子

答案 8 :(得分:0)

无上下文语法通常具有树状结构,功能程序也具有树状结构。我不会声称以下内容可以解决您的所有问题,但如果您确定不想使用SQLite3之类的内容,那么这是朝着这个方向迈出的良好一步。

from functools import partial
def select_keys(keys, from_):
    return ({k : fun(v, row) for k, (v, fun) in keys.items()}
            for row in from_)

def select_where(from_, where):
    return (row for row in from_
            if where(row))

def default_keys_transform(keys, transform=lambda v, row: row[v]):
    return {k : (k, transform) for k in keys}

def select(keys=None, from_=None, where=None):
    """
    SELECT v1 AS k1, 2*v2 AS k2 FROM table WHERE v1 = a AND v2 >= b OR v3 = c

    translates to 

    select(dict(k1=(v1, lambda v1, r: r[v1]), k2=(v2, lambda v2, r: 2*r[v2])
        , from_=table
        , where= lambda r : r[v1] = a and r[v2] >= b or r[v3] = c)
    """
    assert from_ is not None
    idfunc = lambda k, t : t
    select_k = idfunc if keys is None  else select_keys
    if isinstance(keys, list):
        keys = default_keys_transform(keys)
    idfunc = lambda t, w : t
    select_w = idfunc if where is None else select_where
    return select_k(keys, select_w(from_, where))

如何确保您不会授予用户执行任意代码的能力。该框架承认所有可能的功能。好吧,你可以在它上面设置一个包装器以获得安全性,以便公开可接受的固定功能对象列表。

ALLOWED_FUNCS = [ operator.mul, operator.add, ...] # List of allowed funcs

def select_secure(keys=None, from_=None, where=None):
    if keys is not None and isinstance(keys, dict):
       for v, fun keys.values:
           assert fun in ALLOWED_FUNCS
    if where is not None:
       assert_composition_of_allowed_funcs(where, ALLOWED_FUNCS)
    return select(keys=keys, from_=from_, where=where)

如何撰写assert_composition_of_allowed_funcs。在python中很难做到这一点,但在lisp中很容易。让我们假设在哪里是要用嘴唇评估的函数列表,例如where=(operator.add, (operator.getitem, row, v1), 2)where=(operator.mul, (operator.add, (opreator.getitem, row, v2), 2), 3)

这使得编写apply_lisp函数成为可能,该函数确保where函数仅由ALLOWED_FUNCS或常量组成,如float,int,str。

def apply_lisp(where, rowsym, rowval, ALLOWED_FUNCS):
    assert where[0] in ALLOWED_FUNCS
    return apply(where[0],
          [ (apply_lisp(w, rowsym, rowval, ALLOWED_FUNCS)
            if isinstance(w, tuple)
            else rowval if w is rowsym
            else w if isinstance(w, (float, int, str))
            else None ) for w in where[1:] ])

除此之外,您还需要检查确切的类型,因为您不希望覆盖您的类型。因此,请勿使用isinstance,请使用type in (float, int, str)。哦,我们遇到了男孩:

  

格林斯普的第十条规划规则:任何足够复杂的规则   C或Fortran程序包含一个非正式指定的临时   错误的慢速实现Common Lisp的一半。