2014-12-17 1 views
3

쿼리에 합류했습니다. bauble (a program on github)을 채택했으며이 중 일부는 SQL 데이터베이스에 대한 쿼리를 지정하기위한 것입니다. 쿼리 언어는 실제로 세 가지 다른 언어 중 하나 (SQL 쿼리 에서처럼 필터링)를 다시 작성합니다.필터 sqlalchemy가 구문 분석 된 입력에 구성된

원저자가 pyparsing을 선택했기 때문에 필자는 pyparsing을 모른다는 것 외에는 그 선택을 검토 할 이유가 없습니다. 그리고 나는 항상 lex와 yacc를 재미있게 보았습니다 ... 그러나 나는 계속 pyparsing을 유지할 것입니다. 나는 그것을 배우고있다.

나는 주어진 쿼리를 인식하는 파서를 (다시) 써 왔고, 대부분의 문법 카테고리는 클래스로 번역된다. 나는 파싱 부분이 꽤 괜찮은데, 내가 붙어있는 부분은 필자가 pyparsing으로 생성 한 객체가 SQLAlchemy를 사용하여 데이터베이스를 쿼리 할 필요가 있다는 것입니다. 특히 조인 된 테이블의 특성을 기반으로 필터링 할 때 그렇습니다.

관련 대한 파싱 형식의 문법의 일부 :

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

SQLAlchemy에서 제공 한 ORM을 무시하고 'from_statement'를 사용하여 쿼리를 실행하는 힌트를 받았습니다. ORM을 수동으로 재구성해야하기 때문에 이것은 실행 가능하지 않습니다. – mariotomo

답변

0

임시로 수용 가능한 대답을 찾았지만 SQLAlchemy의 내부 정보 (밑줄 접두어 필드)를 사용한다고 가정합니다.

문제의 핵심은 내가 사용자로부터 파싱 된 정보로 작업 된 이후, 나는 같은 클래스의 이름을보고 뭔가 시작하고, 관계 탐색 할 수있는 이름이었다. 예를 들어 plant where accession.species.id=44에서 클래스 이름은 Plant이고 연결된 Species 개체의 id에서 필터링합니다.

위의 예는 상황이 다소 쉽고 대문자 문제 만 생각할 수 있습니다. 그러나 어떤 모듈에 Plant, AccessionSpecies이 있는지를 알아야합니다.

다른 예 : family where genera.id!=0.일반적으로 관계의 이름은 참조 된 클래스의 이름과 동일 할 필요는 없습니다.

문법은 괜찮 았고 더 이상 변경할 필요가 없었습니다. 요점은 SQLAlchemy와의 상호 작용에서 (여전히 부분적으로) 있었기 때문에 IdentifierTokenIdentExpressionToken 클래스의 evaluate 메서드를 수정해야했습니다.

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 방법이 있는지 여부입니다.

0

보인다 - 대한 파싱을 사용할 때이 사실에 "가장 좋은 방법"입니다. 이러한 클래스는 구문 분석 프로세스의 출력물로서 일반적으로 구문 분석 된 요소를 사용하여 자체 클래스의 일부 동작을 지원합니다. 이 경우 요소는 이름으로도 액세스 할 수 있습니다 (다른 "권장 사항"). 이러한 클래스가 구문 분석 프로세스 중에 생성되면, pyparsing은 거의 불가능합니다. 추가 처리는 순전히이 클래스의 함수입니다.

나는 목표가 아마 당신이 가정 할 때, 아마도이 클래스들에 대한 방법이 results.statement.invoke()과 같은 것이라고 생각합니다. 이 클래스의 메소드를 살펴보고 그들이 제공하는 것들, 특히 최상위 레벨 StatementAction 클래스를 확인하십시오. 그런 방법이 없다면 SQLAlchemy 데이터베이스 래퍼에 의미있는 방식으로 파싱 된 값을 적용하는 것이 아마도 다음 단계 일 것입니다.

+0

(실제로이 클래스를 사용하여 논리를 다시 작성하기 시작한 사람은 원래 소프트웨어에 3 가지 구문 분석 함수가 있었지만, 내 생각에 'invoke' /'express '함수를 사용하여 sqlalchemy 쿼리 개체입니다.) 원본 코드를 확인하려면 https://github.com/mfrasca/bauble.classic/blob/ac44f7ddc6ec2c80c43106dfbcfdc10ccc3d33db/bauble/search.py ​​ – mariotomo

+0

클래스 사용에 대한 자세한 내용은이 질문을 참조하십시오. 구문 분석 액션 : http://stackoverflow.com/questions/10113245/pyparsing-one-query-format-to-another-one/10117573#10117573 – PaulMcG

관련 문제