Sandbox/qa - Add multi shard qa test suite - #21951
Conversation
Unmute single shard tests now passing. Signed-off-by: Marc Handalian <marc.handalian@gmail.com>
PR Reviewer Guide 🔍(Review updated until commit a577ad1)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to a577ad1
Previous suggestionsSuggestions up to commit 83c90c7
|
Signed-off-by: Marc Handalian <marc.handalian@gmail.com>
|
Persistent review updated to latest commit a577ad1 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21951 +/- ##
============================================
+ Coverage 73.43% 73.53% +0.10%
- Complexity 75527 75643 +116
============================================
Files 6035 6035
Lines 342721 342720 -1
Branches 49301 49301
============================================
+ Hits 251676 252020 +344
+ Misses 71018 70718 -300
+ Partials 20027 19982 -45 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| * {@link FieldTypeCoverageIT#testIp()} covers {@code ip} at 1 shard; this exercises the 2-shard reduce | ||
| * path. Run unmuted — if the defect is present it fails here, otherwise it passes. | ||
| */ | ||
| public class IpFieldMultiShardIT extends AnalyticsRestTestCase { |
There was a problem hiding this comment.
Why IpFieldMultiShardIT is different from other two shards tests which are extending TwoShardReduceTestCase.java?
|
I found ip_multishard (this PR) and clickbench (plus big5 from #21953) are pinned to number_of_shards: 2 in their mapping files and never get a 1-shard run. Everything else including merge_coverage, says I think mapping file ends up being a misleading source of truth, and I think it's worth deciding whether we want to align on a single convention before more reduce-style suites get added on top. Curious what you think. |
|
Merging this change to get the sandbox healthy. @mch2 Can you please address the comments in a quick follow-up. |
Addresses review feedback to cover the multi-shard case for boolean min/max. min(flag)/max(flag) over the merge_coverage dataset's boolean field are added as golden cases in the TwoShardReduceTestCase suite (opensearch-project#21951 convention), so they run at 1 shard and 2 shards with a differential equality check plus a pinned golden, exercising the two-phase per-shard-partial then coordinator-merge reduce path. The merge_coverage flag field has 15 false and 15 true rows, so min=false and max=true regardless of shard layout. Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
…lytics-engine route + review fixes (#21975) * [analytics-engine] Fix now()-family + enable now(fsp)/rand(seed)/sha2/span-month/ts-subtract/dc-approx on the analytics-engine route Closes several PPL scalar/aggregate gaps on the analytics-engine (DataFusion) route. Each was either an "Unable to convert call ..." Substrait failure or a result-rendering gap; all are verified end-to-end via the CalcitePPL*IT remote ITs on a parquet/composite (analytics-engine-routed) cluster. now()-family (CalciteNowLikeFunctionIT 12/12): - ArrowValues now converts Arrow Date32/Date64 -> LocalDate and Time{Sec,Milli,Micro,Nano} -> LocalTime so DATE/TIME results render as "uuuu-MM-dd" / "HH:mm:ss" via ExprValueUtils.fromObjectValue's existing temporal branch (mirrors how TIMESTAMP already arrives as LocalDateTime). Fixes current_date/curdate/current_time/curtime returning raw epoch-day / units-of-day integers. - UTC_TIMESTAMP/UTC_DATE/UTC_TIME enum constants + adapter registrations route to the same DataFusion builtins as their CURRENT_* equivalents (cluster runs in UTC), reusing the existing now/currentDate/currentTime adapters. - NowFspAdapter drops the optional fractional-seconds-precision arg so now(fsp)/current_timestamp(fsp)/sysdate(fsp) map to DataFusion's niladic now() instead of failing as "Unable to convert call now(i32)". Other scalar/aggregate fixes: - RandSeedAdapter drops the optional rand(seed) operand -> DataFusion random(). - Sha2FunctionAdapter raises a clear "Unsupported SHA2 algorithm [N]" for a concrete unsupported literal bit length, matching the SQL-plugin reference (CryptographicFunction) instead of surfacing a cryptic Substrait error. - SpanAdapter supports variable-length month/quarter/year buckets. - TimestampSubtractRewriter rewrites MINUS(timestamp, timestamp) to an epoch-second difference (to_unixtime), which is Substrait-convertible. - PplAggregateCallRewriter + AggregateFunction map DISTINCT_COUNT_APPROX to APPROX_COUNT_DISTINCT (dc/distinct_count approx form). Adds unit tests for the new adapters/rewriter and ArrowValues DATE/TIME conversion; updates the SHA2 adapter test to assert the clear-error behavior. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Address review: RAND seed fail-clear, shared Substrait preprocess, adapter arity bounds, span hardening, SHA2 message Review-driven hardening on top of the initial PR: - RandSeedAdapter: stop silently dropping the rand(seed) operand (which turned a deterministic seeded call into a non-deterministic one). Niladic rand() still maps to DataFusion random(); seeded rand(seed) now fails with a clear unsupported-shape error until a seeded random is available on the backend. - DataFusionFragmentConvertor: extract preprocessForSubstrait() and call it from both convertToSubstrait and convertStandalone so TimestampSubtractRewriter (and any future rewriter) runs on the wrapper/partial-aggregate path too, not only the top-level fragment path. - NowFspAdapter / RandSeedAdapter: only normalize the valid 0-arg / 1-arg shapes; leave unexpected arities untouched instead of inventing a valid call. - SpanAdapter: guard the month and second interval multiplications with Math.multiplyExact (clear "interval is too large" error on overflow), and use floored modulo/division for the month-index bucketing so pre-1970 (negative month index) timestamps snap down to the correct bucket start. - Sha2FunctionAdapter: list the supported bit lengths (224, 256, 384, 512) in the unsupported-algorithm error. Tests: add NowFsp unexpected-arity test, RAND seeded-reject test, span month-bucket + overflow tests; update SHA2 test to assert the supported-values message. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix grouped distinct_count_approx nullability + make TimestampSubtractRewriter identity-preserving Two correctness fixes found while verifying the review round: - PplAggregateCallRewriter (DISTINCT_COUNT_APPROX): pin the remapped aggregate's explicit return type to NOT NULL BIGINT. APPROX_COUNT_DISTINCT is a count, so its return-type inference is BIGINT NOT NULL; reusing the PPL call's nullable BIGINT left the declared and inferred types disagreeing and tripped Calcite's validity assertion ("aggCall type BIGINT vs inferred BIGINT NOT NULL") on the grouped two-phase path (e.g. `stats distinct_count_approx(x) by g`). This was a latent bug in the un-grouped-only test coverage; the grouped form is now verified green. - TimestampSubtractRewriter: make it a true identity no-op for plans without a MINUS(timestamp, timestamp). Previously it walked every RelNode and called RelNode.accept(RexShuttle) unconditionally, which re-derives expression / aggCall types and can flip a cached nullable BIGINT to BIGINT NOT NULL — breaking unrelated shapes once the shared preprocessing pipeline runs it on the two-phase aggregate path. It now detects the target shape first (read-only visitors) and only applies the rewriting RexShuttle to nodes that actually contain it, returning the original object otherwise. Adds identity (assertSame) regression tests. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Drop TimestampSubtractRewriter — superseded by upstream MinusAdapter (#21978) Upstream #21978 added MinusAdapter, the proper named ScalarFunctionAdapter for timestamp/date subtraction (t1 - t2 -> from_unixtime(to_unixtime(t1) - to_unixtime(t2))). It also coordinates with WidthBucketAdapter by deliberately leaving the MINUS(MAX OVER(), MIN OVER()) binning shape untouched. Our pre-Substrait TimestampSubtractRewriter rewrote every MINUS(timestamp, timestamp) including that binning shape, so keeping both risked clobbering the binning path. Remove the rewriter (and its test); MinusAdapter fully covers the case. The shared preprocessForSubstrait helper is retained for the remaining rewriters. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Bind distinct_count_approx after SQL-plugin emits APPROX_COUNT_DISTINCT (#5525) sql#5525 changed PPLBuiltinOperators.DISTINCT_COUNT_APPROX to emit a user-defined SqlAggFunction NAMED "APPROX_COUNT_DISTINCT" (DistinctCountApproxLogicalAggFunction). isthmus binds aggregate sigs by operator identity, not name, so this custom op has no substrait sig and grouped/ungrouped `stats distinct_count_approx(x)` fails with "Unable to find binding for call APPROX_COUNT_DISTINCT". (The `dc`->COUNT(DISTINCT) path works via OpenSearchDistinctCountRule/#22013; the named UDAF path does not.) Re-key the PplAggregateCallRewriter case from the old name DISTINCT_COUNT_APPROX to APPROX_COUNT_DISTINCT and remap the PPL marker to stock SqlStdOperatorTable.APPROX_COUNT_DISTINCT (which ADDITIONAL_AGGREGATE_SIGS binds to DataFusion approx_distinct), guarding against the stock operator itself. Remove the now-dead DISTINCT_COUNT_APPROX alias in AggregateFunction.fromNameOrError — the enum constant is APPROX_COUNT_DISTINCT, which valueOf resolves directly (addresses review comment). Verified: CalcitePPLAggregationIT.testCountDistinctApprox + testCountDistinctApproxWithAlias pass on a force-routed analytics-engine cluster. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Don't push filters below non-deterministic projects (fixes rand() predicates) `eval r = rand() | where r > 0` failed with "Comparison performance-delegation requires (RexInputRef, RexLiteral); got RAND()". Calcite's stock FILTER_PROJECT_TRANSPOSE only guards against window functions (!containsOver()), so it pushed the filter below the rand() project, inlining RAND() into the predicate — turning a delegatable ($ref > literal) comparison into RAND() > literal on the scan, which Lucene performance-delegation cannot serialize (and which must stay on the single in-memory engine, since each backend would draw different values). Replace the stock rule with FILTER_PROJECT_TRANSPOSE_DETERMINISTIC: a FilterProjectTransposeRule configured to refuse the transpose when the Project computes any non-deterministic expression (RexUtil.isDeterministic). Keeping the Filter above the Project preserves the clean ($ref > literal) shape evaluated in memory. Verified: CalcitePPLBuiltinFunctionIT.testRand passes on a force-routed analytics-engine cluster. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Restore distinct_count_approx binding on the analytics-engine route Validation requested in PR review (sandeshkr419) found that sql#5525 alone does NOT make `stats distinct_count_approx(x)` work on the analytics-engine route, so the engine-side handling removed in the prior commit is still required. On a fresh, force-routed cluster with sql#5525 deployed, CalcitePPLAggregationIT testCountDistinctApprox + testCountDistinctApproxWithAlias fail in two stages: 1. Planner stage: OpenSearchAggregateRule.resolveViableBackendsForCall reads aggCall.getAggregation().getName(), which is still "DISTINCT_COUNT_APPROX" (the DistinctCountApproxLogicalAggFunction marker). fromNameOrError then throws "No enum constant ...AggregateFunction.DISTINCT_COUNT_APPROX". 2. Substrait stage: the PplAggregateCallRewriter case keyed only on "APPROX_COUNT_DISTINCT" never matched the marker, so the unbound op reached isthmus -> "Unable to find binding for call DISTINCT_COUNT_APPROX($1)". sql#5525 only sets the substrait-emission name to APPROX_COUNT_DISTINCT, which is a later stage than both of the above, so the marker's runtime name still reaches them. Fixes: - Restore the DISTINCT_COUNT_APPROX -> APPROX_COUNT_DISTINCT alias in AggregateFunction.fromNameOrError (covers the planner path). - Match both "DISTINCT_COUNT_APPROX" and "APPROX_COUNT_DISTINCT" in the PplAggregateCallRewriter case (covers the substrait-binding path); keep the guard that skips the already-bound stock operator. - Add a fromNameOrError alias unit test. Verified on a force-routed analytics-engine cluster (parquet-backed indices, AE-routed): both tests pass; the surrounding 4-class IT sweep is unchanged at 6 pre-existing failures (date_format/strftime/percentile/nested-field), i.e. no regressions. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Add qa-module ITs for distinct_count_approx + min/max(boolean) Engine-side end-to-end regression coverage for the two analytics-engine binding fixes in this PR, run against the production /_plugins/_ppl surface on the qa-module's analytics-routed cluster (mirrors the SQL-repo CalcitePPLAggregationIT cases that proved the fixes): - DistinctCountApproxAggregationIT: grouped / aliased / dc-shorthand / ungrouped distinct_count_approx (guards the planner enum alias + rewriter rebind). - MinMaxBooleanAggregationIT: ungrouped / grouped / mixed boolean+string min/max (guards the boolean min/max substrait overload). Each IT creates its own parquet-backed (composite/parquet + lucene secondary) index, bulk-ingests a small fixed dataset, and asserts oracle values. All 7 tests pass (4 + 3). Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix date_format %c month padding; correct stale RAND comments Two small follow-ups surfaced while verifying the analytics-engine route: - date_format/strftime %c now emits the zero-padded month (Jan -> "01"), matching the PPL reference (SQL-plugin DateTimeFormatterUtil maps %c -> "MM" in date mode), which differs from stock MySQL's no-leading-zero %c. Time mode keeps the reference's single-"0" literal. Fixes the %c portion of CalciteDateTimeFunctionIT.testDateFormat and updates the Rust token-set unit test expectation accordingly. (The test's %U/%u/%V/%v week-number padding is a separate, unrelated date-format issue left for a follow-up.) - Correct two stale comments that described the old "drop the seed" RAND behavior; the adapter now rejects RAND(seed) with a clear error (addresses review feedback). No behavior change — comment/javadoc only. Verified on a force-routed analytics-engine cluster: date_format(ts,'%c') returns "01"; the now/sha2/dc/min-max ITs remain green (no regressions). Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Drop DISTINCT_COUNT_APPROX enum alias per review Remove the DISTINCT_COUNT_APPROX -> APPROX_COUNT_DISTINCT alias in AggregateFunction.fromNameOrError (and its unit test), and key the PplAggregateCallRewriter case on "APPROX_COUNT_DISTINCT" only, per @sandeshkr419's review. The SQL plugin (sql#5525) emits the operator named APPROX_COUNT_DISTINCT, which the enum resolves directly, so the legacy-name alias is no longer needed. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Address review: bulk NDJSON content-type, dc comments, error wording Review follow-ups (sandeshkr419): - QA ITs: send _bulk as application/x-ndjson via the per-index endpoint with bare {"index": {}} action metadata, matching the existing CountFastPathIT pattern. setJsonEntity alone defaults to application/json, which the bulk endpoint rejects. - Clarify the distinct_count_approx marker naming across layers: current runtime name is APPROX_COUNT_DISTINCT (sql#5525), DISTINCT_COUNT_APPROX is the legacy/defensive spelling; OpenSearchDistinctCountRule is the primary planner rewrite and PplAggregateCallRewriter is a late Substrait-emission defensive fallback. - Document the phase-specific nullability handling: the early planner rewrite preserves the marker's nullable type (LogicalAggregate.copy re-validates row type); the late Substrait rewrite pins NOT NULL (Calcite validates against the stock op's inferred type). - SPAN overflow message now includes the operands. - Drop "yet" from the seeded-RAND error so it doesn't imply imminent support. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Empty commit to retrigger CI No code change. The prior gradle-check failed in the Jenkins-trigger step (jq parse error polling the Jenkins API, 10 retries, empty result) — a transient CI-infra glitch unrelated to this PR's changes. Re-running checks. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix DatetimeCoverageIT %c assertions to match zero-padded month The date_format %c code change (no-leading-zero -> zero-padded "MM") aligns the analytics-engine Rust UDF with the legacy PPL contract: DateTimeFormatterUtil.DATE_HANDLERS maps %c -> "MM" (date_format dispatches through DATE_HANDLERS), so January renders as "01". The Rust code + unit test were updated for this, but the two DatetimeCoverageIT assertions still expected the old unpadded "1" — the gradle-check / sandbox-check failures. Update both assertions to "01", matching the canonical merged ITs (ppl/DateTimeFunctionIT and sql/DateTimeFunctionIT both assert "Sat Jan 01 31st ..." for the same spec). Test-only. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Enable now()/rand(seed)/sha2/span-month on the analytics-engine route + review fixes Enables these PPL functions on the force-routed analytics-engine (DataFusion) path and addresses review feedback: - now()-family: NowFspAdapter handles now([fsp]) / current_timestamp / localtimestamp, where fsp is MySQL fractional-seconds precision (0-6). - rand([seed]): RandSeedAdapter. - sha2(input, bitLen): Sha2FunctionAdapter -> encode(digest(input, 'shaN'), 'hex'), with a backend fail-clear guard for unsupported bit lengths (TODO to move validation to the frontend once the handoff carries function metadata). - span() month bucketing: SpanAdapter. - strftime: os_strftime.rs. - date_format %c: zero-padded month (DatetimeCoverageIT updated to match the merged ppl/sql DateTimeFunctionIT expectations). - PlannerImpl: don't push filters below non-deterministic projects; rand() in a pushed-down predicate would be re-evaluated and draw a fresh value. Documented as a semantic-correctness guard. - DataFusionFragmentConvertor: shared Substrait preprocess entry point. - New QA ITs: MinMaxBooleanAggregationIT. Review fixes: removed the late defensive APPROX_COUNT_DISTINCT fallback in PplAggregateCallRewriter (fail loud instead); restored the Substrait-layer assessment TODO; clarified fsp in NowFspAdapter; tidied AggregateFunction. distinct_count_approx on the analytics-engine route is handled upstream by #22120 and is not part of this PR. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Add 2-shard reduce coverage for boolean min/max Addresses review feedback to cover the multi-shard case for boolean min/max. min(flag)/max(flag) over the merge_coverage dataset's boolean field are added as golden cases in the TwoShardReduceTestCase suite (#21951 convention), so they run at 1 shard and 2 shards with a differential equality check plus a pinned golden, exercising the two-phase per-shard-partial then coordinator-merge reduce path. The merge_coverage flag field has 15 false and 15 true rows, so min=false and max=true regardless of shard layout. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> --------- Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
…lytics-engine route + review fixes (opensearch-project#21975) * [analytics-engine] Fix now()-family + enable now(fsp)/rand(seed)/sha2/span-month/ts-subtract/dc-approx on the analytics-engine route Closes several PPL scalar/aggregate gaps on the analytics-engine (DataFusion) route. Each was either an "Unable to convert call ..." Substrait failure or a result-rendering gap; all are verified end-to-end via the CalcitePPL*IT remote ITs on a parquet/composite (analytics-engine-routed) cluster. now()-family (CalciteNowLikeFunctionIT 12/12): - ArrowValues now converts Arrow Date32/Date64 -> LocalDate and Time{Sec,Milli,Micro,Nano} -> LocalTime so DATE/TIME results render as "uuuu-MM-dd" / "HH:mm:ss" via ExprValueUtils.fromObjectValue's existing temporal branch (mirrors how TIMESTAMP already arrives as LocalDateTime). Fixes current_date/curdate/current_time/curtime returning raw epoch-day / units-of-day integers. - UTC_TIMESTAMP/UTC_DATE/UTC_TIME enum constants + adapter registrations route to the same DataFusion builtins as their CURRENT_* equivalents (cluster runs in UTC), reusing the existing now/currentDate/currentTime adapters. - NowFspAdapter drops the optional fractional-seconds-precision arg so now(fsp)/current_timestamp(fsp)/sysdate(fsp) map to DataFusion's niladic now() instead of failing as "Unable to convert call now(i32)". Other scalar/aggregate fixes: - RandSeedAdapter drops the optional rand(seed) operand -> DataFusion random(). - Sha2FunctionAdapter raises a clear "Unsupported SHA2 algorithm [N]" for a concrete unsupported literal bit length, matching the SQL-plugin reference (CryptographicFunction) instead of surfacing a cryptic Substrait error. - SpanAdapter supports variable-length month/quarter/year buckets. - TimestampSubtractRewriter rewrites MINUS(timestamp, timestamp) to an epoch-second difference (to_unixtime), which is Substrait-convertible. - PplAggregateCallRewriter + AggregateFunction map DISTINCT_COUNT_APPROX to APPROX_COUNT_DISTINCT (dc/distinct_count approx form). Adds unit tests for the new adapters/rewriter and ArrowValues DATE/TIME conversion; updates the SHA2 adapter test to assert the clear-error behavior. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Address review: RAND seed fail-clear, shared Substrait preprocess, adapter arity bounds, span hardening, SHA2 message Review-driven hardening on top of the initial PR: - RandSeedAdapter: stop silently dropping the rand(seed) operand (which turned a deterministic seeded call into a non-deterministic one). Niladic rand() still maps to DataFusion random(); seeded rand(seed) now fails with a clear unsupported-shape error until a seeded random is available on the backend. - DataFusionFragmentConvertor: extract preprocessForSubstrait() and call it from both convertToSubstrait and convertStandalone so TimestampSubtractRewriter (and any future rewriter) runs on the wrapper/partial-aggregate path too, not only the top-level fragment path. - NowFspAdapter / RandSeedAdapter: only normalize the valid 0-arg / 1-arg shapes; leave unexpected arities untouched instead of inventing a valid call. - SpanAdapter: guard the month and second interval multiplications with Math.multiplyExact (clear "interval is too large" error on overflow), and use floored modulo/division for the month-index bucketing so pre-1970 (negative month index) timestamps snap down to the correct bucket start. - Sha2FunctionAdapter: list the supported bit lengths (224, 256, 384, 512) in the unsupported-algorithm error. Tests: add NowFsp unexpected-arity test, RAND seeded-reject test, span month-bucket + overflow tests; update SHA2 test to assert the supported-values message. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix grouped distinct_count_approx nullability + make TimestampSubtractRewriter identity-preserving Two correctness fixes found while verifying the review round: - PplAggregateCallRewriter (DISTINCT_COUNT_APPROX): pin the remapped aggregate's explicit return type to NOT NULL BIGINT. APPROX_COUNT_DISTINCT is a count, so its return-type inference is BIGINT NOT NULL; reusing the PPL call's nullable BIGINT left the declared and inferred types disagreeing and tripped Calcite's validity assertion ("aggCall type BIGINT vs inferred BIGINT NOT NULL") on the grouped two-phase path (e.g. `stats distinct_count_approx(x) by g`). This was a latent bug in the un-grouped-only test coverage; the grouped form is now verified green. - TimestampSubtractRewriter: make it a true identity no-op for plans without a MINUS(timestamp, timestamp). Previously it walked every RelNode and called RelNode.accept(RexShuttle) unconditionally, which re-derives expression / aggCall types and can flip a cached nullable BIGINT to BIGINT NOT NULL — breaking unrelated shapes once the shared preprocessing pipeline runs it on the two-phase aggregate path. It now detects the target shape first (read-only visitors) and only applies the rewriting RexShuttle to nodes that actually contain it, returning the original object otherwise. Adds identity (assertSame) regression tests. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Drop TimestampSubtractRewriter — superseded by upstream MinusAdapter (opensearch-project#21978) Upstream opensearch-project#21978 added MinusAdapter, the proper named ScalarFunctionAdapter for timestamp/date subtraction (t1 - t2 -> from_unixtime(to_unixtime(t1) - to_unixtime(t2))). It also coordinates with WidthBucketAdapter by deliberately leaving the MINUS(MAX OVER(), MIN OVER()) binning shape untouched. Our pre-Substrait TimestampSubtractRewriter rewrote every MINUS(timestamp, timestamp) including that binning shape, so keeping both risked clobbering the binning path. Remove the rewriter (and its test); MinusAdapter fully covers the case. The shared preprocessForSubstrait helper is retained for the remaining rewriters. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Bind distinct_count_approx after SQL-plugin emits APPROX_COUNT_DISTINCT (opensearch-project#5525) sql#5525 changed PPLBuiltinOperators.DISTINCT_COUNT_APPROX to emit a user-defined SqlAggFunction NAMED "APPROX_COUNT_DISTINCT" (DistinctCountApproxLogicalAggFunction). isthmus binds aggregate sigs by operator identity, not name, so this custom op has no substrait sig and grouped/ungrouped `stats distinct_count_approx(x)` fails with "Unable to find binding for call APPROX_COUNT_DISTINCT". (The `dc`->COUNT(DISTINCT) path works via OpenSearchDistinctCountRule/opensearch-project#22013; the named UDAF path does not.) Re-key the PplAggregateCallRewriter case from the old name DISTINCT_COUNT_APPROX to APPROX_COUNT_DISTINCT and remap the PPL marker to stock SqlStdOperatorTable.APPROX_COUNT_DISTINCT (which ADDITIONAL_AGGREGATE_SIGS binds to DataFusion approx_distinct), guarding against the stock operator itself. Remove the now-dead DISTINCT_COUNT_APPROX alias in AggregateFunction.fromNameOrError — the enum constant is APPROX_COUNT_DISTINCT, which valueOf resolves directly (addresses review comment). Verified: CalcitePPLAggregationIT.testCountDistinctApprox + testCountDistinctApproxWithAlias pass on a force-routed analytics-engine cluster. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Don't push filters below non-deterministic projects (fixes rand() predicates) `eval r = rand() | where r > 0` failed with "Comparison performance-delegation requires (RexInputRef, RexLiteral); got RAND()". Calcite's stock FILTER_PROJECT_TRANSPOSE only guards against window functions (!containsOver()), so it pushed the filter below the rand() project, inlining RAND() into the predicate — turning a delegatable ($ref > literal) comparison into RAND() > literal on the scan, which Lucene performance-delegation cannot serialize (and which must stay on the single in-memory engine, since each backend would draw different values). Replace the stock rule with FILTER_PROJECT_TRANSPOSE_DETERMINISTIC: a FilterProjectTransposeRule configured to refuse the transpose when the Project computes any non-deterministic expression (RexUtil.isDeterministic). Keeping the Filter above the Project preserves the clean ($ref > literal) shape evaluated in memory. Verified: CalcitePPLBuiltinFunctionIT.testRand passes on a force-routed analytics-engine cluster. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Restore distinct_count_approx binding on the analytics-engine route Validation requested in PR review (sandeshkr419) found that sql#5525 alone does NOT make `stats distinct_count_approx(x)` work on the analytics-engine route, so the engine-side handling removed in the prior commit is still required. On a fresh, force-routed cluster with sql#5525 deployed, CalcitePPLAggregationIT testCountDistinctApprox + testCountDistinctApproxWithAlias fail in two stages: 1. Planner stage: OpenSearchAggregateRule.resolveViableBackendsForCall reads aggCall.getAggregation().getName(), which is still "DISTINCT_COUNT_APPROX" (the DistinctCountApproxLogicalAggFunction marker). fromNameOrError then throws "No enum constant ...AggregateFunction.DISTINCT_COUNT_APPROX". 2. Substrait stage: the PplAggregateCallRewriter case keyed only on "APPROX_COUNT_DISTINCT" never matched the marker, so the unbound op reached isthmus -> "Unable to find binding for call DISTINCT_COUNT_APPROX($1)". sql#5525 only sets the substrait-emission name to APPROX_COUNT_DISTINCT, which is a later stage than both of the above, so the marker's runtime name still reaches them. Fixes: - Restore the DISTINCT_COUNT_APPROX -> APPROX_COUNT_DISTINCT alias in AggregateFunction.fromNameOrError (covers the planner path). - Match both "DISTINCT_COUNT_APPROX" and "APPROX_COUNT_DISTINCT" in the PplAggregateCallRewriter case (covers the substrait-binding path); keep the guard that skips the already-bound stock operator. - Add a fromNameOrError alias unit test. Verified on a force-routed analytics-engine cluster (parquet-backed indices, AE-routed): both tests pass; the surrounding 4-class IT sweep is unchanged at 6 pre-existing failures (date_format/strftime/percentile/nested-field), i.e. no regressions. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Add qa-module ITs for distinct_count_approx + min/max(boolean) Engine-side end-to-end regression coverage for the two analytics-engine binding fixes in this PR, run against the production /_plugins/_ppl surface on the qa-module's analytics-routed cluster (mirrors the SQL-repo CalcitePPLAggregationIT cases that proved the fixes): - DistinctCountApproxAggregationIT: grouped / aliased / dc-shorthand / ungrouped distinct_count_approx (guards the planner enum alias + rewriter rebind). - MinMaxBooleanAggregationIT: ungrouped / grouped / mixed boolean+string min/max (guards the boolean min/max substrait overload). Each IT creates its own parquet-backed (composite/parquet + lucene secondary) index, bulk-ingests a small fixed dataset, and asserts oracle values. All 7 tests pass (4 + 3). Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix date_format %c month padding; correct stale RAND comments Two small follow-ups surfaced while verifying the analytics-engine route: - date_format/strftime %c now emits the zero-padded month (Jan -> "01"), matching the PPL reference (SQL-plugin DateTimeFormatterUtil maps %c -> "MM" in date mode), which differs from stock MySQL's no-leading-zero %c. Time mode keeps the reference's single-"0" literal. Fixes the %c portion of CalciteDateTimeFunctionIT.testDateFormat and updates the Rust token-set unit test expectation accordingly. (The test's %U/%u/%V/%v week-number padding is a separate, unrelated date-format issue left for a follow-up.) - Correct two stale comments that described the old "drop the seed" RAND behavior; the adapter now rejects RAND(seed) with a clear error (addresses review feedback). No behavior change — comment/javadoc only. Verified on a force-routed analytics-engine cluster: date_format(ts,'%c') returns "01"; the now/sha2/dc/min-max ITs remain green (no regressions). Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Drop DISTINCT_COUNT_APPROX enum alias per review Remove the DISTINCT_COUNT_APPROX -> APPROX_COUNT_DISTINCT alias in AggregateFunction.fromNameOrError (and its unit test), and key the PplAggregateCallRewriter case on "APPROX_COUNT_DISTINCT" only, per @sandeshkr419's review. The SQL plugin (sql#5525) emits the operator named APPROX_COUNT_DISTINCT, which the enum resolves directly, so the legacy-name alias is no longer needed. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Address review: bulk NDJSON content-type, dc comments, error wording Review follow-ups (sandeshkr419): - QA ITs: send _bulk as application/x-ndjson via the per-index endpoint with bare {"index": {}} action metadata, matching the existing CountFastPathIT pattern. setJsonEntity alone defaults to application/json, which the bulk endpoint rejects. - Clarify the distinct_count_approx marker naming across layers: current runtime name is APPROX_COUNT_DISTINCT (sql#5525), DISTINCT_COUNT_APPROX is the legacy/defensive spelling; OpenSearchDistinctCountRule is the primary planner rewrite and PplAggregateCallRewriter is a late Substrait-emission defensive fallback. - Document the phase-specific nullability handling: the early planner rewrite preserves the marker's nullable type (LogicalAggregate.copy re-validates row type); the late Substrait rewrite pins NOT NULL (Calcite validates against the stock op's inferred type). - SPAN overflow message now includes the operands. - Drop "yet" from the seeded-RAND error so it doesn't imply imminent support. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Empty commit to retrigger CI No code change. The prior gradle-check failed in the Jenkins-trigger step (jq parse error polling the Jenkins API, 10 retries, empty result) — a transient CI-infra glitch unrelated to this PR's changes. Re-running checks. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix DatetimeCoverageIT %c assertions to match zero-padded month The date_format %c code change (no-leading-zero -> zero-padded "MM") aligns the analytics-engine Rust UDF with the legacy PPL contract: DateTimeFormatterUtil.DATE_HANDLERS maps %c -> "MM" (date_format dispatches through DATE_HANDLERS), so January renders as "01". The Rust code + unit test were updated for this, but the two DatetimeCoverageIT assertions still expected the old unpadded "1" — the gradle-check / sandbox-check failures. Update both assertions to "01", matching the canonical merged ITs (ppl/DateTimeFunctionIT and sql/DateTimeFunctionIT both assert "Sat Jan 01 31st ..." for the same spec). Test-only. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Enable now()/rand(seed)/sha2/span-month on the analytics-engine route + review fixes Enables these PPL functions on the force-routed analytics-engine (DataFusion) path and addresses review feedback: - now()-family: NowFspAdapter handles now([fsp]) / current_timestamp / localtimestamp, where fsp is MySQL fractional-seconds precision (0-6). - rand([seed]): RandSeedAdapter. - sha2(input, bitLen): Sha2FunctionAdapter -> encode(digest(input, 'shaN'), 'hex'), with a backend fail-clear guard for unsupported bit lengths (TODO to move validation to the frontend once the handoff carries function metadata). - span() month bucketing: SpanAdapter. - strftime: os_strftime.rs. - date_format %c: zero-padded month (DatetimeCoverageIT updated to match the merged ppl/sql DateTimeFunctionIT expectations). - PlannerImpl: don't push filters below non-deterministic projects; rand() in a pushed-down predicate would be re-evaluated and draw a fresh value. Documented as a semantic-correctness guard. - DataFusionFragmentConvertor: shared Substrait preprocess entry point. - New QA ITs: MinMaxBooleanAggregationIT. Review fixes: removed the late defensive APPROX_COUNT_DISTINCT fallback in PplAggregateCallRewriter (fail loud instead); restored the Substrait-layer assessment TODO; clarified fsp in NowFspAdapter; tidied AggregateFunction. distinct_count_approx on the analytics-engine route is handled upstream by opensearch-project#22120 and is not part of this PR. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Add 2-shard reduce coverage for boolean min/max Addresses review feedback to cover the multi-shard case for boolean min/max. min(flag)/max(flag) over the merge_coverage dataset's boolean field are added as golden cases in the TwoShardReduceTestCase suite (opensearch-project#21951 convention), so they run at 1 shard and 2 shards with a differential equality check plus a pinned golden, exercising the two-phase per-shard-partial then coordinator-merge reduce path. The merge_coverage flag field has 15 false and 15 true rows, so min=false and max=true regardless of shard layout. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> --------- Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Signed-off-by: Marc Handalian <marc.handalian@gmail.com>
…lytics-engine route + review fixes (opensearch-project#21975) * [analytics-engine] Fix now()-family + enable now(fsp)/rand(seed)/sha2/span-month/ts-subtract/dc-approx on the analytics-engine route Closes several PPL scalar/aggregate gaps on the analytics-engine (DataFusion) route. Each was either an "Unable to convert call ..." Substrait failure or a result-rendering gap; all are verified end-to-end via the CalcitePPL*IT remote ITs on a parquet/composite (analytics-engine-routed) cluster. now()-family (CalciteNowLikeFunctionIT 12/12): - ArrowValues now converts Arrow Date32/Date64 -> LocalDate and Time{Sec,Milli,Micro,Nano} -> LocalTime so DATE/TIME results render as "uuuu-MM-dd" / "HH:mm:ss" via ExprValueUtils.fromObjectValue's existing temporal branch (mirrors how TIMESTAMP already arrives as LocalDateTime). Fixes current_date/curdate/current_time/curtime returning raw epoch-day / units-of-day integers. - UTC_TIMESTAMP/UTC_DATE/UTC_TIME enum constants + adapter registrations route to the same DataFusion builtins as their CURRENT_* equivalents (cluster runs in UTC), reusing the existing now/currentDate/currentTime adapters. - NowFspAdapter drops the optional fractional-seconds-precision arg so now(fsp)/current_timestamp(fsp)/sysdate(fsp) map to DataFusion's niladic now() instead of failing as "Unable to convert call now(i32)". Other scalar/aggregate fixes: - RandSeedAdapter drops the optional rand(seed) operand -> DataFusion random(). - Sha2FunctionAdapter raises a clear "Unsupported SHA2 algorithm [N]" for a concrete unsupported literal bit length, matching the SQL-plugin reference (CryptographicFunction) instead of surfacing a cryptic Substrait error. - SpanAdapter supports variable-length month/quarter/year buckets. - TimestampSubtractRewriter rewrites MINUS(timestamp, timestamp) to an epoch-second difference (to_unixtime), which is Substrait-convertible. - PplAggregateCallRewriter + AggregateFunction map DISTINCT_COUNT_APPROX to APPROX_COUNT_DISTINCT (dc/distinct_count approx form). Adds unit tests for the new adapters/rewriter and ArrowValues DATE/TIME conversion; updates the SHA2 adapter test to assert the clear-error behavior. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Address review: RAND seed fail-clear, shared Substrait preprocess, adapter arity bounds, span hardening, SHA2 message Review-driven hardening on top of the initial PR: - RandSeedAdapter: stop silently dropping the rand(seed) operand (which turned a deterministic seeded call into a non-deterministic one). Niladic rand() still maps to DataFusion random(); seeded rand(seed) now fails with a clear unsupported-shape error until a seeded random is available on the backend. - DataFusionFragmentConvertor: extract preprocessForSubstrait() and call it from both convertToSubstrait and convertStandalone so TimestampSubtractRewriter (and any future rewriter) runs on the wrapper/partial-aggregate path too, not only the top-level fragment path. - NowFspAdapter / RandSeedAdapter: only normalize the valid 0-arg / 1-arg shapes; leave unexpected arities untouched instead of inventing a valid call. - SpanAdapter: guard the month and second interval multiplications with Math.multiplyExact (clear "interval is too large" error on overflow), and use floored modulo/division for the month-index bucketing so pre-1970 (negative month index) timestamps snap down to the correct bucket start. - Sha2FunctionAdapter: list the supported bit lengths (224, 256, 384, 512) in the unsupported-algorithm error. Tests: add NowFsp unexpected-arity test, RAND seeded-reject test, span month-bucket + overflow tests; update SHA2 test to assert the supported-values message. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix grouped distinct_count_approx nullability + make TimestampSubtractRewriter identity-preserving Two correctness fixes found while verifying the review round: - PplAggregateCallRewriter (DISTINCT_COUNT_APPROX): pin the remapped aggregate's explicit return type to NOT NULL BIGINT. APPROX_COUNT_DISTINCT is a count, so its return-type inference is BIGINT NOT NULL; reusing the PPL call's nullable BIGINT left the declared and inferred types disagreeing and tripped Calcite's validity assertion ("aggCall type BIGINT vs inferred BIGINT NOT NULL") on the grouped two-phase path (e.g. `stats distinct_count_approx(x) by g`). This was a latent bug in the un-grouped-only test coverage; the grouped form is now verified green. - TimestampSubtractRewriter: make it a true identity no-op for plans without a MINUS(timestamp, timestamp). Previously it walked every RelNode and called RelNode.accept(RexShuttle) unconditionally, which re-derives expression / aggCall types and can flip a cached nullable BIGINT to BIGINT NOT NULL — breaking unrelated shapes once the shared preprocessing pipeline runs it on the two-phase aggregate path. It now detects the target shape first (read-only visitors) and only applies the rewriting RexShuttle to nodes that actually contain it, returning the original object otherwise. Adds identity (assertSame) regression tests. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Drop TimestampSubtractRewriter — superseded by upstream MinusAdapter (opensearch-project#21978) Upstream opensearch-project#21978 added MinusAdapter, the proper named ScalarFunctionAdapter for timestamp/date subtraction (t1 - t2 -> from_unixtime(to_unixtime(t1) - to_unixtime(t2))). It also coordinates with WidthBucketAdapter by deliberately leaving the MINUS(MAX OVER(), MIN OVER()) binning shape untouched. Our pre-Substrait TimestampSubtractRewriter rewrote every MINUS(timestamp, timestamp) including that binning shape, so keeping both risked clobbering the binning path. Remove the rewriter (and its test); MinusAdapter fully covers the case. The shared preprocessForSubstrait helper is retained for the remaining rewriters. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Bind distinct_count_approx after SQL-plugin emits APPROX_COUNT_DISTINCT (opensearch-project#5525) sql#5525 changed PPLBuiltinOperators.DISTINCT_COUNT_APPROX to emit a user-defined SqlAggFunction NAMED "APPROX_COUNT_DISTINCT" (DistinctCountApproxLogicalAggFunction). isthmus binds aggregate sigs by operator identity, not name, so this custom op has no substrait sig and grouped/ungrouped `stats distinct_count_approx(x)` fails with "Unable to find binding for call APPROX_COUNT_DISTINCT". (The `dc`->COUNT(DISTINCT) path works via OpenSearchDistinctCountRule/opensearch-project#22013; the named UDAF path does not.) Re-key the PplAggregateCallRewriter case from the old name DISTINCT_COUNT_APPROX to APPROX_COUNT_DISTINCT and remap the PPL marker to stock SqlStdOperatorTable.APPROX_COUNT_DISTINCT (which ADDITIONAL_AGGREGATE_SIGS binds to DataFusion approx_distinct), guarding against the stock operator itself. Remove the now-dead DISTINCT_COUNT_APPROX alias in AggregateFunction.fromNameOrError — the enum constant is APPROX_COUNT_DISTINCT, which valueOf resolves directly (addresses review comment). Verified: CalcitePPLAggregationIT.testCountDistinctApprox + testCountDistinctApproxWithAlias pass on a force-routed analytics-engine cluster. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Don't push filters below non-deterministic projects (fixes rand() predicates) `eval r = rand() | where r > 0` failed with "Comparison performance-delegation requires (RexInputRef, RexLiteral); got RAND()". Calcite's stock FILTER_PROJECT_TRANSPOSE only guards against window functions (!containsOver()), so it pushed the filter below the rand() project, inlining RAND() into the predicate — turning a delegatable ($ref > literal) comparison into RAND() > literal on the scan, which Lucene performance-delegation cannot serialize (and which must stay on the single in-memory engine, since each backend would draw different values). Replace the stock rule with FILTER_PROJECT_TRANSPOSE_DETERMINISTIC: a FilterProjectTransposeRule configured to refuse the transpose when the Project computes any non-deterministic expression (RexUtil.isDeterministic). Keeping the Filter above the Project preserves the clean ($ref > literal) shape evaluated in memory. Verified: CalcitePPLBuiltinFunctionIT.testRand passes on a force-routed analytics-engine cluster. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Restore distinct_count_approx binding on the analytics-engine route Validation requested in PR review (sandeshkr419) found that sql#5525 alone does NOT make `stats distinct_count_approx(x)` work on the analytics-engine route, so the engine-side handling removed in the prior commit is still required. On a fresh, force-routed cluster with sql#5525 deployed, CalcitePPLAggregationIT testCountDistinctApprox + testCountDistinctApproxWithAlias fail in two stages: 1. Planner stage: OpenSearchAggregateRule.resolveViableBackendsForCall reads aggCall.getAggregation().getName(), which is still "DISTINCT_COUNT_APPROX" (the DistinctCountApproxLogicalAggFunction marker). fromNameOrError then throws "No enum constant ...AggregateFunction.DISTINCT_COUNT_APPROX". 2. Substrait stage: the PplAggregateCallRewriter case keyed only on "APPROX_COUNT_DISTINCT" never matched the marker, so the unbound op reached isthmus -> "Unable to find binding for call DISTINCT_COUNT_APPROX($1)". sql#5525 only sets the substrait-emission name to APPROX_COUNT_DISTINCT, which is a later stage than both of the above, so the marker's runtime name still reaches them. Fixes: - Restore the DISTINCT_COUNT_APPROX -> APPROX_COUNT_DISTINCT alias in AggregateFunction.fromNameOrError (covers the planner path). - Match both "DISTINCT_COUNT_APPROX" and "APPROX_COUNT_DISTINCT" in the PplAggregateCallRewriter case (covers the substrait-binding path); keep the guard that skips the already-bound stock operator. - Add a fromNameOrError alias unit test. Verified on a force-routed analytics-engine cluster (parquet-backed indices, AE-routed): both tests pass; the surrounding 4-class IT sweep is unchanged at 6 pre-existing failures (date_format/strftime/percentile/nested-field), i.e. no regressions. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Add qa-module ITs for distinct_count_approx + min/max(boolean) Engine-side end-to-end regression coverage for the two analytics-engine binding fixes in this PR, run against the production /_plugins/_ppl surface on the qa-module's analytics-routed cluster (mirrors the SQL-repo CalcitePPLAggregationIT cases that proved the fixes): - DistinctCountApproxAggregationIT: grouped / aliased / dc-shorthand / ungrouped distinct_count_approx (guards the planner enum alias + rewriter rebind). - MinMaxBooleanAggregationIT: ungrouped / grouped / mixed boolean+string min/max (guards the boolean min/max substrait overload). Each IT creates its own parquet-backed (composite/parquet + lucene secondary) index, bulk-ingests a small fixed dataset, and asserts oracle values. All 7 tests pass (4 + 3). Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix date_format %c month padding; correct stale RAND comments Two small follow-ups surfaced while verifying the analytics-engine route: - date_format/strftime %c now emits the zero-padded month (Jan -> "01"), matching the PPL reference (SQL-plugin DateTimeFormatterUtil maps %c -> "MM" in date mode), which differs from stock MySQL's no-leading-zero %c. Time mode keeps the reference's single-"0" literal. Fixes the %c portion of CalciteDateTimeFunctionIT.testDateFormat and updates the Rust token-set unit test expectation accordingly. (The test's %U/%u/%V/%v week-number padding is a separate, unrelated date-format issue left for a follow-up.) - Correct two stale comments that described the old "drop the seed" RAND behavior; the adapter now rejects RAND(seed) with a clear error (addresses review feedback). No behavior change — comment/javadoc only. Verified on a force-routed analytics-engine cluster: date_format(ts,'%c') returns "01"; the now/sha2/dc/min-max ITs remain green (no regressions). Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Drop DISTINCT_COUNT_APPROX enum alias per review Remove the DISTINCT_COUNT_APPROX -> APPROX_COUNT_DISTINCT alias in AggregateFunction.fromNameOrError (and its unit test), and key the PplAggregateCallRewriter case on "APPROX_COUNT_DISTINCT" only, per @sandeshkr419's review. The SQL plugin (sql#5525) emits the operator named APPROX_COUNT_DISTINCT, which the enum resolves directly, so the legacy-name alias is no longer needed. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Address review: bulk NDJSON content-type, dc comments, error wording Review follow-ups (sandeshkr419): - QA ITs: send _bulk as application/x-ndjson via the per-index endpoint with bare {"index": {}} action metadata, matching the existing CountFastPathIT pattern. setJsonEntity alone defaults to application/json, which the bulk endpoint rejects. - Clarify the distinct_count_approx marker naming across layers: current runtime name is APPROX_COUNT_DISTINCT (sql#5525), DISTINCT_COUNT_APPROX is the legacy/defensive spelling; OpenSearchDistinctCountRule is the primary planner rewrite and PplAggregateCallRewriter is a late Substrait-emission defensive fallback. - Document the phase-specific nullability handling: the early planner rewrite preserves the marker's nullable type (LogicalAggregate.copy re-validates row type); the late Substrait rewrite pins NOT NULL (Calcite validates against the stock op's inferred type). - SPAN overflow message now includes the operands. - Drop "yet" from the seeded-RAND error so it doesn't imply imminent support. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Empty commit to retrigger CI No code change. The prior gradle-check failed in the Jenkins-trigger step (jq parse error polling the Jenkins API, 10 retries, empty result) — a transient CI-infra glitch unrelated to this PR's changes. Re-running checks. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Fix DatetimeCoverageIT %c assertions to match zero-padded month The date_format %c code change (no-leading-zero -> zero-padded "MM") aligns the analytics-engine Rust UDF with the legacy PPL contract: DateTimeFormatterUtil.DATE_HANDLERS maps %c -> "MM" (date_format dispatches through DATE_HANDLERS), so January renders as "01". The Rust code + unit test were updated for this, but the two DatetimeCoverageIT assertions still expected the old unpadded "1" — the gradle-check / sandbox-check failures. Update both assertions to "01", matching the canonical merged ITs (ppl/DateTimeFunctionIT and sql/DateTimeFunctionIT both assert "Sat Jan 01 31st ..." for the same spec). Test-only. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Enable now()/rand(seed)/sha2/span-month on the analytics-engine route + review fixes Enables these PPL functions on the force-routed analytics-engine (DataFusion) path and addresses review feedback: - now()-family: NowFspAdapter handles now([fsp]) / current_timestamp / localtimestamp, where fsp is MySQL fractional-seconds precision (0-6). - rand([seed]): RandSeedAdapter. - sha2(input, bitLen): Sha2FunctionAdapter -> encode(digest(input, 'shaN'), 'hex'), with a backend fail-clear guard for unsupported bit lengths (TODO to move validation to the frontend once the handoff carries function metadata). - span() month bucketing: SpanAdapter. - strftime: os_strftime.rs. - date_format %c: zero-padded month (DatetimeCoverageIT updated to match the merged ppl/sql DateTimeFunctionIT expectations). - PlannerImpl: don't push filters below non-deterministic projects; rand() in a pushed-down predicate would be re-evaluated and draw a fresh value. Documented as a semantic-correctness guard. - DataFusionFragmentConvertor: shared Substrait preprocess entry point. - New QA ITs: MinMaxBooleanAggregationIT. Review fixes: removed the late defensive APPROX_COUNT_DISTINCT fallback in PplAggregateCallRewriter (fail loud instead); restored the Substrait-layer assessment TODO; clarified fsp in NowFspAdapter; tidied AggregateFunction. distinct_count_approx on the analytics-engine route is handled upstream by opensearch-project#22120 and is not part of this PR. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> * [analytics-engine] Add 2-shard reduce coverage for boolean min/max Addresses review feedback to cover the multi-shard case for boolean min/max. min(flag)/max(flag) over the merge_coverage dataset's boolean field are added as golden cases in the TwoShardReduceTestCase suite (opensearch-project#21951 convention), so they run at 1 shard and 2 shards with a differential equality check plus a pinned golden, exercising the two-phase per-shard-partial then coordinator-merge reduce path. The merge_coverage flag field has 15 false and 15 true rows, so min=false and max=true regardless of shard layout. Signed-off-by: Eric Wei <mengwei.eric@gmail.com> --------- Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
Description
Adds a test suite to sandbox/qa for 2 shard setups. Also unmutes single shard tests that are now passing.
see sandbox/qa/analytics-engine-rest/src/test/resources/datasets/merge_coverage/README.md.
for current list of skipped/failing tests.
How
merge_coverage is provisioned at 1 shard (oracle) and 2 shards; each %INDEX% query runs against both. Exact tier asserts the two results are equal (unordered, numeric-tolerant, multiset-normalized for
values/list); curated shapes also pin a golden. Approximate tier (distinct_count/percentile) asserts the 2-shard result within tolerance of a required golden, since sketches drift across a merge. head/limit
carry a unique id tie-breaker for determinism. Indices are composite parquet + lucene, so the planner's per-arm backend selection is exercised.
Structure: one abstract TwoShardReduceTestCase (all machinery) + thin per-category ITs that only declare their dataset tier and known-issue set — TwoShardAggregationIT, TwoShardScalarIT, TwoShardShapeIT,
TwoShardJoinIT, TwoShardCommandIT, plus IpFieldMultiShardIT.
Currently muted tests:
Here's the full list of currently-muted tests (@AwaitsFix) in analytics-engine-rest
Related Issues
Resolves #[Issue number to be closed when this PR is merged]
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.