Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
f95c2a8
Add capability and YAML tests for request and SET time_zone parameters
ivancea Oct 17, 2025
569d9e5
Add SET CSV tests, and adapted CsvTests to work with SET statements
ivancea Oct 17, 2025
cc15c9f
Escape ; in csv tests with backslash
ivancea Oct 17, 2025
449abe6
Randomize configuration in function tests, and make it static for the…
ivancea Oct 17, 2025
5dec7c3
[CI] Auto commit changes from spotless
Oct 17, 2025
fedbf4f
Merge branch 'main' into esql-time-zone-tests
ivancea Oct 27, 2025
b088abf
Moved custom config cases to another method
ivancea Oct 27, 2025
ee1316c
Add comment on function with config dserialization randomization
ivancea Oct 27, 2025
13d8b4b
Fix multi cluster tests to accept SET statements
ivancea Oct 27, 2025
d794329
Merge branch 'main' into esql-time-zone-tests
ivancea Oct 27, 2025
2713280
Fix configuration and source matching in random tests
ivancea Oct 28, 2025
f76f5ec
Added function tests for timezones
ivancea Oct 28, 2025
9005c78
Fix typo
ivancea Oct 28, 2025
fad189f
Fixed capability requirement
ivancea Oct 28, 2025
4f62f23
Fix ScalbTests
ivancea Oct 28, 2025
4c5adc0
[CI] Auto commit changes from spotless
Oct 28, 2025
3f233bf
Add configuration to DATE_TRUNC and related functions
ivancea Oct 28, 2025
d81729b
Merge branch 'main' into esql-time-zone-tests
ivancea Oct 29, 2025
8b8a115
Merge branch 'esql-time-zone-tests' into esql-datetrunc-timezone
ivancea Oct 29, 2025
a11d4b8
Merge branch 'main' into esql-datetrunc-timezone
ivancea Oct 29, 2025
ebd00dd
Added a ConfigurationFUnction interface and fixed tests with static c…
ivancea Oct 29, 2025
de30da7
WIP: Initial DateTrunc unit tests
ivancea Oct 29, 2025
dcce704
Reuse DateTrunc tests in Bucket and TBucket
ivancea Oct 30, 2025
24e99c1
Added unit tests for timezones
ivancea Oct 31, 2025
dd523d5
Extracted matchers
ivancea Oct 31, 2025
ad32a98
Moved matchers to a common place
ivancea Oct 31, 2025
539b7a7
Extracted date_trunc csv tests to date
ivancea Oct 31, 2025
5053983
Update docs/changelog/137450.yaml
ivancea Oct 31, 2025
a75301c
Fix benchmark
ivancea Oct 31, 2025
7e5c01d
Added CSV tests for timezones on affected functions
ivancea Oct 31, 2025
544c4cf
Fixed Rounding builders being reused
ivancea Oct 31, 2025
e499df4
Use DateTrunc zoneId instead of configuration in rule
ivancea Oct 31, 2025
205a15c
Undo roundings test
ivancea Oct 31, 2025
46debec
Fix tests
ivancea Oct 31, 2025
29707e5
Fixed tests and their testcase names
ivancea Nov 3, 2025
a40f913
Merge branch 'main' into esql-datetrunc-timezone
ivancea Nov 3, 2025
00e2d67
Merge branch 'main' into esql-datetrunc-timezone
ivancea Nov 3, 2025
e3ae6ba
Added tests for locale and timezone
ivancea Nov 4, 2025
096541f
Use TypeSafeMatcher on BytesRef matcher
ivancea Nov 5, 2025
b9100bd
Removed ESQL dependency from the matcher docs
ivancea Nov 5, 2025
f6d6d2c
Merge branch 'main' into esql-datetrunc-timezone
ivancea Nov 5, 2025
de3dd41
Moved matchers to server.test
ivancea Nov 5, 2025
ee0af20
Avoid renaming timestamp in TS functions
ivancea Nov 5, 2025
44ad461
Format
ivancea Nov 5, 2025
84b882e
Add extra cases to dateCases
ivancea Nov 5, 2025
2f0b404
[CI] Auto commit changes from spotless
Nov 5, 2025
096d9b3
Add tests for every combination of cases
ivancea Nov 6, 2025
fdc0cf8
Merge branch 'main' into esql-datetrunc-timezone
ivancea Nov 6, 2025
4dc641f
[CI] Auto commit changes from spotless
Nov 6, 2025
d46fc5b
Fixed date intervals with +1 units, and added negative years tests
ivancea Nov 6, 2025
bdd7138
Updated duration tests to cover all timezone kinds
ivancea Nov 7, 2025
55c4efd
Reorganized tests
ivancea Nov 7, 2025
d25ab57
Remove redundant test cases
ivancea Nov 7, 2025
b7d63b9
Added midnight DST tests
ivancea Nov 7, 2025
a3e592c
[CI] Auto commit changes from spotless
Nov 7, 2025
3b34ef6
Fixed midnight DST test
ivancea Nov 7, 2025
e76d5b2
Merge branch 'esql-datetrunc-timezone' of github.com:ivancea/elastics…
ivancea Nov 7, 2025
2194e83
Merge branch 'main' into esql-datetrunc-timezone
ivancea Nov 7, 2025
e26c61d
Merge branch 'esql-datetrunc-timezone' into esql-date-function-tests
ivancea Nov 7, 2025
f9886b1
Simplified timezones for tests
ivancea Nov 10, 2025
df8b7c6
Fixed signature generation failing because of nanos assumptions
ivancea Nov 10, 2025
4723f43
Simplified timezones for tests
ivancea Nov 10, 2025
561f99c
Fixed signature generation failing because of nanos assumptions
ivancea Nov 10, 2025
ba354eb
Merge branch 'esql-datetrunc-timezone' into esql-date-function-tests
ivancea Nov 10, 2025
16bdcf9
Merge branch 'main' into esql-date-function-tests
ivancea Nov 14, 2025
0c91474
Move string matcher to ReadableMatchers
ivancea Nov 14, 2025
f162409
Remove unused method
ivancea Nov 14, 2025
ff51e44
Merge branch 'main' into esql-date-function-tests
ivancea Nov 17, 2025
ca5be79
Merge branch 'main' into esql-date-function-tests
ivancea Nov 18, 2025
5b5a330
Remove redundant qualifier
ivancea Nov 18, 2025
f5f65b4
Merge branch 'main' into esql-date-function-tests
ivancea Nov 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package org.elasticsearch.test;

import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.time.DateFormatter;
import org.elasticsearch.common.time.DateUtils;
import org.hamcrest.Description;
Expand Down Expand Up @@ -39,6 +40,13 @@ public static DateNanosMatcher matchesDateNanos(String date) {
return new DateNanosMatcher(date);
}

/**
* Test matcher for BytesRef that expects BytesRefs, but describes the errors as strings, for better readability.
*/
public static StringBytesRefMatcher matchesBytesRef(String string) {
return new StringBytesRefMatcher(string);
}

public static class DateMillisMatcher extends TypeSafeMatcher<Long> {
private final long timeMillis;

Expand Down Expand Up @@ -84,4 +92,30 @@ public void describeTo(Description description) {
description.appendText(dateFormatter.formatNanos(timeNanos));
}
}

public static class StringBytesRefMatcher extends TypeSafeMatcher<BytesRef> {
private final String string;
private final BytesRef bytesRef;

public StringBytesRefMatcher(String string) {
this.string = string;
this.bytesRef = new BytesRef(string);
}

@Override
protected boolean matchesSafely(BytesRef item) {
return item.equals(bytesRef);
}

@Override
public void describeMismatchSafely(BytesRef item, Description description) {
description.appendText("was ").appendValue(item.utf8ToString());
}

@Override
public void describeTo(Description description) {
description.appendText(string);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public static Configuration randomConfiguration(String query, Map<String, Map<St
);
}

public static ConfigurationBuilder randomConfigurationBuilder() {
return new ConfigurationBuilder(randomConfiguration());
}

private static QueryPragmas randomQueryPragmas() {
return new QueryPragmas(
Settings.builder().put(QueryPragmas.DATA_PARTITIONING.getKey(), randomFrom(DataPartitioning.values())).build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,7 @@ public static List<TestCaseSupplier> mapTestCases(
) {
return suppliers.stream()
.map(supplier -> new TestCaseSupplier(supplier.name(), supplier.types(), () -> mapper.apply(supplier.get())))
.toList();
.collect(Collectors.toCollection(ArrayList::new));
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this not do the same thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

.toList() returned list is immutable, and I wanted to update it after the mapping in some tests (Keep adding test cases).
The (current) usecase for this method was to update the config (or anything really) in TestCaseSuppliers provided by other utilities. Specifically the unary() and similar methods we have that magically generate them. I didn't want to add a weird callback/extra configuration to each of those functions.

}

public static final class TestCase {
Expand Down Expand Up @@ -1822,19 +1822,6 @@ public Object extra() {
return extra;
}

/**
* Build a new {@link TestCase} with the {@link #TEST_CONFIGURATION}.
* <p>
* The source is also set to match the configuration
* </p>
*
* @deprecated Use a custom configuration instead, and test the results.
*/
@Deprecated
public TestCase withStaticConfiguration() {
return withConfiguration(TEST_SOURCE, TEST_CONFIGURATION);
}

/**
* Build a new {@link TestCase} with new {@link #configuration}.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongSupplier;
Expand Down Expand Up @@ -124,7 +125,7 @@ private static void dateCases(List<TestCaseSupplier> suppliers, String name, Lon
+ "rounding=Rounding[DAY_OF_MONTH in Z][fixed to midnight]]",
DataType.DATETIME,
resultsMatcher(args)
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
// same as above, but a low bucket count and datetime bounds that match it (at hour span)
suppliers.add(new TestCaseSupplier(name, List.of(DataType.DATETIME, DataType.INTEGER, fromType, toType), () -> {
Expand All @@ -138,7 +139,7 @@ private static void dateCases(List<TestCaseSupplier> suppliers, String name, Lon
"DateTruncDatetimeEvaluator[fieldVal=Attribute[channel=0], rounding=Rounding[3600000 in Z][fixed]]",
DataType.DATETIME,
equalTo(Rounding.builder(Rounding.DateTimeUnit.HOUR_OF_DAY).build().prepareForUnknown().round(date.getAsLong()))
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
}
}
Expand Down Expand Up @@ -253,7 +254,7 @@ private static void dateCasesWithSpan(
"DateTruncDatetimeEvaluator[fieldVal=Attribute[channel=0], rounding=Rounding" + spanStr + "]",
DataType.DATETIME,
resultsMatcher(args)
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
}

Expand All @@ -274,7 +275,7 @@ private static void dateNanosCasesWithSpan(
Matchers.startsWith("DateTruncDateNanosEvaluator[fieldVal=Attribute[channel=0], rounding=Rounding["),
DataType.DATE_NANOS,
resultsMatcher(args)
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
}

Expand All @@ -293,7 +294,7 @@ private static void dateNanosCases(List<TestCaseSupplier> suppliers, String name
Matchers.startsWith("DateTruncDateNanosEvaluator[fieldVal=Attribute[channel=0], rounding=Rounding["),
DataType.DATE_NANOS,
resultsMatcher(args)
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
// same as above, but a low bucket count and datetime bounds that match it (at hour span)
suppliers.add(new TestCaseSupplier(name, List.of(DataType.DATE_NANOS, DataType.INTEGER, fromType, toType), () -> {
Expand All @@ -307,7 +308,7 @@ private static void dateNanosCases(List<TestCaseSupplier> suppliers, String name
Matchers.startsWith("DateTruncDateNanosEvaluator[fieldVal=Attribute[channel=0], rounding=Rounding["),
DataType.DATE_NANOS,
equalTo(Rounding.builder(Rounding.DateTimeUnit.HOUR_OF_DAY).build().prepareForUnknown().round(date.getAsLong()))
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongSupplier;
Expand Down Expand Up @@ -105,7 +106,7 @@ private static void dateCasesWithSpan(
"DateTruncDatetimeEvaluator[fieldVal=Attribute[channel=0], rounding=Rounding" + spanStr + "]",
DataType.DATETIME,
resultsMatcher(args)
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
}

Expand All @@ -125,7 +126,7 @@ private static void dateNanosCasesWithSpan(
Matchers.startsWith("DateTruncDateNanosEvaluator[fieldVal=Attribute[channel=0], rounding=Rounding["),
DataType.DATE_NANOS,
resultsMatcher(args)
).withStaticConfiguration();
).withConfiguration(TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC));
}));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@

package org.elasticsearch.xpack.esql.expression.function.scalar;

import org.elasticsearch.xpack.esql.ConfigurationBuilder;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.session.Configuration;

import java.time.ZoneId;
import java.util.List;
import java.util.Locale;

import static org.elasticsearch.xpack.esql.ConfigurationTestUtils.randomConfiguration;
import static org.elasticsearch.xpack.esql.ConfigurationTestUtils.randomConfigurationBuilder;
import static org.elasticsearch.xpack.esql.ConfigurationTestUtils.randomTables;
import static org.elasticsearch.xpack.esql.SerializationTestUtils.assertSerialization;
import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.TEST_SOURCE;
Expand Down Expand Up @@ -48,6 +49,14 @@ public void testSerializationWithConfiguration() {
}

protected static Configuration configurationForTimezone(ZoneId zoneId) {
return new ConfigurationBuilder(randomConfiguration()).query(TEST_SOURCE.text()).zoneId(zoneId).build();
return randomConfigurationBuilder().query(TEST_SOURCE.text()).zoneId(zoneId).build();
}

protected static Configuration configurationForLocale(Locale locale) {
return randomConfigurationBuilder().query(TEST_SOURCE.text()).locale(locale).build();
}

protected static Configuration configurationForTimezoneAndLocale(ZoneId zoneId, Locale locale) {
return randomConfigurationBuilder().query(TEST_SOURCE.text()).zoneId(zoneId).locale(locale).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;

import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.time.DateUtils;
import org.elasticsearch.compute.operator.DriverContext;
import org.elasticsearch.xpack.esql.EsqlTestUtils;
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
Expand All @@ -24,12 +25,15 @@
import org.elasticsearch.xpack.esql.session.Configuration;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.TEST_SOURCE;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
Expand All @@ -46,30 +50,6 @@ public static Iterable<Object[]> parameters() {
for (var stringType : DataType.stringTypes()) {
suppliers.addAll(
List.of(
new TestCaseSupplier(
List.of(stringType, DataType.DATETIME),
() -> new TestCaseSupplier.TestCase(
List.of(
new TestCaseSupplier.TypedData(new BytesRef("YeAr"), stringType, "chrono"),
new TestCaseSupplier.TypedData(1687944333000L, DataType.DATETIME, "date")
),
"DateExtractMillisEvaluator[value=Attribute[channel=1], chronoField=Attribute[channel=0], zone=Z]",
DataType.LONG,
equalTo(2023L)
).withStaticConfiguration()
),
new TestCaseSupplier(
List.of(stringType, DataType.DATE_NANOS),
() -> new TestCaseSupplier.TestCase(
List.of(
new TestCaseSupplier.TypedData(new BytesRef("YeAr"), stringType, "chrono"),
new TestCaseSupplier.TypedData(1687944333000000000L, DataType.DATE_NANOS, "date")
),
"DateExtractNanosEvaluator[value=Attribute[channel=1], chronoField=Attribute[channel=0], zone=Z]",
DataType.LONG,
equalTo(2023L)
).withStaticConfiguration()
),
new TestCaseSupplier(
List.of(stringType, DataType.DATE_NANOS),
() -> new TestCaseSupplier.TestCase(
Expand All @@ -80,7 +60,7 @@ public static Iterable<Object[]> parameters() {
"DateExtractNanosEvaluator[value=Attribute[channel=1], chronoField=Attribute[channel=0], zone=Z]",
DataType.LONG,
equalTo(123456L)
).withStaticConfiguration()
).withConfiguration(TestCaseSupplier.TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC))
),
new TestCaseSupplier(
List.of(stringType, DataType.DATETIME),
Expand All @@ -93,7 +73,7 @@ public static Iterable<Object[]> parameters() {
"DateExtractMillisEvaluator[value=Attribute[channel=1], chronoField=Attribute[channel=0], zone=Z]",
DataType.LONG,
is(nullValue())
).withStaticConfiguration()
).withConfiguration(TestCaseSupplier.TEST_SOURCE, configurationForTimezone(ZoneOffset.UTC))
.withWarning(
"Line 1:1: evaluation of [source] failed, treating result as null. Only first 20 failures recorded."
)
Expand All @@ -107,9 +87,46 @@ public static Iterable<Object[]> parameters() {
);
}

suppliers.addAll(casesFor("YeAr", "2023-11-04T16:13:12Z", "Z", 2023));
suppliers.addAll(casesFor("day_of_month", "2020-01-01T00:00:00Z", "America/New_York", 31));
suppliers.addAll(casesFor("month_of_year", "2020-06-30T23:00:00Z", "Europe/Paris", 7));

return parameterSuppliersFromTypedDataWithDefaultChecks(true, suppliers);
}

private static List<TestCaseSupplier> casesFor(String field, String date, String zoneIdName, long expectedResult) {
long dateMillis = Instant.parse(date).toEpochMilli();
ZoneId zoneId = ZoneId.of(zoneIdName);
return List.of(
new TestCaseSupplier(
field + " - " + date + " (millis) - " + zoneId,
List.of(DataType.KEYWORD, DataType.DATETIME),
() -> new TestCaseSupplier.TestCase(
List.of(
new TestCaseSupplier.TypedData(field, DataType.KEYWORD, "field"),
new TestCaseSupplier.TypedData(dateMillis, DataType.DATETIME, "date")
),
"DateExtractMillisEvaluator[value=Attribute[channel=1], chronoField=Attribute[channel=0], zone=" + zoneId + "]",
DataType.LONG,
equalTo(expectedResult)
).withConfiguration(TEST_SOURCE, configurationForTimezone(zoneId))
),
new TestCaseSupplier(
field + " - " + date + " (nanos) - " + zoneId,
List.of(DataType.KEYWORD, DataType.DATE_NANOS),
() -> new TestCaseSupplier.TestCase(
List.of(
new TestCaseSupplier.TypedData(field, DataType.KEYWORD, "field"),
new TestCaseSupplier.TypedData(DateUtils.toNanoSeconds(dateMillis), DataType.DATE_NANOS, "date")
),
"DateExtractNanosEvaluator[value=Attribute[channel=1], chronoField=Attribute[channel=0], zone=" + zoneId + "]",
DataType.LONG,
equalTo(expectedResult)
).withConfiguration(TEST_SOURCE, configurationForTimezone(zoneId))
)
);
}

public void testAllChronoFields() {
long epochMilli = 1687944333123L;
ZonedDateTime date = Instant.ofEpochMilli(epochMilli).atZone(EsqlTestUtils.TEST_CFG.zoneId());
Expand Down
Loading