Skip to content

ES|QL: Add TRANGE ES|QL function#136441

Merged
leontyevdv merged 36 commits intoelastic:mainfrom
leontyevdv:feature/esql-trange-function
Oct 23, 2025
Merged

ES|QL: Add TRANGE ES|QL function#136441
leontyevdv merged 36 commits intoelastic:mainfrom
leontyevdv:feature/esql-trange-function

Conversation

@leontyevdv
Copy link
Contributor

@leontyevdv leontyevdv commented Oct 11, 2025

Add a new ES|QL function that filters @timestamp values for the given time range. It transforms into a fileter and is pushed down to Lucene.

What is currently supported

  1. Explicit start and end times:
TRANGE("2024-05-12T12:00:00", "2024-05-12T15:30:00") - [explicit start; explicit end]
TRANGE(1715504400000, 1715517000000) - [explicit start in millis; explicit end in millis]
  1. Offset from now:
TRANGE(1h) - [now - 1h; now] - supports time_duration (1h, 1min, etc) and period (1day, 1month, etc.)

What is not supported and requires consideration

  1. Explicit start time:
TRANGE("2024-05-12T12:00:00") - [explicit start; now]
TRANGE(1715504400000) - [explicit start in millis; now]
  1. Modifier for an explicit start time (start time with offset):
TRANGE("2024-05-12T12:00:00", 1h) - [explicit start; start + 1h] - supports time_duration (1h, 1min, etc) and period (1day, 1month, etc.)
TRANGE("2024-05-12T12:00:00", -1h) - [explicit start; start - 1h] - supports time_duration (1h, 1min, etc) and period (1day, 1month, etc.)
TRANGE(1715504400000, 1h) - [explicit start in millis; start + 1h] - supports time_duration (1h, 1min, etc) and period (1day, 1month, etc.)
TRANGE(1715504400000, -1h) - [explicit start in millis; start - 1h] - supports time_duration (1h, 1min, etc) and period (1day, 1month, etc.)
  1. TRANGE(1w + 1h, 1w)

Closes #135599

Add a new ES|QL function that filters @timestamp values for the given
time range.

Closes elastic#135599
@leontyevdv leontyevdv requested a review from dnhatn October 11, 2025 00:02
@leontyevdv leontyevdv self-assigned this Oct 11, 2025
@leontyevdv leontyevdv added >enhancement Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) :StorageEngine/TSDB You know, for Metrics :Analytics/ES|QL AKA ESQL Team:StorageEngine :StorageEngine/ES|QL Timeseries / metrics / PromQL / logsdb capabilities in ES|QL labels Oct 11, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-analytical-engine (Team:Analytics)

@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-storage-engine (Team:StorageEngine)

@elasticsearchmachine
Copy link
Collaborator

Hi @leontyevdv, I've created a changelog YAML for you.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 11, 2025

@github-actions
Copy link
Contributor

ℹ️ 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 overview

When to use applies_to tags:

✅ At the page level to indicate which products/deployments the content applies to (mandatory)
✅ When features change state (e.g. preview, ga) in a specific version
✅ When availability differs across deployments and environments

What NOT to do:

❌ Don't remove or replace information that applies to an older version
❌ Don't add new information that applies to a specific version without an applies_to tag
❌ Don't forget that applies_to tags can be used at the page, section, and inline level

🤔 Need help?

@leontyevdv leontyevdv requested a review from a team October 11, 2025 00:10
Add a new ES|QL function that filters @timestamp values for the given
time range.

Closes elastic#135599
@dnhatn
Copy link
Member

dnhatn commented Oct 12, 2025

Thank you, Dima! Is there a reason we don't translate TRANGE to GreaterThanEquals() or BinaryLogicOperation(GreaterThanEquals, LessThanOrEquals)?

@leontyevdv
Copy link
Contributor Author

Thank you, Dima! Is there a reason we don't translate TRANGE to GreaterThanEquals() or BinaryLogicOperation(GreaterThanEquals, LessThanOrEquals)?

Hi @dnhatn ! Thank you for the review! Initially, I implemented TRANGE as a surrogate and struggled with the tests for quite a while. The issue was with the assertion ("Duplicate name ids are not allowed in layouts"): see Layout. Then, inspired by the StartsWith function, I rewrote it using TranslationAware.SingleValueTranslationAware directly.

I have the surrogate version in my stash. We can take a look at it together. Wdyt?

# Conflicts:
#	x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-rate.csv-spec
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
Replace asQuery by a surrogate

Closes elastic#135599
Replace asQuery by a surrogate

Closes elastic#135599
Fix constructor visibility

Closes elastic#135599
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
@leontyevdv leontyevdv requested a review from bpintea October 15, 2025 15:51
@dnhatn
Copy link
Member

dnhatn commented Oct 17, 2025

I'll postpone the TRANGE(1w + 1h, 1w) one. The function will guarantee the order and types of the arguments and will be failing with the wrong input.

Yes, this case needs more thought!

Remove wrong csv test

Closes elastic#135599
Copy link
Contributor

@bpintea bpintea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good.

Copy link
Member

@dnhatn dnhatn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left two questions, but this looks good. Thanks Dima!

Address PR review comments

Closes elastic#135599
Address PR review comments

Closes elastic#135599
Copy link
Member

@dnhatn dnhatn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, Dima! Thank you for all the iterations. Could you please wait for Bogdan’s approval before merging?

Address PR review comments

Closes elastic#135599
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
Address PR review comments

Closes elastic#135599
@leontyevdv leontyevdv requested a review from bpintea October 21, 2025 13:07
Copy link
Contributor

@bpintea bpintea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a set of notes, but LGTM otherwise.

Literal timestampLiteral = as(Alias.unwrap(eval.fields().getFirst()), Literal.class);
long expectedTimestamp = DateUtils.asDateTimeWithNanos(timestampValue, DateUtils.UTC).toInstant().toEpochMilli();
assertThat(timestampLiteral.fold(FoldContext.small()), equalTo(expectedTimestamp));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should continue with the inspection of the plan here: what happens here is that since timestampValue is within the intervals passed to TRANGE, the WHERE should fold away (it's always true). The test should confirm that. We usually inspect the plan all the way down to the source (just like in the null folding case, though there's compacter, cause it's folded down to just one node).

Optionally, you could also add a test that folds to false.

BTW, you could add a similar test as a CSV spec, to also check the results. But optional.

Copy link
Contributor Author

@leontyevdv leontyevdv Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Bogdan! I added inspection all the way down to the source, also added a test which tests folding to false.

}

if (rangeStart.isAfter(rangeEnd)) {
throw new InvalidArgumentException("TRANGE rangeStart time [{}] must be before rangeEnd time [{}]", rangeStart, rangeEnd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test for this condition?
Ideally for the one above too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Added to TRangeErrorTests

Address PR review's comments

Closes elastic#135599
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
#	x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java
Copy link
Contributor

@bpintea bpintea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some notes on instantiating the function. Otherwise all good.

this(source, new UnresolvedAttribute(source, MetadataAttribute.TIMESTAMP_FIELD), first, second, configuration);
}

public TRange(Source source, Expression timestamp, Expression first, Expression second, Configuration configuration) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be protected, as then the public one can be called unanbigously directly from the function registry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this and removed the bic() method from the function registry.

def(MonthName.class, MonthName::new, "month_name"),
def(Now.class, Now::new, "now") },
def(Now.class, Now::new, "now"),
def(TRange.class, bic(TRange::new), "trange") },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having one public c'tor in TRange will allow this direct call, since we have already a BinaryConfigurationAwareBuilder. The bic method can then be removed.

Suggested change
def(TRange.class, bic(TRange::new), "trange") },
def(TRange.class, TRange::new, "trange") },
leontyevdv and others added 8 commits October 22, 2025 14:51
Make constructor protected

Closes elastic#135599
Make constructor public

Closes elastic#135599
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
#	x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
def(MonthName.class, MonthName::new, "month_name"),
def(Now.class, Now::new, "now") },
def(Now.class, Now::new, "now"),
def(TRange.class, bic(TRange::new), "trange") },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leontyevdv, hopefully this will make the call unambiguous and also have EsqlNodeSubclassTests tests pass.

Suggested change
def(TRange.class, bic(TRange::new), "trange") },
def(TRange.class, (BinaryConfigurationAwareBuilder<TRange>)TRange::new, "trange") },
@leontyevdv leontyevdv merged commit d0555e5 into elastic:main Oct 23, 2025
34 checks passed
fzowl pushed a commit to voyage-ai/elasticsearch that referenced this pull request Nov 3, 2025
* ES|QL: Add TRANGE ES|QL function

Add a new ES|QL function that filters @timestamp values for the given
time range.

Closes elastic#135599
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Analytics/ES|QL AKA ESQL >enhancement :StorageEngine/ES|QL Timeseries / metrics / PromQL / logsdb capabilities in ES|QL :StorageEngine/TSDB You know, for Metrics Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) Team:StorageEngine v9.3.0

5 participants