From 4b94b83cef226c9ad1bd88097ae197c8e0c7d3d8 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 11 Jun 2026 17:14:29 +0200 Subject: [PATCH] Revise and polish the query plan viewer --- Makefile | 10 +++- tools/plan-viewer/index.html | Bin 26536 -> 38330 bytes tools/plan-viewer/test.js | 87 +++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tools/plan-viewer/test.js diff --git a/Makefile b/Makefile index 207384a..702467c 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ bench-check: ## Type-check benchmark code without running it fi .PHONY: check -check: format-check lint test bench-check ## Run all checks (format-check, lint, test, bench-check) +check: format-check lint test bench-check viewer-test ## Run all checks (format-check, lint, test, bench-check, viewer-test) .PHONY: clean clean: ## Remove build output @@ -99,6 +99,14 @@ export-fixtures: ## Regenerate plan JSON for every tools/exporter/examples/*.sce examples: export-fixtures ## Regenerate fixtures from scenarios and run them through plan-runner against their oracles. @cargo test -p plan-runner --test examples +.PHONY: viewer-test +viewer-test: ## Check the plan viewer's JS engine against every fixture oracle (needs Node) + @if ! command -v node >/dev/null 2>&1; then \ + echo "node not found. Skipping plan viewer engine check."; \ + else \ + node tools/plan-viewer/test.js; \ + fi + VIEWER_PORT ?= 8000 .PHONY: viewer diff --git a/tools/plan-viewer/index.html b/tools/plan-viewer/index.html index 3965e010d4b4a7e82b6864fd89d11bc1edb63c3c..6ccbab00745327908034d902b6424a8d2ad46d6a 100644 GIT binary patch delta 12147 zcmZ2+o^jV|rVakwz9orCIjMR@sW}QIMS96OnaSB80flOX~Ff=Nt zu%2AcEjBrTha=X|prAs*K*0dwMkG_K6><}c(lhg{6wE*>tWk_GE~tQNNJ&iB%}7m5 zLAOKC6v-9~uq{Rf6$&7`A~vG<0=KQY%U{ttY?dlv0AI zPtMORNG!te3&NfPZWgWL)ST4h5;V7f9h;k2p~) za|=o;6^bC*KyJxSwNik%At^CM(^>%{rkj#joRL^mlvrt{V5(pY^?hD`N~#{j1dxOE zic6C~mS^VVAek|F3vbruSiUC4$(#c7>!D7}OtDo;%d99VElSme2q`IKWTvE~=Gock zLj>%&6clW75|dJM6q0iii;HcQpkbh-0MZTR=z>+iHFGH_C@4UstQ2fA^9o8!6iO-! zQbE?`q$+VE>79H~K#NmB!A2i!#N@>SKUKim6LCB&2IAFI0;`HeK1?0C^au7wMZc|PazSawntV)9T|}WIBUPa|F*jAAI5{y-2W;A8Mj?&K zdO~un5T1{a*5qoTG=8ujKnVz(yp<+D6spjNh^3Y!X6EP?B<7{&D1od2g@#Rr5wavy zPl|Ab5LmbyfCf^aR5(EpUB_@{?Lq#`-iA-iRvQ{WgP36+pS13s>F44;> zR!GcC0XsS&H!&OL6rN7!@|35c|r?sIYmT%p#Hcp!{+LP|i%v zOUz47wNgmS%mD>-qC!z>PGU)Bex7=<0w~ifl;kUvWaJlvvUgr_W^qYsUWo!II295> zPDswrDb3B(Ey>p{OUx-vRY=OrOUcYjS13u$PR)ZvR$`t)erX9P`it_*^%UIlixf&S z>Qfc+3sQ>`OY)0~6*5wb!2Zzy2e?9UYHns$erBGILSAA?X;EU10*I%n0I?x4y(l#` z7vzfkJg~1p*&#JW0pgNkh0J1*yHYC>lS^_c!3vV|^Gb>`lS)f6^U`&T^2-&H^YbE7 zGC`iy<4P;dO9ru^0pL{W1o1(!1}q?SV4f=0RH)_xCF;E55{2Z9)a2}91zUyaSZgi? zc&O=3mbO-@PX?t!XiyYuLe=UOlon@bL}%(iJmQ^NsR2_GogAwfs|hlv7EBhUmXsFd zAq)jYgdQY@G&G9x%QY2jA&Cm&3cb{_)S^lau-VzEm9di-$cWY}*xK4EWT#eYYFcyE za)H7Gl+HmRtXrI!lB$rHmza~EUYe?qpN8RgD}~&|lH?2pP}+l}g2eR1Opv$1kq$CM zp&+rOB(*3{p%k3&541|$eQsVcddxwC6|71RTbP1% z=H?Z{0<+POjFQM%E>HI0>v;$T2ldPDmZn5BDE;LJUTPBUK1Pv za8Zz2y`0PvaQwm&5yVHf@B|hOR#lc*1Wr?+R0U24P-V8Z;J{AH%u7vCsHuU31j0sC z3;8s_y1>@eYHCi7Fw7CjNvupQ(oslM096<2#R|na`6ZLD8Om{jA_BxI-u%;$mx&Fm zu6Xi1BeBWKCN0_!u3koBu||rfrUH~(oLZuhqN5NENw*3R;mKD`f>^-{ChMBILRgzC zOl24uiziPt3lb^@y9ZPl=A~AYDA+0#PyT6E!2&8(Cl{KV8-N%XsZj@95JTe_ntOsX z^2>F&6ee#n_hB+Knq;BFWNJ29&%#h9IUkfv-8>^gLW5j`<6S(16l@ig^z`)g^z+ z1x2aFsd*(j(4q!ZT|g?j%;Nk!uwp$0*Tm!uh19%~qDm`;>b%66=g1c`bb zg``T)lv+KmoYWGqIIJK6@gbFNYKnrbLSAW3jx{K>6!i5KszFtkXG(>RLS{-WSSL&+ zs4RyoMpLN+%D3THCS)HU>(EA4plMP)OJxlD6=B$fp`lPn&4J)VsWugW^svLVoHjJ61>r@ z1W%wK&6(gljO08`NDCW9FQ_0%%|U1^DFT`4T9!K5*ISAYVZTPErsn3;wth^)pz16* zBe5toMWdit2b>!xU-LF$E-2QUEUhVJ1o3N8W_kuFixd>=L7E1|dg-Yp8X(PjU^%c; zA<;BB+Q%XtQpyLXCgpVFgavpyCo%cIzmB>*7>!{R%Jp5LF_m znDEKWOErQ!6BLG%PpQjkL!1b!18l*5&`Si@q(p1t>Ffs=F6x4TtSPQN3ZNctF3rMh1NT4Wy zOoXU~rC@snrTjbvkR4F*tor=SJPjos1tm=dD+Q(G#G;ba;>^T6n6;2F4avwX!XBj1 zmJQUm;FcJqg;APUf>cStLedx|Btg}HC#bjrb=_|Y7M=vu4>>-H1lk;;x1w~P6 z4lH=};JMIPbMhPqQ$a*lNzO0LD^akW{L#SyTNs1Q(gu0^&~|WufYs|L7@0xZ3-ESP zQesMas#|`MMsZ1ENhvflgE9w%56&S<;O2r7ye^DZ%FhOc>7zN5K`jZEmm(wS@@p(+8^1dY;YrSilgQ0yq><%3MriUqfB z;I1uBE%8WAOi3+r0y$eFIj2|$TpDRAR8L+LW-~cBN=OD&d@0x~D1m(j@q11&DE5`C zxhCs{i_DWMu(T9A@hk_s_j1C$j&5eSMuPys*LE=;B#)=$v|i9m`3kZ(aH zcQGstg4$(95Zww2Aa!~m`<(Ohz)fpgNPQ4(5UU501LXmQvSP3+!KOqTLL@*Dt)PJB z-FhWZs6*_nRY=V#P6anuz#5UQHzd}2h%J!R7Yz^0SOr@JaNvQG1=w(qbIVeT3{o(qEM8dkEPr$DAog6s8?8;T2!eAQUeYG1%=6Zj&g#?l3?-4oUtnP zpspooP(w+fR!1Qk+&BW&U#U47N}$nzymTcUg=&ygK`}VB#A-sis~|;63Q7nLSVsZ4 zSgVF~;;qorKgbwRLPRp7UO`C#S~{S*9#k~JLrz;k30(XagVd&^7L;UwQ)^CQWqxUi zUT$KA3s_7W?tHMJkSYzV5)>AY<~X#S0Wz^Dzg&q!L0drsBs*EqQO*hGn^;hf2INCf zg`l92l%GrHlWk(gZTe1xf3Z?l7o95YO))ZMur*+_7N zBj={f;$ZL?5xAWPYhfs;slgjV;MC%oQUNYtK`95sMwZV^0ZW16LN7A~)ojaS&7MFnALB?3EFtJ3j!g#Wxr?5K6 z3ZzOJhZV+~A9zk;6<3Z%r;}4OLv0K6$mb90yX%PFDuf+EBpSV%a>w=Q&%w zzPA!)xxw!?UC8;S2NtKYkAgCLM7#@Lj*;7GX zk(5mE$RcDIPNO(A6|I+5pI)pFYPUgqS(->~7f7E0)MW@xO$H5>6oa!CC?;W@3rNWc z$uUH>Wgw#j>7e!usDaN0Y6j+jMo*+Q5Z&QqP)!4B?tt3Z(M9>?o+%Zv(V4N<;Ftq7 zL?_z?C~Cms8r-pmBziCt(qIO8OApMS91yF+24?9^u8CEyhsIbmXjBJOpn#LFm4Xsz z*rV7ATs9#z!-^khQxC=kxy%Z=g9;mj0PBP)hIL^4j1;IqXE&kY8z1+kC4UK5f2o9(p7>iV;L4qF=!x$MK+KvQ;GI&S< z$;FGJCLHXqYMfqir(Qim{ zgBj(SQeg!ue&O!6M|L-;<^NI+mI;9;Qx3Uf#StO+v?**i$Hd=T#xL(JC$ zm#I*vfy!7&3{ActAyyAl0xdH@<3Qm01r)@^knn^|A=ZKhu;E=iED;DA69>2NLB$%> zgP;iyS z@hMm-z{@Kucw&P&160lxe##&Hp&80BeAxsh8 z&vHO7LW01vQ3|lx5R5_uO&-(%tDk%*TypZga9Q5m%wpJxKsDE7`ABDZ=(wMi0<7zl zn^=@xtN_h63X>0pD>JL9DNL@7RG2&|LVxnPNR7$2BcvwVMoLcBj^bymoxCGlY4Y!I z{>hUg+$JkWN=-f!CC35j;)1P^+k7U;m1LGwg6B8N-~&2k zdJtjI5V^L3k`<_V02ea^S9VbCnR$@85rp30lA_GKbd55-`pi7o^asd6pe`(iHN_=G zpu7S#3^vh%Fbp=pg5*W;cp^v>sN{n->AZsd{q%~#7H6haYLtP;v@#JP1<5q9DAItL z3JUwuqU2P+#N1R!pnz+4_$(hR3&V!yxj^F^pqc*E6iBWH57@I&w{x@K_f4}L?IP4V47HxnxY9F%>mW=ypTzTRLDg8%pyh&~#(59%x1ZIs^`G%R}093JTCruH^!!C`C&gsmZmXAT=4(4TaSe zV9!8;1TxAq*)B|%4a&?9lN3&c>9B?8kI8Fvq{Lw2kgNyltAJKSfZ7V7FezBv=z!86 z*jjLFK}Vrl0W@)hC6His40Jpc5@k@we2h|OQu3SJ8znAP0!rEN)T4yzcW|9ygv0;H zwE`%WKzt4^*TK2UK1QG(%)^qekWw&sv^PIZ!5>^E>t&}_7UM{=dd2xgB^vN%7{oqM zKNOVy;9dY*2~E+Hf5!;9LewP|WtM;jhS1H92H77A3to`E=vYl?1_1d=0lXNXc(Pz@ zA1l~sP_#_mY0WQU5z{ubLlc}biic&-jC zt7vPh0B4nTzsQdKZHInI_H;tsCK598w|J0=)!u1b((6oUjQxXl4c zSD-+&nw*rRBn>hHTvkB_q(OZI=+e95$x9Oz7?UQyPP8=x)q`MPgW8`4n&1Wis8bnT zPz)|Cz(e>Tqd?Qo;7+79BsWSyyAqJHUrAFBvSv%cRv|4hr#KbTypvKysWNb>n!G5< zUk!C`0~9)t$_~_n)BrhA2Q)u5Sua_e4HBV~Bas>GL%UMsOfM?&^WdRco+~=Fd{oGDX|z_yK59AmSm`-xD6ztS6q;jS)!q&ucWD0 zkY4~Yqn3+nvSXO!U;5OIg6ktfOEs`B1{DPGH@;p#|1x>ic3Q3iq)?h(mQ89S=R7R=-WXLYR zs8S&(vlz5?NueYI)bXn<$pEj;$|xx*&?`6|rnVg+k1RcNthj~tZGI*&MsPk5mpPZiqnz|_}DGtvp$8hXhzni5|H1gd7qIIMR}WHq1#7EzoG5oRusuf@~Hj+k)bB zvPHUn9I^zcAq45=r4|+G<)#)FC#I)DqYXKD(F@mFE-d*9ysl{S^Hjqqg@VNNRBHt_ zNb!(rYXd4;>_GV&6a~c!i3-J#MH--z9X!Yn8h+JHs)Ve}gJ=WQb$VQoL{M6k6OdSx zm|Hx#I!!&w8Y&7+4`3MuTLrixP|DVTE$Ri;dk{k)#-`#>4wjv~HcgQQlw~KMOOvVx z`>oLE>AZE03dRCm;e9( delta 2192 zcmdnBnrX#(#tr`5n|Lf4nG~ug2k?eZ4&Y>+?8|?9@^pb%6TPDR{1Szt)Z`L{YK7vG zqWtVs-IT=QjKrd%#7ZlLy!^aWYpA%Dg0iJaqH&VN=9dD!jFT1lwI-Jd&7a&XEH?SA z@V&_nqN1V&iABY!#R?@EsR~7 zgB2_>dAq3GHipjm=+>;f=D<-cJui9)XF`04lFUfn144ZFCePv~2 z*nD1Lk;vqUmMKEX`8lPzc{&P-3VHb{sp`cFm6JKGsJrY#k(*eQU9724z1iF?hLICt$}IOqo8Nml zG3kK(8C;rFl39|IssZBYC={g@m*$j!ba5#tB;*9+AyvbUA zQc@t_fvhfpxK>F+NkLlyBJJX5s-U2)prok;QdE>$Qd*R!09L21prmkUyMmH}_T(-< z2gzD4uC&s;ohQ$&*3bhKh5T99dO`abt$CZcYTTybfV9>|l<`FSO&d7u~sX~Q16lNn=0nM#v3--w&U zIJr1MQy?inrBW}kpdd9bMWeVZU32oX1UUhae&h(5+>oU*Ioq3evW~9QWG`J!K1lR{ zG8xEAlaG5_Y?erT!Zvw93fJTd*1E_ zC{)+#C`3>^87}@$$KhfCmWZjPgV@DoK!05lvt9Sp#%vJ1%;%< zl=M`+6I{ni_@4Z%dU;aaoa+n1aWSCH|_Df0f9wr4|+C7b#7)F14I&S0*ue zR;kS7f-+fN1%+CL)STi}aJ)_ytdavcMjG8Q;F3o{Au~-uW3pRhoF;#)aGh*fsX95a^4Vn0RwZUo zq2STlK6y!-^5oO4sul_gi8-l7B^qFFfT{$gkc`Y?g`E8S>|%wS%KQCchVvu+h?k3Fu`N=jUnafJ!8UN#z-t$r%b6iNy+e`3m3y2ja@g z)Dk^~$+zna*_0HtxfB#8tF;?wDrDvrm!u}9=qZ8o21@*Z+y!^$Gz z4mv$9NdAT@2}mqT%$+=SvN|`oC{O|mPZpb^IN5rNlw472aY24wajK?*tsSVI0dv4* jriP{_*JQUz%KTsrMX6wOKw`C%^aYT`K-J3R&Z&j~{0Zp^ diff --git a/tools/plan-viewer/test.js b/tools/plan-viewer/test.js new file mode 100644 index 0000000..4dc3dd1 --- /dev/null +++ b/tools/plan-viewer/test.js @@ -0,0 +1,87 @@ +#!/usr/bin/env node +// Parity check for the plan viewer's JavaScript engine. +// +// Extracts the engine script block from index.html and runs every fixture +// in crates/plan-runner/fixtures/ through it, requiring the result to match +// the fixture's expected_bindings oracle, exactly as the Rust runner does. +// Also checks the provenance helpers: every output row of every node must +// have at least one contributing row in each of its inputs. +// +// The Rust crates remain the correctness oracle; this test only catches the +// viewer drifting away from them. + +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const html = fs.readFileSync(path.join(__dirname, "index.html"), "utf8"); +const match = html.match(/