diff --git a/sql.py b/sql.py new file mode 100644 index 0000000..4ed749d --- /dev/null +++ b/sql.py @@ -0,0 +1,742 @@ +from parser import * + + +NAME = Terminal( + Re.seq( + Re.set(("a", "z"), ("A", "Z"), "_"), + Re.set(("a", "z"), ("A", "Z"), ("0", "9"), "_").star(), + ), +) + +STRING = Terminal( + Re.seq( + Re.literal("'"), + (~Re.set("'", "\\") | (Re.set("\\") + Re.any())).star(), + Re.literal("'"), + ), + highlight=highlight.string.quoted, +) + +NUMBER = Terminal( + Re.seq( + Re.set(("0", "9")).plus(), + Re.seq( + Re.literal("."), + Re.set(("0", "9")).plus(), + ).question(), + Re.seq( + Re.set("e", "E"), + Re.set("+", "-").question(), + Re.set(("0", "9")).plus(), + ).question(), + ), + highlight=highlight.constant.numeric, +) + +OR = Terminal("or") +AND = Terminal("and") +NOT = Terminal("not") +COMPARISON = Terminal( + Re.literal("=") + | Re.literal("<>") + | Re.literal("<") + | Re.literal(">") + | Re.literal("<=") + | Re.literal(">=") +) +PLUS = Terminal("+") +MINUS = Terminal("-") +STAR = Terminal("*") +SLASH = Terminal("/") + +precedence = [ + (Assoc.LEFT, ["OR"]), + (Assoc.LEFT, ["AND"]), + (Assoc.LEFT, ["NOT"]), + (Assoc.LEFT, ["COMPARISON"]), + (Assoc.LEFT, ["PLUS", "MINUS"]), + (Assoc.LEFT, ["STAR", "SLASH"]), + # TODO: Unary minus +] + +ALL = Terminal("all") +AMMSC = Terminal("ammsc") +ANY = Terminal("any") +ASC = Terminal("asc") +AUTHORIZATION = Terminal("authorization") +BETWEEN = Terminal("between") +BY = Terminal("by") +CHARACTER = Terminal("character") +CHECK = Terminal("check") +CLOSE = Terminal("close") +COMMIT = Terminal("commit") +CONTINUE = Terminal("continue") +CREATE = Terminal("create") +CURRENT = Terminal("current") +CURSOR = Terminal("cursor") +DECIMAL = Terminal("decimal") +DECLARE = Terminal("declare") +DEFAULT = Terminal("default") +DELETE = Terminal("delete") +DESC = Terminal("desc") +DISTINCT = Terminal("distinct") +DOUBLE = Terminal("double") +ESCAPE = Terminal("escape") +EXISTS = Terminal("exists") +FETCH = Terminal("fetch") +FLOAT = Terminal("float") +FOR = Terminal("for") +FOREIGN = Terminal("foreign") +FOUND = Terminal("found") +FROM = Terminal("from") +GOTO = Terminal("goto") +GRANT = Terminal("grant") +GROUP = Terminal("group") +HAVING = Terminal("having") +IN = Terminal("in") +INDICATOR = Terminal("indicator") +INSERT = Terminal("insert") +INTEGER = Terminal("integer") +INTO = Terminal("into") +IS = Terminal("is") +KEY = Terminal("key") +LANGUAGE = Terminal("language") +LIKE = Terminal("like") +NULL = Terminal("null") +NUMERIC = Terminal("numeric") +OF = Terminal("of") +ON = Terminal("on") +OPEN = Terminal("open") +OPTION = Terminal("option") +ORDER = Terminal("order") +PARAMETER = Terminal("parameter") +PRECISION = Terminal("precision") +PRIMARY = Terminal("primary") +PRIVILEGES = Terminal("privileges") +PROCEDURE = Terminal("procedure") +PUBLIC = Terminal("public") +REAL = Terminal("real") +REFERENCES = Terminal("references") +ROLLBACK = Terminal("rollback") +SCHEMA = Terminal("schema") +SELECT = Terminal("select") +SET = Terminal("set") +SMALLINT = Terminal("smallint") +SOME = Terminal("some") +SQLCODE = Terminal("sqlcode") +SQLERROR = Terminal("sqlerror") +TABLE = Terminal("table") +TO = Terminal("to") +UNION = Terminal("union") +UNIQUE = Terminal("unique") +UPDATE = Terminal("update") +USER = Terminal("user") +VALUES = Terminal("values") +VIEW = Terminal("view") +WHENEVER = Terminal("whenever") +WHERE = Terminal("where") +WITH = Terminal("with") +WORK = Terminal("work") + +SEMICOLON = Terminal(";") +LPAREN = Terminal("(") +RPAREN = Terminal(")") +COMMA = Terminal(",") +EQUAL = Terminal("=") +DOT = Terminal(".") +AS = Terminal("as") + + +@rule +def sql_list(): + return alt( + sql + SEMICOLON, + sql_list + sql + SEMICOLON, + ) + + +@rule +def sql(): + return alt( + schema, + cursor_def, + manipulative_statement, + WHENEVER + NOT + FOUND + when_action, + WHENEVER + SQLERROR + when_action, + ) + + +@rule +def schema(): + seq( + CREATE, + SCHEMA, + AUTHORIZATION, + user, + opt(schema_element_list), + ) + + +@rule(transparent=True) +def schema_element_list() -> Rule: + return schema_element | (schema_element_list + schema_element) + + +@rule +def schema_element(): + return base_table_def | view_def | privilege_def + + +@rule +def base_table_def(): + return seq( + CREATE, + TABLE, + table, + LPAREN, + base_table_element_commalist, + RPAREN, + ) + + +@rule(transparent=True) +def base_table_element_commalist() -> Rule: + return opt(base_table_element_commalist + COMMA) + base_table_element + + +@rule(transparent=True) +def base_table_element(): + return column_def | table_constraint_def + + +@rule +def column_def(): + return column + data_type + opt(column_def_list) + + +@rule(transparent=True) +def column_def_list() -> Rule: + return alt( + column_def_list + column_def_opt, + column_def_opt, + ) + + +@rule +def column_def_opt(): + return alt( + NOT + opt(NULL + opt(alt(UNIQUE, PRIMARY + KEY))), + DEFAULT + literal, + DEFAULT + NULL, + DEFAULT + USER, + CHECK + LPAREN + search_condition + RPAREN, + REFERENCES + table, + REFERENCES + table + LPAREN + column_commalist + RPAREN, + ) + + +@rule +def table_constraint_def(): + return alt( + UNIQUE + LPAREN + column_commalist + RPAREN, + PRIMARY + KEY + LPAREN + column_commalist + RPAREN, + seq( + FOREIGN, + KEY, + LPAREN, + column_commalist, + RPAREN, + REFERENCES, + table, + opt(LPAREN + column_commalist + RPAREN), + ), + CHECK + LPAREN + search_condition + RPAREN, + ) + + +@rule(transparent=True) +def column_commalist() -> Rule: + return opt(column_commalist + COMMA) + column + + +@rule +def view_def(): + return seq( + CREATE, + VIEW, + table, + opt(LPAREN + column_commalist + RPAREN), + AS, + query_spec, + opt(with_check_option), + ) + + +@rule +def with_check_option(): + return WITH + CHECK + OPTION + + +@rule +def privilege_def(): + return seq( + GRANT, + privileges, + ON, + table, + TO, + grantee_commalist, + opt(with_grant_option), + ) + + +@rule +def with_grant_option(): + return WITH + GRANT + OPTION + + +@rule +def privileges(): + return (ALL + PRIVILEGES) | ALL | operation_commalist + + +@rule(transparent=True) +def operation_commalist() -> Rule: + return opt(operation_commalist + COMMA) + operation + + +@rule +def operation(): + return alt( + SELECT, + INSERT, + DELETE, + UPDATE + opt(LPAREN + column_commalist + RPAREN), + REFERENCES + opt(LPAREN + column_commalist + RPAREN), + ) + + +@rule(transparent=True) +def grantee_commalist() -> Rule: + return opt(grantee_commalist + COMMA) + grantee + + +@rule +def grantee(): + return PUBLIC | user + + +@rule +def cursor_def(): + return seq( + DECLARE, + cursor, + CURSOR, + FOR, + query_exp, + opt(order_by_clause), + ) + + +@rule +def order_by_clause(): + return ORDER + BY + ordering_spec_commalist + + +@rule +def ordering_spec_commalist() -> Rule: + return opt(ordering_spec_commalist + COMMA) + ordering_spec + + +@rule +def ordering_spec(): + return (NUMBER + opt(asc_desc)) | (column_ref + opt(asc_desc)) + + +@rule +def asc_desc(): + return ASC | DESC + + +@rule +def manipulative_statement(): + return alt( + close_statement, + commit_statement, + delete_statement_positioned, + delete_statement_searched, + fetch_statement, + insert_statement, + open_statement, + rollback_statement, + select_statement, + update_statement_positioned, + update_statement_searched, + ) + + +@rule +def close_statement(): + return CLOSE + cursor + + +@rule +def commit_statement(): + return COMMIT + opt(WORK) + + +@rule +def delete_statement_positioned(): + return DELETE + FROM + table + WHERE + CURRENT + OF + cursor + + +@rule +def delete_statement_searched(): + return DELETE + FROM + table + opt(where_clause) + + +@rule +def fetch_statement(): + return FETCH + cursor + INTO + target_commalist + + +@rule +def insert_statement(): + return seq( + INSERT, + INTO, + table, + opt(LPAREN, column_commalist, RPAREN), + values_or_query_spec, + ) + + +@rule +def values_or_query_spec(): + return alt( + VALUES + LPAREN + insert_atom_commalist + RPAREN, + query_spec, + ) + + +@rule(transparent=True) +def insert_atom_commalist() -> Rule: + return opt(insert_atom_commalist + COMMA) + insert_atom + + +@rule +def insert_atom(): + return atom | NULL + + +@rule +def open_statement(): + return OPEN + cursor + + +@rule +def rollback_statement(): + return ROLLBACK + opt(WORK) + + +@rule +def select_statement(): + return seq( + SELECT, + opt(all_distinct), + selection, + INTO, + target_commalist, + table_exp, + ) + + +@rule(transparent=True) +def all_distinct(): + return ALL | DISTINCT + + +@rule +def update_statement_positioned(): + return UPDATE + table + SET + assignment_commalist + WHERE + CURRENT + OF + cursor + + +@rule(transparent=True) +def assignment_commalist() -> Rule: + return opt(assignment_commalist + COMMA) + assignment + + +@rule +def assignment(): + return column + EQUAL + (scalar_exp | NULL) + + +@rule +def update_statement_searched(): + return UPDATE + table + SET + assignment_commalist + opt(where_clause) + + +@rule(transparent=True) +def target_commalist() -> Rule: + # TODO: So many commalists, it would be great if we could make this a + # macro or something. + return opt(target_commalist + COMMA) + target + + +@rule +def target(): + return parameter_ref + + +# /* query expressions */ + + +@rule +def query_exp() -> Rule: + return query_term | (query_exp + UNION + opt(ALL) + query_term) + + +@rule +def query_term(): + return query_spec | (LPAREN + query_exp + RPAREN) + + +@rule +def query_spec(): + return SELECT + opt(all_distinct) + selection + table_exp + + +@rule +def selection(): + return scalar_exp_commalist | STAR + + +@rule +def table_exp(): + return from_clause + opt(where_clause) + opt(group_by_clause) + opt(having_clause) + + +@rule +def from_clause(): + return FROM + table_ref_commalist + + +@rule(transparent=True) +def table_ref_commalist() -> Rule: + return opt(table_ref_commalist + COMMA) + table_ref + + +@rule +def table_ref(): + return table + opt(range_variable) + + +@rule +def where_clause(): + return WHERE + search_condition + + +@rule +def group_by_clause(): + return GROUP + BY + column_ref_commalist + + +@rule(transparent=True) +def column_ref_commalist() -> Rule: + return opt(column_ref_commalist + COMMA) + column_ref + + +@rule +def having_clause(): + return HAVING + search_condition + + +# /* search conditions */ + + +@rule +def search_condition() -> Rule: + return alt( + search_condition + OR + search_condition, + search_condition + AND + search_condition, + NOT + search_condition, + LPAREN + search_condition + RPAREN, + predicate, + ) + + +@rule +def predicate(): + return alt( + comparison_predicate, + between_predicate, + like_predicate, + test_for_null, + in_predicate, + all_or_any_predicate, + existence_test, + ) + + +@rule +def comparison_predicate(): + return scalar_exp + COMPARISON + (scalar_exp | subquery) + + +@rule +def between_predicate(): + return scalar_exp + opt(NOT) + BETWEEN + scalar_exp + AND + scalar_exp + + +@rule +def like_predicate(): + return scalar_exp + opt(NOT) + LIKE + atom + opt(escape) + + +@rule +def escape(): + return ESCAPE + atom + + +@rule +def test_for_null(): + return column_ref + IS + opt(NOT) + NULL + + +@rule +def in_predicate(): + return scalar_exp + opt(NOT) + IN + LPAREN + alt(subquery | atom_commalist) + RPAREN + + +@rule +def atom_commalist() -> Rule: + return opt(atom_commalist + COMMA) + atom + + +@rule +def all_or_any_predicate(): + return scalar_exp + COMPARISON + any_all_some + subquery + + +@rule(transparent=True) +def any_all_some(): + return ANY | ALL | SOME + + +@rule +def existence_test(): + return EXISTS + subquery + + +@rule +def subquery(): + return LPAREN + SELECT + opt(all_distinct) + selection + table_exp + RPAREN + + +# /* scalar expressions */ + + +@rule +def scalar_exp(): + return alt( + scalar_exp + (PLUS | MINUS | STAR | SLASH) + scalar_exp, + PLUS + scalar_exp, + MINUS + scalar_exp, + atom, + column_ref, + function_ref, + LPAREN + scalar_exp + RPAREN, + ) + + +@rule +def scalar_exp_commalist() -> Rule: + return opt(scalar_exp_commalist + COMMA) + scalar_exp + + +@rule +def atom(): + return parameter_ref | literal | USER + + +@rule +def parameter_ref(): + return parameter | (parameter + parameter) | (parameter + INDICATOR + parameter) + + +@rule +def function_ref(): + return alt( + AMMSC + LPAREN + STAR + RPAREN, + AMMSC + LPAREN + DISTINCT + column_ref + RPAREN, + AMMSC + LPAREN + ALL + scalar_exp + RPAREN, + AMMSC + LPAREN + scalar_exp + RPAREN, + ) + + +@rule +def literal(): + return STRING | NUMBER + + +# /* miscellaneous */ + + +@rule +def table(): + return opt(NAME + DOT) + NAME + + +@rule +def column_ref(): + return opt(opt(NAME + DOT) + NAME + DOT) + NAME + + +# /* data types */ + + +@rule +def data_type(): + return alt( + CHARACTER + opt(LPAREN + NUMBER + RPAREN), + NUMERIC + opt(LPAREN + NUMBER + opt(COMMA + NUMBER) + RPAREN), + DECIMAL + opt(LPAREN + NUMBER + opt(COMMA + NUMBER) + RPAREN), + INTEGER, + SMALLINT, + FLOAT + opt(LPAREN + NUMBER + RPAREN), + REAL, + DOUBLE + PRECISION, + ) + + +# /* the various things you can name */ + + +@rule +def column(): + return NAME + + +@rule +def cursor(): + return NAME + + +@rule +def parameter(): + return PARAMETER # :name handled in parser??? + + +@rule +def range_variable(): + return NAME + + +@rule +def user(): + return NAME + + +@rule +def when_action(): + return (GOTO + NAME) | CONTINUE