Browse Source

ESQL: Simplify TableIdentifier class + rename to IndexPattern (#120797) (#120884)

This class is confusing: - It contains an **unused** `cluster` attribute
- we never separate out the cluster, it remains in the `index` field.
Also, in the constructor, this field is called `catalog`, which is a
concept entirely absent from ESQL at the moment. - It can refer to
multiple indices, even multiple wildcard patterns, but doesn't mention
this neither in its name nor javadoc. - It has little to do with tables,
which is likely a remnant of this class' usage in SQL, before the
`esql.core` split.

This PR removes the `cluster` attribute, renames the class to
`IndexPattern`, and adds javadoc to clarify that it can also contain
stuff like `remote1:idx1,remote-*:idx-*`.

(cherry picked from commit 060c8337e73a4d3d8a4522287c9032dccbedc22d)

Co-authored-by: Alexander Spies <alexander.spies@elastic.co>
Ievgen Degtiarenko 8 months ago
parent
commit
ba5d6bfcb9

+ 8 - 6
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java

@@ -64,7 +64,7 @@ import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.In;
 import org.elasticsearch.xpack.esql.index.EsIndex;
 import org.elasticsearch.xpack.esql.index.IndexResolution;
 import org.elasticsearch.xpack.esql.parser.ParsingException;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
 import org.elasticsearch.xpack.esql.plan.logical.Drop;
 import org.elasticsearch.xpack.esql.plan.logical.Enrich;
@@ -202,7 +202,9 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
         protected LogicalPlan rule(UnresolvedRelation plan, AnalyzerContext context) {
             return resolveIndex(
                 plan,
-                plan.indexMode().equals(IndexMode.LOOKUP) ? context.lookupResolution().get(plan.table().index()) : context.indexResolution()
+                plan.indexMode().equals(IndexMode.LOOKUP)
+                    ? context.lookupResolution().get(plan.indexPattern().indexPattern())
+                    : context.indexResolution()
             );
         }
 
@@ -213,7 +215,7 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
                     ? plan
                     : new UnresolvedRelation(
                         plan.source(),
-                        plan.table(),
+                        plan.indexPattern(),
                         plan.frozen(),
                         plan.metadataFields(),
                         plan.indexMode(),
@@ -221,12 +223,12 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
                         plan.commandName()
                     );
             }
-            TableIdentifier table = plan.table();
-            if (indexResolution.matches(table.index()) == false) {
+            IndexPattern table = plan.indexPattern();
+            if (indexResolution.matches(table.indexPattern()) == false) {
                 // TODO: fix this (and tests), or drop check (seems SQL-inherited, where's also defective)
                 new UnresolvedRelation(
                     plan.source(),
-                    plan.table(),
+                    plan.indexPattern(),
                     plan.frozen(),
                     plan.metadataFields(),
                     plan.indexMode(),

+ 1 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PreAnalyzer.java

@@ -51,7 +51,7 @@ public class PreAnalyzer {
 
         plan.forEachUp(UnresolvedRelation.class, p -> {
             List<TableInfo> list = p.indexMode() == IndexMode.LOOKUP ? lookupIndices : indices;
-            list.add(new TableInfo(p.table()));
+            list.add(new TableInfo(p.indexPattern()));
         });
         plan.forEachUp(Enrich.class, unresolvedEnriches::add);
 

+ 4 - 4
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/TableInfo.java

@@ -7,17 +7,17 @@
 
 package org.elasticsearch.xpack.esql.analysis;
 
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 
 public class TableInfo {
 
-    private final TableIdentifier id;
+    private final IndexPattern id;
 
-    public TableInfo(TableIdentifier id) {
+    public TableInfo(IndexPattern id) {
         this.id = id;
     }
 
-    public TableIdentifier id() {
+    public IndexPattern id() {
         return id;
     }
 }

+ 4 - 4
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java

@@ -36,7 +36,7 @@ import org.elasticsearch.xpack.esql.expression.Order;
 import org.elasticsearch.xpack.esql.expression.UnresolvedNamePattern;
 import org.elasticsearch.xpack.esql.expression.function.UnresolvedFunction;
 import org.elasticsearch.xpack.esql.parser.EsqlBaseParser.MetadataOptionContext;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
 import org.elasticsearch.xpack.esql.plan.logical.Dissect;
 import org.elasticsearch.xpack.esql.plan.logical.Drop;
@@ -258,7 +258,7 @@ public class LogicalPlanBuilder extends ExpressionBuilder {
     @Override
     public LogicalPlan visitFromCommand(EsqlBaseParser.FromCommandContext ctx) {
         Source source = source(ctx);
-        TableIdentifier table = new TableIdentifier(source, null, visitIndexPattern(ctx.indexPattern()));
+        IndexPattern table = new IndexPattern(source, visitIndexPattern(ctx.indexPattern()));
         Map<String, Attribute> metadataMap = new LinkedHashMap<>();
         if (ctx.metadata() != null) {
             var deprecatedContext = ctx.metadata().deprecated_metadata();
@@ -486,7 +486,7 @@ public class LogicalPlanBuilder extends ExpressionBuilder {
             throw new IllegalArgumentException("METRICS command currently requires a snapshot build");
         }
         Source source = source(ctx);
-        TableIdentifier table = new TableIdentifier(source, null, visitIndexPattern(ctx.indexPattern()));
+        IndexPattern table = new IndexPattern(source, visitIndexPattern(ctx.indexPattern()));
 
         if (ctx.aggregates == null && ctx.grouping == null) {
             return new UnresolvedRelation(source, table, false, List.of(), IndexMode.STANDARD, null, "METRICS");
@@ -548,7 +548,7 @@ public class LogicalPlanBuilder extends ExpressionBuilder {
 
         UnresolvedRelation right = new UnresolvedRelation(
             source(target),
-            new TableIdentifier(source(target.index), null, rightPattern),
+            new IndexPattern(source(target.index), rightPattern),
             false,
             emptyList(),
             IndexMode.LOOKUP,

+ 57 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/IndexPattern.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+package org.elasticsearch.xpack.esql.plan;
+
+import org.elasticsearch.xpack.esql.core.tree.Source;
+
+import java.util.Objects;
+
+/**
+ * Contains an index pattern together with its {@link Source}. Can also be a comma-separated list, like {@code idx-*,remote:other-idx*}.
+ */
+public class IndexPattern {
+
+    private final Source source;
+    private final String indexPattern;
+
+    public IndexPattern(Source source, String indexPattern) {
+        this.source = source;
+        this.indexPattern = indexPattern;
+    }
+
+    public String indexPattern() {
+        return indexPattern;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(indexPattern);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        IndexPattern other = (IndexPattern) obj;
+        return Objects.equals(indexPattern, other.indexPattern);
+    }
+
+    public Source source() {
+        return source;
+    }
+
+    @Override
+    public String toString() {
+        return indexPattern;
+    }
+}

+ 0 - 73
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/TableIdentifier.java

@@ -1,73 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-package org.elasticsearch.xpack.esql.plan;
-
-import org.elasticsearch.xpack.esql.core.tree.Source;
-
-import java.util.Objects;
-
-import static org.elasticsearch.transport.RemoteClusterAware.REMOTE_CLUSTER_INDEX_SEPARATOR;
-
-public class TableIdentifier {
-
-    private final Source source;
-
-    private final String cluster;
-    private final String index;
-
-    public TableIdentifier(Source source, String catalog, String index) {
-        this.source = source;
-        this.cluster = catalog;
-        this.index = index;
-    }
-
-    public String cluster() {
-        return cluster;
-    }
-
-    public String index() {
-        return index;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(cluster, index);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-
-        TableIdentifier other = (TableIdentifier) obj;
-        return Objects.equals(index, other.index) && Objects.equals(cluster, other.cluster);
-    }
-
-    public Source source() {
-        return source;
-    }
-
-    public String qualifiedIndex() {
-        return cluster != null ? cluster + REMOTE_CLUSTER_INDEX_SEPARATOR + index : index;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        if (cluster != null) {
-            builder.append(cluster);
-            builder.append(REMOTE_CLUSTER_INDEX_SEPARATOR);
-        }
-        builder.append(index);
-        return builder.toString();
-    }
-}

+ 12 - 12
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/UnresolvedRelation.java

@@ -12,7 +12,7 @@ import org.elasticsearch.xpack.esql.core.capabilities.Unresolvable;
 import org.elasticsearch.xpack.esql.core.expression.Attribute;
 import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
 import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 
 import java.util.Collections;
 import java.util.List;
@@ -22,7 +22,7 @@ import static java.util.Collections.singletonList;
 
 public class UnresolvedRelation extends LeafPlan implements Unresolvable {
 
-    private final TableIdentifier table;
+    private final IndexPattern indexPattern;
     private final boolean frozen;
     private final List<Attribute> metadataFields;
     /*
@@ -40,7 +40,7 @@ public class UnresolvedRelation extends LeafPlan implements Unresolvable {
 
     public UnresolvedRelation(
         Source source,
-        TableIdentifier table,
+        IndexPattern indexPattern,
         boolean frozen,
         List<Attribute> metadataFields,
         IndexMode indexMode,
@@ -48,11 +48,11 @@ public class UnresolvedRelation extends LeafPlan implements Unresolvable {
         String commandName
     ) {
         super(source);
-        this.table = table;
+        this.indexPattern = indexPattern;
         this.frozen = frozen;
         this.metadataFields = metadataFields;
         this.indexMode = indexMode;
-        this.unresolvedMsg = unresolvedMessage == null ? "Unknown index [" + table.index() + "]" : unresolvedMessage;
+        this.unresolvedMsg = unresolvedMessage == null ? "Unknown index [" + indexPattern.indexPattern() + "]" : unresolvedMessage;
         this.commandName = commandName;
     }
 
@@ -68,11 +68,11 @@ public class UnresolvedRelation extends LeafPlan implements Unresolvable {
 
     @Override
     protected NodeInfo<UnresolvedRelation> info() {
-        return NodeInfo.create(this, UnresolvedRelation::new, table, frozen, metadataFields, indexMode, unresolvedMsg, commandName);
+        return NodeInfo.create(this, UnresolvedRelation::new, indexPattern, frozen, metadataFields, indexMode, unresolvedMsg, commandName);
     }
 
-    public TableIdentifier table() {
-        return table;
+    public IndexPattern indexPattern() {
+        return indexPattern;
     }
 
     public boolean frozen() {
@@ -124,7 +124,7 @@ public class UnresolvedRelation extends LeafPlan implements Unresolvable {
 
     @Override
     public int hashCode() {
-        return Objects.hash(source(), table, metadataFields, indexMode, unresolvedMsg);
+        return Objects.hash(source(), indexPattern, metadataFields, indexMode, unresolvedMsg);
     }
 
     @Override
@@ -138,7 +138,7 @@ public class UnresolvedRelation extends LeafPlan implements Unresolvable {
         }
 
         UnresolvedRelation other = (UnresolvedRelation) obj;
-        return Objects.equals(table, other.table)
+        return Objects.equals(indexPattern, other.indexPattern)
             && Objects.equals(frozen, other.frozen)
             && Objects.equals(metadataFields, other.metadataFields)
             && indexMode == other.indexMode
@@ -147,11 +147,11 @@ public class UnresolvedRelation extends LeafPlan implements Unresolvable {
 
     @Override
     public List<Object> nodeProperties() {
-        return singletonList(table);
+        return singletonList(indexPattern);
     }
 
     @Override
     public String toString() {
-        return UNRESOLVED_PREFIX + table.index();
+        return UNRESOLVED_PREFIX + indexPattern.indexPattern();
     }
 }

+ 17 - 10
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java

@@ -56,7 +56,7 @@ import org.elasticsearch.xpack.esql.optimizer.PhysicalOptimizerContext;
 import org.elasticsearch.xpack.esql.optimizer.PhysicalPlanOptimizer;
 import org.elasticsearch.xpack.esql.parser.EsqlParser;
 import org.elasticsearch.xpack.esql.parser.QueryParams;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
 import org.elasticsearch.xpack.esql.plan.logical.Enrich;
 import org.elasticsearch.xpack.esql.plan.logical.Keep;
@@ -321,7 +321,9 @@ public class EsqlSession {
         EsqlSessionCCSUtils.checkForCcsLicense(executionInfo, indices, indicesExpressionGrouper, verifier.licenseState());
 
         final Set<String> targetClusters = enrichPolicyResolver.groupIndicesPerCluster(
-            indices.stream().flatMap(t -> Arrays.stream(Strings.commaDelimitedListToStringArray(t.id().index()))).toArray(String[]::new)
+            indices.stream()
+                .flatMap(t -> Arrays.stream(Strings.commaDelimitedListToStringArray(t.id().indexPattern())))
+                .toArray(String[]::new)
         ).keySet();
 
         var listener = SubscribableListener.<EnrichResolution>newForked(
@@ -373,14 +375,14 @@ public class EsqlSession {
     }
 
     private void preAnalyzeLookupIndex(TableInfo tableInfo, PreAnalysisResult result, ActionListener<PreAnalysisResult> listener) {
-        TableIdentifier table = tableInfo.id();
-        Set<String> fieldNames = result.wildcardJoinIndices().contains(table.index()) ? IndexResolver.ALL_FIELDS : result.fieldNames;
+        IndexPattern table = tableInfo.id();
+        Set<String> fieldNames = result.wildcardJoinIndices().contains(table.indexPattern()) ? IndexResolver.ALL_FIELDS : result.fieldNames;
         // call the EsqlResolveFieldsAction (field-caps) to resolve indices and get field types
         indexResolver.resolveAsMergedMapping(
-            table.index(),
+            table.indexPattern(),
             fieldNames,
             null,
-            listener.map(indexResolution -> result.addLookupIndexResolution(table.index(), indexResolution))
+            listener.map(indexResolution -> result.addLookupIndexResolution(table.indexPattern(), indexResolution))
         );
         // TODO: Verify that the resolved index actually has indexMode: "lookup"
     }
@@ -400,9 +402,12 @@ public class EsqlSession {
             // known to be unavailable from the enrich policy API call
             Map<String, Exception> unavailableClusters = result.enrichResolution.getUnavailableClusters();
             TableInfo tableInfo = indices.get(0);
-            TableIdentifier table = tableInfo.id();
+            IndexPattern table = tableInfo.id();
 
-            Map<String, OriginalIndices> clusterIndices = indicesExpressionGrouper.groupIndices(IndicesOptions.DEFAULT, table.index());
+            Map<String, OriginalIndices> clusterIndices = indicesExpressionGrouper.groupIndices(
+                IndicesOptions.DEFAULT,
+                table.indexPattern()
+            );
             for (Map.Entry<String, OriginalIndices> entry : clusterIndices.entrySet()) {
                 final String clusterAlias = entry.getKey();
                 String indexExpr = Strings.arrayToCommaDelimitedString(entry.getValue().indices());
@@ -431,7 +436,9 @@ public class EsqlSession {
             String indexExpressionToResolve = EsqlSessionCCSUtils.createIndexExpressionFromAvailableClusters(executionInfo);
             if (indexExpressionToResolve.isEmpty()) {
                 // if this was a pure remote CCS request (no local indices) and all remotes are offline, return an empty IndexResolution
-                listener.onResponse(result.withIndexResolution(IndexResolution.valid(new EsIndex(table.index(), Map.of(), Map.of()))));
+                listener.onResponse(
+                    result.withIndexResolution(IndexResolution.valid(new EsIndex(table.indexPattern(), Map.of(), Map.of())))
+                );
             } else {
                 // call the EsqlResolveFieldsAction (field-caps) to resolve indices and get field types
                 indexResolver.resolveAsMergedMapping(
@@ -588,7 +595,7 @@ public class EsqlSession {
                 }
                 if (keepCommandReferences.isEmpty()) {
                     // No KEEP commands after the JOIN, so we need to mark this index for "*" field resolution
-                    wildcardJoinIndices.add(((UnresolvedRelation) join.right()).table().index());
+                    wildcardJoinIndices.add(((UnresolvedRelation) join.right()).indexPattern().indexPattern());
                 } else {
                     // Keep commands can reference the join columns with names that shadow aliases, so we block their removal
                     keepJoinReferences.addAll(keepCommandReferences);

+ 1 - 1
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSessionCCSUtils.java

@@ -311,7 +311,7 @@ class EsqlSessionCCSUtils {
         for (TableInfo tableInfo : indices) {
             Map<String, OriginalIndices> groupedIndices;
             try {
-                groupedIndices = indicesGrouper.groupIndices(IndicesOptions.DEFAULT, tableInfo.id().index());
+                groupedIndices = indicesGrouper.groupIndices(IndicesOptions.DEFAULT, tableInfo.id().indexPattern());
             } catch (NoSuchRemoteClusterException e) {
                 if (EsqlLicenseChecker.isCcsAllowed(licenseState)) {
                     throw e;

+ 1 - 1
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java

@@ -456,7 +456,7 @@ public class CsvTests extends ESTestCase {
             throw new IllegalArgumentException("unexpected index resolution to multiple entries [" + preAnalysis.indices.size() + "]");
         }
 
-        String indexName = indices.get(0).id().index();
+        String indexName = indices.get(0).id().indexPattern();
         List<CsvTestsDataLoader.TestDataset> datasets = new ArrayList<>();
         if (indexName.endsWith("*")) {
             String indexPrefix = indexName.substring(0, indexName.length() - 1);

+ 2 - 2
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java

@@ -47,7 +47,7 @@ import org.elasticsearch.xpack.esql.index.EsIndex;
 import org.elasticsearch.xpack.esql.index.IndexResolution;
 import org.elasticsearch.xpack.esql.parser.ParsingException;
 import org.elasticsearch.xpack.esql.parser.QueryParams;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
 import org.elasticsearch.xpack.esql.plan.logical.Enrich;
 import org.elasticsearch.xpack.esql.plan.logical.EsRelation;
@@ -105,7 +105,7 @@ public class AnalyzerTests extends ESTestCase {
 
     private static final UnresolvedRelation UNRESOLVED_RELATION = new UnresolvedRelation(
         EMPTY,
-        new TableIdentifier(EMPTY, null, "idx"),
+        new IndexPattern(EMPTY, "idx"),
         false,
         List.of(),
         IndexMode.STANDARD,

+ 2 - 2
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/AbstractStatementParserTests.java

@@ -17,7 +17,7 @@ import org.elasticsearch.xpack.esql.core.expression.MapExpression;
 import org.elasticsearch.xpack.esql.core.expression.UnresolvedAttribute;
 import org.elasticsearch.xpack.esql.core.type.DataType;
 import org.elasticsearch.xpack.esql.expression.function.UnresolvedFunction;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
 import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation;
 
@@ -72,7 +72,7 @@ abstract class AbstractStatementParserTests extends ESTestCase {
     }
 
     static UnresolvedRelation relation(String name) {
-        return new UnresolvedRelation(EMPTY, new TableIdentifier(EMPTY, null, name), false, List.of(), IndexMode.STANDARD, null, "FROM");
+        return new UnresolvedRelation(EMPTY, new IndexPattern(EMPTY, name), false, List.of(), IndexMode.STANDARD, null, "FROM");
     }
 
     static Literal integer(int i) {

+ 6 - 14
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java

@@ -42,7 +42,7 @@ import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Gre
 import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThanOrEqual;
 import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan;
 import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThanOrEqual;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
 import org.elasticsearch.xpack.esql.plan.logical.Dissect;
 import org.elasticsearch.xpack.esql.plan.logical.Drop;
@@ -2058,7 +2058,7 @@ public class StatementParserTests extends AbstractStatementParserTests {
         LogicalPlan from = statement(statement);
         assertThat(from, instanceOf(UnresolvedRelation.class));
         UnresolvedRelation table = (UnresolvedRelation) from;
-        assertThat(table.table().index(), is(string));
+        assertThat(table.indexPattern().indexPattern(), is(string));
     }
 
     private void assertStringAsLookupIndexPattern(String string, String statement) {
@@ -2289,20 +2289,12 @@ public class StatementParserTests extends AbstractStatementParserTests {
     }
 
     private LogicalPlan unresolvedRelation(String index) {
-        return new UnresolvedRelation(EMPTY, new TableIdentifier(EMPTY, null, index), false, List.of(), IndexMode.STANDARD, null, "FROM");
+        return new UnresolvedRelation(EMPTY, new IndexPattern(EMPTY, index), false, List.of(), IndexMode.STANDARD, null, "FROM");
     }
 
     private LogicalPlan unresolvedTSRelation(String index) {
         List<Attribute> metadata = List.of(new MetadataAttribute(EMPTY, MetadataAttribute.TSID_FIELD, DataType.KEYWORD, false));
-        return new UnresolvedRelation(
-            EMPTY,
-            new TableIdentifier(EMPTY, null, index),
-            false,
-            metadata,
-            IndexMode.TIME_SERIES,
-            null,
-            "FROM TS"
-        );
+        return new UnresolvedRelation(EMPTY, new IndexPattern(EMPTY, index), false, metadata, IndexMode.TIME_SERIES, null, "FROM TS");
     }
 
     public void testMetricWithGroupKeyAsAgg() {
@@ -2955,8 +2947,8 @@ public class StatementParserTests extends AbstractStatementParserTests {
         var plan = statement("FROM " + basePattern + " | " + type + " JOIN " + joinPattern + " ON " + onField);
 
         var join = as(plan, LookupJoin.class);
-        assertThat(as(join.left(), UnresolvedRelation.class).table().index(), equalTo(unquoteIndexPattern(basePattern)));
-        assertThat(as(join.right(), UnresolvedRelation.class).table().index(), equalTo(unquoteIndexPattern(joinPattern)));
+        assertThat(as(join.left(), UnresolvedRelation.class).indexPattern().indexPattern(), equalTo(unquoteIndexPattern(basePattern)));
+        assertThat(as(join.right(), UnresolvedRelation.class).indexPattern().indexPattern(), equalTo(unquoteIndexPattern(joinPattern)));
 
         var joinType = as(join.config().type(), JoinTypes.UsingJoinType.class);
         assertThat(joinType.columns(), hasSize(1));

+ 5 - 5
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/EsqlSessionCCSUtilsTests.java

@@ -32,7 +32,7 @@ import org.elasticsearch.xpack.esql.analysis.TableInfo;
 import org.elasticsearch.xpack.esql.core.type.EsField;
 import org.elasticsearch.xpack.esql.index.EsIndex;
 import org.elasticsearch.xpack.esql.index.IndexResolution;
-import org.elasticsearch.xpack.esql.plan.TableIdentifier;
+import org.elasticsearch.xpack.esql.plan.IndexPattern;
 import org.elasticsearch.xpack.esql.type.EsFieldTests;
 
 import java.util.ArrayList;
@@ -702,7 +702,7 @@ public class EsqlSessionCCSUtilsTests extends ESTestCase {
         // local only search does not require an enterprise license
         {
             List<TableInfo> indices = new ArrayList<>();
-            indices.add(new TableInfo(new TableIdentifier(EMPTY, null, randomFrom("idx", "idx1,idx2*"))));
+            indices.add(new TableInfo(new IndexPattern(EMPTY, randomFrom("idx", "idx1,idx2*"))));
 
             checkForCcsLicense(executionInfo, indices, indicesGrouper, enterpriseLicenseValid);
             checkForCcsLicense(executionInfo, indices, indicesGrouper, platinumLicenseValid);
@@ -727,10 +727,10 @@ public class EsqlSessionCCSUtilsTests extends ESTestCase {
             List<TableInfo> indices = new ArrayList<>();
             final String indexExprWithRemotes = randomFrom("remote:idx", "idx1,remote:idx2*,remote:logs,c*:idx4");
             if (randomBoolean()) {
-                indices.add(new TableInfo(new TableIdentifier(EMPTY, null, indexExprWithRemotes)));
+                indices.add(new TableInfo(new IndexPattern(EMPTY, indexExprWithRemotes)));
             } else {
-                indices.add(new TableInfo(new TableIdentifier(EMPTY, null, randomFrom("idx", "idx1,idx2*"))));
-                indices.add(new TableInfo(new TableIdentifier(EMPTY, null, indexExprWithRemotes)));
+                indices.add(new TableInfo(new IndexPattern(EMPTY, randomFrom("idx", "idx1,idx2*"))));
+                indices.add(new TableInfo(new IndexPattern(EMPTY, indexExprWithRemotes)));
             }
 
             // licenses that work