5

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

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

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

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

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

编辑:扩展描述以消除一些误解。

4

9 回答 9

4

构建由 Python 解释的 DSL。

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

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

至此,您基本上完成了。您的运行时只是从上述域创建的对象的配置。[这并不像听起来那么容易。定义一组可以实例化的类,然后“相互交流”以完成应用程序的工作,需要小心谨慎。]

请注意,您将拥有的只是声明。程序有什么问题?当您开始编写带有过程元素的 DSL 时,您会发现需要越来越多的功能,直到您使用不同的语法编写 Python。不好。

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

您可以使用本机 Python ——而不必担心“走出沙箱”。实际上,这就是您将如何对所有内容进行单元测试,使用一个简短的 Python 脚本来创建您的对象。Python 将成为 DSL。

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

声明式 DSL 是最简单的。这完全是一种表现形式的练习。只设置一些变量的值的 Python 块很好。这就是 Django 使用的。

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

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

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

或者,您可以深入研究并发明自己的语法并编写自己的解析器。

于 2008-09-26T22:14:14.890 回答
1

我想我们在这里需要更多信息。让我知道以下任何内容是否基于不正确的假设。

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

如果是这种情况,您可能不需要实现 DSL(尽管这肯定是一种方法);如果您习惯于面向对象,那么创建过滤器对象可能会更容易。

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

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

最后,将这个 Collection 的内容转换为相应的 SQL,并将其传递给数据库应该很简单。

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

于 2008-09-26T15:35:16.627 回答
1

“实现领域特定语言”

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

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

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

而且——如果 Python 不是一个可接受的运行时——为什么它有一个 Python 标记?

于 2008-09-26T20:46:31.187 回答
0

为什么不创建一种在“编译”时生成 SQL 或数据存储所需的任何查询语言的语言?

您将基本上在持久层上创建一个抽象。

于 2008-09-26T15:02:56.707 回答
0

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

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

于 2008-09-26T15:07:03.933 回答
0

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

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

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

祝你好运。

于 2008-09-26T15:29:21.667 回答
0

听起来确实像 SQL,但如果您想保持简单,也许值得尝试使用 SQLite?

于 2008-09-26T15:39:40.877 回答
0

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

此外,这里是一个用 C# 创建的 ANTLR计算引擎的好例子

于 2008-09-26T21:05:15.197 回答
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 是一个要以嘴唇形式评估的函数列表,例如格式 iewhere=(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)。哦,男孩,我们遇到了:

Greenspun 的第十条编程规则:任何足够复杂的 C 或 Fortran 程序都包含一个临时的、非正式指定的、充满 bug 的慢速实现,它是 Common Lisp 的一半。

于 2017-10-16T03:56:11.537 回答