1919import org .elasticsearch .compute .data .Page ;
2020import org .elasticsearch .compute .test .MockBlockFactory ;
2121import org .elasticsearch .test .ESTestCase ;
22+ import org .elasticsearch .xpack .esql .EsqlTestUtils ;
2223import org .elasticsearch .xpack .esql .VerificationException ;
2324import org .elasticsearch .xpack .esql .analysis .AnalyzerTestUtils ;
2425import org .elasticsearch .xpack .esql .core .expression .FoldContext ;
2526import org .elasticsearch .xpack .esql .core .expression .Literal ;
2627import org .elasticsearch .xpack .esql .core .expression .NamedExpression ;
28+ import org .elasticsearch .xpack .esql .core .expression .ReferenceAttribute ;
2729import org .elasticsearch .xpack .esql .core .tree .Source ;
2830import org .elasticsearch .xpack .esql .expression .Foldables ;
31+ import org .elasticsearch .xpack .esql .expression .function .scalar .approximate .ConfidenceInterval ;
2932import org .elasticsearch .xpack .esql .inference .InferenceService ;
33+ import org .elasticsearch .xpack .esql .optimizer .LogicalOptimizerContext ;
34+ import org .elasticsearch .xpack .esql .optimizer .LogicalPlanOptimizer ;
3035import org .elasticsearch .xpack .esql .optimizer .LogicalPlanPreOptimizer ;
3136import org .elasticsearch .xpack .esql .optimizer .LogicalPreOptimizerContext ;
3237import org .elasticsearch .xpack .esql .parser .EsqlParser ;
3540import org .elasticsearch .xpack .esql .plan .logical .Filter ;
3641import org .elasticsearch .xpack .esql .plan .logical .LogicalPlan ;
3742import org .elasticsearch .xpack .esql .plan .logical .Sample ;
43+ import org .elasticsearch .xpack .esql .plan .logical .local .EmptyLocalSupplier ;
44+ import org .elasticsearch .xpack .esql .plan .physical .LocalSourceExec ;
45+ import org .elasticsearch .xpack .esql .plan .physical .PhysicalPlan ;
46+ import org .elasticsearch .xpack .esql .session .Configuration ;
47+ import org .elasticsearch .xpack .esql .session .EsqlSession ;
3848import org .elasticsearch .xpack .esql .session .Result ;
3949import org .hamcrest .Description ;
4050import org .hamcrest .Matcher ;
4151import org .hamcrest .TypeSafeMatcher ;
4252
4353import java .util .ArrayList ;
54+ import java .util .Arrays ;
55+ import java .util .HashMap ;
4456import java .util .List ;
57+ import java .util .Map ;
58+ import java .util .function .Function ;
4559import java .util .function .Predicate ;
4660
61+ import static java .lang .Double .NaN ;
4762import static org .elasticsearch .xpack .esql .EsqlTestUtils .withDefaultLimitWarning ;
4863import static org .hamcrest .CoreMatchers .allOf ;
4964import static org .hamcrest .CoreMatchers .not ;
@@ -73,13 +88,19 @@ public class ApproximateTests extends ESTestCase {
7388 * sampling in the query, the returned number of rows is multiplied by
7489 * the sampling probability.
7590 * <p>
76- * The runner collects all its invocations.
91+ * The runner also provides the LogicalPlan to PhysicalPlan conversion,
92+ * but it does not return a realistic PhysicalPlan. When running a
93+ * PhysicalPlan is invoked, it maps it back to the original LogicalPlan,
94+ * because LogicalPlans are easier to analyze in tests.
95+ * <p>
96+ * The runner collects the LogicalPlans of its invocations.
7797 */
78- private static class TestRunner implements Approximate . LogicalPlanRunner {
98+ private static class TestRunner implements Function < LogicalPlan , PhysicalPlan >, EsqlSession . PlanRunner {
7999
80100 private final long totalRows ;
81101 private final long filteredRows ;
82102 private final List <LogicalPlan > invocations ;
103+ private final Map <PhysicalPlan , LogicalPlan > toLogicalPlan = new HashMap <>();
83104
84105 static ActionListener <Result > resultCloser = ActionListener .wrap (result -> result .pages ().getFirst ().close (), e -> {});
85106
@@ -90,7 +111,20 @@ private static class TestRunner implements Approximate.LogicalPlanRunner {
90111 }
91112
92113 @ Override
93- public void run (LogicalPlan logicalPlan , ActionListener <Result > listener ) {
114+ public PhysicalPlan apply (LogicalPlan logicalPlan ) {
115+ // Return a dummy PhysicalPlan that can be mapped back to the LogicalPlan.
116+ PhysicalPlan physicalPlan = new LocalSourceExec (
117+ Source .EMPTY ,
118+ List .of (new ReferenceAttribute (Source .EMPTY , null , "id" , null )),
119+ EmptyLocalSupplier .EMPTY
120+ );
121+ toLogicalPlan .put (physicalPlan , logicalPlan );
122+ return physicalPlan ;
123+ }
124+
125+ @ Override
126+ public void run (PhysicalPlan physicalPlan , Configuration configuration , FoldContext foldContext , ActionListener <Result > listener ) {
127+ LogicalPlan logicalPlan = toLogicalPlan .get (physicalPlan );
94128 invocations .add (logicalPlan );
95129 List <LogicalPlan > filters = logicalPlan .collect (plan -> plan instanceof Filter );
96130 long numResults = filters .isEmpty () ? totalRows : filteredRows ;
@@ -101,6 +135,7 @@ public void run(LogicalPlan logicalPlan, ActionListener<Result> listener) {
101135 LongBlock block = blockFactory .newConstantLongBlockWith (numResults , 1 );
102136 listener .onResponse (new Result (null , List .of (new Page (block )), null , null ));
103137 }
138+
104139 }
105140
106141 @ Override
@@ -358,8 +393,15 @@ private void verify(String query) throws Exception {
358393 Approximate .verifyPlan (getLogicalPlan (query ));
359394 }
360395
361- private Approximate createApproximate (String query , Approximate .LogicalPlanRunner runner ) throws Exception {
362- return new Approximate (getLogicalPlan (query ), runner );
396+ private Approximate createApproximate (String query , TestRunner runner ) throws Exception {
397+ return new Approximate (
398+ getLogicalPlan (query ),
399+ new LogicalPlanOptimizer (new LogicalOptimizerContext (EsqlTestUtils .TEST_CFG , FoldContext .small (), EsqlTestUtils .randomMinimumVersion ())),
400+ runner ,
401+ runner ,
402+ EsqlTestUtils .TEST_CFG ,
403+ FoldContext .small ()
404+ );
363405 }
364406
365407 private LogicalPlan getLogicalPlan (String query ) throws Exception {
@@ -374,4 +416,16 @@ private LogicalPlan getLogicalPlan(String query) throws Exception {
374416 }
375417 return resultHolder .get ();
376418 }
419+
420+ public void test () {
421+ double bestEstimate = 17600.0 ;
422+ double [] estimates = new double [] {
423+ NaN , NaN , NaN , 93768.0 , NaN , NaN , NaN , 93916.0 , NaN , NaN , NaN , NaN , NaN , NaN , 93916.0 , NaN ,
424+ 93916.0 , NaN , NaN , NaN , NaN , NaN , 93768.0 , NaN , NaN , NaN , NaN , NaN , NaN , 93916.0 , NaN , NaN ,
425+ 93916.0 , NaN , NaN , NaN , NaN , NaN , NaN , NaN , 93768.0 , 93916.0 , NaN , NaN , NaN , NaN , NaN , NaN
426+ };
427+ int trialCount =3 ;
428+ int bucketCount =16 ;
429+ System .out .println (Arrays .toString (ConfidenceInterval .computeConfidenceInterval (bestEstimate , estimates , trialCount , bucketCount , 0.9 )));
430+ }
377431}
0 commit comments