From 8d1c840ee5822e6f3a3bb13360d6e9c45e3d5190 Mon Sep 17 00:00:00 2001 From: Sara Jun Date: Sun, 8 Jun 2025 22:58:38 +0100 Subject: [PATCH 1/3] removing first where --- reducer/code/reduce_query.py | 13 ++++++++---- reducer/code/semantic.py | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 reducer/code/semantic.py diff --git a/reducer/code/reduce_query.py b/reducer/code/reduce_query.py index b85393b..cebfb8c 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() - ast_list = parser.parse(query_string) + ast_list = parser.parse([reduced_query]) if not ast_list: print("No valid statement to reduce") @@ -21,8 +26,8 @@ def validator(expr): return execute_query(query_string, test_script, output_path) - # Update AST with delta debugging technique - ast_list = delta_debugging(ast_list, validator) + # # Update AST with delta debugging technique + # ast_list = delta_debugging(ast_list, validator) minimized = parser.to_sql(ast_list) diff --git a/reducer/code/semantic.py b/reducer/code/semantic.py new file mode 100644 index 0000000..a942a9a --- /dev/null +++ b/reducer/code/semantic.py @@ -0,0 +1,39 @@ +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) + From 7e62b69601af6c38b2431319b00c1b210d5ef6c3 Mon Sep 17 00:00:00 2001 From: Sara Jun Date: Mon, 9 Jun 2025 08:06:54 +0100 Subject: [PATCH 2/3] simplified remove where --- reducer/code/executor.py | 6 +-- reducer/code/semantic.py | 84 ++++++++++++++++++++++++++-------------- reducer/test.sql | 2 + 3 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 reducer/test.sql 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/semantic.py b/reducer/code/semantic.py index a942a9a..f880657 100644 --- a/reducer/code/semantic.py +++ b/reducer/code/semantic.py @@ -1,39 +1,67 @@ -import sqlparse -from code.executor import execute_query +# 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 extract_where_expressions(where_clause): - return [expr.strip() for expr in where_clause.split("AND")] +# def reduce_where_clause(query_str, test_script, query_output_path="query.sql"): +# if "WHERE" not in query_str.upper(): +# return query_str -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};" +# where_start = query_str.upper().find("WHERE") +# base_query = query_str[:where_start] +# where_clause = query_str[where_start + len("WHERE"):].strip().rstrip(";") -def reduce_where_clause(query_str, test_script, query_output_path="query.sql"): - if "WHERE" not in query_str.upper(): - return query_str +# expressions = extract_where_expressions(where_clause) +# i = 0 - where_start = query_str.upper().find("WHERE") - base_query = query_str[:where_start] - where_clause = query_str[where_start + len("WHERE"):].strip().rstrip(";") +# while i < len(expressions): +# trial_exprs = expressions[:i] + expressions[i+1:] +# trial_query = rebuild_query(base_query, trial_exprs) - expressions = extract_where_expressions(where_clause) - i = 0 +# 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 - while i < len(expressions): - trial_exprs = expressions[:i] + expressions[i+1:] - trial_query = rebuild_query(base_query, trial_exprs) +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) - 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 +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) - return rebuild_query(base_query, expressions) + 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/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; From fda58ba8b89363f58ab47045191ae8e60b7dff18 Mon Sep 17 00:00:00 2001 From: Mallo Date: Mon, 9 Jun 2025 12:09:01 +0200 Subject: [PATCH 3/3] fix uncaught conflicts --- reducer/code/reduce_query.py | 2 +- reducer/code/utils.py | 1 - reducer/reducer.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/reducer/code/reduce_query.py b/reducer/code/reduce_query.py index 909410e..7fd23d5 100644 --- a/reducer/code/reduce_query.py +++ b/reducer/code/reduce_query.py @@ -28,7 +28,7 @@ def validator(expr): return execute_query(query_string, test_script, output_path) # Update AST with delta debugging technique - # token_tree = delta_debugging(token_tree, validator) + token_tree = delta_debugging(token_tree, validator) minimzed_token_size = sum(len(parser.flatten_tokens(tree)) for tree in token_tree) minimized = parser.to_sql(token_tree) 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")