Analizowanie SQL przy użyciu pyparsingu
Uczę się PyParsing w ciągu ostatnich kilku tygodni. Planuję użyć go do pobierania nazw tabel z instrukcji SQL. Patrzyłem na tohttp://pyparsing.wikispaces.com/file/view/simpleSQL.py. Ale zamierzam zachować prostotę gramatyki, ponieważ nie staram się, aby każda część instrukcji select była analizowana, a szukam tylko nazw tabel. Jest również dość zaangażowane w zdefiniowanie pełnej gramatyki dla każdej komercyjnie dostępnej współczesnej bazy danych, takiej jak 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
Warto tutaj wspomnieć. Używam „SkipTo (from_kw)”, aby przeskakiwać nad listą kolumn w instrukcji SQL. Ma to przede wszystkim na celu uniknięcie definiowania gramatyki dla listy kolumn, która może być rozdzielaną przecinkami listą identyfikatorów, wieloma nazwami funkcji, funkcjami analitycznymi DW, a co nie. Dzięki tej gramatyce jestem w stanie przeanalizować powyższą instrukcję, jak również dowolny poziom zagnieżdżenia w liście kolumn SELECT lub liście tabel.
['foo']
['a', 'd', 'z', 'b']
Mam problem, gdy SELECT ma gdzie klauzula:
nested_table = lparen.suppress() + select_kw.suppress() + SkipTo(from_kw).suppress() + \
from_kw.suppress() + table + rparen.suppress()
Gdy istnieje klauzula WHERE, to ta sama instrukcja może wyglądać następująco: SELECT ... FROM a, d, (SELECT * FROM z WHERE (c1 = 1) i (c2 = 3)), p Pomyślałem o zmianie definicji „zagnieżdżonej tabeli” do:
nested_table = lparen.suppress() + select_kw.suppress() + SkipTo(from_kw).suppress() + \
from_kw.suppress() + table + Optional(where_kw + SkipTo(rparen)) + rparen
Ale to nie działa, ponieważ pasuje do prawego nawiasu po „c = 1”. Chciałbym wiedzieć, jak przejść do prawego nawiasu, który pasuje do lewego nawiasu tuż przed „SELECT * FROM z ...” Nie wiem, jak to zrobić za pomocą PyParsing
Również w innej notatce szukam porady, jak najlepiej uzyskać nazwy tabel ze złożonych zagnieżdżonych SQL. Każda pomoc jest na prawdę doceniana.
Dzięki Abhijit