Análise SQL usando pyparsing

Eu estou aprendendo PyParsing nas últimas semanas. Eu pretendo usá-lo para obter nomes de tabelas de instruções SQL. Eu olhei parahttp://pyparsing.wikispaces.com/file/view/simpleSQL.py. Mas pretendo manter a gramática simples, porque não estou tentando analisar cada parte da instrução select em vez de procurar apenas nomes de tabelas. Também é bastante complicado definir a gramática completa para qualquer banco de dados moderno comercialmente disponível como o Teradata.

#!/usr/bin/env python

from pyparsing import *
import sys

semicolon = Combine(Literal(';') + lineEnd)
comma = Literal(',')
lparen = Literal('(')
rparen = Literal(')')

# Keyword definition
update_kw, volatile_kw, create_kw, table_kw, as_kw, from_kw, \
where_kw, join_kw, left_kw, right_kw, cross_kw, outer_kw, \
on_kw , insert_kw , into_kw= \
    map(lambda x: Keyword(x, caseless=True), \
        ['UPDATE', 'VOLATILE', 'CREATE', 'TABLE', 'AS', 'FROM',
         'WHERE', 'JOIN' , 'LEFT', 'RIGHT' , \
         'CROSS', 'OUTER', 'ON', 'INSERT', 'INTO'])

# Teradata SQL allows SELECT and well as SEL keyword
select_kw = Keyword('SELECT', caseless=True) | Keyword('SEL' , caseless=True)

# list of reserved keywords
reserved_words = (update_kw | volatile_kw | create_kw | table_kw | as_kw |
                  select_kw | from_kw | where_kw | join_kw |
                  left_kw | right_kw | cross_kw | on_kw | insert_kw |
                  into_kw)

# Identifier can be used as table or column names. They can't be reserved words
ident = ~reserved_words + Word(alphas, alphanums + '_')

# Recursive definition for table
table = Forward()
# simple table name can be identifer or qualified identifier e.g. schema.table
simple_table = Combine(Optional(ident + Literal('.')) + ident)
# table name can also a complete select statement used as table
nested_table = lparen.suppress() + select_kw.suppress() + SkipTo(from_kw).suppress() + \   
               from_kw.suppress() + table + rparen.suppress()
# table can be simple table or nested table
table << (nested_table | simple_table)
# comma delimited list of tables
table_list = delimitedList(table)
# Building from clause only because table name(s) will always appears after that
from_clause = from_kw.suppress() + table_list


txt = """
SELECT p, (SELECT * FROM foo),e FROM a, d, (SELECT * FROM z), b
"""
for token, start, end in from_clause.scanString(txt):
    print token

Uma coisa que vale a pena mencionar aqui. Eu uso "SkipTo (from_kw)" para pular a lista de colunas na instrução SQL. Isso é principalmente para evitar a definição de gramática para lista de colunas, que pode ser uma lista delimitada por vírgulas de identificadores, muitos nomes de funções, funções analíticas de DW e quais não. Com essa gramática, posso analisar a instrução acima, bem como qualquer nível de aninhamento na lista de colunas SELECT ou na lista de tabelas.

['foo']
['a', 'd', 'z', 'b']

Eu estou enfrentando problema quando SELECT tem cláusula where:

nested_table = lparen.suppress() + select_kw.suppress() + SkipTo(from_kw).suppress() + \   
               from_kw.suppress() + table + rparen.suppress()

Quando a cláusula WHERE está lá então a mesma declaração pode parecer: SELECT ... FROM a, d, (SELECT * FROMZ WHERE (c1 = 1) e (c2 = 3)), p Eu pensei em mudar a definição "nested_table" para:

nested_table = lparen.suppress() + select_kw.suppress() + SkipTo(from_kw).suppress() + \   
               from_kw.suppress() + table + Optional(where_kw + SkipTo(rparen)) + rparen

Mas isso não está funcionando, pois corresponde ao parêntese direito após "c = 1". O que eu gostaria de saber é como pular para o parêntese direito que corresponde ao parêntese esquerdo logo antes de "SELECT * FROM z ..." Eu não sei como fazer isso usando PyParsing

Também em uma nota diferente, eu busco alguns conselhos sobre a melhor maneira de obter nomes de tabelas a partir de SQLs aninhados complexos. Qualquer ajuda é muito apreciada.

Obrigado Abhijit

questionAnswers(1)

yourAnswerToTheQuestion