feat(wpcarro/simple-select): Compile AST to SQL
Currently supports the SQLite flavor of SQL. Preliminary testing seems to show encouraging results. Change-Id: Ib2ed6a695352f41185c8e8abdadfd76ce38bdbcc Reviewed-on: https://cl.tvl.fyi/c/depot/+/5344 Reviewed-by: wpcarro <wpcarro@gmail.com> Autosubmit: wpcarro <wpcarro@gmail.com> Tested-by: BuildkiteCI
This commit is contained in:
parent
69956d9a0b
commit
9eefa2e484
1 changed files with 62 additions and 7 deletions
|
@ -117,14 +117,12 @@ def expression(p):
|
|||
def conjunction(p):
|
||||
lhs = selection(p)
|
||||
|
||||
while not p.exhausted() and p.test(lambda tokens, i: tokens[i] not in {LPAREN, RPAREN}):
|
||||
conj = p.advance() if p.peek()[0] == "CONJUNCTION" else AND
|
||||
# TODO(wpcarro): Support default AND conjuctions when they're undefined.
|
||||
while not p.exhausted() and p.match({AND, OR}):
|
||||
conj = p.peek(n=-1)
|
||||
rhs = selection(p)
|
||||
lhs = ("CONJUNCTION", conj[1], lhs, rhs)
|
||||
|
||||
if not p.exhausted():
|
||||
raise Exception("Encountered more tokens than we can parse: \"{}\"".format(p.tokens[p.i:]))
|
||||
|
||||
return lhs
|
||||
|
||||
def selection(p):
|
||||
|
@ -154,6 +152,54 @@ def grouping(p):
|
|||
p.expect(lambda x: x == RPAREN)
|
||||
return ("GROUPING", expr)
|
||||
|
||||
################################################################################
|
||||
# Compiler
|
||||
################################################################################
|
||||
|
||||
def compile(source, table, columns):
|
||||
ast = parse(source)
|
||||
return "SELECT * FROM {} WHERE {};".format(table, do_compile(ast, columns))
|
||||
|
||||
def do_compile(ast, columns):
|
||||
if ast[0] == "REGEX":
|
||||
cols = "({})".format(" || ".join(columns))
|
||||
return "{} REGEXP '.*{}.*'".format(cols, ast[1])
|
||||
|
||||
if ast[0] == "STRING":
|
||||
cols = "({})".format(" || ".join(columns))
|
||||
return "{} LIKE '%{}%'".format(cols, ast[1])
|
||||
|
||||
if ast[0] == "SELECTION":
|
||||
return compile_selection(ast)
|
||||
|
||||
if ast[0] == "CONJUNCTION":
|
||||
_, conj, lhs, rhs = ast
|
||||
lhs = do_compile(lhs, columns)
|
||||
rhs = do_compile(rhs, columns)
|
||||
return "{} {} {}".format(lhs, conj, rhs)
|
||||
|
||||
if ast[0] == "GROUPING":
|
||||
return "({})".format(do_compile(ast[1], columns))
|
||||
|
||||
raise Exception("Unexpected AST: \"{}\"".format(ast))
|
||||
|
||||
def compile_selection(ast):
|
||||
_, negate, column, query = ast
|
||||
match = compile_query(negate, query)
|
||||
return "{} {}".format(column, match)
|
||||
|
||||
def compile_query(negate, query):
|
||||
query_type, query_string = query
|
||||
if query_type == "REGEX":
|
||||
if negate:
|
||||
return "NOT REGEXP '.*{}.*'".format(query_string)
|
||||
return "REGEXP '.*{}.*'".format(query_string)
|
||||
|
||||
if query_type == "STRING":
|
||||
if negate:
|
||||
return "NOT LIKE '%{}%'".format(query_string)
|
||||
return "LIKE '%{}%'".format(query_string)
|
||||
|
||||
################################################################################
|
||||
# Main
|
||||
################################################################################
|
||||
|
@ -161,7 +207,16 @@ def grouping(p):
|
|||
def main():
|
||||
while True:
|
||||
x = input("> ")
|
||||
print(parse(x))
|
||||
|
||||
print("tokens:\t{}".format(tokenize(x)))
|
||||
print("AST:\t{}".format(parse(x)))
|
||||
# TODO(wpcarro): Read columns from CSV.
|
||||
print("query:\t\"{}\"".format(compile(x, "Movies", [
|
||||
"year",
|
||||
"rating",
|
||||
"haveWatched",
|
||||
"director",
|
||||
"isCartoon",
|
||||
"requiresSubtitles",
|
||||
])))
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
Loading…
Reference in a new issue