我坚持这个:我已经采用了bauble (a program on github),其中一部分是为了在sql数据库上指定一个查询。查询语言实际上是三种不同的语言,其中一种语言(在SQL查询中进行过滤)我正在重写。
原作者选择了pyparsing,我没有理由审查这个选择,除了我不知道pyparsing,我总是和lex和yacc玩得很开心......但我决定继续pyparsing,所以我和#39;我正在学习它。
我(重新)编写了一个识别给定查询的解析器,大多数语法类别都转换为类。我认为解析部分非常好,我遇到的问题就是我用pyparsing创建的对象需要使用SQLAlchemy来查询数据库,特别是当我根据属性进行过滤时来自联合表。
语法的相关部分,采用pyparsing格式:
query_expression = Forward()
identifier = Group(delimitedList(Word(alphas, alphanums+'_'),
'.')).setParseAction(IdentifierToken)
ident_expression = (
Group(identifier + binop + value).setParseAction(IdentExpressionToken)
| (
Literal('(') + query_expression + Literal(')')
).setParseAction(ParenthesisedQuery))
query_expression << infixNotation(
ident_expression,
[ (NOT_, 1, opAssoc.RIGHT, SearchNotAction),
(AND_, 2, opAssoc.LEFT, SearchAndAction),
(OR_, 2, opAssoc.LEFT, SearchOrAction) ] )
和相应的类(最后两个的evaluate
方法是我还不知道怎么写的):
class BinaryLogical(object):
## abstract base class. `name` is defined in derived classes
def __init__(self, t):
self.op = t[0][1]
self.operands = t[0][0::2] # every second object is an operand
def __repr__(self):
return "(%s %s %s)" % (self.operands[0], self.name, self.operands[1])
class SearchAndAction(BinaryLogical):
name = 'AND'
def evaluate(self, domain, session):
return self.operands[0].evaluate(domain, session).intersect_all(
map(lambda i: i.evaluate(domain, session), self.operands[1:]))
class SearchOrAction(BinaryLogical):
name = 'OR'
def evaluate(self, domain, session):
return self.operands[0].evaluate(domain, session).union_all(
map(lambda i: i.evaluate(domain, session), self.operands[1:]))
class SearchNotAction(object):
name = 'NOT'
def __init__(self, t):
self.op, self.operand = t[0]
def evaluate(self, domain, session):
return session.query(domain).except_(self.operand.evaluate(domain, session))
def __repr__(self):
return "%s %s" % (self.name, str(self.operand))
class ParenthesisedQuery(object):
def __init__(self, t):
self.query = t[1]
def __repr__(self):
return "(%s)" % self.query.__repr__()
def evaluate(self, domain, session):
return self.query.evaluate(domain, session)
class IdentifierToken(object):
def __init__(self, t):
self.value = t[0]
def __repr__(self):
return '.'.join(self.value)
def evaluate(self, domain, session):
q = session.query(domain)
if len(self.value) > 1:
q = q.join(self.value[:-1], aliased=True)
return q.subquery().c[self.value[-1]]
class IdentExpressionToken(object):
def __init__(self, t):
self.op = t[0][1]
self.operation = {'>': lambda x,y: x>y,
'<': lambda x,y: x<y,
'>=': lambda x,y: x>=y,
'<=': lambda x,y: x<=y,
'=': lambda x,y: x==y,
'!=': lambda x,y: x!=y,
}[self.op]
self.operands = t[0][0::2] # every second object is an operand
def __repr__(self):
return "(%s %s %s)" % ( self.operands[0], self.op, self.operands[1])
def evaluate(self, domain, session):
return session.query(domain).filter(self.operation(self.operands[0].evaluate(domain, session),
self.operands[1].express()))
上述代码段的完整且最新的代码为here。
几个可能的查询:
results = mapper_search.search("plant where accession.species.id=44")
results = mapper_search.search("species where genus.genus='Ixora'")
results = mapper_search.search("species where genus.genus=Maxillaria and not genus.family=Orchidaceae")
答案 0 :(得分:0)
似乎以前的开发人员在创建这些类时遇到了很多麻烦 - 这实际上是使用pyparsing时的“最佳实践”。目的是这些类作为解析过程的输出,通常使用解析的元素支持它们自己的一些行为。在这种情况下,元素也可以通过名称访问(另一个pyparsing“最佳实践”)。一旦在解析过程中构造了这些类,pyparsing几乎不在图片中 - 任何进一步的处理纯粹是这些类的函数。
我认为目标可能和你假设的一样,这些类有results.statement.invoke()
这样的方法。查看这些类的方法并查看它们为您提供的内容,尤其是顶级StatementAction类。如果没有这样的方法,那么可能是下一步,您可以以对SQLAlchemy数据库包装器有意义的方式应用解析的值。
答案 1 :(得分:0)
我想我找到了一个暂时可以接受的答案,但它使用了来自SQLAlchemy的内部信息(下划线前缀字段)。
问题的核心是,由于我正在使用来自用户的解析信息,我开始使用看起来像类的名称,以及名称导航的关系。例如,在plant where accession.species.id=44
中,类名称为Plant
,我正在对连接的id
对象的Species
进行过滤。
上面的例子可能会让人觉得事情很容易,只是资本化问题。但我们仍然需要知道在哪个模块中找到Plant
,Accession
和Species
。
另一个例子:family where genera.id!=0
。通常,关系的名称不需要等于所引用的类的名称。
语法还可以,我不需要进一步修改它。在与SQLAlchemy的交互中,这一点是(现在仍然是部分),因此我必须更正类evaluate
和IdentifierToken
中的IdentExpressionToken
方法。
我的解决方案包含以下代码:
class IdentifierToken(object):
....
def evaluate(self, env):
"""return pair (query, attribute)
the value associated to the identifier is an altered query where the
joinpoint is the one relative to the attribute, and the attribute
itself.
"""
query = env.session.query(env.domain)
if len(self.value) == 1:
# identifier is an attribute of the table being queried
attr = getattr(env.domain, self.value[0])
elif len(self.value) > 1:
# identifier is an attribute of a joined table
query = query.join(*self.value[:-1], aliased=True)
attr = getattr(query._joinpoint['_joinpoint_entity'], self.value[-1])
return query, attr
class IdentExpressionToken(object):
...
def evaluate(self, env):
q, a = self.operands[0].evaluate(env)
clause = lambda x: self.operation(a, x)
return q.filter(clause(self.operands[1].express()))
一些观点:
_joinpoint
,它看起来像非公开信息。 query._joinpoint['_joinpoint_entity']
是对我需要检索已解析查询中指定的字段的类的引用。 _joinpoint
字典在非别名查询上看起来不同。
问题仍然是开放部分是否有“官方”SQLAlchemy方法来检索此信息。