diff --git a/reducer/code/executor.py b/reducer/code/executor.py index d445052..de2ed22 100644 --- a/reducer/code/executor.py +++ b/reducer/code/executor.py @@ -15,9 +15,9 @@ def execute_query(sql_query, test_script, query_path="query.sql"): try: # result = subprocess.run([test_script], env=env, capture_output=True, shell=True) result = subprocess.run(f"./{test_script}", env=env, capture_output=True, shell=True) - # print("STDOUT:", result.stdout.decode()) - # print("STDERR:", result.stderr.decode()) - # print("Return code:", result.returncode) + print("STDOUT:", result.stdout.decode()) + print("STDERR:", result.stderr.decode()) + print("Return code:", result.returncode) #print("result returncode: ", result) return result.returncode diff --git a/reducer/code/reduce_query.py b/reducer/code/reduce_query.py index 2f88cc7..7fd23d5 100644 --- a/reducer/code/reduce_query.py +++ b/reducer/code/reduce_query.py @@ -1,14 +1,19 @@ from code.parser import SQLParser from code.executor import execute_query from code.delta_debugging import delta_debugging +from code.semantic import reduce_where_clause def reduce_query(query_path, test_script, output_path): with open(f"{query_path}/original_test.sql", "r") as original_query: - query_string = original_query.readlines() + query_string = original_query.read() + + + reduced_query = reduce_where_clause(query_string, test_script, output_path) + print("reduced_query ", reduced_query) # Parse the query to an AST parser = SQLParser() - token_tree = parser.parse(query_string) + token_tree = parser.parse([reduced_query]) token_tree_size = sum(len(parser.flatten_tokens(tree)) for tree in token_tree) if not token_tree: diff --git a/reducer/code/semantic.py b/reducer/code/semantic.py new file mode 100644 index 0000000..f880657 --- /dev/null +++ b/reducer/code/semantic.py @@ -0,0 +1,67 @@ +# import sqlparse +# from code.executor import execute_query + + +# def extract_where_expressions(where_clause): +# return [expr.strip() for expr in where_clause.split("AND")] + +# def rebuild_query(base_query, where_exprs): +# if not where_exprs: +# return base_query.split("WHERE")[0].strip() + ";" +# new_where = " AND ".join(where_exprs) +# return base_query.split("WHERE")[0].strip() + f" WHERE {new_where};" + +# def reduce_where_clause(query_str, test_script, query_output_path="query.sql"): +# if "WHERE" not in query_str.upper(): +# return query_str + +# where_start = query_str.upper().find("WHERE") +# base_query = query_str[:where_start] +# where_clause = query_str[where_start + len("WHERE"):].strip().rstrip(";") + +# expressions = extract_where_expressions(where_clause) +# i = 0 + +# while i < len(expressions): +# trial_exprs = expressions[:i] + expressions[i+1:] +# trial_query = rebuild_query(base_query, trial_exprs) + +# result_code = execute_query(trial_query, test_script, query_output_path) + +# if result_code == 0: +# print(f"[REDUCED] Removed WHERE clause: {expressions[i]}") +# expressions = trial_exprs +# else: +# print(f"[RETAINED] Keeping WHERE clause: {expressions[i]}") +# i += 1 + +# return rebuild_query(base_query, expressions) +import re +from code.executor import execute_query + +def remove_where_clause(query_string: str) -> str: + """ + Removes the WHERE clause from the SQL query using regex (simplified). + Assumes only one WHERE clause exists and is not nested in subqueries. + """ + pattern = re.compile(r"\bWHERE\b.+?(?=(GROUP BY|ORDER BY|LIMIT|;|$))", re.IGNORECASE | re.DOTALL) + return pattern.sub('', query_string) + + +def reduce_where_clause(query_string: str, test_script: str, query_path: str) -> str: + """ + Try to semantically reduce the WHERE clause. Only keep the reduction + if the test script returns exit code 0 (bug still happens). + """ + reduced_query = remove_where_clause(query_string) + + # Test the reduced query + result = execute_query(reduced_query, test_script, query_path) + + if result == 0: + print("[✔] Removed WHERE clause — bug still happens.") + return reduced_query + else: + print("[✘] Removing WHERE clause changed behavior — keeping original.") + return query_string + diff --git a/reducer/code/utils.py b/reducer/code/utils.py index 885463e..47ad9c8 100644 --- a/reducer/code/utils.py +++ b/reducer/code/utils.py @@ -1,6 +1,5 @@ import shutil import os -import sqlparse def prepare_workspace(query_path): src = os.path.abspath(query_path) diff --git a/reducer/reducer.py b/reducer/reducer.py index 025b4c4..58713d9 100644 --- a/reducer/reducer.py +++ b/reducer/reducer.py @@ -6,7 +6,6 @@ from code.reduce_query import reduce_query from code.utils import prepare_workspace import time -from code.utils import count_tokens if __name__ == "__main__": parser = argparse.ArgumentParser(description="Reduce bug-triggering SQL queries") diff --git a/reducer/test.sql b/reducer/test.sql new file mode 100644 index 0000000..fa0ea7c --- /dev/null +++ b/reducer/test.sql @@ -0,0 +1,2 @@ +CREATE TABLE F (p BOOLEAN NOT NULL NULL NOT NULL, i BOOLEAN); +INSERT INTO F SELECT * FROM (VALUES ((NOT false), false), (NULL, (NOT (NOT true)))) AS L;