From 50c533965dbf6aa6a9a0c6df313a9ff1e1f69f53 Mon Sep 17 00:00:00 2001 From: Pangqiang Gary Date: Sat, 22 Aug 2026 12:12:29 -0400 Subject: [PATCH] feat: add additional MF query cases --- .gitignore | 4 + __pycache__/codegen.cpython-313.pyc | Bin 9856 -> 0 bytes __pycache__/phi_parser.cpython-312.pyc | Bin 7894 -> 0 bytes __pycache__/phi_parser.cpython-313.pyc | Bin 6431 -> 0 bytes codegen.py | 4 +- phi_input_case2.txt | 28 +++---- phi_input_case3.txt | 33 ++------ phi_parser.py | 6 +- qpe_case1.py | 97 +++++++++++++++++++++++ qpe_case2.py | 103 +++++++++++++++++++++++++ qpe_case3.py | 100 ++++++++++++++++++++++++ 11 files changed, 329 insertions(+), 46 deletions(-) create mode 100644 .gitignore delete mode 100644 __pycache__/codegen.cpython-313.pyc delete mode 100644 __pycache__/phi_parser.cpython-312.pyc delete mode 100644 __pycache__/phi_parser.cpython-313.pyc create mode 100644 qpe_case1.py create mode 100644 qpe_case2.py create mode 100644 qpe_case3.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfb8234 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +*.py[cod] +.venv/ +.env diff --git a/__pycache__/codegen.cpython-313.pyc b/__pycache__/codegen.cpython-313.pyc deleted file mode 100644 index b3f05c8ea972963236d4468b645f94f97191c232..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9856 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 diff --git a/__pycache__/phi_parser.cpython-312.pyc b/__pycache__/phi_parser.cpython-312.pyc deleted file mode 100644 index 1d17fc3a49a755a997082cb3ae4ca71f90123771..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7894 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

q{E?zIN8a#?(3c$^)z8=QRm#*^{=` z&GtTU!_qGQc((sxRn2VoeZAp<%d<-TvOnJaz)`+pU9u+lw4)*3b>HcFP{t-3PJZ$H z7kiVZ1IfCvbXhRz2xdx3SIU>l6D7-4@skgTl8>g}n_hk6{n(o4K+1CAF zoh!{t&563@*7&JRdF3LXs7rJwj=(q%uQnxJE%Ba=#j$X1?%W6Z)k8PB@A$uP->vAo zXE~iMLGFgE3)MHyp8bQZGERSOu`QgQJDqeN{j4-?IUc9(n=M<0k#V|KjxHTd99-^L zb2g-$4QXc+j9^b){OlrrrythH;Y_rq?2VcF#!p*5Zuz8j(Uds+SN4aEO{?Q~oF7M$ zO-JqoJ{$S%*ym&aJpS3x?@N;P=W;CBN@TD4wgzA(qv)OMT|AOFyh?ql|5*QtF>Ps$ zQ;&4WUA}U9>2%`CjkhU1T2F zV3)`>9RHT4-5oF`cjdZSM;fy>R8p~GTe2mjdyd9z1FEdgHX^HY;r!hBMDsmMeYOpi zc^*B7EY6I(E@O9R%A3FrGtNDbe`M8LbXla=>b{#H`%%i+x*?L?bfIKGt4V9M19tru zM?ed0;OkNA{{xv7a`C*AhqQUjLtyzl1eUZ+?{=7zcDPV3gR5_%&njUx~mQr&1lADUWEJx{vr>A zxD=Ie8023u=Pzqur*^nk%uD8FYg~KZ?jqZ;!Mb3Xvn*a(xw3R+dH9~8A!|gXwcnZ$ z+mtpRdM}!&%Q=9*HpaUa`sVun@=PL{G9OAZhyMFN%!fqcng7o2?&@HE*Hj90%jy!} zR9)R}$n50i6>!@PQ=TFhFyQ&pIoHffs{pA`c`1Aq8jF1|xOhOAkl&LAa@YeFX3N6S zxufsCxNu_bMA}@Fcr9i2BpFZ6VPfzPNgwjfRncJqqU>i>x9{tGpy zQ1c&<5ng!O{2k3u_ACOju7P4!E!xxu9aWP>K=KWwSc=MPRs(m0FM5&}|14$u;j9^i tnBDREh1R*&qhL2MMX@T({|mlI&es3{ diff --git a/__pycache__/phi_parser.cpython-313.pyc b/__pycache__/phi_parser.cpython-313.pyc deleted file mode 100644 index f3d9ad68dc3f2bc15f603b1f08291519c23bf251..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6431 zcmd5=O>7&-6`m!Rzsp}OWy-QFuKprXmgLw<;#f)SD3&c*vJ^+#b%Ih3yCPQ<6KYA% zF6~(GK}Cv|irYx4>X>O#R6!3-dPyz?QWR(tpg;?>K*X@H*p1=@XnOEXhEoL1q3D|> zmzHE%1&SOx1mDiiyq}qG-n@A`cie6#g7WVNk0(nt2>qSB7{ywyPd(|?AShGu0zd;x6~uv`mm|2@8R+TVMnFc z2EF!sdY!z3ck&GH;@!Nb$AXy&`*>YNbkhU0d!eU(Q&XjCXv7|#<$d+&1sZ$#0Pojp zKE9T((QEz$9o}<`$b>145oI~8ifS?~EAZ-wi>eq)iHZ_7YxF=erfT$=q@rr}r!#~{ zOpV1h8#b3;s5{4?at%!&Oz{YtcnX`_0I><0H@8ENUZbluOSNXzYc_02*uxIZ)}Ki5 z87X$l4lOjUZk(dbkXD{3k!OL7{zp<&aEp`kV!Rml8fQBhTKax$w*N;HanFoHYqM_S*B zz6n8*uriSm<-~=Ff%GLgl@{a51Pn-=1pR=PU<+5S$^#x3mgHk&ucD4O2I62C=|_{lvT|tXJ?|Y2%ZE^XERATQSO{| z>r#~kJFn4VBB9KBh{q6BU7W7ZnZZℷ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 diff --git a/codegen.py b/codegen.py index d9a36e1..1a5a96b 100644 --- a/codegen.py +++ b/codegen.py @@ -204,8 +204,6 @@ def _emit_filter(spec: PhiSpec) -> str: or_clauses.append("" + " and ".join(and_group) + "") condition = " or ".join(or_clauses) - if len(condition.strip()) == 0: - condition = True lines.append(f" if ({condition}):") lines.append(" filtered_mf_struct[_key] = entry") @@ -234,4 +232,4 @@ def main() -> None: if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/phi_input_case2.txt b/phi_input_case2.txt index 9334778..e4ea0ad 100644 --- a/phi_input_case2.txt +++ b/phi_input_case2.txt @@ -1,18 +1,18 @@ -# Case 2: Count and Max quant per customer """ -SELECT - cust, - COUNT(*), - MAX(quant) -FROM sales -WHERE quant IS NOT NULL -GROUP BY cust; +select state, month, a_sum_quant, b_avg_quant +from sales +where year = 2018 +group by state, month; a, b +such that a_state = state and a_month = month + and b_state = state and b_month = month +having a_sum_quant >= 3 * b_avg_quant """ -S: cust, 1_count_*, 1_max_quant -n: 1 -V: cust -F: 1_count_*, 1_max_quant +S: state, month, 1_sum_quant, 2_avg_quant +n: 2 +V: state, month +F: 1_sum_quant, 2_avg_quant sigma: - 1: (cust == g_cust) and (quant is not None) -G: \ No newline at end of file + 1: state == g_state and month == g_month and year == 2018 + 2: state == g_state and month == g_month and year == 2018 +G: 1_sum_quant >= 3 * 2_avg_quant diff --git a/phi_input_case3.txt b/phi_input_case3.txt index 34f6b5f..8b3da39 100644 --- a/phi_input_case3.txt +++ b/phi_input_case3.txt @@ -1,30 +1,11 @@ """ -WITH a AS ( - SELECT - state, - month, - SUM(quant) AS a_sum_quant - FROM sales - WHERE state = 'CA' - GROUP BY state, month -), -b AS ( - SELECT - state, - MAX(quant) AS b_max_quant - FROM sales - GROUP BY state -) -SELECT - a.state, - a.month, - a.a_sum_quant, - b.b_max_quant -FROM a -JOIN b - ON a.state = b.state -WHERE a.a_sum_quant >= b.b_max_quant * 2; - +select state, month, a_sum_quant, b_max_quant +from sales +where state = 'CA' +group by state,month; a,b +such that a_state = state and a_month = month + and b_state = state +having a_sum_quant >= b_max_quant * 2 """ S: state, month, 1_sum_quant, 2_max_quant n: 2 diff --git a/phi_parser.py b/phi_parser.py index ca7be18..5462f2e 100644 --- a/phi_parser.py +++ b/phi_parser.py @@ -118,7 +118,6 @@ def parse_phi_file(path: str) -> PhiSpec: grouping_attrs = _split_list(V) aggs: List[AggSpec] = [] - having: List[List[str]] = [] if F: for item in _split_list(F): m = _AGG_RE.match(item) @@ -152,6 +151,8 @@ def parse_phi_file(path: str) -> PhiSpec: # Split OR (case-insensitive) or_blocks = re.split(r'\s+OR\s+', G, flags=re.IGNORECASE) + having: List[List[str]] = [] + for block in or_blocks: and_parts = re.split(r'\s+AND\s+', block, flags=re.IGNORECASE) rewrite = [] @@ -197,5 +198,4 @@ def _split_list(s: str) -> List[str]: def _first_token(s: str) -> str: - return s.strip().split()[0] - + return s.strip().split()[0] \ No newline at end of file diff --git a/qpe_case1.py b/qpe_case1.py new file mode 100644 index 0000000..d05a097 --- /dev/null +++ b/qpe_case1.py @@ -0,0 +1,97 @@ +from __future__ import annotations +import os +import psycopg2 +import psycopg2.extras +from dotenv import load_dotenv + +# Auto-generated by codegen.py. Re-generate anytime. + +def _safe_eval_predicate(expr: str, env: dict) -> bool: + """ + English comments: + Evaluate a predicate expression in a restricted environment. + - Variables are taken from the given env dict. + - Supports Python boolean logic (and/or/not). + """ + if not expr: + return True + return bool(eval(expr, {"__builtins__": {}}, env)) + + +def run_query(): + try: + load_dotenv() + user = os.getenv("USER") + password = os.getenv("PASSWORD") + dbname = os.getenv("DBNAME") + host = os.getenv("HOST", "localhost") + port = os.getenv("PORT", "5432") + + conn = psycopg2.connect( + dbname=dbname, + user=user, + password=password, + host=host, + port=port, + cursor_factory=psycopg2.extras.DictCursor + ) + cur = conn.cursor() + + except: + raise RuntimeError("Incorrect USER/PASSWORD/DBNAME in .env") + + # mf_struct maps grouping key tuple -> entry dict + mf_struct = {} + + # SCAN 0: initialize mf_struct entries for distinct grouping keys + cur.execute('SELECT * FROM sales') + for row in cur: + key = (row['prod'], row['month']) + if key not in mf_struct: + entry = {} + entry['prod'] = row['prod'] + entry['month'] = row['month'] + entry['1_sum_quant'] = 0 + entry['2_sum_quant'] = 0 + mf_struct[key] = entry + + # SCAN 1: compute aggregates for grouping variable 1 + cur.execute('SELECT * FROM sales') + for row in cur: + for _key, entry in mf_struct.items(): + env = dict(row) + env['g_prod'] = entry.get('prod') + env['g_month'] = entry.get('month') + if not _safe_eval_predicate('prod == g_prod and month == g_month', env): + continue + entry['1_sum_quant'] += (row['quant'] if row['quant'] is not None else 0) + + # SCAN 2: compute aggregates for grouping variable 2 + cur.execute('SELECT * FROM sales') + for row in cur: + for _key, entry in mf_struct.items(): + env = dict(row) + env['g_prod'] = entry.get('prod') + env['g_month'] = entry.get('month') + if not _safe_eval_predicate('prod == g_prod', env): + continue + entry['2_sum_quant'] += (row['quant'] if row['quant'] is not None else 0) + + filtered_mf_struct = {} + for _key, entry in mf_struct.items(): + if (entry['1_sum_quant'] > 0.9*entry['2_sum_quant']/10 and (entry['1_sum_quant'] > 40000 or entry['2_sum_quant']<500000) and entry['month'] == 11): + filtered_mf_struct[_key] = entry + + # Output + out_cols = ['prod', 'month', '1_sum_quant', '2_sum_quant'] + print("\t".join(out_cols)) + for _key, entry in filtered_mf_struct.items(): + row_out = [str(entry.get(c, "")) for c in out_cols] + print("\t".join(row_out)) + + cur.close() + conn.close() + + +if __name__ == "__main__": + run_query() diff --git a/qpe_case2.py b/qpe_case2.py new file mode 100644 index 0000000..e71efc2 --- /dev/null +++ b/qpe_case2.py @@ -0,0 +1,103 @@ +from __future__ import annotations +import os +import psycopg2 +import psycopg2.extras +from dotenv import load_dotenv + +# Auto-generated by codegen.py. Re-generate anytime. + +def _safe_eval_predicate(expr: str, env: dict) -> bool: + """ + English comments: + Evaluate a predicate expression in a restricted environment. + - Variables are taken from the given env dict. + - Supports Python boolean logic (and/or/not). + """ + if not expr: + return True + return bool(eval(expr, {"__builtins__": {}}, env)) + + +def run_query(): + try: + load_dotenv() + user = os.getenv("USER") + password = os.getenv("PASSWORD") + dbname = os.getenv("DBNAME") + host = os.getenv("HOST", "localhost") + port = os.getenv("PORT", "5432") + + conn = psycopg2.connect( + dbname=dbname, + user=user, + password=password, + host=host, + port=port, + cursor_factory=psycopg2.extras.DictCursor + ) + cur = conn.cursor() + + except: + raise RuntimeError("Incorrect USER/PASSWORD/DBNAME in .env") + + # mf_struct maps grouping key tuple -> entry dict + mf_struct = {} + + # SCAN 0: initialize mf_struct entries for distinct grouping keys + cur.execute('SELECT * FROM sales') + for row in cur: + key = (row['state'], row['month']) + if key not in mf_struct: + entry = {} + entry['state'] = row['state'] + entry['month'] = row['month'] + entry['1_sum_quant'] = 0 + entry['2_avg_quant__sum'] = 0 + entry['2_avg_quant__count'] = 0 + entry['2_avg_quant'] = 0 + mf_struct[key] = entry + + # SCAN 1: compute aggregates for grouping variable 1 + cur.execute('SELECT * FROM sales') + for row in cur: + for _key, entry in mf_struct.items(): + env = dict(row) + env['g_state'] = entry.get('state') + env['g_month'] = entry.get('month') + if not _safe_eval_predicate('state == g_state and month == g_month and year == 2018', env): + continue + entry['1_sum_quant'] += (row['quant'] if row['quant'] is not None else 0) + + # SCAN 2: compute aggregates for grouping variable 2 + cur.execute('SELECT * FROM sales') + for row in cur: + for _key, entry in mf_struct.items(): + env = dict(row) + env['g_state'] = entry.get('state') + env['g_month'] = entry.get('month') + if not _safe_eval_predicate('state == g_state and month == g_month and year == 2018', env): + continue + val = row['quant'] + if val is not None: + entry['2_avg_quant__sum'] += val + entry['2_avg_quant__count'] += 1 + entry['2_avg_quant'] = entry['2_avg_quant__sum'] / entry['2_avg_quant__count'] + + filtered_mf_struct = {} + for _key, entry in mf_struct.items(): + if (entry['1_sum_quant'] >= 3 * entry['2_avg_quant']): + filtered_mf_struct[_key] = entry + + # Output + out_cols = ['state', 'month', '1_sum_quant', '2_avg_quant'] + print("\t".join(out_cols)) + for _key, entry in filtered_mf_struct.items(): + row_out = [str(entry.get(c, "")) for c in out_cols] + print("\t".join(row_out)) + + cur.close() + conn.close() + + +if __name__ == "__main__": + run_query() diff --git a/qpe_case3.py b/qpe_case3.py new file mode 100644 index 0000000..163c21c --- /dev/null +++ b/qpe_case3.py @@ -0,0 +1,100 @@ +from __future__ import annotations +import os +import psycopg2 +import psycopg2.extras +from dotenv import load_dotenv + +# Auto-generated by codegen.py. Re-generate anytime. + +def _safe_eval_predicate(expr: str, env: dict) -> bool: + """ + English comments: + Evaluate a predicate expression in a restricted environment. + - Variables are taken from the given env dict. + - Supports Python boolean logic (and/or/not). + """ + if not expr: + return True + return bool(eval(expr, {"__builtins__": {}}, env)) + + +def run_query(): + try: + load_dotenv() + user = os.getenv("USER") + password = os.getenv("PASSWORD") + dbname = os.getenv("DBNAME") + host = os.getenv("HOST", "localhost") + port = os.getenv("PORT", "5432") + + conn = psycopg2.connect( + dbname=dbname, + user=user, + password=password, + host=host, + port=port, + cursor_factory=psycopg2.extras.DictCursor + ) + cur = conn.cursor() + + except: + raise RuntimeError("Incorrect USER/PASSWORD/DBNAME in .env") + + # mf_struct maps grouping key tuple -> entry dict + mf_struct = {} + + # SCAN 0: initialize mf_struct entries for distinct grouping keys + cur.execute('SELECT * FROM sales') + for row in cur: + key = (row['state'], row['month']) + if key not in mf_struct: + entry = {} + entry['state'] = row['state'] + entry['month'] = row['month'] + entry['1_sum_quant'] = 0 + entry['2_max_quant'] = None + mf_struct[key] = entry + + # SCAN 1: compute aggregates for grouping variable 1 + cur.execute('SELECT * FROM sales') + for row in cur: + for _key, entry in mf_struct.items(): + env = dict(row) + env['g_state'] = entry.get('state') + env['g_month'] = entry.get('month') + if not _safe_eval_predicate('state == g_state and month == g_month', env): + continue + entry['1_sum_quant'] += (row['quant'] if row['quant'] is not None else 0) + + # SCAN 2: compute aggregates for grouping variable 2 + cur.execute('SELECT * FROM sales') + for row in cur: + for _key, entry in mf_struct.items(): + env = dict(row) + env['g_state'] = entry.get('state') + env['g_month'] = entry.get('month') + if not _safe_eval_predicate('state == g_state', env): + continue + val = row['quant'] + if val is not None: + if entry['2_max_quant'] is None or val > entry['2_max_quant']: + entry['2_max_quant'] = val + + filtered_mf_struct = {} + for _key, entry in mf_struct.items(): + if (entry['1_sum_quant'] >= entry['2_max_quant'] * 2 and entry['state'] == 'CA'): + filtered_mf_struct[_key] = entry + + # Output + out_cols = ['state', 'month', '1_sum_quant', '2_max_quant'] + print("\t".join(out_cols)) + for _key, entry in filtered_mf_struct.items(): + row_out = [str(entry.get(c, "")) for c in out_cols] + print("\t".join(row_out)) + + cur.close() + conn.close() + + +if __name__ == "__main__": + run_query()