Skip to content

Conversation

@craigtaverner
Copy link
Contributor

@craigtaverner craigtaverner commented Apr 2, 2025

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the LOOKUP JOIN command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.

For example, if we are testing the pairs (double, double), (double, float), (float, double) and (float, float), we will create the following indexes:

index_double_double
Index containing a single document with a field of type 'double' like:
        {
            "field_double": 1.0,  // this is mapped as type 'double'
            "other": "value"
        }
    
index_double_float
Index containing a single document with a field of type 'float' like:
        {
            "field_double": 1.0,  // this is mapped as type 'float' (a float with the name of the main index field) 
            "other": "value"
        }
    
index_float_double
Index containing a single document with a field of type 'double' like:
        {
            "field_float": 1.0,  // this is mapped as type 'double' (a double with the name of the main index field)
            "other": "value"
        }
    
index_float_float
Index containing single document with a field of type 'float' like:
        {
            "field_float": 1.0,  // this is mapped as type 'float'
            "other": "value"
        }
    
index
Index containing document like:
        {
            "field_double": 1.0,  // this is mapped as type 'double'
            "field_float": 1.0    // this is mapped as type 'float'
        }
    

Note that the lookup indexes have fields with a name that matches the type in the main index, and not the type actually used in the lookup index. Instead, the mapped type should be the type of the right-hand side of the pair being tested.
Then we can run queries like:

    FROM index | LOOKUP JOIN index_double_float ON field_double | KEEP other

And assert that the result exists and is equal to "value".

Checklist:

  • Replicate existing KEYWORD/TEXT tests
  • Test Float/Double combinations
  • Test all integer type combinations
  • Test mixed numerical types (integer vs float)
  • Test for all types where left-right are the same type (all is a fixed list of testable types)
  • Test for all type combinations where left-right are different types
  • Expand the all list by depending on more modules (eg. unsigned_long and spatial types)
    • unsigned_long (from x-pack:plugin:mapper-unsigned-long)
    • version (found in module VersionFieldPlugin)
    • spatial types (requires local type mapping to remove cartesian_ prefix and module access for shapes
    • datetime (added tests)
    • date_nanos(no results are found even with identical fields, so we added this to the unsupported list and throw a validation error) - Created an issue for this at LOOKUP JOIN on date_nanos fields finds zero matches #127249
  • Explicitly test all unsupported types (should throw errors)
    • Define list of unsupported types
    • Assert they throw errors, and update Join validation if they do not
    • Assert that the set of supported and unsupported types covers all types (minus some non-types, like _doc, etc.)

One thing to consider changing here is allowing float and integer types to be used together. Right now the only thing blocking this is the validation code. The join actually succeeds if we remove the validation.

@craigtaverner craigtaverner added >test Issues or PRs that are addressing/adding tests Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) :Analytics/ES|QL AKA ESQL labels Apr 2, 2025
@elasticsearchmachine
Copy link
Collaborator

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

Copy link
Contributor

@alex-spies alex-spies left a comment

Choose a reason for hiding this comment

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

Nice! This is already looking great!

Before we merge, I wonder if we could simplify the test setup and make the test class a tad easier to follow. Although this is already a great value-add and is safe to merge.

I agree that as a next step, we should expand the number of different types covered.

* <dt>index_double_float</dt>
* <dd>Index containing a single document with a field of type 'float' like: <pre>
* {
* "field_double": 1.0, // this is mapped as type 'float' (a float with the name of the main index field)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may cut down on the number of created indices (maybe speeding up the test a little) and make this simpler to debug if we always name the fields by the type. We can just perform a RENAME in the query. Prefixing the field names with main_field or lookup_field would also guarantee that we don't accidentally shadow while renaming, and it will make the javadoc more self explanatory.

In fact, the test index creation could be simplified to 2 indices: one main index with every type we currently consider, and one lookup index with every type we currently consider. This means index creation can be done entirely during the setup of the test suite, rather than in each test.

This concern may be more important later, though, when we generalize the join condition so that multiple fields (a composite key) can be used. Then we probably also want to expand this test to use different combinations of types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I love the idea, but think we can re-visit this later.

* For example, if we are testing the pairs (double, double), (double, float), (float, double) and (float, float),
* we will create the following indexes:
* <dl>
* <dt>index_double_double</dt>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: it's unclear whether this is the main or the lookup index when reading this javadoc.

{
TestConfigs configs = testConfigurations.computeIfAbsent("strings", TestConfigs::new);
configs.addPasses(KEYWORD, KEYWORD);
configs.addPasses(TEXT, KEYWORD);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to have a TEXT field without a .keyword subfield? I think we fail in this situation. Could you maybe double check and add a test if that's possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We always pass with TEXT on the left because we will read from source for the main index if no KEYWORD subfield exists. Likewise we always fail with TEXT on the right, even if a KEYWORD subfield exists because we have not coded a special case for that (yet).

Comment on lines 345 to 347
private void addEmptyResult(DataType mainType, DataType lookupType) {
add(new TestConfigPasses(mainType, lookupType, false));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This appears unused?

Similarly, the boolean arg in the TestConfigPasses could always be true, then, no?

Copy link
Contributor Author

@craigtaverner craigtaverner May 7, 2025

Choose a reason for hiding this comment

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

Indeed, I was using this to assert on known failing cases (lookup does not match, but no errors are thrown), but I have since taken a different direction, adding these cases to the type validation so we get an error instead of an empty result. I'll delete this method.

Comment on lines 382 to 384
private <E extends Exception> void addFails(DataType mainType, DataType lookupType, Class<E> exception, Consumer<E> assertion) {
add(new TestConfigFails<>(mainType, lookupType, exception, assertion));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this was meant to be used in the other addFails methods above? Currently, I think it is unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, this method adds very close to zero value, so instead of using it, I will delete it.

TestConfigs configs = testConfigurations.computeIfAbsent("mixed-numerical", TestConfigs::new);
for (DataType mainType : integerTypes) {
for (DataType lookupType : floatTypes) {
// TODO: We should probably allow this, but we need to change the validation code in Join.java
Copy link
Contributor

Choose a reason for hiding this comment

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

++, the behavior should be exactly as if we evaluated an ==, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a followup issue to investigate this and try to support it: #127806

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.

LGTM.
The setup is thorough, but I'd maybe echo Alex's note on complexity: setting up indices does take time and while this test itself takes under 10s (w/o gradle setup), it could probably be sped-up with just two indices and renames (which would be skipped on same type in both indices, so the RENAME itself should not introduce a blind corner).
This complexity surfaces a bit also in the need to test the test (validateIndex()).

Collection<TestConfigs> existing = testConfigurations.values();
TestConfigs configs = testConfigurations.computeIfAbsent("same", TestConfigs::new);
for (DataType type : all) {
if (existingIndex(existing, type, type)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Supernit: test against false and skip the continue for slightly better legibility.
Here and below.

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

@costin
Copy link
Member

costin commented Apr 23, 2025

Let's get this PR merged in since it's been sitting here for a while - and move the nanos and potentially speed-up (if it can't be addressed shortly) in a follow-up.

@craigtaverner craigtaverner added the auto-backport Automatically create backport pull requests when merged label May 7, 2025
@craigtaverner
Copy link
Contributor Author

Since this PR adds additional validation errors for unsupported types, instead of silently failing (getting no matches in the join), I've viewing that as a kind-of bug-fix worth backporting to 9.0.

@craigtaverner craigtaverner merged commit afc53a3 into elastic:main May 7, 2025
17 checks passed
@elasticsearchmachine
Copy link
Collaborator

💚 Backport successful

Status Branch Result
9.0

craigtaverner added a commit to craigtaverner/elasticsearch that referenced this pull request May 7, 2025
…stic#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.
@craigtaverner
Copy link
Contributor Author

@bpintea and @alex-spies, you both suggested refining the index setup, and so I have created an issue recommending just that, at #127819

elasticsearchmachine pushed a commit that referenced this pull request May 7, 2025
#126150) (#127818)

* Integration tests for LOOKUP JOIN over wider range of data types (#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.

* SEMANTIC_TEXT Still exists on 9.0 as a zombie type
ywangd pushed a commit to ywangd/elasticsearch that referenced this pull request May 9, 2025
…stic#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.
afoucret pushed a commit to afoucret/elasticsearch that referenced this pull request May 9, 2025
…stic#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.
jfreden pushed a commit to jfreden/elasticsearch that referenced this pull request May 12, 2025
…stic#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.
@fang-xing-esql
Copy link
Member

💚 All backports created successfully

Status Branch Result
8.19

Questions ?

Please refer to the Backport tool documentation

elasticsearchmachine pushed a commit that referenced this pull request Jun 3, 2025
…es (#126150) (#128776)

* Integration tests for LOOKUP JOIN over wider range of data types (#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.

(cherry picked from commit afc53a3)

* fix compilng error

* add missing part of the backport of #126456

* add missing part of the backport of #126456

---------

Co-authored-by: Craig Taverner <[email protected]>
craigtaverner added a commit to craigtaverner/elasticsearch that referenced this pull request Jun 9, 2025
…stic#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.
elasticsearchmachine pushed a commit that referenced this pull request Jun 10, 2025
)

* Integration tests for LOOKUP JOIN over wider range of data types (#126150)

This test suite tests the lookup join functionality in ESQL with various data types.

For each pair of types being tested, it builds a main index called "index" containing a single document with as many fields as types being tested on the left of the pair, and then creates that many other lookup indexes, each with a single document containing exactly two fields: the field to join on, and a field to return.

The assertion is that for valid combinations, the return result should exist, and for invalid combinations an exception should be thrown. If no exception is thrown, and no result is returned, our validation rules are not aligned with the internal behaviour (ie. a bug).

Since the `LOOKUP JOIN` command requires the match field name to be the same between the main index and the lookup index, we will have field names that correctly represent the type of the field in the main index, but not the type of the field in the lookup index. This can be confusing, but it is important to remember that the field names are not the same as the types.

* Just use one lookup-settings file

This change simplifies backports from 9.x branches where these changes were done as part of other work.

* Support DATE_NANOS in LOOKUP JOIN (#127962)

We reported in #127249, there is no support for DATE_NANOS in LOOKUP JOIN, even though DATETIME is supported. This PR attempts to fix that.

The way that date-time was supported in LOOKUP JOIN (and ENRICH) was by using the `DateFieldMapper.DateFieldType.rangeQuery` (hidden behind the `termQuery` function) which internally takes our long values, casts them to Object, renders them to a string, parses that string back into an Instant (with a bunch of fancy and unnecessary checks for date-math, etc.), and then converts that instant back into a long for the actual query. Parts of this complex process are precision aware (ie. differentiate between ms and ns dates), but not the whole process. Simply dividing the original longs by 1_000_000 before passing them in actually works, but obviously looses precision. And the only reason it works anyway is that the date parsing code will accept a string containing a simple number and interpret it as either ms since the epoch, or years if the number is short enough. This does not work for nano-second dates, and in fact is far from ideal for LOOKUP JOIN on dates which does not need to re-parse the values at all.

This complex loop only makes sense in the Query DSL, where we can get all kinds of interesting sources of range values, but seems quite crazy for LOOKUP JOIN where we will always provide the join key from a LongBlock (the backing store of the DATE_TIME DataType, and the DATE_NANOS too).

So what we do here for DateNanos is provide two new methods to `DateFieldType`:
* `equalityQuery(Long, ...)` to replace `termQuery(Object, ...)`
* `rangeQuery(Long, Long, ...)` to replace `rangeQuery(Object, Object, ...)`

This allows us to pass in already parsed `long` values, and entirely skip the conversion to strings and re-parsing logic. The new methods are based on the original methods, but considerably simplified due to the removal of the complex parsing logic. The reason for both `equalityQuery` and `rangeQuery` is that it mimics the pattern used by the old `termQuery` with delegated directly down to `rangeQuery`. In addition to this, we hope to support range matching in `LOOKUP JOIN` in the near future.

* Fix compile error after backport

* Fix compile error after backport

* Update docs/changelog/129138.yaml

* SEMANTIC_TEXT was removed in later PRs, so not really testable in 8.18.

* Delete docs/changelog/129138.yaml

* Removed incorrectly added changelog

This is a backport
dnhatn added a commit that referenced this pull request Jun 16, 2025
In this test, we create a hundred indices for different combinations of 
data types. The number of file descriptors used exceeds the limit of
HandleLimitFS; therefore, we avoid using it in this test.

Relates #126150
Closes #129344
dnhatn added a commit to dnhatn/elasticsearch that referenced this pull request Jun 16, 2025
In this test, we create a hundred indices for different combinations of 
data types. The number of file descriptors used exceeds the limit of
HandleLimitFS; therefore, we avoid using it in this test.

Relates elastic#126150
Closes elastic#129344
dnhatn added a commit to dnhatn/elasticsearch that referenced this pull request Jun 16, 2025
In this test, we create a hundred indices for different combinations of 
data types. The number of file descriptors used exceeds the limit of
HandleLimitFS; therefore, we avoid using it in this test.

Relates elastic#126150
Closes elastic#129344
elasticsearchmachine pushed a commit that referenced this pull request Jun 16, 2025
In this test, we create a hundred indices for different combinations of 
data types. The number of file descriptors used exceeds the limit of
HandleLimitFS; therefore, we avoid using it in this test.

Relates #126150
Closes #129344
elasticsearchmachine pushed a commit that referenced this pull request Jun 16, 2025
In this test, we create a hundred indices for different combinations of 
data types. The number of file descriptors used exceeds the limit of
HandleLimitFS; therefore, we avoid using it in this test.

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

Labels

:Analytics/ES|QL AKA ESQL auto-backport Automatically create backport pull requests when merged Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) >test Issues or PRs that are addressing/adding tests v8.19.0 v9.0.2 v9.1.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants