diff --git a/docs/changelog/132934.yaml b/docs/changelog/132934.yaml new file mode 100644 index 0000000000000..bd7b0bfd70f74 --- /dev/null +++ b/docs/changelog/132934.yaml @@ -0,0 +1,5 @@ +pr: 132934 +summary: Support filters on inlinestats +area: ES|QL +type: enhancement +issues: [] diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java index b140f7ded44f0..9e1ed7417efe6 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java +++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java @@ -51,7 +51,7 @@ import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.ENABLE_LOOKUP_JOIN_ON_REMOTE; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.FORK_V9; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.INLINESTATS; -import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.INLINESTATS_V9; +import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.INLINESTATS_V10; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_LOOKUP_V12; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_PLANNING_V1; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.METADATA_FIELDS_REMOTE_TEST; @@ -137,7 +137,7 @@ protected void shouldSkipTest(String testName) throws IOException { assumeTrue("Test " + testName + " is skipped on " + oldVersion, isEnabled(testName, instructions, oldVersion)); assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS.capabilityName())); assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(JOIN_PLANNING_V1.capabilityName())); - assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS_V9.capabilityName())); + assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS_V10.capabilityName())); if (testCase.requiredCapabilities.contains(JOIN_LOOKUP_V12.capabilityName())) { assumeTrue( "LOOKUP JOIN not yet supported in CCS", diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec index b90135f43a417..d0381725d5c34 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec @@ -1,9 +1,5 @@ -// -// TODO: re-enable the commented tests once the Join functionality stabilizes -// - allFieldsReturned -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM hosts METADATA _index | INLINESTATS c = COUNT(*) BY host_group @@ -16,7 +12,7 @@ eth0 |epsilon gw instance|epsilon |[fe80::cae2:65ff:fece:feb9, ; maxOfInt -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 // tag::max-languages[] FROM employees | KEEP emp_no, languages @@ -38,7 +34,7 @@ emp_no:integer | languages:integer | max_lang:integer ; maxOfIntByKeyword -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender @@ -56,7 +52,7 @@ emp_no:integer | languages:integer | max_lang:integer | gender:keyword ; maxOfLongByKeyword -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, avg_worked_seconds, gender @@ -71,7 +67,7 @@ emp_no:integer | avg_worked_seconds:long | max_avg_worked_seconds:long | gender: ; maxOfLong -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, avg_worked_seconds, gender @@ -84,7 +80,7 @@ emp_no:integer | avg_worked_seconds:long | gender:keyword | max_avg_worked_secon ; maxOfLongByCalculatedKeyword -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 // tag::longest-tenured-by-first[] FROM employees @@ -107,7 +103,7 @@ emp_no:integer | avg_worked_seconds:long | last_name:keyword | max_avg_worked_se ; maxOfLongByCalculatedNamedKeyword -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, avg_worked_seconds, last_name @@ -126,7 +122,7 @@ emp_no:integer | avg_worked_seconds:long | last_name:keyword | max_avg_worked_se ; maxOfLongByCalculatedDroppedKeyword -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS max_avg_worked_seconds = MAX(avg_worked_seconds) BY l = SUBSTRING(last_name, 0, 1) @@ -145,7 +141,7 @@ emp_no:integer | avg_worked_seconds:long | last_name:keyword | max_avg_worked_se ; maxOfLongByEvaledKeyword -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | EVAL l = SUBSTRING(last_name, 0, 1) @@ -165,7 +161,7 @@ emp_no:integer | avg_worked_seconds:long | max_avg_worked_seconds:long | l:keywo ; maxOfLongByInt -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, avg_worked_seconds, languages @@ -183,7 +179,7 @@ emp_no:integer | avg_worked_seconds:long | max_avg_worked_seconds:long | languag ; maxOfLongByIntDouble -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, avg_worked_seconds, languages, height @@ -201,7 +197,7 @@ emp_no:integer | avg_worked_seconds:long | max_avg_worked_seconds:long | languag ; two -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, avg_worked_seconds, gender @@ -225,8 +221,7 @@ emp_no:integer |avg_worked_seconds:long|avg_avg_worked_seconds:double|languages: ; three -required_capability: inlinestats_v9 -// used to fail with AssertionError at org.elasticsearch.xpack.esql.plan.logical.Limit.writeTo(Limit.java:70) +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, avg_worked_seconds, gender @@ -253,7 +248,7 @@ emp_no:integer |avg_worked_seconds:long|avg_avg_worked_seconds:double|languages: // TODO: INLINESTATS unit test needed for this one pushDownSort_To_LeftSideOnly -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 from employees | sort emp_no @@ -271,7 +266,7 @@ from employees ; byMultivaluedSimple -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 // tag::mv-group[] FROM airports @@ -289,7 +284,7 @@ abbrev:keyword | type:keyword | scalerank:integer | min_scalerank:integer ; byMultivaluedMvExpand -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 // tag::mv-expand[] FROM airports @@ -309,7 +304,7 @@ GWL |9 |4 |military ; byMvExpand -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 // tag::extreme-airports[] FROM airports @@ -338,7 +333,7 @@ FROM airports ; mvMinMvExpand -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | EVAL original_type = type @@ -361,7 +356,7 @@ ZAR |Zaria |POINT (7.7 11.0667) |Nigeria |POINT ( ; afterStats -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | STATS count=COUNT(*) BY country @@ -384,7 +379,7 @@ count:long | country:keyword | avg:double ; afterWhere -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | WHERE country != "United States" @@ -402,7 +397,7 @@ abbrev:keyword | country:keyword | count:long ; afterLookup -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 required_capability: join_lookup_v12 FROM airports @@ -426,7 +421,7 @@ ZNZ |4 |German ; afterEnrich -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 required_capability: enrich_load FROM airports @@ -447,7 +442,7 @@ abbrev:keyword | city:keyword | "COUNT(*)":long | region:text ; beforeStats -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | EVAL lat = ST_Y(location) @@ -460,7 +455,7 @@ northern:long | southern:long ; beforeKeepSort -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS max_salary = MAX(salary) by languages @@ -475,7 +470,7 @@ emp_no:integer | languages:integer | max_salary:integer ; beforeKeepWhere -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS max_salary = MAX(salary) by languages @@ -488,7 +483,7 @@ emp_no:integer | languages:integer | max_salary:integer ; beforeEnrich -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 required_capability: enrich_load FROM airports @@ -507,7 +502,7 @@ ACA |Acapulco de Juárez|385 |major |Acapulco de ; beforeAndAfterEnrich -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 required_capability: enrich_load FROM airports @@ -530,7 +525,7 @@ ALL |Albenga |499 |mid |1 ; shadowing -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW left = "left", client_ip = "172.21.0.5", env = "env", right = "right" | INLINESTATS env = VALUES(right) BY client_ip @@ -541,7 +536,7 @@ left | right | right | 172.21.0.5 ; shadowingMulti -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW left = "left", airport = "Zurich Airport ZRH", city = "Zürich", middle = "middle", region = "North-East Switzerland", right = "right" | INLINESTATS airport=VALUES(left), region=VALUES(left), city_boundary=VALUES(left) BY city @@ -552,7 +547,7 @@ left | middle | right | left | left ; shadowingSelf -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW city = "Raleigh" | INLINESTATS city = COUNT(city) @@ -563,7 +558,7 @@ city:long ; shadowingSelfBySelf -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW city = "Raleigh" | INLINESTATS city = COUNT(city) BY city @@ -575,7 +570,7 @@ Raleigh ; shadowingInternal -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW city = "Zürich" | INLINESTATS x = VALUES(city), x = VALUES(city) @@ -587,7 +582,7 @@ Zürich | Zürich ; multiInlinestatsWithRow -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 row x = 1 | inlinestats x = max(x) + min(x) @@ -601,7 +596,7 @@ row x = 1 ; ignoreUnusedEvaledValue_AndInlineStats -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW x = 1 | INLINESTATS max(x) @@ -614,7 +609,7 @@ x:integer ; ignoreUnusedEvaledValue_AndInlineStats2 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW x = 1, z = 2 | INLINESTATS max(x) @@ -627,7 +622,7 @@ x:integer | z:integer ; ignoreUnusedEvaledValue_AndInlineStats3 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 from employees | inlinestats max(salary) @@ -642,7 +637,7 @@ from employees ; ignoreUnusedEvaledValue_AndInlineStats4 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 from employees | inlinestats max(salary), m = min(salary) by gender @@ -657,7 +652,7 @@ emp_no:integer ; ignoreUnusedEvaledValue_AndInlineStats5 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 from employees | inlinestats max(salary), m = min(salary) by gender @@ -672,7 +667,7 @@ emp_no:integer ; shadowEntireInlinestats -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS x = avg(salary), y = min(salary) BY emp_no @@ -687,7 +682,7 @@ x:integer |y:integer |emp_no:integer ; byConstant -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages @@ -706,7 +701,7 @@ emp_no:integer | languages:integer | max_lang:integer | y:integer ; aggConstant -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no @@ -724,7 +719,7 @@ one:integer | emp_no:integer ; percentile -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, salary @@ -743,7 +738,7 @@ emp_no:integer | salary:integer | ninety_fifth_salary:double ; byTwoCalculated -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | WHERE abbrev IS NOT NULL @@ -763,7 +758,7 @@ abbrev:keyword | scalerank:integer | location:geo_point byTwoCalculatedSecondOverwrites required_capability: stats_alias_collision_warnings -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | WHERE abbrev IS NOT NULL @@ -784,7 +779,7 @@ abbrev:keyword | scalerank:integer | location:geo_point byTwoCalculatedSecondOverwritesReferencingFirst required_capability: stats_alias_collision_warnings -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | WHERE abbrev IS NOT NULL @@ -807,7 +802,7 @@ abbrev:keyword | scalerank:integer | location:geo_point groupShadowsAgg required_capability: stats_alias_collision_warnings -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM airports | WHERE abbrev IS NOT NULL @@ -827,7 +822,7 @@ abbrev:keyword | scalerank:integer | location:geo_point ; groupShadowsField -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, salary, hire_date @@ -846,7 +841,7 @@ emp_no:integer | salary:integer | avg_salary:double | hire_date:datetime ; groupByExpression_And_ExistentField -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender | EVAL x = "ABC" @@ -864,7 +859,7 @@ emp_no:integer | languages:integer | x:keyword | max_lang:integer | y:keyword | ; groupByRenamedColumn -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender | INLINESTATS max_lang = MAX(languages) BY y = gender @@ -881,9 +876,8 @@ emp_no:integer | languages:integer | gender:keyword | max_lang:integer | y:keywo 10014 | 5 | null | 5 | null ; -// fails with AssertionError at org.elasticsearch.xpack.esql.plan.logical.Limit.writeTo(Limit.java:70) groupByMultipleRenamedColumns_AndOneExpression_Last -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender, first_name @@ -905,9 +899,8 @@ emp_no:integer | languages:integer | gender:keyword|first_name:keyword|max_lang: 10010 |4 |null |Duangkaew |4 |null |4 |D ; -// fails with AssertionError at org.elasticsearch.xpack.esql.plan.logical.Limit.writeTo(Limit.java:70) groupByMultipleRenamedColumns_AndTwoExpressions -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender, first_name @@ -929,9 +922,8 @@ emp_no:integer | languages:integer | gender:keyword|first_name:keyword|max_lang: 10010 |4 |null |Duangkaew |4 |D |null |D |4 ; -// fails with AssertionError at org.elasticsearch.xpack.esql.plan.logical.Limit.writeTo(Limit.java:70) groupByMultipleRenamedColumns_AndMultipleRenames -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender, first_name @@ -954,9 +946,8 @@ emp_no:integer | languages:integer | gender:keyword| f:keyword |max_lang: 10010 |4 |null |Duangkaew |4 |null |4 |D ; -// fails with AssertionError at org.elasticsearch.xpack.esql.plan.logical.Limit.writeTo(Limit.java:70) groupByMultipleRenamedColumns_AndSameNameExpressionGroupingOverride -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender, first_name @@ -980,7 +971,7 @@ emp_no:integer | languages:integer | gender:keyword|max_lang:integer| y:keyword ; twoAggregatesGroupedBy_AField_And_AnExpression -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender, last_name @@ -1002,7 +993,7 @@ emp_no:integer |languages:integer|last_name:keyword|max_lang:integer|min_lang:in ; groupByMultipleRenamedColumns_InversedOrder -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, still_hired, gender @@ -1020,7 +1011,7 @@ emp_no:integer |languages:integer|still_hired:boolean| gender:keyword|max_lang:i ; groupByMultipleRenamedColumns_InversedOrder_ComplexEval -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, still_hired, gender @@ -1039,7 +1030,7 @@ emp_no:integer |languages:integer|still_hired:boolean| gender:keyword|multilingu ; groupByMultipleRenamedColumns_AndComplexEval -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, still_hired, gender @@ -1057,9 +1048,8 @@ emp_no:integer |languages:integer|still_hired:boolean| gender:keyword|multilingu 10005 |1 |true |M |monolingual |1 |M |true |monolingual ; -// fails with AssertionError at org.elasticsearch.xpack.esql.plan.logical.Limit.writeTo(Limit.java:70) groupByMultipleRenamedColumns_AndConstantValue -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender, first_name @@ -1083,7 +1073,7 @@ emp_no:integer |languages:integer|gender:keyword |first_name:keyword | x:keyw ; groupByRenamedExpression -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP emp_no, languages, gender, last_name @@ -1105,7 +1095,7 @@ emp_no:integer |languages:integer|last_name:keyword|max_lang:integer|min_lang:in ; doubleFilterOnLeftAndRight_InlineStats_Sides -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS max_salary = MAX(salary), min_salary = MIN(salary) by languages @@ -1126,7 +1116,7 @@ emp_no:integer |languages:integer|salary:integer |max_salary:integer|min_salary: ; filterOnInlineStatsAggs -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS max_salary = MAX(salary), min_salary = MIN(salary) by languages @@ -1145,7 +1135,7 @@ emp_no:integer |languages:integer|salary:integer |max_salary:integer|min_salary: ; filterOnInlineStatsAggsValues_And_Groupings -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS max_salary = MAX(salary), min_salary = MIN(salary) by languages @@ -1164,7 +1154,7 @@ emp_no:integer |languages:integer|salary:integer |max_salary:integer|min_salary: ; inlineStatsOverrideEVALed_FieldWithSameName -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM hosts METADATA _index | EVAL x = ip1 @@ -1178,7 +1168,7 @@ beta k8s server |beta |127.0.0.1 |hosts |127.0.0.2|2 ; doubleShadowing -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS salary = min(salary) BY gender @@ -1197,7 +1187,7 @@ salary:integer |gender:keyword ; doubleShadowing_WithIntertwinedFilters -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | WHERE salary > 30000 @@ -1222,7 +1212,7 @@ salary:integer |gender:keyword ; shadowingAggregateByNextGrouping -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP gender, languages, emp_no, salary @@ -1239,7 +1229,7 @@ emp_no:integer |salary:integer |languages:integer|avg(salary):double|gender:long ; doubleShadowingWithEval -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 from employees | eval salary = salary/100 @@ -1259,7 +1249,7 @@ salary:integer|gender:keyword ; doubleShadowingWithDoubleStats -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 from employees | stats salary=min(salary) by gender @@ -1276,7 +1266,7 @@ M |25324 ; renamingGroupingWithItself -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | EVAL x = gender @@ -1295,7 +1285,7 @@ salary:integer |x:keyword|gender:keyword |min_sl:integer |emp_no:integer ; overridingGroupings -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS min_sl = MIN(salary) BY x = gender, x = languages @@ -1314,7 +1304,7 @@ salary:integer |x:integer |gender:keyword |min_sl:integer |emp_no:integer ; overridingExpressionGroupings -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | INLINESTATS min_sl = MIN(salary) BY x = TO_LOWER(gender), x = CONCAT(gender, gender) @@ -1333,7 +1323,7 @@ salary:integer |x:keyword |gender:keyword |min_sl:integer |emp_no:integer ; reusingEvalExpressions_UsedInGroupings -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | KEEP salary, gender, emp_no @@ -1352,7 +1342,7 @@ salary:integer |gender:keyword |emp_no:integer |min_sl:integer | x:keyword ; statsBeforeInlinestatsWithTopAndBucket1 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM books | STATS avg_rating = AVG(ratings) BY decade = BUCKET(year, 10) @@ -1372,7 +1362,7 @@ avg_rating:double | decade:double | decades:double ; statsBeforeInlinestatsWithTopAndBucket2 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM sample_data | STATS total_duration = SUM(event_duration) BY day = BUCKET(@timestamp, 1 HOUR) @@ -1388,7 +1378,7 @@ total_duration:long | day:date | days:date evalBeforeInlinestatsAndKeepAfter1 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | WHERE still_hired == false @@ -1408,7 +1398,7 @@ emp_no:integer |still_hired:boolean|totalK:long|count:long ; evalBeforeInlinestatsAndKeepAfter2 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | EVAL salaryK = salary/1000 @@ -1428,7 +1418,7 @@ emp_no:integer |still_hired:boolean|total:long|count:long ; evalBeforeInlinestatsAndKeepAfter3 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | EVAL salaryK = salary/1000 @@ -1447,7 +1437,7 @@ emp_no:integer |still_hired:boolean|total:long ; evalBeforeInlinestatsAndKeepAfter4 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 FROM employees | EVAL salaryK = salary/1000 @@ -1466,7 +1456,7 @@ emp_no:integer |still_hired:boolean|count:long ; evalBeforeInlinestatsAndKeepAfter5 -required_capability: inlinestats_v9 +required_capability: inlinestats_v10 ROW salary = 12300, emp_no = 5, gender = "F" | EVAL salaryK = salary/1000 @@ -1477,3 +1467,947 @@ ROW salary = 12300, emp_no = 5, gender = "F" emp_no:integer 5 ; +/////////////////////////// +// inlinestats with filters +/////////////////////////// + +doubleFilterOnInlineStats +required_capability: inlinestats_v10 + +from employees +| keep salary, gender +| inlinestats max1 = max(salary) where salary < 60000, + max2 = max(salary) where salary < 70000 AND salary >= 60000, + max3 = max(salary) where salary >= 70000 + by gender +| sort salary desc +| limit 10 +; + +salary:integer | max1:integer | max2:integer | max3:integer | gender:keyword +74999 |58121 |68547 |74999 |M +74970 |58121 |68547 |74999 |M +74572 |56415 |69904 |74572 |F +73851 |56415 |69904 |74572 |F +73717 |58715 |61358 |73717 |null +73578 |56415 |69904 |74572 |F +71165 |58121 |68547 |74999 |M +70011 |58121 |68547 |74999 |M +69904 |56415 |69904 |74572 |F +68547 |58121 |68547 |74999 |M +; + +inlinestatsWithFiltering +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(salary), max_f = max(salary) where salary < 50000, max_a = max(salary) where salary > 100, + min = min(salary), min_f = min(salary) where salary > 50000, min_a = min(salary) where salary > 100 +| keep max*, min*, salary +| sort salary asc +| limit 3 +; + +max:integer |max_f:integer |max_a:integer | min:integer | min_f:integer | min_a:integer | salary:integer +74999 |49818 |74999 |25324 |50064 |25324 |25324 +74999 |49818 |74999 |25324 |50064 |25324 |25945 +74999 |49818 |74999 |25324 |50064 |25324 |25976 +; + +inlinestatsWithEverythingFiltered +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(salary), max_a = max(salary) where salary < 100, + min = min(salary), min_a = min(salary) where salary > 99999 +| keep max*, min*, emp_no*, salary +| sort emp_no desc +| limit 5 +; + +max:integer |max_a:integer|min:integer | min_a:integer | emp_no:integer|salary:integer +74999 |null |25324 |null |10100 |68431 +74999 |null |25324 |null |10099 |73578 +74999 |null |25324 |null |10098 |44817 +74999 |null |25324 |null |10097 |71165 +74999 |null |25324 |null |10096 |43889 +; + +inlinestatsWithNullFilter +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(salary), max_a = max(salary) where null, + min = min(salary), min_a = min(salary) where to_string(null) == "abc" +| keep max*, min*, emp_no +| sort emp_no +| limit 3 +; + +max:integer |max_a:integer|min:integer | min_a:integer | emp_no:integer +74999 |null |25324 |null |10001 +74999 |null |25324 |null |10002 +74999 |null |25324 |null |10003 +; + +inlinestatsWithAllFiltersFalse +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(height.float) where false, + min = min(height.float) where to_string(null) == "abc", + count = count(height.float) where false, + count_distinct = count_distinct(salary) where to_string(null) == "def" +| sort emp_no desc +| keep emp_no, salary, max, min, count* +| limit 3 +; + +emp_no:integer|salary:integer| max:double |min:double |count:long |count_distinct:long +10100 |68431 |null |null |0 |0 +10099 |73578 |null |null |0 |0 +10098 |44817 |null |null |0 |0 +; + +inlinestatsWithAllFiltersFalse_GroupByOneField +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(height.float) where false, + min = min(height.float) where to_string(null) == "abc", + count = count(height.float) where false, + count_distinct = count_distinct(salary) where to_string(null) == "def" by gender +| sort emp_no desc +| keep emp_no, salary, max, min, count*, gender +| limit 5 +; + + emp_no:i | salary:i | max:d | min:d | count:l |count_distinct:l| gender:s +10100 |68431 |null |null |0 |0 |F +10099 |73578 |null |null |0 |0 |F +10098 |44817 |null |null |0 |0 |F +10097 |71165 |null |null |0 |0 |M +10096 |43889 |null |null |0 |0 |M +; + +inlinestatsWithAllFiltersFalse_GroupByTwoFields +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(height.float) where false, + min = min(height.float) where to_string(null) == "abc", + count = count(height.float) where false, + count_distinct = count_distinct(salary) where to_string(null) == "def" by gender, languages +| sort emp_no desc +| keep emp_no, salary, max, min, count*, gender, languages +| limit 5 +; + + emp_no:i | salary:i | max:d | min:d | count:l |count_distinct:l| gender:s |languages:i +10100 |68431 |null |null |0 |0 |F |4 +10099 |73578 |null |null |0 |0 |F |2 +10098 |44817 |null |null |0 |0 |F |4 +10097 |71165 |null |null |0 |0 |M |3 +10096 |43889 |null |null |0 |0 |M |4 +; + +prunedInlinestatsFollowedByInlinestats_GroupByOneFieldEach_DifferentFields +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count = count(my_length) where false, + values = mv_slice(values(first_name), 0, 1) where my_length > 0 by languages +| keep emp_no, first_name, *length, count, values, languages, gender +| inlinestats count_distinct = count_distinct(count) by gender +| sort emp_no +| limit 3 +; + + emp_no:i | first_name:s | my_length:i | count:l | values:s | languages:i |count_distinct:l| gender:s +10001 |Georgi |null |0 |null |2 |1 |M +10002 |Bezalel |null |0 |null |5 |1 |F +10003 |Parto |null |0 |null |4 |1 |M +; + +prunedInlinestatsFollowedByInlinestats_GroupByOneFieldEach_SameFields +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count = count(my_length) where false, + values = mv_slice(values(first_name), 0, 1) where my_length > 0 by languages +| keep emp_no, first_name, *length, count, values, languages, gender +| inlinestats count_distinct = count_distinct(count) by languages +| sort emp_no +| limit 3 +; + + emp_no:i | first_name:s | my_length:i | count:l | values:s | gender:s |count_distinct:l| languages:i +10001 |Georgi |null |0 |null |M |1 |2 +10002 |Bezalel |null |0 |null |F |1 |5 +10003 |Parto |null |0 |null |M |1 |4 +; + +prunedInlinestatsFollowedByInlinestats_GroupByOneFieldOnSecondInlinestats-Ignore +// values doesn't end up as null +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count = count(my_length) where false, + values = mv_slice(values(first_name), 0, 1) where my_length > 0 +| keep emp_no, first_name, *length, count, values, languages, gender +| inlinestats count_distinct = count_distinct(count) by languages +| sort emp_no +| limit 3 +; + + emp_no:i | first_name:s | my_length:i | count:l | values:s | gender:s |count_distinct:l| languages:i +10001 |Georgi |null |0 |null |M |1 |2 +10002 |Bezalel |null |0 |null |F |1 |5 +10003 |Parto |null |0 |null |M |1 |4 +; + +partial_PrunedInlinestatsFollowedByInlinestats_GroupByOneFieldOnFirstInlinestats +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count = count(is_rehired) where true, + values = mv_slice(values(first_name), 0, 1) where my_length > 0 by gender +| keep emp_no, first_name, *length, count, values, languages, gender +| inlinestats count_distinct = count_distinct(count) +| sort emp_no +| limit 10 +; + + emp_no:i | first_name:s | my_length:i | count:l | values:s | languages:i | gender:s |count_distinct:l +10001 |Georgi |null |111 |null |2 |M |3 +10002 |Bezalel |null |65 |null |5 |F |3 +10003 |Parto |null |111 |null |4 |M |3 +10004 |Chirstian |null |111 |null |5 |M |3 +10005 |Kyoichi |null |111 |null |1 |M |3 +10006 |Anneke |null |65 |null |3 |F |3 +10007 |Tzvetan |null |65 |null |4 |F |3 +10008 |Saniya |null |111 |null |2 |M |3 +10009 |Sumant |null |65 |null |1 |F |3 +10010 |Duangkaew |null |28 |null |4 |null |3 +; + + +partial_PrunedInlinestatsFollowedByInlinestats_GroupByOneFieldOnFirstInlinestats2 +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count = count(is_rehired) where true, + values = mv_slice(values(first_name), 0, 1) where my_length > 0, + count_d = count_distinct(is_rehired) by gender +| keep emp_no, first_name, *length, count*, values, languages, gender +| inlinestats count_distinct = count_distinct(count) +| sort emp_no +| limit 10 +; + + emp_no:i | first_name:s | my_length:i | count:l | count_d:l | values:s | languages:i | gender:s |count_distinct:l +10001 |Georgi |null |111 |2 |null |2 |M |3 +10002 |Bezalel |null |65 |2 |null |5 |F |3 +10003 |Parto |null |111 |2 |null |4 |M |3 +10004 |Chirstian |null |111 |2 |null |5 |M |3 +10005 |Kyoichi |null |111 |2 |null |1 |M |3 +10006 |Anneke |null |65 |2 |null |3 |F |3 +10007 |Tzvetan |null |65 |2 |null |4 |F |3 +10008 |Saniya |null |111 |2 |null |2 |M |3 +10009 |Sumant |null |65 |2 |null |1 |F |3 +10010 |Duangkaew |null |28 |2 |null |4 |null |3 +; + + +inlinestatsWithExpressionsAllFiltersFalse +required_capability: inlinestats_v10 +from employees +| keep height.f*, emp_no +| sort emp_no desc +| inlinestats max = max(height.float + 1) where null, + count = count(height.float) + 2 where false, + mix = min(height.float + 1) + count_distinct(emp_no) + 2 where length(null) == 3 +| limit 3 +| drop height.float +; + + emp_no:i | max:d | count:l | mix:d +10100 |null |2 |null +10099 |null |2 |null +10098 |null |2 |null +; + +inlinestatsWithFalseFilterAndGroup +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(height.float + 1) where null, + count = count(height.float) + 2 where false + by job_positions +| sort emp_no desc +| limit 4 +| keep emp_no, job_positions, height.float, max, count +; + + emp_no:i | job_positions:s | height.float:d | max:d | count:l +10100 |Purchase Manager |1.7699999809265137|null |2 +10099 |null |1.809999942779541 |null |2 +10098 |[Architect, Internship, Senior Team Lead]|2.0 |null |[2, 2, 2] +10097 |[Reporting Analyst, Tech Lead] |1.5299999713897705|null |[2, 2] +; + +inlinestatsWithFalseFiltersAndGroups +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count_distinct = count_distinct(height.float + 1) where null, + count = count(height.float) + 2 where false, + values = values(first_name) where my_length > 3 + by job_positions, is_rehired +| sort emp_no desc +| limit 10 +| keep emp_no, my_length, count*, values, job_positions, is_rehired +; + + emp_no:integer|my_length:integer|count_distinct:long| count:long |values:keyword | job_positions:keyword | is_rehired:boolean +10100 |null |[0, 0] |[2, 2] |null |Purchase Manager |[false, false, true, true] +10099 |null |0 |2 |null |null |[true, true] +10098 |null |[0, 0, 0] |[2, 2, 2] |null |[Architect, Internship, Senior Team Lead] |false +10097 |null |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Reporting Analyst, Tech Lead] |[false, true] +10096 |null |[0, 0] |[2, 2] |null |[Architect, Reporting Analyst] |[false, false, false] +10095 |null |[0, 0] |[2, 2] |null |null |[false, false, true, true] +10094 |null |[0, 0, 0, 0, 0, 0] |[2, 2, 2, 2, 2, 2]|null |[Accountant, Principal Support Engineer, Senior Python Developer] |[false, true, true] +10093 |null |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Principal Support Engineer, Purchase Manager, Reporting Analyst, Tech Lead]|null +10092 |null |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Accountant, Junior Developer] |[false, false, true, true] +10091 |null |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Python Developer, Reporting Analyst] |[false, false, true, true] +; + +inlinestatsWithFalseFiltersAndGroups_DropEvaledValue +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count_distinct = count_distinct(height.float + 1) where null, + count = count(height.float) + 2 where false, + values = values(first_name) where my_length > 3 + by job_positions, is_rehired +| sort emp_no desc +| limit 10 +| keep emp_no, count*, values, job_positions, is_rehired +; + + + emp_no:integer|count_distinct:long| count:long |values:keyword | job_positions:keyword | is_rehired:boolean +10100 |[0, 0] |[2, 2] |null |Purchase Manager |[false, false, true, true] +10099 |0 |2 |null |null |[true, true] +10098 |[0, 0, 0] |[2, 2, 2] |null |[Architect, Internship, Senior Team Lead] |false +10097 |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Reporting Analyst, Tech Lead] |[false, true] +10096 |[0, 0] |[2, 2] |null |[Architect, Reporting Analyst] |[false, false, false] +10095 |[0, 0] |[2, 2] |null |null |[false, false, true, true] +10094 |[0, 0, 0, 0, 0, 0] |[2, 2, 2, 2, 2, 2]|null |[Accountant, Principal Support Engineer, Senior Python Developer] |[false, true, true] +10093 |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Principal Support Engineer, Purchase Manager, Reporting Analyst, Tech Lead]|null +10092 |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Accountant, Junior Developer] |[false, false, true, true] +10091 |[0, 0, 0, 0] |[2, 2, 2, 2] |null |[Python Developer, Reporting Analyst] |[false, false, true, true] +; + +inlinestatsWithMixedFiltersAndGroup +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count = count(my_length) where false, + values = mv_slice(mv_sort(values(first_name)), 0, 1) + by job_positions +| sort emp_no +| limit 4 +| keep *length, count, values, job_positions, emp_no +; + +my_length:integer| count:long | values:keyword | job_positions:keyword |emp_no:integer +null |[0, 0] |[Arumugam, Bojan, Arumugam, Basil] |[Accountant, Senior Python Developer] |10001 +null |0 |[Alejandro, Anneke] |Senior Team Lead |10002 +null |0 |[Gino, Heping] |null |10003 +null |[0, 0, 0, 0] |[Berni, Chirstian, Amabile, Berni, Bojan, Chirstian, Anneke, Chirstian]|[Head Human Resources, Reporting Analyst, Support Engineer, Tech Lead]|10004 +; + +prunedInlinestatsFollowedByinlinestats-Ignore +// values doesn't end up as null +required_capability: inlinestats_v10 +from employees +| eval my_length = length(concat(first_name, null)) +| inlinestats count = count(my_length) where false, + values = mv_slice(values(first_name), 0, 1) where my_length > 0 +| keep emp_no, first_name, *length, count, values +| inlinestats count_distinct = count_distinct(count) +| sort emp_no +| limit 3 +; + + emp_no:i | first_name:s | my_length:i | count:l | values:s |count_distinct:l +10001 |Georgi |null |0 |null |1 +10002 |Bezalel |null |0 |null |1 +10003 |Parto |null |0 |null |1 +; + +inlinestatsWithFalseFiltersFromRow +// is this result correct? The stats variant of this query has this result: +// c:integer |b:integer +// null |2 +// null |3 +// null |4 +required_capability: inlinestats_v10 +row x = null, a = 1, b = [2,3,4] +| inlinestats c=max(a) where x + by b +; + + x:null | a:integer | c:integer | b:integer +null |1 |null |[2, 3, 4] +; + +inlinestatsWithBasicExpressionFiltered +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(salary), max_f = max(salary) where salary < 50000, + min = min(salary), min_f = min(salary) where salary > 50000, + exp_p = max(salary) + 10000 where salary < 50000, + exp_m = min(salary) % 10000 where salary > 50000 +| keep max*, min*, exp*, emp_no | sort emp_no | limit 3 +; + + max:i | max_f:i | min:i | min_f:i | exp_p:i | exp_m:i | emp_no:i +74999 |49818 |25324 |50064 |59818 |64 |10001 +74999 |49818 |25324 |50064 |59818 |64 |10002 +74999 |49818 |25324 |50064 |59818 |64 |10003 +; + +inlinestatsWithExpressionOverFilters +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(salary), max_f = max(salary) where salary < 50000, + min = min(salary), min_f = min(salary) where salary > 50000, + exp_gt = max(salary) - min(salary) where salary > 50000, + exp_lt = max(salary) - min(salary) where salary < 50000 +| keep max*, min*, exp*, emp_no +| sort emp_no +| limit 3 +; + + max:i | max_f:i | min:i | min_f:i | exp_gt:i | exp_lt:i | emp_no:i +74999 |49818 |25324 |50064 |24935 |24494 |10001 +74999 |49818 |25324 |50064 |24935 |24494 |10002 +74999 |49818 |25324 |50064 |24935 |24494 |10003 +; + + +inlinestatsWithExpressionOfExpressionsOverFilters +required_capability: inlinestats_v10 +from employees +| inlinestats max = max(salary + 1), max_f = max(salary + 2) where salary < 50000, + min = min(salary - 1), min_f = min(salary - 2) where salary > 50000, + exp_gt = max(salary + 3) - min(salary - 3) where salary > 50000, + exp_lt = max(salary + 4) - min(salary - 4) where salary < 50000 +| keep max*, min*, exp*, emp_no +| sort emp_no +| limit 3 +; + + max:i | max_f:i | min:i | min_f:i | exp_gt:i | exp_lt:i | emp_no:i +75000 |49820 |25323 |50062 |24941 |24502 |10001 +75000 |49820 |25323 |50062 |24941 |24502 |10002 +75000 |49820 |25323 |50062 |24941 |24502 |10003 +; + +inlinestatsWithSubstitutedExpressionOverFilters +required_capability: inlinestats_v10 +from employees +| inlinestats sum = sum(salary), s_l = sum(salary) where salary < 50000, s_u = sum(salary) where salary > 50000, + count = count(salary), c_l = count(salary) where salary < 50000, c_u = count(salary) where salary > 50000, + avg = round(avg(salary), 2), a_l = round(avg(salary), 2) where salary < 50000, a_u = round(avg(salary),2) where salary > 50000 +| keep sum, s_*, count, c_*, avg, a_*, emp_no +| sort emp_no +| limit 3 +; + + sum:l | s_l:l | s_u:l | count:l | c_l:l | c_u:l | avg:d | a_l:d | a_u:d | emp_no:i +4824855 |2220951 |2603904 |100 |58 |42 |48248.55 |38292.26 |61997.71 |10001 +4824855 |2220951 |2603904 |100 |58 |42 |48248.55 |38292.26 |61997.71 |10002 +4824855 |2220951 |2603904 |100 |58 |42 |48248.55 |38292.26 |61997.71 |10003 +; + + +inlinestatsWithFilterAndGroupBy +required_capability: inlinestats_v10 +from employees +| keep height, gender, is_rehired, emp_no +| inlinestats m = max(height), m_f = max(height + 1) where gender == "M" OR is_rehired is null BY gender, is_rehired +| sort emp_no +| drop height +| limit 10 +; + + emp_no:i | m:d | m_f:d | gender:s | is_rehired:boolean +10001 |[2.1, 2.1] |[3.1, 3.1] |M |[false, true] +10002 |2.1 |null |F |[false, false] +10003 |2.01 |3.01 |M |null +10004 |2.1 |3.1 |M |true +10005 |[2.1, 2.1] |[3.1, 3.1] |M |[false, false, false, true] +10006 |1.85 |2.85 |F |null +10007 |[2.1, 2.1] |null |F |[false, false, true, true] +10008 |[2.1, 2.1] |[3.1, 3.1] |M |[false, true] +10009 |1.85 |2.85 |F |null +10010 |[2.06, 1.97] |null |null |[false, false, true, true] +; + + +inlinestatsWithFilterOnGroupBy +required_capability: inlinestats_v10 +from employees +| inlinestats m_f = max(height) where gender == "M" BY gender +| sort emp_no +| keep gender, m_f +| limit 10 +; + + gender:s | m_f:d +M |2.1 +F |null +M |2.1 +M |2.1 +M |2.1 +F |null +F |null +M |2.1 +F |null +null |null +; + +inlinestatsWithGroupByLiteral +required_capability: inlinestats_v10 +from employees +| inlinestats m = max(languages) by salary = 2 +| sort salary +| keep m, salary +| limit 3 +; + + m:i | salary:i +5 |2 +5 |2 +5 |2 +; + + +inlinestatsWithFilterOnSameColumn +required_capability: inlinestats_v10 +from employees +| inlinestats m = max(languages), m_f = max(languages) where salary > 50000 by salary = 2 +| sort salary +| keep m, m_f, salary +| limit 3 +; + + m:i | m_f:i | salary:i +5 |null |2 +5 |null |2 +5 |null |2 +; + +inlinestatsWithFilteringAndGrouping +required_capability: inlinestats_v10 +from employees +| inlinestats c = count(), c_f = count(languages) where l > 1, + m_f = max(height) where salary > 50000 + by l = languages +| sort salary +| keep c, *_f, l, salary +| limit 10 +; + + c:l | c_f:l | m_f:d | l:i | salary:i +21 |21 |2.1 |5 |25324 +21 |21 |2.1 |5 |25945 +15 |0 |2.06 |1 |25976 +17 |17 |2.1 |3 |26436 +18 |18 |1.83 |4 |27215 +15 |0 |2.06 |1 |28035 +10 |0 |2.08 |null |28336 +17 |17 |2.1 |3 |28941 +19 |19 |2.03 |2 |29175 +17 |17 |2.1 |3 |30404 +; + + +multiinlinestatsWithFiltering +required_capability: inlinestats_v10 +from employees +| inlinestats c = count(), c_f = count(languages) where l > 1, + m_f = max(height) where salary > 50000 + by l = languages +| inlinestats c2 = count(), c2_f = count() where m_f > 2.06 , m2 = max(l), m2_f = max(l) where l > 1 by c +| sort emp_no +| where emp_no >= 10029 and emp_no <= 10039 +| keep c, *_f, l, c2, m2 +; + + c:l | c_f:l | m_f:d | c2_f:l | m2_f:i | l:i | c2:l | m2:i +10 |0 |2.08 |10 |null |null |10 |null +17 |17 |2.1 |17 |3 |3 |17 |3 +18 |18 |1.83 |0 |4 |4 |18 |4 +17 |17 |2.1 |17 |3 |3 |17 |3 +15 |0 |2.06 |0 |null |1 |15 |1 +15 |0 |2.06 |0 |null |1 |15 |1 +21 |21 |2.1 |21 |5 |5 |21 |5 +18 |18 |1.83 |0 |4 |4 |18 |4 +19 |19 |2.03 |0 |2 |2 |19 |2 +18 |18 |1.83 |0 |4 |4 |18 |4 +19 |19 |2.03 |0 |2 |2 |19 |2 +; + + +simpleCountOnFieldWithFilteringAndNoGrouping +required_capability: inlinestats_v10 +from employees +| inlinestats c1 = count(emp_no) where emp_no < 10042 +| keep emp_no, c1 +| sort emp_no +| limit 3 +; + +emp_no:integer | c1:long +10001 |41 +10002 |41 +10003 |41 +; + +simpleCountOnFieldWithFilteringOnDifferentFieldAndNoGrouping +required_capability: inlinestats_v10 +from employees +| inlinestats c1 = count(hire_date) where emp_no < 10042 +| keep emp_no, c1 +| sort emp_no +| limit 3 +; + +emp_no:integer | c1:long +10001 |41 +10002 |41 +10003 |41 +; + +simpleCountOnStarWithFilteringAndNoGrouping +required_capability: inlinestats_v10 +from employees +| inlinestats c1 = count(*) where emp_no < 10042 +| keep emp_no, c1 +| sort emp_no +| limit 3 +; + +emp_no:integer | c1:long +10001 |41 +10002 |41 +10003 |41 +; + +simpleCountWithFilteringAndNoGroupingOnFieldWithNulls +required_capability: inlinestats_v10 +from employees +| inlinestats c1 = count(birth_date) where emp_no <= 10050 +| keep emp_no, c1 +| sort emp_no +| limit 3 +; + +emp_no:integer | c1:long +10001 |40 +10002 |40 +10003 |40 +; + + +simpleCountWithFilteringAndNoGroupingOnFieldWithMultivalues +required_capability: inlinestats_v10 +from employees +| inlinestats c1 = count(job_positions) where emp_no <= 10003 +| keep emp_no, c1 +| sort emp_no +| limit 3 +; + + emp_no:integer| c1:long +10001 |3 +10002 |3 +10003 |3 +; + +commonFilterExtractionWithAliasing +required_capability: inlinestats_v10 +from employees +| eval eno = emp_no +| drop emp_no +| inlinestats min_sal = min(salary) where eno <= 10010, + min_hei = min(height) where eno <= 10010 +| keep eno, min_* +| sort eno +| limit 3 +; + + eno:integer |min_sal:integer|min_hei:double +10001 |36174 |1.56 +10002 |36174 |1.56 +10003 |36174 |1.56 +; + +commonFilterExtractionWithAliasAndOriginal +required_capability: inlinestats_v10 +from employees +| eval eno = emp_no +| inlinestats min_sal = min(salary) where eno <= 10010, + min_hei = min(height) where emp_no <= 10010 +| keep emp_no, min_* +| sort emp_no +| limit 3 +; + + emp_no:integer|min_sal:integer|min_hei:double +10001 |36174 |1.56 +10002 |36174 |1.56 +10003 |36174 |1.56 +; + + +commonFilterExtractionWithAliasAndOriginalNeedingNormalization +required_capability: inlinestats_v10 +from employees +| eval eno = emp_no +| inlinestats min_sal = min(salary) where eno <= 10010, + min_hei = min(height) where emp_no <= 10010, + max_hei = max(height) where 10010 >= emp_no +| keep eno, min*, max* +| sort eno +| limit 5 +; + + eno:integer |min_sal:integer|min_hei:double |max_hei:double +10001 |36174 |1.56 |2.1 +10002 |36174 |1.56 |2.1 +10003 |36174 |1.56 |2.1 +10004 |36174 |1.56 |2.1 +10005 |36174 |1.56 |2.1 +; + +commonFilterExtractionWithAliasAndOriginalNeedingNormalizationAndSimplification +required_capability: inlinestats_v10 +from employees +| eval eno = emp_no +| inlinestats min_sal = min(salary) where eno <= 10010, + min_hei = min(height) where not (emp_no > 10010), + max_hei = max(height) where 10010 >= emp_no +| keep eno, min*, max* +| sort eno +| limit 5 +; + + eno:integer |min_sal:integer|min_hei:double |max_hei:double +10001 |36174 |1.56 |2.1 +10002 |36174 |1.56 |2.1 +10003 |36174 |1.56 |2.1 +10004 |36174 |1.56 |2.1 +10005 |36174 |1.56 |2.1 +; + +filterIsAlwaysTrue +required_capability: inlinestats_v10 +FROM employees +| inlinestats max = max(salary) WHERE salary > 0 +| keep max, salary, emp_no +| sort emp_no +| limit 3 +; + + max:integer |salary:integer | emp_no:integer +74999 |57305 |10001 +74999 |56371 |10002 +74999 |61805 |10003 +; + +filterIsAlwaysFalse +required_capability: inlinestats_v10 +FROM employees +| inlinestats max = max(salary) WHERE first_name == "" +| sort emp_no +| keep max, first_name +| limit 10 +; + + max:integer |first_name:keyword +null |Georgi +null |Bezalel +null |Parto +null |Chirstian +null |Kyoichi +null |Anneke +null |Tzvetan +null |Saniya +null |Sumant +null |Duangkaew +; + + +filterSometimesMatches +required_capability: inlinestats_v10 +FROM employees +| inlinestats max = max(salary) WHERE first_name IS NULL +| sort emp_no +| keep max, first_name +| limit 10 +; + + max:integer |first_name:keyword +70011 |Georgi +70011 |Bezalel +70011 |Parto +70011 |Chirstian +70011 |Kyoichi +70011 |Anneke +70011 |Tzvetan +70011 |Saniya +70011 |Sumant +70011 |Duangkaew +; + +groupingFilterIsAlwaysTrue +required_capability: inlinestats_v10 +FROM employees +| MV_EXPAND job_positions +| inlinestats max = max(salary) WHERE salary > 0 BY job_positions = SUBSTRING(job_positions, 1, 1) +| SORT job_positions, emp_no +| LIMIT 4 +| keep max, salary, first_name, job_positions, emp_no +; + + max:integer |salary:integer |first_name:keyword| job_positions:keyword | emp_no:integer +74970 |57305 |Georgi |A |10001 +74970 |45797 |Duangkaew |A |10010 +74970 |31120 |Mary |A |10011 +74970 |48942 |Patricio |A |10012 +; + + +groupingFilterIsAlwaysFalse +required_capability: inlinestats_v10 +FROM employees +| MV_EXPAND job_positions +| inlinestats max = max(salary) WHERE first_name == "" BY job_positions = SUBSTRING(job_positions, 1, 1) +| SORT job_positions, emp_no +| LIMIT 4 +| keep max, salary, first_name, job_positions, emp_no +; + + max:integer |salary:integer |first_name:keyword| job_positions:keyword | emp_no:integer +null |57305 |Georgi |A |10001 +null |45797 |Duangkaew |A |10010 +null |31120 |Mary |A |10011 +null |48942 |Patricio |A |10012 +; + + +groupingFilterSometimesMatches +required_capability: inlinestats_v10 +FROM employees +| MV_EXPAND job_positions +| inlinestats max = max(salary) WHERE first_name IS NULL BY job_positions = SUBSTRING(job_positions, 1, 1) +| SORT job_positions, emp_no +| LIMIT 4 +| keep max, salary, first_name, job_positions, emp_no +; + + max:integer |salary:integer |first_name:keyword| job_positions:keyword | emp_no:integer +62233 |57305 |Georgi |A |10001 +62233 |45797 |Duangkaew |A |10010 +62233 |31120 |Mary |A |10011 +62233 |48942 |Patricio |A |10012 +; + +groupingByOrdinalsFilterIsAlwaysTrue +required_capability: inlinestats_v10 +FROM employees +| inlinestats max = max(salary) WHERE salary > 0 BY job_positions +| SORT job_positions ASC, salary DESC +| LIMIT 4 +| keep max, salary, job_positions, emp_no +; + + max:integer |salary:integer | job_positions:keyword | emp_no:integer +[74970, 74970, 74999, 74970]|74970 |[Accountant, Junior Developer, Principal Support Engineer, Purchase Manager]|10045 +[74970, 74999, 74999] |66817 |[Accountant, Principal Support Engineer, Senior Python Developer] |10094 +[74970, 74970, 65030, 71165]|61358 |[Accountant, Purchase Manager, Python Developer, Reporting Analyst] |10016 +[74970, 58121, 74970] |58121 |[Accountant, Business Analyst, Purchase Manager] |10051 +; + +groupingByOrdinalsFilterIsAlwaysFalse +required_capability: inlinestats_v10 + +FROM employees +| inlinestats max = max(salary) WHERE first_name == "" BY job_positions +| SORT job_positions DESC NULLS last, emp_no +| LIMIT 4 +| keep first_name, salary, emp_no, job_positions, max +; + +first_name:s |salary:i | emp_no:i | job_positions:s | max:i +Chirstian |36174 |10004 |[Head Human Resources, Reporting Analyst, Support Engineer, Tech Lead]|null +Anneke |60335 |10006 |[Principal Support Engineer, Senior Team Lead, Tech Lead] |null +Duangkaew |45797 |10010 |[Architect, Purchase Manager, Reporting Analyst, Tech Lead] |null +Mary |31120 |10011 |[Architect, Reporting Analyst, Senior Team Lead, Tech Lead] |null +; + +groupingByOrdinalsFilterSometimesMatches +required_capability: inlinestats_v10 + +FROM employees +| keep salary, first_name, job_positions, emp_no +| inlinestats max = max(salary) WHERE first_name IS NULL BY job_positions +| SORT emp_no +| LIMIT 4 +; + + salary:i |first_name:s | emp_no:i | max:i | job_positions:s +57305 |Georgi |10001 |[39878, 62233] |[Accountant, Senior Python Developer] +56371 |Bezalel |10002 |67492 |Senior Team Lead +61805 |Parto |10003 |70011 |null +36174 |Chirstian |10004 |[35222, 67492] |[Head Human Resources, Reporting Analyst, Support Engineer, Tech Lead] +; + +stdDevFilter +required_capability: inlinestats_v10 +FROM employees +| inlinestats greater_than = STD_DEV(salary_change) WHERE languages > 3 +, less_than = STD_DEV(salary_change) WHERE languages <= 3 +, salary = STD_DEV(salary * 2) +, count = COUNT(*) BY gender +| keep emp_no, gender, languages, *than, salary, count +| SORT emp_no asc +| limit 10 +; + + emp_no:integer|gender:keyword |languages:integer|greater_than:double|less_than:double | salary:double | count:long +10001 |M |2 |6.975232333891946 |6.604807075547775|26171.331109641273|57 +10002 |F |5 |6.4543266953142835 |7.57786788789264 |29045.770666969744|33 +10003 |M |4 |6.975232333891946 |6.604807075547775|26171.331109641273|57 +10004 |M |5 |6.975232333891946 |6.604807075547775|26171.331109641273|57 +10005 |M |1 |6.975232333891946 |6.604807075547775|26171.331109641273|57 +10006 |F |3 |6.4543266953142835 |7.57786788789264 |29045.770666969744|33 +10007 |F |4 |6.4543266953142835 |7.57786788789264 |29045.770666969744|33 +10008 |M |2 |6.975232333891946 |6.604807075547775|26171.331109641273|57 +10009 |F |1 |6.4543266953142835 |7.57786788789264 |29045.770666969744|33 +10010 |null |4 |6.949207097931448 |7.127229475750027|27921.220736207077|10 +; + +twoConsecutiveInlinestatsWithFalseFilters +required_capability: inlinestats_v10 +from employees +| keep emp_no +| sort emp_no +| limit 3 +| inlinestats count = count(*) where false +| inlinestats cc = count_distinct(emp_no) where false +; + + emp_no:i | count:l | cc:l +10001 |0 |0 +10002 |0 |0 +10003 |0 |0 +; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 94c6c415ed952..871e24f69470b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -972,7 +972,7 @@ public enum Cap { * Fixes a series of issues with inlinestats which had an incomplete implementation after lookup and inlinestats * were refactored. */ - INLINESTATS_V9(EsqlPlugin.INLINESTATS_FEATURE_FLAG), + INLINESTATS_V10(EsqlPlugin.INLINESTATS_FEATURE_FLAG), /** * Support partial_results diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/ReplaceStatsFilteredAggWithEval.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/ReplaceStatsFilteredAggWithEval.java index a7e56a5f25fc8..fcc59b4268348 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/ReplaceStatsFilteredAggWithEval.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/ReplaceStatsFilteredAggWithEval.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.expression.NamedExpression; import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.util.Holder; import org.elasticsearch.xpack.esql.expression.function.aggregate.AggregateFunction; import org.elasticsearch.xpack.esql.expression.function.aggregate.Count; import org.elasticsearch.xpack.esql.expression.function.aggregate.CountDistinct; @@ -21,6 +22,7 @@ import org.elasticsearch.xpack.esql.plan.logical.Eval; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.logical.Project; +import org.elasticsearch.xpack.esql.plan.logical.join.InlineJoin; import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation; import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier; import org.elasticsearch.xpack.esql.planner.PlannerUtils; @@ -31,49 +33,101 @@ /** * Replaces an aggregation function having a false/null filter with an EVAL node. *
- *     ... | STATS x = someAgg(y) WHERE FALSE {BY z} | ...
+ *     ... | STATS/INLINESTATS x = someAgg(y) WHERE FALSE {BY z} | ...
  *     =>
- *     ... | STATS x = someAgg(y) {BY z} > | EVAL x = NULL | KEEP x{, z} | ...
+ *     ... | STATS/INLINESTATS x = someAgg(y) {BY z} > | EVAL x = NULL | KEEP x{, z} | ...
  * 
+ * + * This rule is applied to both STATS' {@link Aggregate} and {@link InlineJoin} right-hand side {@link Aggregate} plans. + * The logic is common for both, but the handling of the {@link InlineJoin} is slightly different when it comes to pruning + * its right-hand side {@link Aggregate}. */ -public class ReplaceStatsFilteredAggWithEval extends OptimizerRules.OptimizerRule { +public class ReplaceStatsFilteredAggWithEval extends OptimizerRules.OptimizerRule { @Override - protected LogicalPlan rule(Aggregate aggregate) { - int oldAggSize = aggregate.aggregates().size(); - List newAggs = new ArrayList<>(oldAggSize); - List newEvals = new ArrayList<>(oldAggSize); - List newProjections = new ArrayList<>(oldAggSize); + protected LogicalPlan rule(LogicalPlan plan) { + Aggregate aggregate; + InlineJoin ij = null; + if (plan instanceof Aggregate a) { + aggregate = a; + } else if (plan instanceof InlineJoin inlineJoin) { + ij = inlineJoin; + Holder aggHolder = new Holder<>(); + inlineJoin.right().forEachDown(Aggregate.class, aggHolder::setIfAbsent); + aggregate = aggHolder.get(); + } else { + return plan; // not an Aggregate or InlineJoin, nothing to do + } + + if (aggregate != null) { + int oldAggSize = aggregate.aggregates().size(); + List newAggs = new ArrayList<>(oldAggSize); + List newEvals = new ArrayList<>(oldAggSize); + List newProjections = new ArrayList<>(oldAggSize); - for (var ne : aggregate.aggregates()) { - if (ne instanceof Alias alias - && alias.child() instanceof AggregateFunction aggFunction - && aggFunction.hasFilter() - && aggFunction.filter() instanceof Literal literal - && Boolean.FALSE.equals(literal.value())) { + for (var ne : aggregate.aggregates()) { + if (ne instanceof Alias alias + && alias.child() instanceof AggregateFunction aggFunction + && aggFunction.hasFilter() + && aggFunction.filter() instanceof Literal literal + && Boolean.FALSE.equals(literal.value())) { - Object value = aggFunction instanceof Count || aggFunction instanceof CountDistinct ? 0L : null; - Alias newAlias = alias.replaceChild(Literal.of(aggFunction, value)); - newEvals.add(newAlias); - newProjections.add(newAlias.toAttribute()); - } else { - newAggs.add(ne); // agg function unchanged or grouping key - newProjections.add(ne.toAttribute()); + Object value = aggFunction instanceof Count || aggFunction instanceof CountDistinct ? 0L : null; + Alias newAlias = alias.replaceChild(Literal.of(aggFunction, value)); + newEvals.add(newAlias); + newProjections.add(newAlias.toAttribute()); + } else { + newAggs.add(ne); // agg function unchanged or grouping key + newProjections.add(ne.toAttribute()); + } } - } - LogicalPlan plan = aggregate; - if (newEvals.isEmpty() == false) { - if (newAggs.isEmpty()) { // the Aggregate node is pruned - plan = localRelation(aggregate.source(), newEvals); - } else { - plan = aggregate.with(aggregate.child(), aggregate.groupings(), newAggs); - plan = new Eval(aggregate.source(), plan, newEvals); - plan = new Project(aggregate.source(), plan, newProjections); + if (newEvals.isEmpty() == false) { + if (newAggs.isEmpty()) { // the Aggregate node is pruned + if (ij != null) { // this is an Aggregate part of right-hand side of an InlineJoin + final LogicalPlan leftHandSide = ij.left(); // final so we can use it in the lambda below + // the aggregate becomes a simple Eval since it's not needed anymore (it was replaced with Literals) + var newRight = ij.right() + .transformDown( + Aggregate.class, + agg -> agg == aggregate ? new Eval(aggregate.source(), aggregate.child(), newEvals) : agg + ); + // Remove the StubRelation since the right-hand side of the join is now part of the main plan + // and it won't be executed separately by the EsqlSession inlinestats planning. + newRight = InlineJoin.replaceStub(leftHandSide, newRight); + + // project the correct output (the one of the former inlinejoin) and remove the InlineJoin altogether, + // replacing it with its right-hand side followed by its left-hand side + plan = new Project(ij.source(), newRight, ij.output()); + } else { // this is a standalone Aggregate + plan = localRelation(aggregate.source(), newEvals); + } + } else { + if (ij != null) { // this is an Aggregate part of right-hand side of an InlineJoin + plan = ij.replaceRight( + ij.right().transformUp(Aggregate.class, agg -> updateAggregate(agg, newAggs, newEvals, newProjections)) + ); + } else { // this is a standalone Aggregate + plan = updateAggregate(aggregate, newAggs, newEvals, newProjections); + } + } } } return plan; } + private static LogicalPlan updateAggregate( + Aggregate agg, + List newAggs, + List newEvals, + List newProjections + ) { + // only update the Aggregate and add an Eval for the removed aggregations + LogicalPlan newAgg = agg.with(agg.child(), agg.groupings(), newAggs); + newAgg = new Eval(agg.source(), newAgg, newEvals); + newAgg = new Project(agg.source(), newAgg, newProjections); + return newAgg; + } + private static LocalRelation localRelation(Source source, List newEvals) { Block[] blocks = new Block[newEvals.size()]; List attributes = new ArrayList<>(newEvals.size()); @@ -83,6 +137,5 @@ private static LocalRelation localRelation(Source source, List newEvals) blocks[i] = BlockUtils.constantBlock(PlannerUtils.NON_BREAKING_BLOCK_FACTORY, ((Literal) alias.child()).value(), 1); } return new LocalRelation(source, attributes, LocalSupplier.of(blocks)); - } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/join/InlineJoin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/join/InlineJoin.java index 653722ec131f8..1c3f341321f6f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/join/InlineJoin.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/join/InlineJoin.java @@ -77,20 +77,31 @@ public static LogicalPlan inlineData(InlineJoin target, LocalRelation data) { /** * Replaces the stubbed source with the actual source. - * NOTE: this will replace all {@link StubRelation}s found with the source and the method is meant to be used to replace one node only - * when being called on a plan that has only ONE StubRelation in it. + * NOTE: this will replace the first {@link StubRelation}s found with the source and the method is meant to be used to replace one node + * only when being called on a plan that has only ONE StubRelation in it. */ - public static LogicalPlan replaceStub(LogicalPlan source, LogicalPlan stubbed) { + public static LogicalPlan replaceStub(LogicalPlan stubReplacement, LogicalPlan stubbedPlan) { // here we could have used stubbed.transformUp(StubRelation.class, stubRelation -> source) // but transformUp skips changing a node if its transformed variant is equal to its original variant. // A StubRelation can contain in its output ReferenceAttributes which do not use NameIds for equality, but only names and // two ReferenceAttributes with the same name are equal and the transformation will not be applied. - return stubbed.transformUp(UnaryPlan.class, up -> { + Holder doneReplacing = new Holder<>(false); + var result = stubbedPlan.transformUp(UnaryPlan.class, up -> { if (up.child() instanceof StubRelation) { - return up.replaceChild(source); + if (doneReplacing.get() == false) { + doneReplacing.set(true); + return up.replaceChild(stubReplacement); + } + throw new IllegalStateException("Expected to replace a single StubRelation in the plan, but found more than one"); } return up; }); + + if (doneReplacing.get() == false) { + throw new IllegalStateException("Expected to replace a single StubRelation in the plan, but none found"); + } + + return result; } /** @@ -161,7 +172,8 @@ private static InlineJoin readFrom(StreamInput in) throws IOException { LogicalPlan left = in.readNamedWriteable(LogicalPlan.class); LogicalPlan right = in.readNamedWriteable(LogicalPlan.class); JoinConfig config = new JoinConfig(in); - return new InlineJoin(source, left, replaceStub(left, right), config); + // return new InlineJoin(source, left, replaceStub(left, right), config); + return new InlineJoin(source, left, right, config); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index d72a97647c110..a90ca7c99cc9a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -4236,7 +4236,7 @@ public void testGroupingOverridesInStats() { } public void testGroupingOverridesInInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); verifyUnsupported(""" from test | inlinestats MIN(salary) BY x = languages, x = x + 1 diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java index b6d66d673ea7a..fff9abe0ca2f0 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java @@ -12,8 +12,6 @@ import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.compute.aggregation.QuantileStates; -import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.LongVectorBlock; import org.elasticsearch.compute.test.TestBlockFactory; import org.elasticsearch.core.Tuple; import org.elasticsearch.dissect.DissectParser; @@ -176,7 +174,6 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT; import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; -import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.type.DataType.TEXT; import static org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison.BinaryComparisonOperation.EQ; import static org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison.BinaryComparisonOperation.GT; @@ -199,7 +196,6 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.startsWith; //@TestLogging(value = "org.elasticsearch.xpack.esql:TRACE", reason = "debug") @@ -536,349 +532,6 @@ public void testStatsWithFilteringDefaultAliasing() { assertThat(Expressions.names(agg.aggregates()), contains("sum(salary)", "sum(salary) WheRe last_name == \"Doe\"")); } - /** - *
{@code
-     * Limit[1000[INTEGER]]
-     * \_LocalRelation[[sum(salary) where false{r}#26],[ConstantNullBlock[positions=1]]]
-     * }
- */ - public void testReplaceStatsFilteredAggWithEvalSingleAgg() { - var plan = plan(""" - from test - | stats sum(salary) where false - """); - - var project = as(plan, Limit.class); - var source = as(project.child(), LocalRelation.class); - assertThat(Expressions.names(source.output()), contains("sum(salary) where false")); - Block[] blocks = source.supplier().get(); - assertThat(blocks.length, is(1)); - assertThat(blocks[0].getPositionCount(), is(1)); - assertTrue(blocks[0].areAllValuesNull()); - } - - /** - *
{@code
-     * Project[[sum(salary) + 1 where false{r}#68]]
-     * \_Eval[[$$SUM$sum(salary)_+_1$0{r$}#79 + 1[INTEGER] AS sum(salary) + 1 where false]]
-     *   \_Limit[1000[INTEGER]]
-     *     \_LocalRelation[[$$SUM$sum(salary)_+_1$0{r$}#79],[ConstantNullBlock[positions=1]]]
-     * }
- */ - public void testReplaceStatsFilteredAggWithEvalSingleAggWithExpression() { - var plan = plan(""" - from test - | stats sum(salary) + 1 where false - """); - - var project = as(plan, Project.class); - assertThat(Expressions.names(project.projections()), contains("sum(salary) + 1 where false")); - - var eval = as(project.child(), Eval.class); - assertThat(eval.fields().size(), is(1)); - var alias = as(eval.fields().getFirst(), Alias.class); - assertThat(alias.name(), is("sum(salary) + 1 where false")); - var add = as(alias.child(), Add.class); - var literal = as(add.right(), Literal.class); - assertThat(literal.value(), is(1)); - - var limit = as(eval.child(), Limit.class); - var source = as(limit.child(), LocalRelation.class); - - Block[] blocks = source.supplier().get(); - assertThat(blocks.length, is(1)); - assertThat(blocks[0].getPositionCount(), is(1)); - assertTrue(blocks[0].areAllValuesNull()); - } - - /** - *
{@code
-     * Project[[sum(salary) + 1 where false{r}#4, sum(salary) + 2{r}#6, emp_no{f}#7]]
-     * \_Eval[[null[LONG] AS sum(salary) + 1 where false, $$SUM$sum(salary)_+_2$1{r$}#18 + 2[INTEGER] AS sum(salary) + 2]]
-     *   \_Limit[1000[INTEGER]]
-     *     \_Aggregate[STANDARD,[emp_no{f}#7],[SUM(salary{f}#12,true[BOOLEAN]) AS $$SUM$sum(salary)_+_2$1, emp_no{f}#7]]
-     *       \_EsRelation[test][_meta_field{f}#13, emp_no{f}#7, first_name{f}#8, ge..]
-     * }
- */ - public void testReplaceStatsFilteredAggWithEvalMixedFilterAndNoFilter() { - var plan = plan(""" - from test - | stats sum(salary) + 1 where false, - sum(salary) + 2 - by emp_no - """); - - var project = as(plan, Project.class); - assertThat(Expressions.names(project.projections()), contains("sum(salary) + 1 where false", "sum(salary) + 2", "emp_no")); - var eval = as(project.child(), Eval.class); - assertThat(eval.fields().size(), is(2)); - - var alias = as(eval.fields().getFirst(), Alias.class); - assertTrue(alias.child().foldable()); - assertThat(alias.child().fold(FoldContext.small()), nullValue()); - assertThat(alias.child().dataType(), is(LONG)); - - alias = as(eval.fields().getLast(), Alias.class); - assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 2")); - - var limit = as(eval.child(), Limit.class); - var aggregate = as(limit.child(), Aggregate.class); - var source = as(aggregate.child(), EsRelation.class); - } - - /** - *
{@code
-     * Project[[sum(salary) + 1 where false{r}#3, sum(salary) + 3{r}#5, sum(salary) + 2 where null{r}#7,
-     * sum(salary) + 4 where not true{r}#9]]
-     * \_Eval[[null[LONG] AS sum(salary) + 1 where false#3, $$SUM$sum(salary)_+_3$1{r$}#22 + 3[INTEGER] AS sum(salary) + 3#5
-     * , null[LONG] AS sum(salary) + 2 where null#7, null[LONG] AS sum(salary) + 4 where not true#9]]
-     *   \_Limit[1000[INTEGER],false]
-     *     \_Aggregate[[],[SUM(salary{f}#15,true[BOOLEAN],compensated[KEYWORD]) AS $$SUM$sum(salary)_+_3$1#22]]
-     *       \_EsRelation[test][_meta_field{f}#16, emp_no{f}#10, first_name{f}#11, ..]
-     * }
- * - */ - public void testReplaceStatsFilteredAggWithEvalFilterFalseAndNull() { - var plan = plan(""" - from test - | stats sum(salary) + 1 where false, - sum(salary) + 3, - sum(salary) + 2 where null, - sum(salary) + 4 where not true - """); - - var project = as(plan, Project.class); - assertThat( - Expressions.names(project.projections()), - contains("sum(salary) + 1 where false", "sum(salary) + 3", "sum(salary) + 2 where null", "sum(salary) + 4 where not true") - ); - var eval = as(project.child(), Eval.class); - assertThat(eval.fields().size(), is(4)); - - var alias = as(eval.fields().getFirst(), Alias.class); - assertTrue(alias.child().foldable()); - assertThat(alias.child().fold(FoldContext.small()), nullValue()); - assertThat(alias.child().dataType(), is(LONG)); - - alias = as(eval.fields().get(0), Alias.class); - assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 1")); - - alias = as(eval.fields().get(1), Alias.class); - assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 3")); - - alias = as(eval.fields().get(2), Alias.class); - assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 2")); - - alias = as(eval.fields().get(3), Alias.class); - assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 4")); - - alias = as(eval.fields().getLast(), Alias.class); - assertTrue(alias.child().foldable()); - assertThat(alias.child().fold(FoldContext.small()), nullValue()); - assertThat(alias.child().dataType(), is(LONG)); - - var limit = as(eval.child(), Limit.class); - var aggregate = as(limit.child(), Aggregate.class); - var source = as(aggregate.child(), EsRelation.class); - } - - /* - * Limit[1000[INTEGER]] - * \_LocalRelation[[count(salary) where false{r}#3],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]] - */ - public void testReplaceStatsFilteredAggWithEvalNotTrue() { - var plan = plan(""" - from test - | stats count(salary) where not true - """); - - var limit = as(plan, Limit.class); - var source = as(limit.child(), LocalRelation.class); - assertThat(Expressions.names(source.output()), contains("count(salary) where not true")); - Block[] blocks = source.supplier().get(); - assertThat(blocks.length, is(1)); - var block = as(blocks[0], LongVectorBlock.class); - assertThat(block.getPositionCount(), is(1)); - assertThat(block.asVector().getLong(0), is(0L)); - } - - /* - * Limit[1000[INTEGER],false] - * \_Aggregate[[],[COUNT(salary{f}#10,true[BOOLEAN]) AS m1#4]] - * \_EsRelation[test][_meta_field{f}#11, emp_no{f}#5, first_name{f}#6, ge..] - */ - public void testReplaceStatsFilteredAggWithEvalNotFalse() { - var plan = plan(""" - from test - | stats m1 = count(salary) where not false - """); - var limit = as(plan, Limit.class); - var aggregate = as(limit.child(), Aggregate.class); - assertEquals(1, aggregate.aggregates().size()); - assertEquals(1, aggregate.aggregates().get(0).children().size()); - assertTrue(aggregate.aggregates().get(0).children().get(0) instanceof Count); - assertEquals("true", (((Count) aggregate.aggregates().get(0).children().get(0)).filter().toString())); - assertThat(Expressions.names(aggregate.aggregates()), contains("m1")); - var source = as(aggregate.child(), EsRelation.class); - } - - /** - *
{@code
-     * Limit[1000[INTEGER]]
-     * \_LocalRelation[[count(salary) where false{r}#3],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]]
-     * }
- */ - public void testReplaceStatsFilteredAggWithEvalCount() { - var plan = plan(""" - from test - | stats count(salary) where false - """); - - var limit = as(plan, Limit.class); - var source = as(limit.child(), LocalRelation.class); - assertThat(Expressions.names(source.output()), contains("count(salary) where false")); - Block[] blocks = source.supplier().get(); - assertThat(blocks.length, is(1)); - var block = as(blocks[0], LongVectorBlock.class); - assertThat(block.getPositionCount(), is(1)); - assertThat(block.asVector().getLong(0), is(0L)); - } - - /** - *
{@code
-     * Project[[count_distinct(salary + 2) + 3 where false{r}#3]]
-     * \_Eval[[$$COUNTDISTINCT$count_distinct(>$0{r$}#15 + 3[INTEGER] AS count_distinct(salary + 2) + 3 where false]]
-     *   \_Limit[1000[INTEGER]]
-     *     \_LocalRelation[[$$COUNTDISTINCT$count_distinct(>$0{r$}#15],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]]
-     * }
- */ - public void testReplaceStatsFilteredAggWithEvalCountDistinctInExpression() { - var plan = plan(""" - from test - | stats count_distinct(salary + 2) + 3 where false - """); - - var project = as(plan, Project.class); - assertThat(Expressions.names(project.projections()), contains("count_distinct(salary + 2) + 3 where false")); - - var eval = as(project.child(), Eval.class); - assertThat(eval.fields().size(), is(1)); - var alias = as(eval.fields().getFirst(), Alias.class); - assertThat(alias.name(), is("count_distinct(salary + 2) + 3 where false")); - var add = as(alias.child(), Add.class); - var literal = as(add.right(), Literal.class); - assertThat(literal.value(), is(3)); - - var limit = as(eval.child(), Limit.class); - var source = as(limit.child(), LocalRelation.class); - - Block[] blocks = source.supplier().get(); - assertThat(blocks.length, is(1)); - var block = as(blocks[0], LongVectorBlock.class); - assertThat(block.getPositionCount(), is(1)); - assertThat(block.asVector().getLong(0), is(0L)); - } - - /** - *
{@code
-     * Project[[max{r}#91, max_a{r}#94, min{r}#97, min_a{r}#100, emp_no{f}#101]]
-     * \_Eval[[null[INTEGER] AS max_a, null[INTEGER] AS min_a]]
-     *   \_Limit[1000[INTEGER]]
-     *     \_Aggregate[STANDARD,[emp_no{f}#101],[MAX(salary{f}#106,true[BOOLEAN]) AS max, MIN(salary{f}#106,true[BOOLEAN]) AS min, emp_
-     * no{f}#101]]
-     *       \_EsRelation[test][_meta_field{f}#107, emp_no{f}#101, first_name{f}#10..]
-     * }
- */ - public void testReplaceStatsFilteredAggWithEvalSameAggWithAndWithoutFilter() { - var plan = plan(""" - from test - | stats max = max(salary), max_a = max(salary) where null, - min = min(salary), min_a = min(salary) where to_string(null) == "abc" - by emp_no - """); - - var project = as(plan, Project.class); - assertThat(Expressions.names(project.projections()), contains("max", "max_a", "min", "min_a", "emp_no")); - var eval = as(project.child(), Eval.class); - assertThat(eval.fields().size(), is(2)); - - var alias = as(eval.fields().getFirst(), Alias.class); - assertThat(Expressions.name(alias), containsString("max_a")); - assertTrue(alias.child().foldable()); - assertThat(alias.child().fold(FoldContext.small()), nullValue()); - assertThat(alias.child().dataType(), is(INTEGER)); - - alias = as(eval.fields().getLast(), Alias.class); - assertThat(Expressions.name(alias), containsString("min_a")); - assertTrue(alias.child().foldable()); - assertThat(alias.child().fold(FoldContext.small()), nullValue()); - assertThat(alias.child().dataType(), is(INTEGER)); - - var limit = as(eval.child(), Limit.class); - - var aggregate = as(limit.child(), Aggregate.class); - assertThat(Expressions.names(aggregate.aggregates()), contains("max", "min", "emp_no")); - - var source = as(aggregate.child(), EsRelation.class); - } - - /** - *
{@code
-     * Limit[1000[INTEGER]]
-     * \_LocalRelation[[count{r}#7],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]]
-     * }
- */ - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/100634") // i.e. PropagateEvalFoldables applicability to Aggs - public void testReplaceStatsFilteredAggWithEvalFilterUsingEvaledValue() { - var plan = plan(""" - from test - | eval my_length = length(concat(first_name, null)) - | stats count = count(my_length) where my_length > 0 - """); - - var limit = as(plan, Limit.class); - var source = as(limit.child(), LocalRelation.class); - assertThat(Expressions.names(source.output()), contains("count")); - Block[] blocks = source.supplier().get(); - assertThat(blocks.length, is(1)); - var block = as(blocks[0], LongVectorBlock.class); - assertThat(block.getPositionCount(), is(1)); - assertThat(block.asVector().getLong(0), is(0L)); - } - - /** - * - *
{@code
-     * Project[[c{r}#67, emp_no{f}#68]]
-     * \_Eval[[0[LONG] AS c]]
-     *   \_Limit[1000[INTEGER]]
-     *     \_Aggregate[STANDARD,[emp_no{f}#68],[emp_no{f}#68]]
-     *       \_EsRelation[test][_meta_field{f}#74, emp_no{f}#68, first_name{f}#69, ..]
-     * }
- */ - public void testReplaceStatsFilteredAggWithEvalSingleAggWithGroup() { - var plan = plan(""" - from test - | stats c = count(emp_no) where false - by emp_no - """); - - var project = as(plan, Project.class); - assertThat(Expressions.names(project.projections()), contains("c", "emp_no")); - - var eval = as(project.child(), Eval.class); - assertThat(eval.fields().size(), is(1)); - var alias = as(eval.fields().getFirst(), Alias.class); - assertThat(Expressions.name(alias), containsString("c")); - - var limit = as(eval.child(), Limit.class); - - var aggregate = as(limit.child(), Aggregate.class); - assertThat(Expressions.names(aggregate.aggregates()), contains("emp_no")); - - var source = as(aggregate.child(), EsRelation.class); - } - public void testExtractStatsCommonFilter() { var plan = plan(""" from test diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java index 3b183c803ecd1..b9a469edae907 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java @@ -83,7 +83,7 @@ public static void init() { * \_StubRelation[[emp_no{f}#11, languages{f}#14, gender{f}#13, y{r}#10]] */ public void testGroupingAliasingMoved_To_LeftSideOfJoin() { - assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); var plan = plan(""" from test | keep emp_no, languages, gender @@ -126,7 +126,7 @@ public void testGroupingAliasingMoved_To_LeftSideOfJoin() { * {r}#21]] */ public void testGroupingAliasingMoved_To_LeftSideOfJoin_WithExpression() { - assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); var plan = plan(""" from test | keep emp_no, languages, gender, last_name, first_name diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/ReplaceStatsFilteredAggWithEvalTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/ReplaceStatsFilteredAggWithEvalTests.java new file mode 100644 index 0000000000000..464c12a81f97d --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/ReplaceStatsFilteredAggWithEvalTests.java @@ -0,0 +1,774 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.optimizer.rules.logical; + +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.LongVectorBlock; +import org.elasticsearch.xpack.esql.core.expression.Alias; +import org.elasticsearch.xpack.esql.core.expression.Expressions; +import org.elasticsearch.xpack.esql.core.expression.FoldContext; +import org.elasticsearch.xpack.esql.core.expression.Literal; +import org.elasticsearch.xpack.esql.expression.function.aggregate.Count; +import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add; +import org.elasticsearch.xpack.esql.optimizer.AbstractLogicalPlanOptimizerTests; +import org.elasticsearch.xpack.esql.plan.logical.Aggregate; +import org.elasticsearch.xpack.esql.plan.logical.EsRelation; +import org.elasticsearch.xpack.esql.plan.logical.Eval; +import org.elasticsearch.xpack.esql.plan.logical.Limit; +import org.elasticsearch.xpack.esql.plan.logical.Project; +import org.elasticsearch.xpack.esql.plan.logical.TopN; +import org.elasticsearch.xpack.esql.plan.logical.join.InlineJoin; +import org.elasticsearch.xpack.esql.plan.logical.join.StubRelation; +import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject; +import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation; + +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.as; +import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; +import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; + +public class ReplaceStatsFilteredAggWithEvalTests extends AbstractLogicalPlanOptimizerTests { + + /** + *
{@code
+     * Limit[1000[INTEGER]]
+     * \_LocalRelation[[sum(salary) where false{r}#26],[ConstantNullBlock[positions=1]]]
+     * }
+ */ + public void testReplaceStatsFilteredAggWithEvalSingleAgg() { + var plan = plan(""" + from test + | stats sum(salary) where false + """); + + var project = as(plan, Limit.class); + var source = as(project.child(), LocalRelation.class); + assertThat(Expressions.names(source.output()), contains("sum(salary) where false")); + Block[] blocks = source.supplier().get(); + assertThat(blocks.length, is(1)); + assertThat(blocks[0].getPositionCount(), is(1)); + assertTrue(blocks[0].areAllValuesNull()); + } + + /** + *
{@code
+     * Project[[sum(salary) + 1 where false{r}#68]]
+     * \_Eval[[$$SUM$sum(salary)_+_1$0{r$}#79 + 1[INTEGER] AS sum(salary) + 1 where false]]
+     *   \_Limit[1000[INTEGER]]
+     *     \_LocalRelation[[$$SUM$sum(salary)_+_1$0{r$}#79],[ConstantNullBlock[positions=1]]]
+     * }
+ */ + public void testReplaceStatsFilteredAggWithEvalSingleAggWithExpression() { + var plan = plan(""" + from test + | stats sum(salary) + 1 where false + """); + + var project = as(plan, Project.class); + assertThat(Expressions.names(project.projections()), contains("sum(salary) + 1 where false")); + + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(alias.name(), is("sum(salary) + 1 where false")); + var add = as(alias.child(), Add.class); + var literal = as(add.right(), Literal.class); + assertThat(literal.value(), is(1)); + + var limit = as(eval.child(), Limit.class); + var source = as(limit.child(), LocalRelation.class); + + Block[] blocks = source.supplier().get(); + assertThat(blocks.length, is(1)); + assertThat(blocks[0].getPositionCount(), is(1)); + assertTrue(blocks[0].areAllValuesNull()); + } + + /** + *
{@code
+     * Project[[sum(salary) + 1 where false{r}#4, sum(salary) + 2{r}#6, emp_no{f}#7]]
+     * \_Eval[[null[LONG] AS sum(salary) + 1 where false, $$SUM$sum(salary)_+_2$1{r$}#18 + 2[INTEGER] AS sum(salary) + 2]]
+     *   \_Limit[1000[INTEGER]]
+     *     \_Aggregate[STANDARD,[emp_no{f}#7],[SUM(salary{f}#12,true[BOOLEAN]) AS $$SUM$sum(salary)_+_2$1, emp_no{f}#7]]
+     *       \_EsRelation[test][_meta_field{f}#13, emp_no{f}#7, first_name{f}#8, ge..]
+     * }
+ */ + public void testReplaceStatsFilteredAggWithEvalMixedFilterAndNoFilter() { + var plan = plan(""" + from test + | stats sum(salary) + 1 where false, + sum(salary) + 2 + by emp_no + """); + + var project = as(plan, Project.class); + assertThat(Expressions.names(project.projections()), contains("sum(salary) + 1 where false", "sum(salary) + 2", "emp_no")); + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(2)); + + var alias = as(eval.fields().getFirst(), Alias.class); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(LONG)); + + alias = as(eval.fields().getLast(), Alias.class); + assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 2")); + + var limit = as(eval.child(), Limit.class); + var aggregate = as(limit.child(), Aggregate.class); + var source = as(aggregate.child(), EsRelation.class); + } + + /** + *
{@code
+     * Project[[sum(salary) + 1 where false{r}#3, sum(salary) + 3{r}#5, sum(salary) + 2 where null{r}#7,
+     * sum(salary) + 4 where not true{r}#9]]
+     * \_Eval[[null[LONG] AS sum(salary) + 1 where false#3, $$SUM$sum(salary)_+_3$1{r$}#22 + 3[INTEGER] AS sum(salary) + 3#5
+     * , null[LONG] AS sum(salary) + 2 where null#7, null[LONG] AS sum(salary) + 4 where not true#9]]
+     *   \_Limit[1000[INTEGER],false]
+     *     \_Aggregate[[],[SUM(salary{f}#15,true[BOOLEAN],compensated[KEYWORD]) AS $$SUM$sum(salary)_+_3$1#22]]
+     *       \_EsRelation[test][_meta_field{f}#16, emp_no{f}#10, first_name{f}#11, ..]
+     * }
+ * + */ + public void testReplaceStatsFilteredAggWithEvalFilterFalseAndNull() { + var plan = plan(""" + from test + | stats sum(salary) + 1 where false, + sum(salary) + 3, + sum(salary) + 2 where null, + sum(salary) + 4 where not true + """); + + var project = as(plan, Project.class); + assertThat( + Expressions.names(project.projections()), + contains("sum(salary) + 1 where false", "sum(salary) + 3", "sum(salary) + 2 where null", "sum(salary) + 4 where not true") + ); + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(4)); + + var alias = as(eval.fields().getFirst(), Alias.class); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(LONG)); + + alias = as(eval.fields().get(0), Alias.class); + assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 1")); + + alias = as(eval.fields().get(1), Alias.class); + assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 3")); + + alias = as(eval.fields().get(2), Alias.class); + assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 2")); + + alias = as(eval.fields().get(3), Alias.class); + assertThat(Expressions.name(alias.child()), containsString("sum(salary) + 4")); + + alias = as(eval.fields().getLast(), Alias.class); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(LONG)); + + var limit = as(eval.child(), Limit.class); + var aggregate = as(limit.child(), Aggregate.class); + var source = as(aggregate.child(), EsRelation.class); + } + + /* + * Limit[1000[INTEGER]] + * \_LocalRelation[[count(salary) where false{r}#3],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]] + */ + public void testReplaceStatsFilteredAggWithEvalNotTrue() { + var plan = plan(""" + from test + | stats count(salary) where not true + """); + + var limit = as(plan, Limit.class); + var source = as(limit.child(), LocalRelation.class); + assertThat(Expressions.names(source.output()), contains("count(salary) where not true")); + Block[] blocks = source.supplier().get(); + assertThat(blocks.length, is(1)); + var block = as(blocks[0], LongVectorBlock.class); + assertThat(block.getPositionCount(), is(1)); + assertThat(block.asVector().getLong(0), is(0L)); + } + + /* + * Limit[1000[INTEGER],false] + * \_Aggregate[[],[COUNT(salary{f}#10,true[BOOLEAN]) AS m1#4]] + * \_EsRelation[test][_meta_field{f}#11, emp_no{f}#5, first_name{f}#6, ge..] + */ + public void testReplaceStatsFilteredAggWithEvalNotFalse() { + var plan = plan(""" + from test + | stats m1 = count(salary) where not false + """); + var limit = as(plan, Limit.class); + var aggregate = as(limit.child(), Aggregate.class); + assertEquals(1, aggregate.aggregates().size()); + assertEquals(1, aggregate.aggregates().get(0).children().size()); + assertTrue(aggregate.aggregates().get(0).children().get(0) instanceof Count); + assertEquals("true", (((Count) aggregate.aggregates().get(0).children().get(0)).filter().toString())); + assertThat(Expressions.names(aggregate.aggregates()), contains("m1")); + var source = as(aggregate.child(), EsRelation.class); + } + + /** + *
{@code
+     * Limit[1000[INTEGER]]
+     * \_LocalRelation[[count(salary) where false{r}#3],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]]
+     * }
+ */ + public void testReplaceStatsFilteredAggWithEvalCount() { + var plan = plan(""" + from test + | stats count(salary) where false + """); + + var limit = as(plan, Limit.class); + var source = as(limit.child(), LocalRelation.class); + assertThat(Expressions.names(source.output()), contains("count(salary) where false")); + Block[] blocks = source.supplier().get(); + assertThat(blocks.length, is(1)); + var block = as(blocks[0], LongVectorBlock.class); + assertThat(block.getPositionCount(), is(1)); + assertThat(block.asVector().getLong(0), is(0L)); + } + + /** + *
{@code
+     * Project[[count_distinct(salary + 2) + 3 where false{r}#3]]
+     * \_Eval[[$$COUNTDISTINCT$count_distinct(>$0{r$}#15 + 3[INTEGER] AS count_distinct(salary + 2) + 3 where false]]
+     *   \_Limit[1000[INTEGER]]
+     *     \_LocalRelation[[$$COUNTDISTINCT$count_distinct(>$0{r$}#15],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]]
+     * }
+ */ + public void testReplaceStatsFilteredAggWithEvalCountDistinctInExpression() { + var plan = plan(""" + from test + | stats count_distinct(salary + 2) + 3 where false + """); + + var project = as(plan, Project.class); + assertThat(Expressions.names(project.projections()), contains("count_distinct(salary + 2) + 3 where false")); + + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(alias.name(), is("count_distinct(salary + 2) + 3 where false")); + var add = as(alias.child(), Add.class); + var literal = as(add.right(), Literal.class); + assertThat(literal.value(), is(3)); + + var limit = as(eval.child(), Limit.class); + var source = as(limit.child(), LocalRelation.class); + + Block[] blocks = source.supplier().get(); + assertThat(blocks.length, is(1)); + var block = as(blocks[0], LongVectorBlock.class); + assertThat(block.getPositionCount(), is(1)); + assertThat(block.asVector().getLong(0), is(0L)); + } + + /** + *
{@code
+     * Project[[max{r}#91, max_a{r}#94, min{r}#97, min_a{r}#100, emp_no{f}#101]]
+     * \_Eval[[null[INTEGER] AS max_a, null[INTEGER] AS min_a]]
+     *   \_Limit[1000[INTEGER]]
+     *     \_Aggregate[STANDARD,[emp_no{f}#101],[MAX(salary{f}#106,true[BOOLEAN]) AS max, MIN(salary{f}#106,true[BOOLEAN]) AS min, emp_
+     * no{f}#101]]
+     *       \_EsRelation[test][_meta_field{f}#107, emp_no{f}#101, first_name{f}#10..]
+     * }
+ */ + public void testReplaceStatsFilteredAggWithEvalSameAggWithAndWithoutFilter() { + var plan = plan(""" + from test + | stats max = max(salary), max_a = max(salary) where null, + min = min(salary), min_a = min(salary) where to_string(null) == "abc" + by emp_no + """); + + var project = as(plan, Project.class); + assertThat(Expressions.names(project.projections()), contains("max", "max_a", "min", "min_a", "emp_no")); + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(2)); + + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(Expressions.name(alias), containsString("max_a")); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(INTEGER)); + + alias = as(eval.fields().getLast(), Alias.class); + assertThat(Expressions.name(alias), containsString("min_a")); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(INTEGER)); + + var limit = as(eval.child(), Limit.class); + + var aggregate = as(limit.child(), Aggregate.class); + assertThat(Expressions.names(aggregate.aggregates()), contains("max", "min", "emp_no")); + + var source = as(aggregate.child(), EsRelation.class); + } + + /** + *
{@code
+     * Limit[1000[INTEGER]]
+     * \_LocalRelation[[count{r}#7],[LongVectorBlock[vector=ConstantLongVector[positions=1, value=0]]]]
+     * }
+ */ + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/100634") // i.e. PropagateEvalFoldables applicability to Aggs + public void testReplaceStatsFilteredAggWithEvalFilterUsingEvaledValue() { + var plan = plan(""" + from test + | eval my_length = length(concat(first_name, null)) + | stats count = count(my_length) where my_length > 0 + """); + + var limit = as(plan, Limit.class); + var source = as(limit.child(), LocalRelation.class); + assertThat(Expressions.names(source.output()), contains("count")); + Block[] blocks = source.supplier().get(); + assertThat(blocks.length, is(1)); + var block = as(blocks[0], LongVectorBlock.class); + assertThat(block.getPositionCount(), is(1)); + assertThat(block.asVector().getLong(0), is(0L)); + } + + /** + * + *
{@code
+     * Project[[c{r}#67, emp_no{f}#68]]
+     * \_Eval[[0[LONG] AS c]]
+     *   \_Limit[1000[INTEGER]]
+     *     \_Aggregate[STANDARD,[emp_no{f}#68],[emp_no{f}#68]]
+     *       \_EsRelation[test][_meta_field{f}#74, emp_no{f}#68, first_name{f}#69, ..]
+     * }
+ */ + public void testReplaceStatsFilteredAggWithEvalSingleAggWithGroup() { + var plan = plan(""" + from test + | stats c = count(emp_no) where false + by emp_no + """); + + var project = as(plan, Project.class); + assertThat(Expressions.names(project.projections()), contains("c", "emp_no")); + + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(Expressions.name(alias), containsString("c")); + + var limit = as(eval.child(), Limit.class); + + var aggregate = as(limit.child(), Aggregate.class); + assertThat(Expressions.names(aggregate.aggregates()), contains("emp_no")); + + var source = as(aggregate.child(), EsRelation.class); + } + + /** + * Project[[_meta_field{f}#10, emp_no{f}#4, first_name{f}#5, gender{f}#6, hire_date{f}#11, job{f}#12, job.raw{f}#13, lang + * uages{f}#7, last_name{f}#8, long_noidx{f}#14, salary{f}#9, sum(salary) where false{r}#3]] + * \_Eval[[null[LONG] AS sum(salary) where false#3]] + * \_Limit[1000[INTEGER],false] + * \_EsRelation[test][_meta_field{f}#10, emp_no{f}#4, first_name{f}#5, ge..] + */ + public void testReplaceInlinestatsFilteredAggWithEvalSingleAgg() { + var plan = plan(""" + from test + | inlinestats sum(salary) where false + """); + + var project = as(plan, Project.class); + assertMap( + Expressions.names(project.projections()).stream().map(Object::toString).toList(), + matchesList().item(startsWith("_meta_field")) + .item(startsWith("emp_no")) + .item(startsWith("first_name")) + .item(startsWith("gender")) + .item(startsWith("hire_date")) + .item(startsWith("job")) + .item(startsWith("job.raw")) + .item(startsWith("languages")) + .item(startsWith("last_name")) + .item(startsWith("long_noidx")) + .item(startsWith("salary")) + .item(containsString("sum(salary) where false")) + ); + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(Expressions.name(alias), containsString("sum(salary) where false")); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(LONG)); + var limit = as(eval.child(), Limit.class); + as(limit.child(), EsRelation.class); + } + + /** + * Project[[_meta_field{f}#10, emp_no{f}#4, first_name{f}#5, gender{f}#6, hire_date{f}#11, job{f}#12, job.raw{f}#13, lang + * uages{f}#7, last_name{f}#8, long_noidx{f}#14, salary{f}#9, sum(salary) + 1 where false{r}#3]] + * \_Eval[[null[LONG] AS sum(salary) + 1 where false#3]] + * \_Limit[1000[INTEGER],false] + * \_EsRelation[test][_meta_field{f}#10, emp_no{f}#4, first_name{f}#5, ge..] + */ + public void testReplaceInlinestatsFilteredAggWithEvalSingleAggWithExpression() { + var plan = plan(""" + from test + | inlinestats sum(salary) + 1 where false + """); + + var project = as(plan, Project.class); + assertMap( + Expressions.names(project.projections()).stream().map(Object::toString).toList(), + matchesList().item(startsWith("_meta_field")) + .item(startsWith("emp_no")) + .item(startsWith("first_name")) + .item(startsWith("gender")) + .item(startsWith("hire_date")) + .item(startsWith("job")) + .item(startsWith("job.raw")) + .item(startsWith("languages")) + .item(startsWith("last_name")) + .item(startsWith("long_noidx")) + .item(startsWith("salary")) + .item(containsString("sum(salary) + 1 where false")) + ); + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(Expressions.name(alias), containsString("sum(salary) + 1 where false")); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(LONG)); + var limit = as(eval.child(), Limit.class); + as(limit.child(), EsRelation.class); + } + + /** + * Limit[1000[INTEGER],true] + * \_InlineJoin[LEFT,[emp_no{f}#9],[emp_no{f}#9],[emp_no{r}#9]] + * |_EsqlProject[[salary{f}#14, emp_no{f}#9]] + * | \_Limit[1000[INTEGER],false] + * | \_EsRelation[test][_meta_field{f}#15, emp_no{f}#9, first_name{f}#10, g..] + * \_Project[[sum(salary) 1 where false{r}#5, sum(salary) 2{r}#7, emp_no{f}#9]] + * \_Eval[[null[LONG] AS sum(salary) 1 where false#5, $$SUM$sum(salary)_ _2$1{r$}#21 2[INTEGER] AS sum(salary) 2#7]] + * \_Aggregate[[emp_no{f}#9],[SUM(salary{f}#14,true[BOOLEAN],compensated[KEYWORD]) AS $$SUM$sum(salary)_ _2$1#21, emp_no{f}#9]] + * \_StubRelation[[salary{f}#14, emp_no{f}#9]] + */ + public void testReplaceInlinestatsFilteredAggWithEvalMixedFilterAndNoFilter() { + var plan = plan(""" + from test + | keep salary, emp_no + | inlinestats sum(salary) + 1 where false, + sum(salary) + 2 + by emp_no + """); + + var limit = as(plan, Limit.class); + var ij = as(limit.child(), InlineJoin.class); + var left = as(ij.left(), EsqlProject.class); + assertThat(Expressions.names(left.projections()), contains("salary", "emp_no")); + limit = as(left.child(), Limit.class); + as(limit.child(), EsRelation.class); + var right = as(ij.right(), Project.class); + assertMap( + Expressions.names(right.projections()).stream().map(Object::toString).toList(), + matchesList().item(startsWith("sum(salary) + 1 where false")).item(startsWith("sum(salary) + 2")).item(startsWith("emp_no")) + ); + var eval = as(right.child(), Eval.class); + assertThat(eval.fields().size(), is(2)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), nullValue()); + assertThat(alias.child().dataType(), is(LONG)); + assertThat(Expressions.name(alias), containsString("sum(salary) + 1 where false")); + alias = as(eval.fields().get(1), Alias.class); + assertFalse(alias.child().foldable()); + assertThat(alias.child().dataType(), is(LONG)); + assertThat(Expressions.name(alias), containsString("sum(salary) + 2")); + assertThat(alias.child() instanceof Add, is(true)); + var add = as(alias.child(), Add.class); + assertThat(add.left().toString(), containsString("$$SUM$sum(salary)_+_2$1")); + var agg = as(eval.child(), Aggregate.class); + assertThat(Expressions.names(agg.aggregates()), contains("$$SUM$sum(salary)_+_2$1", "emp_no")); + var source = as(agg.child(), StubRelation.class); + assertThat(Expressions.names(source.output()), contains("salary", "emp_no")); + } + + /** + * Limit[1000[INTEGER],true] + * \_InlineJoin[LEFT,[],[],[]] + * |_EsqlProject[[salary{f}#16]] + * | \_Limit[1000[INTEGER],false] + * | \_EsRelation[test][_meta_field{f}#17, emp_no{f}#11, first_name{f}#12, ..] + * \_Project[[sum(salary) 1 where false{r}#4, sum(salary) 3{r}#6, sum(salary) 2 where null{r}#8, sum(salary) 4 wher + * e not true{r}#10]] + * \_Eval[[null[LONG] AS sum(salary) 1 where false#4, $$SUM$sum(salary)_ _3$1{r$}#23 3[INTEGER] AS sum(salary) 3#6 + * , null[LONG] AS sum(salary) 2 where null#8, null[LONG] AS sum(salary) 4 where not true#10]] + * \_Aggregate[[],[SUM(salary{f}#16,true[BOOLEAN],compensated[KEYWORD]) AS $$SUM$sum(salary)_ _3$1#23]] + * \_StubRelation[[salary{f}#16]] + */ + public void testReplaceInlinestatsFilteredAggWithEvalFilterFalseAndNull() { + var plan = plan(""" + from test + | keep salary + | inlinestats sum(salary) + 1 where false, + sum(salary) + 3, + sum(salary) + 2 where null, + sum(salary) + 4 where not true + """); + var limit = as(plan, Limit.class); + var ij = as(limit.child(), InlineJoin.class); + + // Check right side Project node + var right = as(ij.right(), Project.class); + assertThat( + Expressions.names(right.projections()), + contains("sum(salary) + 1 where false", "sum(salary) + 3", "sum(salary) + 2 where null", "sum(salary) + 4 where not true") + ); + + var eval = as(right.child(), Eval.class); + assertThat(eval.fields().size(), is(4)); + + var alias1 = as(eval.fields().get(0), Alias.class); + assertTrue(alias1.child().foldable()); + assertThat(alias1.child().fold(FoldContext.small()), nullValue()); + + var alias2 = as(eval.fields().get(1), Alias.class); + assertFalse(alias2.child().foldable()); + + var alias3 = as(eval.fields().get(2), Alias.class); + assertTrue(alias3.child().foldable()); + assertThat(alias3.child().fold(FoldContext.small()), nullValue()); + + var alias4 = as(eval.fields().get(3), Alias.class); + assertTrue(alias4.child().foldable()); + assertThat(alias4.child().fold(FoldContext.small()), nullValue()); + + var aggregate = as(eval.child(), Aggregate.class); + assertThat(Expressions.names(aggregate.aggregates()), contains("$$SUM$sum(salary)_+_3$1")); + + var source = as(aggregate.child(), StubRelation.class); + assertThat(Expressions.names(source.output()), contains("salary")); + } + + /** + * EsqlProject[[emp_no{f}#6, salary{f}#11, count(salary) where not true{r}#5]] + * \_Eval[[0[LONG] AS count(salary) where not true#5]] + * \_Limit[1000[INTEGER],false] + * \_EsRelation[test][_meta_field{f}#12, emp_no{f}#6, first_name{f}#7, ge..] + */ + public void testReplaceInlinestatsFilteredAggWithEvalNotTrue() { + var plan = plan(""" + from test + | keep emp_no, salary + | inlinestats count(salary) where not true + """); + + var project = as(plan, EsqlProject.class); + assertThat(Expressions.names(project.projections()), contains("emp_no", "salary", "count(salary) where not true")); + + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(alias.name(), is("count(salary) where not true")); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), is(0L)); + + var limit = as(eval.child(), Limit.class); + as(limit.child(), EsRelation.class); + } + + /** + * Limit[1000[INTEGER],true] + * \_InlineJoin[LEFT,[],[],[]] + * |_EsqlProject[[emp_no{f}#8, salary{f}#13, gender{f}#10]] + * | \_Limit[1000[INTEGER],false] + * | \_EsRelation[test][_meta_field{f}#14, emp_no{f}#8, first_name{f}#9, ge..] + * \_Aggregate[[],[COUNT(salary{f}#13,true[BOOLEAN]) AS m1#7]] + * \_StubRelation[[emp_no{f}#8, salary{f}#13, gender{f}#10]] + */ + public void testReplaceInlinestatsFilteredAggWithEvalNotFalse() { + var plan = plan(""" + from test + | keep emp_no, salary, gender + | inlinestats m1 = count(salary) where not false + """); + var limit = as(plan, Limit.class); + var ij = as(limit.child(), InlineJoin.class); + + var left = as(ij.left(), EsqlProject.class); + assertThat(Expressions.names(left.projections()), contains("emp_no", "salary", "gender")); + var leftLimit = as(left.child(), Limit.class); + as(leftLimit.child(), EsRelation.class); + + var right = as(ij.right(), Aggregate.class); + assertThat(Expressions.names(right.aggregates()), contains("m1")); + assertEquals(1, right.aggregates().size()); + assertTrue(right.aggregates().get(0).children().get(0) instanceof Count); + assertEquals("true", ((Count) right.aggregates().get(0).children().get(0)).filter().toString()); + + var source = as(right.child(), StubRelation.class); + assertThat(Expressions.names(source.output()), contains("emp_no", "salary", "gender")); + } + + /** + * EsqlProject[[salary{f}#10, count(salary) where false{r}#4]] + * \_Eval[[0[LONG] AS count(salary) where false#4]] + * \_Limit[1000[INTEGER],false] + * \_EsRelation[test][_meta_field{f}#11, emp_no{f}#5, first_name{f}#6, ge..] + */ + public void testReplaceInlinestatsFilteredAggWithEvalCount() { + var plan = plan(""" + from test + | keep salary + | inlinestats count(salary) where false + """); + var project = as(plan, EsqlProject.class); + assertThat(Expressions.names(project.projections()), contains("salary", "count(salary) where false")); + + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(alias.name(), is("count(salary) where false")); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), is(0L)); + + var limit = as(eval.child(), Limit.class); + as(limit.child(), EsRelation.class); + } + + /** + * EsqlProject[[salary{f}#10, count_distinct(salary 2) 3 where false{r}#4]] + * \_Eval[[3[LONG] AS count_distinct(salary 2) 3 where false#4]] + * \_Limit[1000[INTEGER],false] + * \_EsRelation[test][_meta_field{f}#11, emp_no{f}#5, first_name{f}#6, ge..] + */ + public void testReplaceInlinestatsFilteredAggWithEvalCountDistinctInExpression() { + var plan = plan(""" + from test + | keep salary + | inlinestats count_distinct(salary + 2) + 3 where false + """); + var project = as(plan, EsqlProject.class); + assertThat(Expressions.names(project.projections()), contains("salary", "count_distinct(salary + 2) + 3 where false")); + + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(1)); + var alias = as(eval.fields().getFirst(), Alias.class); + assertThat(alias.name(), is("count_distinct(salary + 2) + 3 where false")); + assertTrue(alias.child().foldable()); + assertThat(alias.child().fold(FoldContext.small()), is(3L)); + + var limit = as(eval.child(), Limit.class); + as(limit.child(), EsRelation.class); + } + + /* + * Limit[1000[INTEGER],true] + * \_InlineJoin[LEFT,[emp_no{f}#17],[emp_no{f}#17],[emp_no{r}#17]] + * |_EsqlProject[[emp_no{f}#17, salary{f}#22]] + * | \_Limit[1000[INTEGER],false] + * | \_EsRelation[test][_meta_field{f}#23, emp_no{f}#17, first_name{f}#18, ..] + * \_Project[[max{r}#6, max_a{r}#9, min{r}#12, min_a{r}#15, emp_no{f}#17]] + * \_Eval[[null[INTEGER] AS max_a#9, null[INTEGER] AS min_a#15]] + * \_Aggregate[[emp_no{f}#17],[MAX(salary{f}#22,true[BOOLEAN]) AS max#6, MIN(salary{f}#22,true[BOOLEAN]) AS min#12, emp_no{f}#17]] + * \_StubRelation[[emp_no{f}#17, salary{f}#22]] + */ + public void testReplaceInlinestatsFilteredAggWithEvalSameAggWithAndWithoutFilter() { + var plan = plan(""" + from test + | keep emp_no, salary + | inlinestats max = max(salary), max_a = max(salary) where null, + min = min(salary), + min_a = min(salary) where to_string(null) == "abc" + by emp_no + """); + var limit = as(plan, Limit.class); + var ij = as(limit.child(), InlineJoin.class); + + var left = as(ij.left(), EsqlProject.class); + assertThat(Expressions.names(left.projections()), contains("emp_no", "salary")); + var leftLimit = as(left.child(), Limit.class); + as(leftLimit.child(), EsRelation.class); + + var right = as(ij.right(), Project.class); + assertThat(Expressions.names(right.projections()), contains("max", "max_a", "min", "min_a", "emp_no")); + + var eval = as(right.child(), Eval.class); + assertThat(eval.fields().size(), is(2)); + + var aliasMaxA = as(eval.fields().get(0), Alias.class); + assertThat(Expressions.name(aliasMaxA), containsString("max_a")); + assertTrue(aliasMaxA.child().foldable()); + assertThat(aliasMaxA.child().fold(FoldContext.small()), nullValue()); + assertThat(aliasMaxA.child().dataType(), is(INTEGER)); + + var aliasMinA = as(eval.fields().get(1), Alias.class); + assertThat(Expressions.name(aliasMinA), containsString("min_a")); + assertTrue(aliasMinA.child().foldable()); + assertThat(aliasMinA.child().fold(FoldContext.small()), nullValue()); + assertThat(aliasMinA.child().dataType(), is(INTEGER)); + + var aggregate = as(eval.child(), Aggregate.class); + assertThat(Expressions.names(aggregate.aggregates()), contains("max", "min", "emp_no")); + + var source = as(aggregate.child(), StubRelation.class); + assertThat(Expressions.names(source.output()), contains("emp_no", "salary")); + } + + /* + * EsqlProject[[emp_no{f}#9, count{r}#5, cc{r}#8]] + * \_Eval[[0[LONG] AS count#5, 0[LONG] AS cc#8]] + * \_TopN[[Order[emp_no{f}#9,ASC,LAST]],3[INTEGER]] + * \_EsRelation[test][_meta_field{f}#15, emp_no{f}#9, first_name{f}#10, g..] + */ + public void testReplaceTwoConsecutiveInlinestats_WithFalseFilters() { + var plan = plan(""" + from test + | keep emp_no + | sort emp_no + | limit 3 + | inlinestats count = count(*) where false + | inlinestats cc = count_distinct(emp_no) where false + """); + + var project = as(plan, EsqlProject.class); + assertThat(Expressions.names(project.projections()), contains("emp_no", "count", "cc")); + + var eval = as(project.child(), Eval.class); + assertThat(eval.fields().size(), is(2)); + + var aliasCount = as(eval.fields().get(0), Alias.class); + assertThat(Expressions.name(aliasCount), startsWith("count")); + assertTrue(aliasCount.child().foldable()); + assertThat(aliasCount.child().fold(FoldContext.small()), is(0L)); + + var aliasCc = as(eval.fields().get(1), Alias.class); + assertThat(Expressions.name(aliasCc), startsWith("cc")); + assertTrue(aliasCc.child().foldable()); + assertThat(aliasCc.child().fold(FoldContext.small()), is(0L)); + + var topN = as(eval.child(), TopN.class); + as(topN.child(), EsRelation.class); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/FieldNameUtilsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/FieldNameUtilsTests.java index 5f773a69e8664..916b14c8de9e9 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/FieldNameUtilsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/FieldNameUtilsTests.java @@ -34,7 +34,7 @@ public void testBasicFromCommand() { } public void testBasicFromCommandWithInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames("from test | inlinestats max(salary) by gender", ALL_FIELDS); } @@ -43,7 +43,7 @@ public void testBasicFromCommandWithMetadata() { } public void testBasicFromCommandWithMetadata_AndInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames("from test metadata _index, _id, _version | inlinestats max(salary)", ALL_FIELDS); } @@ -320,7 +320,7 @@ public void testLimitZero() { } public void testLimitZero_WithInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" FROM employees | INLINESTATS COUNT(*), MAX(salary) BY gender @@ -335,7 +335,7 @@ public void testDocsDropHeight() { } public void testDocsDropHeight_WithInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" FROM employees | DROP height @@ -351,7 +351,7 @@ public void testDocsDropHeightWithWildcard() { } public void testDocsDropHeightWithWildcard_AndInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" FROM employees | INLINESTATS MAX(salary) BY gender @@ -518,7 +518,7 @@ public void testSortWithLimitOne_DropHeight() { } public void testSortWithLimitOne_DropHeight_WithInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames("from employees | inlinestats avg(salary) by languages | sort languages | limit 1 | drop height*", ALL_FIELDS); } @@ -818,7 +818,7 @@ public void testFilterById() { } public void testFilterById_WithInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames("FROM apps metadata _id | INLINESTATS max(rate) | WHERE _id == \"4\"", ALL_FIELDS); } @@ -1289,7 +1289,7 @@ public void testProjectDropPattern() { } public void testProjectDropPattern_WithInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | inlinestats max(foo) by bar @@ -1372,7 +1372,7 @@ public void testCountAllAndOtherStatGrouped() { } public void testCountAllAndOtherStatGrouped_WithInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | inlinestats c = count(*), min = min(emp_no) by languages @@ -1411,7 +1411,7 @@ public void testCountAllWithEval() { } public void testCountAllWithEval_AndInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | rename languages as l @@ -1424,7 +1424,7 @@ public void testCountAllWithEval_AndInlinestats() { } public void testKeepAfterEval_AndInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | rename languages as l @@ -1437,7 +1437,7 @@ public void testKeepAfterEval_AndInlinestats() { } public void testKeepBeforeEval_AndInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | rename languages as l @@ -1450,7 +1450,7 @@ public void testKeepBeforeEval_AndInlinestats() { } public void testStatsBeforeEval_AndInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | rename languages as l @@ -1462,7 +1462,7 @@ public void testStatsBeforeEval_AndInlinestats() { } public void testStatsBeforeInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | stats min = min(salary) by languages @@ -1471,7 +1471,7 @@ public void testStatsBeforeInlinestats() { } public void testKeepBeforeInlinestats() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertFieldNames(""" from test | keep languages, salary @@ -2845,7 +2845,7 @@ public void testForkAfterMvExpand() { } public void testForkBeforeInlineStatsIgnore() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertTrue("FORK required", EsqlCapabilities.Cap.FORK_V9.isEnabled()); assertFieldNames(""" FROM employees @@ -2858,7 +2858,7 @@ public void testForkBeforeInlineStatsIgnore() { } public void testForkBranchWithInlineStatsIgnore() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertTrue("FORK required", EsqlCapabilities.Cap.FORK_V9.isEnabled()); assertFieldNames(""" FROM employees @@ -2872,7 +2872,7 @@ public void testForkBranchWithInlineStatsIgnore() { } public void testForkAfterInlineStatsIgnore() { - assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V9.isEnabled()); + assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V10.isEnabled()); assertTrue("FORK required", EsqlCapabilities.Cap.FORK_V9.isEnabled()); assertFieldNames(""" FROM employees