ESQL: gate heterogeneous FROM behind a feature flag#154987
Conversation
|
Pinging @elastic/es-analytical-engine (Team:Analytics) |
7db2bab to
29c91e4
Compare
🔍 Preview links for changed docs⏳ Building and deploying preview... View progress This comment will be updated with preview links when the build is complete. |
ℹ️ Important: Docs version tagging👋 Thanks for updating the docs! Just a friendly reminder that our docs are now cumulative. This means all 9.x versions are documented on the same page and published off of the main branch, instead of creating separate pages for each minor version. We use applies_to tags to mark version-specific features and changes. Expand for a quick overviewWhen to use applies_to tags:✅ At the page level to indicate which products/deployments the content applies to (mandatory) What NOT to do:❌ Don't remove or replace information that applies to an older version 🤔 Need help?
|
29c91e4 to
33ebd3d
Compare
33ebd3d to
7dbf1ca
Compare
7dbf1ca to
89d3e29
Compare
Add the esql_heterogeneous_from feature flag (off in release builds). When it is off, a FROM is rejected if, for what the caller is authorized to read, it resolves to both a dataset and an index-like target (index/alias/data stream). Resolving to only datasets works (any expression, wildcards and exclusions included); resolving to only index-likes works. Expression form is never a factor — only what it resolves to. The index-like side is detected from the security-narrowed request indices, so a wildcard that also matched an index the caller cannot read is not a mix: the query reads the dataset, is not rejected, and no error names the unreadable index. Data streams are included in that narrowing. The check runs in DatasetRewriter.rewriteOne, before index field-caps and external-source schema resolution, so a rejected query never resolves a dataset schema. The flag is enabled for the esql test tasks so the on behavior runs in release-tests. The resolve response is local-only, so there is no wire change.
89d3e29 to
421c0d9
Compare
What this is about
FROMcurrently lets one query read a dataset and an index-like target (index, alias, data stream) together — a heterogeneous FROM, whether written explicitly (FROM my_index, my_dataset) or produced when a wildcard spans both (FROM logs_*). We want that off in released builds while the shape is still being stabilized, without disturbing queries that read only datasets or only indices.What this PR does
Adds a feature flag,
esql_heterogeneous_from(off by default in released builds, on in snapshot/dev). When it's off, the rule is simply:FROM ds*, exclusions, exact names — all fine. What matters is only what the expression resolves to.FROM data*where the caller can read the dataset but not an index the wildcard also matched is not a mix — it reads the dataset, isn't rejected, and no error names the unreadable index. (The un-narrowed index branch is still built and then pruned downstream by field-caps security, exactly as the flag-on path does — no index data is read.)When the flag is on (snapshot/dev), heterogeneous FROM behaves exactly as before.
The decision runs in
DatasetRewriter.rewriteOne, during dataset resolution — before index field-caps and before any external-source schema discovery. So a query that will be rejected never resolves a dataset schema, and a query that resolves to only indices never builds a dataset branch: no wasted dataset schema I/O in either case.Why detect against the authorized set
An earlier version keyed the "index" side off the un-narrowed pattern. Adversarial review found that over-rejected on secured clusters:
FROM data*where the caller could read only the dataset was rejected, and the 400 named the unreadable index — a false-reject plus an existence-oracle. Detecting the index-like side from the security-narrowed request indices removes both: a name the caller can't see isn't counted and is never named.Does it work?
DatasetRewriterTestscover the flag-off paths: a wildcard resolving to both is rejected; a wildcard resolving to only datasets reads them; dataset-only and multi-dataset FROMs resolve; an explicit dataset + index is rejected; on a secured cluster a wildcard spanning a dataset + an unauthorized index is not rejected and leaks nothing; a genuine mix names only the authorized index; and the flag defaults off in release builds.FromDatasetIT,FromDatasetSubqueryIT, the External*ITs) run with the flag enabled — the same way the datasets feature flag itself is enabled for tests inbuild.gradle— so the on behavior is exercised in both snapshot and release-tests.DatasetRewriterTests,DatasetResolverTests, the flag-on ITs,spotlessApply, andforbiddenApis(main / test / internalClusterTest).What's in the diff
DatasetRewriter: the flag constant, the authorized-mix gate inrewriteOne, anauthorizedNonDatasetNamesfield on the resolution computed from the security-narrowed request indices, and package-private overloads that thread the enabled state through for tests.EsqlResolveDatasetAction: carry the authorized non-dataset set on the (local-only) response; include data streams in the request's security narrowing.DatasetResolver/DatasetRewriterTests/DatasetResolverTests: thread the field and cover the behavior.x-pack/plugin/esql/build.gradle: enable the flag for the esql test tasks (alongside the existing dataset flags), so the on behavior is exercised in release-tests.No wire/transport-version change — the resolve response is local-only.
What's out of scope
A cross-relation mix written as a subquery (
FROM idx, (FROM ds)) still runs — those are separate relations, each homogeneous — and the cross-project (CPS) preserved-wildcard path, which is serverless-only and not reachable on a stateful release build. Neither is the wildcard-spanning-both case this targets.To be backported to 9.5.