diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java index 7d79e00406637..937122040bd7b 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java @@ -16,6 +16,7 @@ import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.esql.analysis.*; import org.elasticsearch.xpack.esql.core.expression.FoldContext; +import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.core.util.DateUtils; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; @@ -26,6 +27,7 @@ import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; import org.elasticsearch.xpack.esql.parser.EsqlParser; import org.elasticsearch.xpack.esql.parser.QueryParams; +import org.elasticsearch.xpack.esql.plan.IndexPattern; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; @@ -108,7 +110,7 @@ public void setup() { new AnalyzerContext( config, functionRegistry, - IndexResolution.valid(esIndex), + Map.of(new IndexPattern(Source.EMPTY, esIndex.name()), IndexResolution.valid(esIndex)), Map.of(), new EnrichResolution(), InferenceResolution.EMPTY, diff --git a/docs/changelog/134995.yaml b/docs/changelog/134995.yaml new file mode 100644 index 0000000000000..4f62addd307a8 --- /dev/null +++ b/docs/changelog/134995.yaml @@ -0,0 +1,5 @@ +pr: 134995 +summary: ES|QL Views prototype +area: ES|QL +type: feature +issues: [] diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/esql.delete_view.json b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.delete_view.json new file mode 100644 index 0000000000000..034f00c17b04d --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.delete_view.json @@ -0,0 +1,30 @@ +{ + "esql.delete_view":{ + "documentation":{ + "url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-delete", + "description":"Creates a VIEW for ESQL." + }, + "stability": "experimental", + "visibility": "public", + "headers": { + "accept": [ "application/json"], + "content_type": ["application/json"] + }, + "url":{ + "paths":[ + { + "path":"/_query/view/{name}", + "methods":[ + "DELETE" + ], + "parts": { + "name": { + "type": "string", + "description": "The name of the view" + } + } + } + ] + } + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/esql.get_view.json b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.get_view.json new file mode 100644 index 0000000000000..903dc89067195 --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.get_view.json @@ -0,0 +1,34 @@ +{ + "esql.get_view":{ + "documentation":{ + "url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-get", + "description":"Get a VIEW for ESQL." + }, + "stability": "experimental", + "visibility": "public", + "headers": { + "accept": [ + "application/json" + ], + "content_type": [ + "application/json" + ] + }, + "url":{ + "paths":[ + { + "path":"/_query/view/{name}", + "methods":[ + "GET" + ], + "parts": { + "name": { + "type": "string", + "description": "The name of the view" + } + } + } + ] + } + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/esql.list_views.json b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.list_views.json new file mode 100644 index 0000000000000..9de40851d009e --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.list_views.json @@ -0,0 +1,28 @@ +{ + "esql.list_views": { + "documentation": { + "url": null, + "description": "List all defined ES|QL views" + }, + "stability": "experimental", + "visibility": "public", + "headers": { + "accept": [ + "application/json" + ], + "content_type": [ + "application/json" + ] + }, + "url": { + "paths": [ + { + "path": "/_query/views", + "methods": [ + "GET" + ] + } + ] + } + } +} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/esql.put_view.json b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.put_view.json new file mode 100644 index 0000000000000..beb16834d1cac --- /dev/null +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.put_view.json @@ -0,0 +1,34 @@ +{ + "esql.put_view":{ + "documentation":{ + "url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-put", + "description":"Creates a VIEW for ESQL." + }, + "stability": "experimental", + "visibility": "public", + "headers": { + "accept": [ "application/json"], + "content_type": ["application/json"] + }, + "url":{ + "paths":[ + { + "path":"/_query/view/{name}", + "methods":[ + "PUT" + ], + "parts": { + "name": { + "type": "string", + "description": "The name of the view" + } + } + } + ] + }, + "body":{ + "description":"Use the `query` element to start a query.", + "required":true + } + } +} diff --git a/server/src/main/resources/transport/definitions/referable/esql_views.csv b/server/src/main/resources/transport/definitions/referable/esql_views.csv new file mode 100644 index 0000000000000..773be2c2c150c --- /dev/null +++ b/server/src/main/resources/transport/definitions/referable/esql_views.csv @@ -0,0 +1 @@ +9198000 diff --git a/server/src/main/resources/transport/upper_bounds/9.3.csv b/server/src/main/resources/transport/upper_bounds/9.3.csv index 0b1b264125525..6be85c8fe0e7e 100644 --- a/server/src/main/resources/transport/upper_bounds/9.3.csv +++ b/server/src/main/resources/transport/upper_bounds/9.3.csv @@ -1 +1 @@ -dimension_values,9197000 +esql_views,9198000 diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java index b7af6041ea56a..b875627f55c13 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java @@ -35,7 +35,9 @@ public abstract class Attribute extends NamedExpression { /** * Changing this will break bwc with 8.15, see {@link FieldAttribute#fieldName()}. */ - protected static final String SYNTHETIC_ATTRIBUTE_NAME_PREFIX = "$$"; + public static final String SYNTHETIC_ATTRIBUTE_NAME_PREFIX = "$$"; + + public static final String SYNTHETIC_ATTRIBUTE_NAME_SEPARATOR = "$"; private static final TransportVersion ESQL_QUALIFIERS_IN_ATTRIBUTES = TransportVersion.fromName("esql_qualifiers_in_attributes"); @@ -77,7 +79,7 @@ public Attribute( } public static String rawTemporaryName(String... parts) { - var name = String.join("$", parts); + var name = String.join(SYNTHETIC_ATTRIBUTE_NAME_SEPARATOR, parts); return name.isEmpty() || name.startsWith(SYNTHETIC_ATTRIBUTE_NAME_PREFIX) ? name : SYNTHETIC_ATTRIBUTE_NAME_PREFIX + name; } diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java index c917929dd83ce..2bb08578d0eb7 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java +++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java @@ -56,6 +56,7 @@ import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_PLANNING_V1; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.METADATA_FIELDS_REMOTE_TEST; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.UNMAPPED_FIELDS; +import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.VIEWS_V1; import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -157,6 +158,8 @@ protected void shouldSkipTest(String testName) throws IOException { } // Unmapped fields require a correct capability response from every cluster, which isn't currently implemented. assumeFalse("UNMAPPED FIELDS not yet supported in CCS", testCase.requiredCapabilities.contains(UNMAPPED_FIELDS.capabilityName())); + // Views require a correct capability response from every cluster, which isn't currently implemented. + assumeFalse("VIEWS not yet supported in CCS", testCase.requiredCapabilities.contains(VIEWS_V1.capabilityName())); // Tests that use capabilities not supported in CCS assumeFalse( "This syntax is not supported with remote LOOKUP JOIN", diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java index 2ecd089dedd2f..0a5a3d937afe3 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java @@ -51,6 +51,7 @@ import java.util.Locale; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.Callable; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.LongStream; @@ -70,7 +71,9 @@ import static org.elasticsearch.xpack.esql.CsvTestUtils.loadCsvSpecValues; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.createInferenceEndpoints; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.deleteInferenceEndpoints; +import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.deleteViews; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.loadDataSetIntoEs; +import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.loadViewsIntoEs; import static org.elasticsearch.xpack.esql.EsqlTestUtils.classpathResources; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.COMPLETION; import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.KNN_FUNCTION_V5; @@ -97,6 +100,7 @@ public abstract class EsqlSpecTestCase extends ESRestTestCase { protected final String instructions; protected final Mode mode; protected static Boolean supportsTook; + protected static Boolean supportsViews; @ParametersFactory(argumentFormatting = "csv-spec:%2$s.%3$s") public static List readScriptSpec() throws Exception { @@ -122,23 +126,67 @@ protected EsqlSpecTestCase( this.mode = randomFrom(Mode.values()); } - private static boolean dataLoaded = false; + private static class Protected { + private volatile boolean completed = false; + private volatile boolean started = false; + private volatile Throwable failure = null; + + private void protectedBlock(Callable callable) { + if (completed) { + return; + } + // In case tests get run in parallel, we ensure only one setup is run, and other tests wait for this + synchronized (this) { + if (completed) { + return; + } + if (started) { + // Should only happen if a previous test setup failed, possibly with partial setup, let's fail fast the current test + if (failure != null) { + fail(failure, "Previous test setup failed: " + failure.getMessage()); + } + fail("Previous test setup failed with unknown error"); + } + started = true; + try { + callable.call(); + completed = true; + } catch (Throwable t) { + failure = t; + fail(failure, "Current test setup failed: " + failure.getMessage()); + } + } + } + + private synchronized void reset() { + completed = false; + started = false; + failure = null; + } + } + + private static final Protected INGEST = new Protected(); + private static final Protected VIEWS = new Protected(); protected static boolean testClustersOk = true; @Before - public void setup() throws IOException { + public void setup() { assumeTrue("test clusters were broken", testClustersOk); - boolean supportsLookup = supportsIndexModeLookup(); - boolean supportsSourceMapping = supportsSourceFieldMapping(); - boolean supportsInferenceTestService = supportsInferenceTestService(); - if (dataLoaded == false) { - if (supportsInferenceTestService) { + INGEST.protectedBlock(() -> { + // Inference endpoints must be created before ingesting any datasets that rely on them (mapping of inference_id) + if (supportsInferenceTestService()) { createInferenceEndpoints(adminClient()); } - - loadDataSetIntoEs(client(), supportsLookup, supportsSourceMapping, supportsInferenceTestService); - dataLoaded = true; - } + loadDataSetIntoEs(client(), supportsIndexModeLookup(), supportsSourceFieldMapping(), supportsInferenceTestService()); + return null; + }); + // Views can be created before or after ingest, since index resolution is currently only done on the combined query + VIEWS.protectedBlock(() -> { + if (supportsViews()) { + loadViewsIntoEs(adminClient()); + } + return null; + }); } @AfterClass @@ -147,7 +195,6 @@ public static void wipeTestData() throws IOException { return; } try { - dataLoaded = false; adminClient().performRequest(new Request("DELETE", "/*")); } catch (ResponseException e) { // 404 here just means we had no indexes @@ -155,7 +202,9 @@ public static void wipeTestData() throws IOException { throw e; } } - + INGEST.reset(); + deleteViews(adminClient()); + VIEWS.reset(); deleteInferenceEndpoints(adminClient()); } @@ -481,6 +530,13 @@ protected boolean supportsTook() throws IOException { return supportsTook; } + protected boolean supportsViews() { + if (supportsViews == null) { + supportsViews = hasCapabilities(adminClient(), List.of("views_v1")); + } + return supportsViews; + } + private String tookKey(long took) { if (took < 10) { return "lt_10ms"; diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java index 5b2ea9a525489..00f4d5a13918f 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java @@ -13,7 +13,10 @@ import java.io.IOException; import java.util.List; -import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.*; +import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.FORK_V9; +import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND; +import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.UNMAPPED_FIELDS; +import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.VIEWS_V1; import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities; /** @@ -55,6 +58,16 @@ protected void shouldSkipTest(String testName) throws IOException { testCase.requiredCapabilities.contains(UNMAPPED_FIELDS.capabilityName()) ); + assumeFalse( + "Tests using VIEWS not supported for now (until we merge VIEWS and Subqueries/FORK including branch merging)", + testCase.requiredCapabilities.contains(VIEWS_V1.capabilityName()) + ); + + assumeFalse( + "Tests using subqueries are skipped since we don't support nested subqueries", + testCase.requiredCapabilities.contains(SUBQUERY_IN_FROM_COMMAND.capabilityName()) + ); + assumeTrue("Cluster needs to support FORK", hasCapabilities(adminClient(), List.of(FORK_V9.capabilityName()))); } } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java index 96f8ffdcccbbe..c9572fb8e8756 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java @@ -78,8 +78,9 @@ public class CsvTestsDataLoader { private static final TestDataset LANGUAGES = new TestDataset("languages"); private static final TestDataset LANGUAGES_LOOKUP = LANGUAGES.withIndex("languages_lookup").withSetting("lookup-settings.json"); private static final TestDataset LANGUAGES_NON_UNIQUE_KEY = new TestDataset("languages_non_unique_key"); - private static final TestDataset LANGUAGES_LOOKUP_NON_UNIQUE_KEY = LANGUAGES_NON_UNIQUE_KEY.withIndex("languages_lookup_non_unique_key") - .withSetting("lookup-settings.json"); + private static final TestDataset LANGUAGES_LOOKUP_NON_UNIQUE_KEY = LANGUAGES_LOOKUP.withIndex("languages_lookup_non_unique_key") + .withData("languages_non_unique_key.csv") + .withDynamicTypeMapping(Map.of("country", "text")); private static final TestDataset LANGUAGES_NESTED_FIELDS = new TestDataset( "languages_nested_fields", "mapping-languages_nested_fields.json", @@ -269,6 +270,11 @@ public class CsvTestsDataLoader { ); public static final String NUMERIC_REGEX = "-?\\d+(\\.\\d+)?"; + private static final ViewConfig ADDRESS_COUNTRIES = new ViewConfig("address_countries"); + private static final ViewConfig AIRPORTS_COUNTRIES = new ViewConfig("airports_countries"); + private static final ViewConfig LANGUAGES_COUNTRIES = new ViewConfig("languages_countries"); + public static final List VIEW_CONFIGS = List.of(ADDRESS_COUNTRIES, AIRPORTS_COUNTRIES, LANGUAGES_COUNTRIES); + /** *

* Loads spec data on a local ES server. @@ -423,16 +429,46 @@ private static void loadDataSetIntoEs( Logger logger = LogManager.getLogger(CsvTestsDataLoader.class); Set loadedDatasets = new HashSet<>(); + logger.info("Loading test datasets"); for (var dataset : availableDatasetsForEs(supportsIndexModeLookup, supportsSourceFieldMapping, inferenceEnabled, timeSeriesOnly)) { load(client, dataset, logger, indexCreator); loadedDatasets.add(dataset.indexName); } forceMerge(client, loadedDatasets, logger); + logger.info("Loading enrich policies"); for (var policy : ENRICH_POLICIES) { loadEnrichPolicy(client, policy.policyName, policy.policyFileName, logger); } } + public static void loadViewsIntoEs(RestClient client) throws IOException { + Logger logger = LogManager.getLogger(CsvTestsDataLoader.class); + if (clusterHasViewSupport(client, logger)) { + logger.info("Loading views"); + for (var view : VIEW_CONFIGS) { + loadView(client, view.viewName, view.viewFileName, logger); + } + // Just for debugging output TODO: remove + clusterHasViewSupport(client, logger); + } else { + logger.info("Skipping loading views as the cluster does not support views"); + } + } + + public static void deleteViews(RestClient client) throws IOException { + Logger logger = LogManager.getLogger(CsvTestsDataLoader.class); + if (clusterHasViewSupport(client, logger)) { + logger.info("Deleting views"); + for (var view : VIEW_CONFIGS) { + deleteView(client, view.viewName, logger); + } + // Just for debugging output TODO: remove + clusterHasViewSupport(client, logger); + } else { + logger.info("Skipping deleting views as the cluster does not support views"); + } + } + public static void createInferenceEndpoints(RestClient client) throws IOException { if (clusterHasSparseEmbeddingInferenceEndpoint(client) == false) { createSparseEmbeddingInferenceEndpoint(client); @@ -569,6 +605,7 @@ private static void deleteInferenceEndpoint(RestClient client, String inferenceI } private static void loadEnrichPolicy(RestClient client, String policyName, String policyFileName, Logger logger) throws IOException { + logger.info("Loading enrich policy [{}] from file [{}]", policyName, policyFileName); URL policyMapping = getResource("/" + policyFileName); String entity = readTextFile(policyMapping); Request request = new Request("PUT", "/_enrich/policy/" + policyName); @@ -579,6 +616,71 @@ private static void loadEnrichPolicy(RestClient client, String policyName, Strin client.performRequest(request); } + private static void loadView(RestClient client, String viewName, String viewFilename, Logger logger) throws IOException { + String viewQuery = loadViewQuery(viewName, viewFilename, logger); + Request request = new Request("PUT", "/_query/view/" + viewName); + request.setJsonEntity("{\"query\":\"" + viewQuery.replace("\"", "\\\"").replace("\n", " ") + "\"}"); + Response response = client.performRequest(request); + logger.info("View creation response: {}", response.getStatusLine()); + getView(client, viewName, logger); + } + + static String loadViewQuery(String viewName, String viewFilename, Logger logger) throws IOException { + logger.info("Loading view [{}] from file [{}]", viewName, viewFilename); + URL viewFile = getResource("/" + viewFilename); + return readTextFile(viewFile); + } + + private static boolean getView(RestClient client, String viewName, Logger logger) throws IOException { + Request request = new Request("GET", "/_query/view/" + viewName); + try { + Response response = client.performRequest(request); + logger.info("View response status: {}", response.getStatusLine()); + logger.info("View response body info: {}", response.getEntity()); + logger.info("View response body: {}", new String(response.getEntity().getContent().readAllBytes())); + } catch (ResponseException e) { + logger.info("View error: {}", e.getMessage()); + int code = e.getResponse().getStatusLine().getStatusCode(); + if (code == 400 || code == 404) { + return false; + } + throw e; + } + return true; + } + + private static boolean clusterHasViewSupport(RestClient client, Logger logger) throws IOException { + Request request = new Request("GET", "/_query/views"); + try { + Response response = client.performRequest(request); + logger.info("View listing response: {}", response.getStatusLine()); + logger.info("View response body info: {}", response.getEntity()); + logger.info("View response body: {}", new String(response.getEntity().getContent().readAllBytes())); + } catch (ResponseException e) { + logger.info("View listing error: {}", e.getMessage()); + int code = e.getResponse().getStatusLine().getStatusCode(); + // Different versions of Elasticsearch return different codes when views are not supported + if (code == 400 || code == 500 || code == 405) { + return false; + } + throw e; + } + return true; + } + + private static void deleteView(RestClient client, String viewName, Logger logger) throws IOException { + try { + client.performRequest(new Request("DELETE", "/_query/view/" + viewName)); + } catch (ResponseException e) { + logger.info("View delete error: {}", e.getMessage()); + int code = e.getResponse().getStatusLine().getStatusCode(); + // On older servers the view listing succeeds when it should not, so we get here when we should not, hence the 400 and 500 + if (code != 404 && code != 400 && code != 500) { + throw e; + } + } + } + private static URL getResource(String name) { URL result = CsvTestsDataLoader.class.getResource(name); if (result == null) { @@ -588,6 +690,7 @@ private static URL getResource(String name) { } private static void load(RestClient client, TestDataset dataset, Logger logger, IndexCreator indexCreator) throws IOException { + logger.info("Loading dataset [{}] into ES index [{}]", dataset.dataFileName, dataset.indexName); URL mapping = getResource("/" + dataset.mappingFileName); Settings indexSettings = dataset.readSettingsFile(); indexCreator.createIndex(client, dataset.indexName, readMappingFile(mapping, dataset.typeMapping), indexSettings); @@ -854,15 +957,16 @@ public record TestDataset( String dataFileName, String settingFileName, boolean allowSubFields, - @Nullable Map typeMapping, + @Nullable Map typeMapping, // Override mappings read from mappings file + @Nullable Map dynamicTypeMapping, // Define mappings not in the mapping files, but available from field-caps boolean requiresInferenceEndpoint ) { public TestDataset(String indexName, String mappingFileName, String dataFileName) { - this(indexName, mappingFileName, dataFileName, null, true, null, false); + this(indexName, mappingFileName, dataFileName, null, true, null, null, false); } public TestDataset(String indexName) { - this(indexName, "mapping-" + indexName + ".json", indexName + ".csv", null, true, null, false); + this(indexName, "mapping-" + indexName + ".json", indexName + ".csv", null, true, null, null, false); } public TestDataset withIndex(String indexName) { @@ -873,6 +977,7 @@ public TestDataset withIndex(String indexName) { settingFileName, allowSubFields, typeMapping, + dynamicTypeMapping, requiresInferenceEndpoint ); } @@ -885,6 +990,7 @@ public TestDataset withData(String dataFileName) { settingFileName, allowSubFields, typeMapping, + dynamicTypeMapping, requiresInferenceEndpoint ); } @@ -897,6 +1003,7 @@ public TestDataset noData() { settingFileName, allowSubFields, typeMapping, + dynamicTypeMapping, requiresInferenceEndpoint ); } @@ -909,6 +1016,7 @@ public TestDataset withSetting(String settingFileName) { settingFileName, allowSubFields, typeMapping, + dynamicTypeMapping, requiresInferenceEndpoint ); } @@ -921,6 +1029,7 @@ public TestDataset noSubfields() { settingFileName, false, typeMapping, + dynamicTypeMapping, requiresInferenceEndpoint ); } @@ -933,12 +1042,35 @@ public TestDataset withTypeMapping(Map typeMapping) { settingFileName, allowSubFields, typeMapping, + dynamicTypeMapping, + requiresInferenceEndpoint + ); + } + + public TestDataset withDynamicTypeMapping(Map dynamicTypeMapping) { + return new TestDataset( + indexName, + mappingFileName, + dataFileName, + settingFileName, + allowSubFields, + typeMapping, + dynamicTypeMapping, requiresInferenceEndpoint ); } public TestDataset withInferenceEndpoint(boolean needsInference) { - return new TestDataset(indexName, mappingFileName, dataFileName, settingFileName, allowSubFields, typeMapping, needsInference); + return new TestDataset( + indexName, + mappingFileName, + dataFileName, + settingFileName, + allowSubFields, + typeMapping, + dynamicTypeMapping, + needsInference + ); } private Settings readSettingsFile() throws IOException { @@ -954,6 +1086,12 @@ private Settings readSettingsFile() throws IOException { } } + public record ViewConfig(String viewName, String viewFileName) { + public ViewConfig(String viewName) { + this(viewName, "views/" + viewName + ".esql"); + } + } + public record EnrichConfig(String policyName, String policyFileName) {} private interface IndexCreator { diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java index 88469a8d19e8c..1bdc68dd29101 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java @@ -99,6 +99,7 @@ import org.elasticsearch.xpack.esql.inference.InferenceService; import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; import org.elasticsearch.xpack.esql.parser.QueryParam; +import org.elasticsearch.xpack.esql.plan.IndexPattern; import org.elasticsearch.xpack.esql.plan.logical.Enrich; import org.elasticsearch.xpack.esql.plan.logical.EsRelation; import org.elasticsearch.xpack.esql.plan.logical.Limit; @@ -441,11 +442,11 @@ public static TransportVersion randomMinimumVersion() { public static AnalyzerContext testAnalyzerContext( Configuration configuration, EsqlFunctionRegistry functionRegistry, - IndexResolution indexResolution, + Map indexResolutions, EnrichResolution enrichResolution, InferenceResolution inferenceResolution ) { - return testAnalyzerContext(configuration, functionRegistry, indexResolution, Map.of(), enrichResolution, inferenceResolution); + return testAnalyzerContext(configuration, functionRegistry, indexResolutions, Map.of(), enrichResolution, inferenceResolution); } /** @@ -454,7 +455,7 @@ public static AnalyzerContext testAnalyzerContext( public static AnalyzerContext testAnalyzerContext( Configuration configuration, EsqlFunctionRegistry functionRegistry, - IndexResolution indexResolution, + Map indexResolutions, Map lookupResolution, EnrichResolution enrichResolution, InferenceResolution inferenceResolution @@ -462,7 +463,7 @@ public static AnalyzerContext testAnalyzerContext( return new AnalyzerContext( configuration, functionRegistry, - indexResolution, + indexResolutions, lookupResolution, enrichResolution, inferenceResolution, diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec index e6dbfa1950764..f14e53284bb35 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/inlinestats.csv-spec @@ -3976,6 +3976,7 @@ required_capability: inline_stats required_capability: fix_join_output_merging FROM languages_lookup_non_unique_key +| EVAL country = MV_SORT(country) | KEEP country, language_name | EVAL language_code = null::integer | INLINE STATS MAX(language_code) BY language_code @@ -3983,7 +3984,7 @@ FROM languages_lookup_non_unique_key | LIMIT 5 ; -country:text |language_name:keyword |MAX(language_code):integer |language_code:integer +country:keyword |language_name:keyword |MAX(language_code):integer |language_code:integer Atlantis |null |null |null [Austria, Germany]|German |null |null Canada |English |null |null diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec index bf3502b866595..51296b6ca9820 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec @@ -247,11 +247,12 @@ FROM employees | EVAL language_code = emp_no % 10 | LOOKUP JOIN languages_lookup_non_unique_key ON language_code | WHERE emp_no > 10090 AND emp_no < 10096 +| EVAL country = MV_SORT(country) | SORT emp_no, country | KEEP emp_no, language_code, language_name, country ; -emp_no:integer | language_code:integer | language_name:keyword | country:text +emp_no:integer | language_code:integer | language_name:keyword | country:keyword 10091 | 1 | English | Canada 10091 | 1 | null | United Kingdom 10091 | 1 | English | United States of America @@ -272,11 +273,12 @@ FROM employees | LIMIT 5 | EVAL language_code = emp_no % 10 | LOOKUP JOIN languages_lookup_non_unique_key ON language_code +| EVAL country = MV_SORT(country) | KEEP emp_no, language_code, language_name, country ; ignoreOrder:true -emp_no:integer | language_code:integer | language_name:keyword | country:text +emp_no:integer | language_code:integer | language_name:keyword | country:keyword 10001 | 1 | English | Canada 10001 | 1 | English | null 10001 | 1 | null | United Kingdom @@ -324,7 +326,7 @@ ROW language_code = 2 ignoreOrder:true language_code:integer | country:text | language_name:keyword -2 | [Austria, Germany] | German +2 | [Germany, Austria] | German 2 | Switzerland | German 2 | null | German ; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views.csv-spec new file mode 100644 index 0000000000000..6255a1312e5a8 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views.csv-spec @@ -0,0 +1,162 @@ +addressCountries +required_capability: views_v1 + +FROM address_countries +; +ignoreOrder:true + +count:long | country:keyword +1 | Japan +1 | Netherlands +1 | United States +; + +airports +FROM airports +| WHERE country IS NOT NULL +| STATS count=COUNT() BY country +| SORT count DESC, country ASC +| WHERE count > 10 +; +ignoreOrder:true + +count:long | country:keyword +129 | United States +50 | India +45 | Mexico +41 | China +37 | Canada +31 | Brazil +26 | Russia +17 | Australia +17 | United Kingdom +13 | Argentina +12 | France +12 | Indonesia +11 | Germany +11 | Italy +11 | Nigeria +; + +airportsCountries +required_capability: views_v1 + +FROM airports_countries +; +ignoreOrder:true + +count:long | country:keyword +129 | United States +50 | India +45 | Mexico +41 | China +37 | Canada +31 | Brazil +26 | Russia +17 | Australia +17 | United Kingdom +13 | Argentina +12 | France +12 | Indonesia +11 | Germany +11 | Italy +11 | Nigeria +; + +airportsCountriesFiltered +required_capability: views_v1 + +FROM airports_countries +| WHERE count > 15 +; +ignoreOrder:true + +count:long | country:keyword +129 | United States +50 | India +45 | Mexico +41 | China +37 | Canada +31 | Brazil +26 | Russia +17 | Australia +17 | United Kingdom +; + +countriesAirportsAddresses +required_capability: views_v1 +required_capability: views_with_branching + +FROM address_countries, airports_countries +| STATS count=SUM(count) BY country +| WHERE count > 11 +; +ignoreOrder:true + +count:long | country:keyword +130 | United States +50 | India +45 | Mexico +41 | China +37 | Canada +31 | Brazil +26 | Russia +17 | Australia +17 | United Kingdom +13 | Argentina +12 | France +12 | Indonesia +; + +countriesAirportsAddressesUS +required_capability: views_v1 +required_capability: views_with_branching + +FROM address_countries, airports_countries +| WHERE country == "United States" +; +ignoreOrder:true + +count:long | country:keyword +129 | United States +1 | United States +; + +languages +required_capability: views_v1 + +FROM languages_lookup_non_unique_key +| MV_EXPAND country +| WHERE country IS NOT NULL AND country NOT LIKE ("*-Land*") +| EVAL country = CASE(country == "United States of America", "United States", country) +| STATS count=COUNT() BY country +| SORT count DESC, country ASC +; +ignoreOrder:true + +count:long | country:keyword +1 | Atlantis +1 | Austria +1 | Canada +1 | Germany +1 | Switzerland +1 | United Kingdom +1 | United States +; + +languagesCountries +required_capability: views_v1 + +FROM languages_countries +; +ignoreOrder:true + +count:long | country:keyword +1 | Atlantis +1 | Austria +1 | Canada +1 | Germany +1 | Switzerland +1 | United Kingdom +1 | United States +; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/address_countries.esql b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/address_countries.esql new file mode 100644 index 0000000000000..3acb6d141262d --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/address_countries.esql @@ -0,0 +1,4 @@ +FROM addresses +| RENAME city.country.name AS country +| EVAL country = CASE(country == "United States of America", "United States", country) +| STATS count=COUNT() BY country diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/airports_countries.esql b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/airports_countries.esql new file mode 100644 index 0000000000000..ee14b3a9c6c30 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/airports_countries.esql @@ -0,0 +1,5 @@ +FROM airports +| WHERE country IS NOT NULL +| STATS count=COUNT() BY country +| SORT count DESC +| WHERE count > 10 diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/languages_countries.esql b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/languages_countries.esql new file mode 100644 index 0000000000000..4e0e93fb127bf --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/views/languages_countries.esql @@ -0,0 +1,5 @@ +FROM languages_lookup_non_unique_key +| MV_EXPAND country +| WHERE country IS NOT NULL AND country NOT LIKE ("*-Land*") +| EVAL country = CASE(country == "United States of America", "United States", country) +| STATS count=COUNT() BY country diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 53c3143f084c7..79ab94c862e3e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -1081,6 +1081,21 @@ public enum Cap { */ FORK_UNION_TYPES, + /** + * Views. + */ + VIEWS_V1(Build.current().isSnapshot()), + + /** + * Views with branching (requires FORK). + */ + VIEWS_WITH_BRANCHING(VIEWS_V1.isEnabled()), + + /** + * Support non-correlated subqueries in the FROM clause. + */ + SUBQUERY_IN_FROM_COMMAND(Build.current().isSnapshot()), + /** * Support for the {@code leading_zeros} named parameter. */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 57bcf37b68de8..87fc756077f2e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -33,6 +33,7 @@ import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute; +import org.elasticsearch.xpack.esql.core.expression.NameId; import org.elasticsearch.xpack.esql.core.expression.NamedExpression; import org.elasticsearch.xpack.esql.core.expression.Nullability; import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute; @@ -120,6 +121,7 @@ import org.elasticsearch.xpack.esql.plan.logical.Project; import org.elasticsearch.xpack.esql.plan.logical.Rename; import org.elasticsearch.xpack.esql.plan.logical.TimeSeriesAggregate; +import org.elasticsearch.xpack.esql.plan.logical.UnionAll; import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation; import org.elasticsearch.xpack.esql.plan.logical.fuse.Fuse; import org.elasticsearch.xpack.esql.plan.logical.fuse.FuseScoreEval; @@ -213,7 +215,8 @@ public class Analyzer extends ParameterizedRuleExecutor("Finish Analysis", Limiter.ONCE, new AddImplicitLimit(), new AddImplicitForkLimit(), new UnionTypesCleanup()) ); @@ -247,12 +250,10 @@ private static class ResolveTable extends ParameterizedAnalyzerRule childrenOutput) { @@ -958,7 +962,7 @@ private List resolveUsingColumns(List cols, List childrenOutput, IndexResolution indexResolution) { + private LogicalPlan resolveInsist(Insist insist, List childrenOutput, Collection indexResolution) { List list = new ArrayList<>(); for (Attribute a : insist.insistedAttributes()) { list.add(resolveInsistAttribute(a, childrenOutput, indexResolution)); @@ -966,7 +970,11 @@ private LogicalPlan resolveInsist(Insist insist, List childrenOutput, return insist.withAttributes(list); } - private Attribute resolveInsistAttribute(Attribute attribute, List childrenOutput, IndexResolution indexResolution) { + private Attribute resolveInsistAttribute( + Attribute attribute, + List childrenOutput, + Collection indexResolution + ) { Attribute resolvedCol = maybeResolveAttribute((UnresolvedAttribute) attribute, childrenOutput); // Field isn't mapped anywhere. if (resolvedCol instanceof UnresolvedAttribute) { @@ -974,7 +982,9 @@ private Attribute resolveInsistAttribute(Attribute attribute, List ch } // Field is partially unmapped. - if (resolvedCol instanceof FieldAttribute fa && indexResolution.get().isPartiallyUnmappedField(fa.name())) { + // TODO: Should the check for partially unmapped fields be done specific to each sub-query in a fork? + if (resolvedCol instanceof FieldAttribute fa + && indexResolution.stream().anyMatch(r -> r.get().isPartiallyUnmappedField(fa.name()))) { return fa.dataType() == KEYWORD ? insistKeyword(fa) : invalidInsistAttribute(fa); } @@ -2259,4 +2269,329 @@ private static AggregateMetricDoubleBlockBuilder.Metric getMetric(AggregateFunct return null; } } + + /** + * Handle union types in UnionAll: + * 1. Push down explicit conversion functions into the UnionAll legs + * 2. Replace the explicit conversion functions with the corresponding attributes in the UnionAll output + * 3. Implicitly cast the outputs of the UnionAll legs to the common type if necessary + * 4. Update the attributes referencing the updated UnionAll output + */ + private static class ResolveUnionTypesInUnionAll extends Rule { + // The mapping between explicit conversion functions and the corresponding attributes in the UnionAll output + private Map convertFunctionsToAttributes; + // The list of attributes in the UnionAll output that have been updated. The parent plans + // that reference these attributes need to be updated accordingly + private List updatedUnionAllOutput; + + @Override + public LogicalPlan apply(LogicalPlan plan) { + convertFunctionsToAttributes = new HashMap<>(); + updatedUnionAllOutput = new ArrayList<>(); + // First push down the conversion functions into the UnionAll legs + LogicalPlan planWithConvertFunctionsPushedDown = plan.transformUp( + UnionAll.class, + unionAll -> maybePushDownConvertFunctions(unionAll, plan) + ); + + // Then replace the conversion functions with the corresponding attributes in the UnionAll output + LogicalPlan planWithConvertFunctionsReplaced = replaceConvertFunctions(planWithConvertFunctionsPushedDown); + + // Next implicitly cast the outputs of the UnionAll legs to the common type if necessary + LogicalPlan planWithImplicitCasting = planWithConvertFunctionsReplaced.transformUp( + UnionAll.class, + unionAll -> unionAll.resolved() ? implicitCastingUnionAllOutput(unionAll, plan) : unionAll + ); + + // Finally update the attributes referencing the updated UnionAll output + return updatedUnionAllOutput.isEmpty() + ? planWithImplicitCasting + : updateAttributesReferencingUpdatedUnionAllOutput(planWithImplicitCasting); + } + + private LogicalPlan maybePushDownConvertFunctions(UnionAll unionAll, LogicalPlan plan) { + // collect all conversion functions in the plan that convert the unionAll outputs to a different type + Map convertFunctions = collectConvertFunctions(unionAll, plan); + if (convertFunctions.isEmpty()) { + // nothing to push down + return unionAll; + } + + // push down the conversion functions into the unionAll legs + List newChildren = new ArrayList<>(unionAll.children().size()); + boolean outputChanged = false; + for (LogicalPlan child : unionAll.children()) { + List childOutput = child.output(); + List newAliases = new ArrayList<>(); + List newChildOutput = new ArrayList<>(childOutput.size()); + for (Attribute oldAttr : childOutput) { + AbstractConvertFunction convertFunction = convertFunctions.get(oldAttr.name()); + // if the name of the attribute matches, and the data type is different, we need to add a conversion function + if (convertFunction != null && oldAttr.dataType() != convertFunction.dataType()) { + // create a new alias for the conversion function + Alias newAlias = new Alias( + oldAttr.source(), + oldAttr.name(), + convertFunction.replaceChildren(Collections.singletonList(oldAttr)) + ); + newAliases.add(newAlias); + newChildOutput.add(newAlias.toAttribute()); + outputChanged = true; + } else { + newChildOutput.add(oldAttr); + } + } + newChildren.add(maybePushDownConvertFunctionsToChild(child, newAliases, newChildOutput)); + } + + return outputChanged ? rebuildUnionAllOutput(unionAll, newChildren, convertFunctions) : unionAll; + } + + private Map collectConvertFunctions(UnionAll unionAll, LogicalPlan plan) { + Map convertFunctions = new HashMap<>(); + plan.forEachDown(p -> p.forEachExpression(AbstractConvertFunction.class, f -> { + if (f.field() instanceof Attribute attr && unionAll.output().contains(attr)) { + convertFunctions.putIfAbsent(attr.name(), f); + } + })); + return convertFunctions; + } + + private LogicalPlan maybePushDownConvertFunctionsToChild(LogicalPlan child, List aliases, List output) { + // Fork/UnionAll adds an EsqlProject on top of each child plan during resolveFork, check this pattern before pushing down + if (aliases.isEmpty() == false && child instanceof EsqlProject esqlProject) { + LogicalPlan childOfProject = esqlProject.child(); + Eval eval = new Eval(childOfProject.source(), childOfProject, aliases); + return new EsqlProject(esqlProject.source(), eval, output); + } + return child; + } + + private LogicalPlan rebuildUnionAllOutput( + UnionAll unionAll, + List newChildren, + Map convertFunctions + ) { + List newOutput = new ArrayList<>(); + for (Attribute oldAttr : unionAll.output()) { + DataType targetType = convertFunctions.containsKey(oldAttr.name()) + ? convertFunctions.get(oldAttr.name()).dataType() + : oldAttr.dataType(); + if (oldAttr.dataType() != targetType) { + ReferenceAttribute newAttr = new ReferenceAttribute( + oldAttr.source(), + null, + oldAttr.name(), + targetType, + oldAttr.nullable(), + oldAttr.id(), + oldAttr.synthetic() + ); + newOutput.add(newAttr); + convertFunctionsToAttributes.putIfAbsent(convertFunctions.get(oldAttr.name()), newAttr); + } else { + newOutput.add(oldAttr); + } + } + return new UnionAll(unionAll.source(), newChildren, newOutput); + } + + private LogicalPlan replaceConvertFunctions(LogicalPlan plan) { + if (convertFunctionsToAttributes.isEmpty()) { + return plan; + } + return plan.transformExpressionsUp(AbstractConvertFunction.class, expr -> { + Attribute attr = convertFunctionsToAttributes.get(expr); + return attr != null ? attr : expr; + }); + } + + private LogicalPlan implicitCastingUnionAllOutput(UnionAll unionAll, LogicalPlan plan) { + // build a map of UnionAll output to a list of LogicalPlan that reference this output + Map> outputToPlans = outputToPlans(unionAll, plan); + + List> outputs = unionAll.children().stream().map(LogicalPlan::output).toList(); + List commonTypes = commonTypes(outputs); + + Map indexToCommonType = new HashMap<>(); + + // Cast each leg's output to the common type + List newChildren = new ArrayList<>(unionAll.children().size()); + boolean outputChanged = false; + for (LogicalPlan child : unionAll.children()) { + List newAliases = new ArrayList<>(); + List oldChildOutput = child.output(); + List newChildOutput = new ArrayList<>(oldChildOutput.size()); + for (int i = 0; i < oldChildOutput.size(); i++) { + Attribute oldOutput = oldChildOutput.get(i); + DataType targetType = commonTypes.get(i); + Attribute resolved = resolveAttribute( + oldOutput, + targetType, + i, + outputs, + unionAll, + outputToPlans, + newAliases, + indexToCommonType + ); + newChildOutput.add(resolved); + if (resolved != oldOutput) { + outputChanged = true; + } + } + // create a new eval for the casting expressions, and push it down under the EsqlProject + newChildren.add(maybePushDownConvertFunctionsToChild(child, newAliases, newChildOutput)); + } + + // Update common types with overrides + indexToCommonType.forEach(commonTypes::set); + + return outputChanged ? rebuildUnionAllOutput(unionAll, newChildren, commonTypes) : unionAll; + } + + private Map> outputToPlans(UnionAll unionAll, LogicalPlan plan) { + Map> outputToPlans = new HashMap<>(); + plan.forEachDown(p -> p.forEachExpression(Attribute.class, attr -> { + if (p instanceof UnionAll == false && p instanceof Project == false && unionAll.output().contains(attr)) { + outputToPlans.computeIfAbsent(attr, k -> new ArrayList<>()).add(p); + } + })); + return outputToPlans; + } + + private List commonTypes(List> outputs) { + int columnCount = outputs.get(0).size(); + List commonTypes = new ArrayList<>(columnCount); + for (int i = 0; i < columnCount; i++) { + DataType type = outputs.get(0).get(i).dataType(); + for (List out : outputs) { + type = commonType(type, out.get(i).dataType()); + } + commonTypes.add(type); + } + return commonTypes; + } + + private DataType commonType(DataType t1, DataType t2) { + if (t1 == null || t2 == null) { + return null; + } + if (t1.isDate() && t2.isDate() && t1 != t2) { + return DATE_NANOS; + } + return EsqlDataTypeConverter.commonType(t1, t2); + } + + private Attribute resolveAttribute( + Attribute oldAttr, + DataType targetType, + int columnIndex, + List> outputs, + UnionAll unionAll, + Map> outputToPlans, + List newAliases, + Map indexToCommonType + ) { + if (targetType == null) { + return createUnsupportedOrNull(oldAttr, columnIndex, outputs, unionAll, outputToPlans, newAliases, indexToCommonType); + } + + if (targetType != NULL && oldAttr.dataType() != targetType) { + var converterFactory = EsqlDataTypeConverter.converterFunctionFactory(targetType); + if (converterFactory != null) { + var converter = converterFactory.apply(oldAttr.source(), oldAttr); + if (converter != null) { + Alias alias = new Alias(oldAttr.source(), oldAttr.name(), converter); + newAliases.add(alias); + return alias.toAttribute(); + } + } + } + return oldAttr; + } + + private Attribute createUnsupportedOrNull( + Attribute oldAttr, + int columnIndex, + List> outputs, + UnionAll unionAll, + Map> outputToPlans, + List newAliases, + Map indexToCommonType + ) { + Attribute unionAttr = unionAll.output().get(columnIndex); + + if (outputToPlans.containsKey(unionAttr)) { + // Unsupported attribute + List dataTypes = collectIncompatibleTypes(columnIndex, outputs); + UnsupportedAttribute unsupported = new UnsupportedAttribute( + oldAttr.source(), + oldAttr.name(), + new UnsupportedEsField(oldAttr.name(), dataTypes), + "Column [" + oldAttr.name() + "] has conflicting data types in subqueries: " + dataTypes, + oldAttr.id() + ); + newAliases.add(new Alias(oldAttr.source(), oldAttr.name(), unsupported)); + indexToCommonType.putIfAbsent(columnIndex, UNSUPPORTED); + return unsupported; + } else { + // Null alias with keyword type + Alias nullAlias = new Alias(oldAttr.source(), oldAttr.name(), new Literal(oldAttr.source(), null, KEYWORD)); + newAliases.add(nullAlias); + indexToCommonType.putIfAbsent(columnIndex, KEYWORD); + return nullAlias.toAttribute(); + } + } + + private List collectIncompatibleTypes(int columnIndex, List> outputs) { + List dataTypes = new ArrayList<>(); + for (List out : outputs) { + Attribute attr = out.get(columnIndex); + if (attr instanceof FieldAttribute fa && fa.field() instanceof InvalidMappedField imf) { + dataTypes.addAll(imf.types().stream().map(DataType::typeName).toList()); + } else { + dataTypes.add(attr.dataType().typeName()); + } + } + return dataTypes; + } + + private UnionAll rebuildUnionAllOutput(UnionAll unionAll, List newChildren, List commonTypes) { + // Rebuild the newUnionAll's output to ensure the correct attributes are used + List oldOutput = unionAll.output(); + List newOutput = new ArrayList<>(oldOutput.size()); + + for (int i = 0; i < oldOutput.size(); i++) { + Attribute oldAttr = oldOutput.get(i); + DataType commonType = commonTypes.get(i); + + if (oldAttr.dataType() != commonType) { + // keep the id unchanged, otherwise the downstream operators won't recognize the attribute + ReferenceAttribute newAttr = new ReferenceAttribute( + oldAttr.source(), + null, + oldAttr.name(), + commonType, + oldAttr.nullable(), + oldAttr.id(), + oldAttr.synthetic() + ); + newOutput.add(newAttr); + updatedUnionAllOutput.add(newAttr); + } else { + newOutput.add(oldAttr); + } + } + return new UnionAll(unionAll.source(), newChildren, newOutput); + } + + private LogicalPlan updateAttributesReferencingUpdatedUnionAllOutput(LogicalPlan plan) { + Map idToUpdatedAttr = updatedUnionAllOutput.stream().collect(Collectors.toMap(Attribute::id, attr -> attr)); + return plan.transformExpressionsUp(Attribute.class, expr -> { + Attribute updated = idToUpdatedAttr.get(expr.id()); + return (updated != null && expr.dataType() != updated.dataType()) ? updated : expr; + }); + } + } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java index da74cd2bd779c..8a7153e31493c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java @@ -11,6 +11,7 @@ import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.inference.InferenceResolution; +import org.elasticsearch.xpack.esql.plan.IndexPattern; import org.elasticsearch.xpack.esql.session.Configuration; import org.elasticsearch.xpack.esql.session.EsqlSession; @@ -19,7 +20,7 @@ public record AnalyzerContext( Configuration configuration, EsqlFunctionRegistry functionRegistry, - IndexResolution indexResolution, + Map indexResolution, Map lookupResolution, EnrichResolution enrichResolution, InferenceResolution inferenceResolution, @@ -29,7 +30,7 @@ public record AnalyzerContext( public AnalyzerContext( Configuration configuration, EsqlFunctionRegistry functionRegistry, - IndexResolution indexResolution, + Map indexResolution, Map lookupResolution, EnrichResolution enrichResolution, InferenceResolution inferenceResolution, @@ -52,7 +53,7 @@ public AnalyzerContext(Configuration configuration, EsqlFunctionRegistry functio this( configuration, functionRegistry, - result.indices(), + result.indexResolutions(), result.lookupIndices(), result.enrichResolution(), result.inferenceResolution(), diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PreAnalyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PreAnalyzer.java index 2488172cb184a..19d9d10941f72 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PreAnalyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PreAnalyzer.java @@ -16,7 +16,9 @@ import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * This class is part of the planner. Acts somewhat like a linker, to find the indices and enrich policies referenced by the query. @@ -24,14 +26,13 @@ public class PreAnalyzer { public record PreAnalysis( - IndexMode indexMode, - IndexPattern indexPattern, + Map indexes, List enriches, List lookupIndices, boolean supportsAggregateMetricDouble, boolean supportsDenseVector ) { - public static final PreAnalysis EMPTY = new PreAnalysis(null, null, List.of(), List.of(), false, false); + public static final PreAnalysis EMPTY = new PreAnalysis(null, List.of(), List.of(), false, false); } public PreAnalysis preAnalyze(LogicalPlan plan) { @@ -43,17 +44,19 @@ public PreAnalysis preAnalyze(LogicalPlan plan) { } protected PreAnalysis doPreAnalyze(LogicalPlan plan) { - Holder indexMode = new Holder<>(); - Holder indexPattern = new Holder<>(); + Map indexes = new HashMap<>(); List lookupIndices = new ArrayList<>(); plan.forEachUp(UnresolvedRelation.class, p -> { if (p.indexMode() == IndexMode.LOOKUP) { lookupIndices.add(p.indexPattern()); - } else if (indexMode.get() == null || indexMode.get() == p.indexMode()) { - indexMode.set(p.indexMode()); - indexPattern.set(p.indexPattern()); + } else if (indexes.containsKey(p.indexPattern()) == false || indexes.get(p.indexPattern()) == p.indexMode()) { + indexes.put(p.indexPattern(), p.indexMode()); } else { - throw new IllegalStateException("index mode is already set"); + IndexMode m1 = p.indexMode(); + IndexMode m2 = indexes.get(p.indexPattern()); + throw new IllegalStateException( + "index pattern '" + p.indexPattern() + "' found with with different index mode: " + m2 + " != " + m1 + ); } }); @@ -71,6 +74,11 @@ protected PreAnalysis doPreAnalyze(LogicalPlan plan) { */ Holder supportsAggregateMetricDouble = new Holder<>(false); Holder supportsDenseVector = new Holder<>(false); + indexes.forEach((ip, mode) -> { + if (mode == IndexMode.TIME_SERIES) { + supportsAggregateMetricDouble.set(true); + } + }); plan.forEachDown(p -> p.forEachExpression(UnresolvedFunction.class, fn -> { if (fn.name().equalsIgnoreCase("knn") || fn.name().equalsIgnoreCase("to_dense_vector") @@ -90,13 +98,6 @@ protected PreAnalysis doPreAnalyze(LogicalPlan plan) { // mark plan as preAnalyzed (if it were marked, there would be no analysis) plan.forEachUp(LogicalPlan::setPreAnalyzed); - return new PreAnalysis( - indexMode.get(), - indexPattern.get(), - unresolvedEnriches, - lookupIndices, - indexMode.get() == IndexMode.TIME_SERIES || supportsAggregateMetricDouble.get(), - supportsDenseVector.get() - ); + return new PreAnalysis(indexes, unresolvedEnriches, lookupIndices, supportsAggregateMetricDouble.get(), supportsDenseVector.get()); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java index 90a0f5a3a88fe..94219aeec3e63 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java @@ -30,6 +30,7 @@ import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry; import org.elasticsearch.xpack.esql.telemetry.PlanTelemetryManager; import org.elasticsearch.xpack.esql.telemetry.QueryMetric; +import org.elasticsearch.xpack.esql.view.ViewService; import java.util.List; import java.util.function.BiConsumer; @@ -49,6 +50,7 @@ public class PlanExecutor { public PlanExecutor( IndexResolver indexResolver, + EsqlFunctionRegistry functionRegistry, MeterRegistry meterRegistry, XPackLicenseState licenseState, EsqlQueryLog queryLog, @@ -56,7 +58,7 @@ public PlanExecutor( ) { this.indexResolver = indexResolver; this.preAnalyzer = new PreAnalyzer(); - this.functionRegistry = new EsqlFunctionRegistry(); + this.functionRegistry = functionRegistry; this.mapper = new Mapper(); this.metrics = new Metrics(functionRegistry); this.verifier = new Verifier(metrics, licenseState, extraCheckers); @@ -69,6 +71,7 @@ public void esql( String sessionId, AnalyzerSettings analyzerSettings, EnrichPolicyResolver enrichPolicyResolver, + ViewService viewService, EsqlExecutionInfo executionInfo, IndicesExpressionGrouper indicesExpressionGrouper, EsqlSession.PlanRunner planRunner, @@ -81,6 +84,7 @@ public void esql( analyzerSettings, indexResolver, enrichPolicyResolver, + viewService, preAnalyzer, functionRegistry, mapper, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFilters.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFilters.java index 29a9f10270b0d..0a7bff0d979ea 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFilters.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFilters.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.optimizer.rules.logical; +import org.elasticsearch.core.Tuple; import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.AttributeMap; @@ -15,6 +16,7 @@ import org.elasticsearch.xpack.esql.core.expression.Expressions; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; +import org.elasticsearch.xpack.esql.core.expression.NamedExpression; import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute; import org.elasticsearch.xpack.esql.core.util.CollectionUtils; import org.elasticsearch.xpack.esql.expression.predicate.Predicates; @@ -22,11 +24,13 @@ import org.elasticsearch.xpack.esql.plan.logical.Enrich; import org.elasticsearch.xpack.esql.plan.logical.Eval; import org.elasticsearch.xpack.esql.plan.logical.Filter; +import org.elasticsearch.xpack.esql.plan.logical.Limit; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.logical.OrderBy; import org.elasticsearch.xpack.esql.plan.logical.Project; import org.elasticsearch.xpack.esql.plan.logical.RegexExtract; import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan; +import org.elasticsearch.xpack.esql.plan.logical.UnionAll; import org.elasticsearch.xpack.esql.plan.logical.inference.InferencePlan; import org.elasticsearch.xpack.esql.plan.logical.join.InlineJoin; import org.elasticsearch.xpack.esql.plan.logical.join.Join; @@ -37,6 +41,9 @@ import java.util.function.Function; import java.util.function.Predicate; +import static org.elasticsearch.xpack.esql.core.expression.Attribute.SYNTHETIC_ATTRIBUTE_NAME_SEPARATOR; +import static org.elasticsearch.xpack.esql.core.expression.Attribute.rawTemporaryName; + /** * Perform filters as early as possible in the logical plan by pushing them past certain plan nodes (like {@link Eval}, * {@link RegexExtract}, {@link Enrich}, {@link Project}, {@link OrderBy} and left {@link Join}s) where possible. @@ -109,6 +116,9 @@ protected LogicalPlan rule(Filter filter, LogicalOptimizerContext ctx) { // See also https://github.com/elastic/elasticsearch/issues/127497 // Push down past INLINE STATS if the condition is on the groupings return pushDownPastJoin(filter, join, ctx.foldCtx()); + } else if (child instanceof UnionAll unionAll) { + // Push down filters that can be evaluated using only the output of the UnionAll + plan = maybePushDownPastUnionAll(filter, unionAll); } // cannot push past a Limit, this could change the tailing result set returned return plan; @@ -283,4 +293,222 @@ private static LogicalPlan maybePushDownPastUnary( } return plan; } + + /* Push down filters that can be evaluated by the UnionAll child/leg to each child/leg, + * so that the filters can be pushed down further to the data source when possible. + * Filters that cannot be pushed down remain above the UnionAll. + * + * The children of a UnionAll/Fork plan has a similar pattern, as Fork adds EsqlProject, + * an optional Eval and Limit on top of its actual children. + * UnionAll + * EsqlProject + * Eval (optional) + * Limit + * EsRelation + * EsqlProject + * Eval (optional) + * Limit + * Subquery + * + * Push down the filter below limit when possible + */ + private static LogicalPlan maybePushDownPastUnionAll(Filter filter, UnionAll unionAll) { + List pushable = new ArrayList<>(); + List nonPushable = new ArrayList<>(); + for (Expression exp : Predicates.splitAnd(filter.condition())) { + if (exp.references().subsetOf(unionAll.outputSet())) { + pushable.add(exp); + } else { + nonPushable.add(exp); + } + } + if (pushable.isEmpty()) { + return filter; // nothing to push down + } + // Push the filter down to each child of the UnionAll, the child of a UnionAll is always + // a project followed by an optional eval and then limit or a limit added by fork and + // then the real child, if there is unknown pattern, keep the filter and UnionAll plan unchanged + List newChildren = new ArrayList<>(); + boolean changed = false; + for (LogicalPlan child : unionAll.children()) { + LogicalPlan newChild = switch (child) { + case Project project -> maybePushDownFilterPastProjectForUnionAllChild(pushable, project); + case Limit limit -> maybePushDownFilterPastLimitForUnionAllChild(pushable, limit); + default -> null; // TODO add a general push down for unexpected pattern + }; + + if (newChild == null) { + // Unexpected pattern, keep plan unchanged without pushing down filters + return filter; + } + + if (newChild != child) { + changed = true; + newChildren.add(newChild); + } else { + // Theoretically, all the pushable predicates should be pushed down into each child, + // in case one child is not changed, preserve the filter on top of UnionAll to make sure + // correct results are returned and avoid infinite loop of the rule. + return filter; + } + } + + if (changed == false) { // nothing changed, return the original plan + return filter; + } + + LogicalPlan newUnionAll = unionAll.replaceChildren(newChildren); + if (nonPushable.isEmpty()) { + return newUnionAll; + } else { + return filter.with(newUnionAll, Predicates.combineAnd(nonPushable)); + } + } + + private static LogicalPlan maybePushDownFilterPastProjectForUnionAllChild(List pushable, Project project) { + List resolvedPushable = resolvePushableAgainstOutput(pushable, project.projections()); + if (resolvedPushable == null) { + return project; + } + LogicalPlan child = project.child(); + if (child instanceof Eval eval) { + return pushDownFilterPastEvalForUnionAllChild(resolvedPushable, project, eval); + } else if (child instanceof Limit limit) { + LogicalPlan newLimit = pushDownFilterPastLimitForUnionAllChild(resolvedPushable, limit); + return project.replaceChild(newLimit); + } + return project; + } + + private static LogicalPlan maybePushDownFilterPastLimitForUnionAllChild(List pushable, Limit limit) { + List resolvedPushable = resolvePushableAgainstOutput(pushable, limit.output()); + if (resolvedPushable == null) { + return limit; + } + return pushDownFilterPastLimitForUnionAllChild(resolvedPushable, limit); + } + + /** + * Attempts to resolve all pushable expressions against the given output attributes. + * Returns a fully resolved list if successful, or null if any expression cannot be resolved. + */ + private static List resolvePushableAgainstOutput(List pushable, List output) { + List resolved = new ArrayList<>(); + for (Expression exp : pushable) { + Expression replaced = resolveUnionAllOutputByName(exp, output); + // Make sure the pushable predicates can find their corresponding attributes in the output + if (replaced == null || replaced == exp) { + // cannot find the attribute in the child project, cannot push down this filter + return null; + } + resolved.add(replaced); + } + // If some pushable predicates cannot be resolved against the output, cannot push filter down. + // This should not happen, however we need to be cautious here, if the predicate is removed from + // the main query, and it is not pushed down into the UnionAll child, the result will be incorrect. + return resolved.size() == pushable.size() ? resolved : null; + } + + private static LogicalPlan pushDownFilterPastEvalForUnionAllChild(List pushable, Project project, Eval eval) { + // if the pushable references any attribute created by the eval, we cannot push down + AttributeMap evalAliases = buildEvaAliases(eval); + Tuple, List> pushablesAndNonPushables = splitPushableAndNonPushablePredicates( + pushable, + exp -> exp.references().stream().anyMatch(evalAliases::containsKey) + ); + List pushables = pushablesAndNonPushables.v1(); + List nonPushables = pushablesAndNonPushables.v2(); + + LogicalPlan evalChild = eval.child(); + + // Nothing to push down under eval and limit + if (pushables.isEmpty()) { + return nonPushables.isEmpty() + ? project // nothing at all + : withFilter(project, eval, nonPushables); // Push down filter references eval created attributes below project, above eval + } + + // Push down all pushable predicates below eval and limit + if (evalChild instanceof Limit limit) { + LogicalPlan newLimit = pushDownFilterPastLimitForUnionAllChild(pushables, limit); + LogicalPlan newEval = eval.replaceChild(newLimit); + + return nonPushables.isEmpty() ? project.replaceChild(newEval) : withFilter(project, newEval, nonPushables); + } + + return project; + } + + private static LogicalPlan withFilter(Project project, LogicalPlan child, List predicates) { + Expression combined = Predicates.combineAnd(predicates); + return project.replaceChild(new Filter(project.source(), child, combined)); + } + + /** + * limit does not create any new attributes, so we should push down all pushable predicates, + * the caller should make sure the pushable is really pushable. + */ + private static LogicalPlan pushDownFilterPastLimitForUnionAllChild(List pushable, Limit limit) { + if (pushable.isEmpty()) { + return limit; + } + Expression combined = Predicates.combineAnd(pushable); + Filter pushed = new Filter(limit.source(), limit.child(), combined); + return limit.replaceChild(pushed); + } + + private static AttributeMap buildEvaAliases(Eval eval) { + AttributeMap.Builder builder = AttributeMap.builder(); + for (Alias alias : eval.fields()) { + builder.put(alias.toAttribute(), alias.child()); + } + return builder.build(); + } + + private static Tuple, List> splitPushableAndNonPushablePredicates( + List predicates, + Predicate nonPushableCheck + ) { + List pushable = new ArrayList<>(); + List nonPushable = new ArrayList<>(); + for (Expression exp : predicates) { + if (nonPushableCheck.test(exp)) { + nonPushable.add(exp); + } else { + pushable.add(exp); + } + } + return Tuple.tuple(pushable, nonPushable); + } + + /** + * The UnionAll/Fork outputs have the same names as it children's outputs, however they have different ids. + * Convert the pushable predicates to use the child's attributes, so that they can be pushed down further. + */ + private static Expression resolveUnionAllOutputByName(Expression expr, List namedExpressions) { + // A temporary expression is created with temporary attributes names, as sometimes transform expression does not transform + // one ReferenceAttribute to another ReferenceAttribute with the same name, different id successfully. + String UNIONALL = "unionall"; + // rename the output of the UnionAll to a temporary name with a prefix + Expression renamed = expr.transformUp(Attribute.class, attr -> { + for (NamedExpression ne : namedExpressions) { + if (ne.name().equals(attr.name())) { + // $$subquery$attr.name() + return attr.withName(rawTemporaryName(UNIONALL, ne.name())); + } + } + return attr; + }); + + String prefix = Attribute.SYNTHETIC_ATTRIBUTE_NAME_PREFIX + UNIONALL + SYNTHETIC_ATTRIBUTE_NAME_SEPARATOR; + return renamed.transformUp(Attribute.class, attr -> { + String originalName = attr.name().startsWith(prefix) ? attr.name().substring(prefix.length()) : attr.name(); + for (NamedExpression ne : namedExpressions) { + if (ne.name().equals(originalName)) { + return ne.toAttribute(); + } + } + return attr; + }); + } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp index 9a0de8cf2243e..326fbc2807ea3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp @@ -627,4 +627,4 @@ SET_MODE SHOW_MODE atn: -[4, 0, 151, 2150, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 2, 199, 7, 199, 2, 200, 7, 200, 2, 201, 7, 201, 2, 202, 7, 202, 2, 203, 7, 203, 2, 204, 7, 204, 2, 205, 7, 205, 2, 206, 7, 206, 2, 207, 7, 207, 2, 208, 7, 208, 2, 209, 7, 209, 2, 210, 7, 210, 2, 211, 7, 211, 2, 212, 7, 212, 2, 213, 7, 213, 2, 214, 7, 214, 2, 215, 7, 215, 2, 216, 7, 216, 2, 217, 7, 217, 2, 218, 7, 218, 2, 219, 7, 219, 2, 220, 7, 220, 2, 221, 7, 221, 2, 222, 7, 222, 2, 223, 7, 223, 2, 224, 7, 224, 2, 225, 7, 225, 2, 226, 7, 226, 2, 227, 7, 227, 2, 228, 7, 228, 2, 229, 7, 229, 2, 230, 7, 230, 2, 231, 7, 231, 2, 232, 7, 232, 2, 233, 7, 233, 2, 234, 7, 234, 2, 235, 7, 235, 2, 236, 7, 236, 2, 237, 7, 237, 2, 238, 7, 238, 2, 239, 7, 239, 2, 240, 7, 240, 2, 241, 7, 241, 2, 242, 7, 242, 2, 243, 7, 243, 2, 244, 7, 244, 2, 245, 7, 245, 2, 246, 7, 246, 2, 247, 7, 247, 2, 248, 7, 248, 2, 249, 7, 249, 2, 250, 7, 250, 2, 251, 7, 251, 2, 252, 7, 252, 2, 253, 7, 253, 2, 254, 7, 254, 2, 255, 7, 255, 2, 256, 7, 256, 2, 257, 7, 257, 2, 258, 7, 258, 2, 259, 7, 259, 2, 260, 7, 260, 2, 261, 7, 261, 2, 262, 7, 262, 2, 263, 7, 263, 2, 264, 7, 264, 2, 265, 7, 265, 2, 266, 7, 266, 2, 267, 7, 267, 2, 268, 7, 268, 2, 269, 7, 269, 2, 270, 7, 270, 2, 271, 7, 271, 2, 272, 7, 272, 2, 273, 7, 273, 2, 274, 7, 274, 2, 275, 7, 275, 2, 276, 7, 276, 2, 277, 7, 277, 2, 278, 7, 278, 2, 279, 7, 279, 2, 280, 7, 280, 2, 281, 7, 281, 2, 282, 7, 282, 2, 283, 7, 283, 2, 284, 7, 284, 2, 285, 7, 285, 2, 286, 7, 286, 2, 287, 7, 287, 2, 288, 7, 288, 2, 289, 7, 289, 2, 290, 7, 290, 2, 291, 7, 291, 2, 292, 7, 292, 2, 293, 7, 293, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 611, 8, 0, 10, 0, 12, 0, 614, 9, 0, 1, 0, 3, 0, 617, 8, 0, 1, 0, 3, 0, 620, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 629, 8, 1, 10, 1, 12, 1, 632, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 640, 8, 2, 11, 2, 12, 2, 641, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 4, 35, 929, 8, 35, 11, 35, 12, 35, 930, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 4, 54, 1014, 8, 54, 11, 54, 12, 54, 1015, 1, 54, 1, 54, 3, 54, 1020, 8, 54, 1, 54, 4, 54, 1023, 8, 54, 11, 54, 12, 54, 1024, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 87, 1, 87, 3, 87, 1157, 8, 87, 1, 87, 4, 87, 1160, 8, 87, 11, 87, 12, 87, 1161, 1, 88, 1, 88, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 3, 90, 1171, 8, 90, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 3, 92, 1178, 8, 92, 1, 93, 1, 93, 1, 93, 5, 93, 1183, 8, 93, 10, 93, 12, 93, 1186, 9, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 5, 93, 1194, 8, 93, 10, 93, 12, 93, 1197, 9, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 3, 93, 1204, 8, 93, 1, 93, 3, 93, 1207, 8, 93, 3, 93, 1209, 8, 93, 1, 94, 4, 94, 1212, 8, 94, 11, 94, 12, 94, 1213, 1, 95, 4, 95, 1217, 8, 95, 11, 95, 12, 95, 1218, 1, 95, 1, 95, 5, 95, 1223, 8, 95, 10, 95, 12, 95, 1226, 9, 95, 1, 95, 1, 95, 4, 95, 1230, 8, 95, 11, 95, 12, 95, 1231, 1, 95, 4, 95, 1235, 8, 95, 11, 95, 12, 95, 1236, 1, 95, 1, 95, 5, 95, 1241, 8, 95, 10, 95, 12, 95, 1244, 9, 95, 3, 95, 1246, 8, 95, 1, 95, 1, 95, 1, 95, 1, 95, 4, 95, 1252, 8, 95, 11, 95, 12, 95, 1253, 1, 95, 1, 95, 3, 95, 1258, 8, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 102, 1, 102, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 129, 1, 129, 1, 130, 1, 130, 1, 131, 1, 131, 1, 132, 1, 132, 1, 133, 1, 133, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 3, 137, 1399, 8, 137, 1, 137, 5, 137, 1402, 8, 137, 10, 137, 12, 137, 1405, 9, 137, 1, 137, 1, 137, 4, 137, 1409, 8, 137, 11, 137, 12, 137, 1410, 3, 137, 1413, 8, 137, 1, 138, 1, 138, 1, 138, 3, 138, 1418, 8, 138, 1, 138, 5, 138, 1421, 8, 138, 10, 138, 12, 138, 1424, 9, 138, 1, 138, 1, 138, 4, 138, 1428, 8, 138, 11, 138, 12, 138, 1429, 3, 138, 1432, 8, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 5, 143, 1456, 8, 143, 10, 143, 12, 143, 1459, 9, 143, 1, 143, 1, 143, 3, 143, 1463, 8, 143, 1, 143, 4, 143, 1466, 8, 143, 11, 143, 12, 143, 1467, 3, 143, 1470, 8, 143, 1, 144, 1, 144, 4, 144, 1474, 8, 144, 11, 144, 12, 144, 1475, 1, 144, 1, 144, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 3, 156, 1532, 8, 156, 1, 157, 4, 157, 1535, 8, 157, 11, 157, 12, 157, 1536, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 199, 1, 199, 1, 199, 1, 199, 1, 200, 1, 200, 1, 200, 1, 200, 1, 201, 1, 201, 1, 201, 1, 201, 1, 202, 1, 202, 1, 202, 1, 202, 1, 202, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 204, 1, 204, 1, 204, 1, 204, 1, 205, 1, 205, 1, 205, 1, 205, 1, 206, 1, 206, 1, 206, 1, 206, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 208, 1, 208, 1, 208, 1, 208, 1, 209, 1, 209, 1, 209, 1, 209, 1, 210, 1, 210, 1, 210, 1, 210, 1, 211, 1, 211, 1, 211, 1, 211, 1, 212, 1, 212, 1, 212, 1, 212, 1, 213, 1, 213, 1, 213, 1, 213, 1, 213, 1, 213, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 215, 1, 215, 1, 215, 1, 215, 1, 216, 1, 216, 1, 216, 1, 216, 1, 217, 1, 217, 1, 217, 1, 217, 1, 218, 1, 218, 1, 218, 1, 218, 1, 219, 1, 219, 1, 219, 1, 219, 1, 220, 1, 220, 1, 220, 1, 220, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 222, 1, 222, 1, 222, 1, 222, 1, 222, 1, 222, 1, 223, 1, 223, 1, 223, 1, 223, 1, 224, 1, 224, 1, 224, 1, 224, 1, 225, 1, 225, 1, 225, 1, 225, 1, 226, 1, 226, 1, 226, 1, 226, 1, 227, 1, 227, 1, 227, 1, 227, 1, 228, 1, 228, 1, 228, 1, 228, 1, 229, 1, 229, 1, 229, 1, 229, 1, 230, 1, 230, 1, 230, 1, 230, 1, 231, 1, 231, 1, 231, 1, 231, 1, 232, 1, 232, 1, 232, 1, 232, 1, 233, 1, 233, 1, 233, 1, 233, 1, 234, 1, 234, 1, 234, 1, 234, 1, 235, 1, 235, 1, 235, 1, 235, 1, 235, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 237, 1, 237, 1, 237, 1, 237, 1, 238, 1, 238, 1, 238, 1, 238, 1, 239, 1, 239, 1, 239, 1, 239, 1, 240, 1, 240, 1, 240, 1, 240, 1, 241, 1, 241, 1, 241, 1, 241, 1, 242, 1, 242, 1, 242, 1, 242, 1, 243, 1, 243, 1, 243, 1, 243, 1, 244, 1, 244, 1, 244, 1, 244, 1, 245, 1, 245, 1, 245, 1, 245, 3, 245, 1933, 8, 245, 1, 246, 1, 246, 3, 246, 1937, 8, 246, 1, 246, 5, 246, 1940, 8, 246, 10, 246, 12, 246, 1943, 9, 246, 1, 246, 1, 246, 3, 246, 1947, 8, 246, 1, 246, 4, 246, 1950, 8, 246, 11, 246, 12, 246, 1951, 3, 246, 1954, 8, 246, 1, 247, 1, 247, 4, 247, 1958, 8, 247, 11, 247, 12, 247, 1959, 1, 248, 1, 248, 1, 248, 1, 248, 1, 249, 1, 249, 1, 249, 1, 249, 1, 250, 1, 250, 1, 250, 1, 250, 1, 251, 1, 251, 1, 251, 1, 251, 1, 251, 1, 252, 1, 252, 1, 252, 1, 252, 1, 252, 1, 252, 1, 253, 1, 253, 1, 253, 1, 253, 1, 254, 1, 254, 1, 254, 1, 254, 1, 255, 1, 255, 1, 255, 1, 255, 1, 256, 1, 256, 1, 256, 1, 256, 1, 257, 1, 257, 1, 257, 1, 257, 1, 258, 1, 258, 1, 258, 1, 258, 1, 259, 1, 259, 1, 259, 1, 259, 1, 260, 1, 260, 1, 260, 1, 260, 1, 261, 1, 261, 1, 261, 1, 261, 1, 262, 1, 262, 1, 262, 1, 263, 1, 263, 1, 263, 1, 263, 1, 264, 1, 264, 1, 264, 1, 264, 1, 265, 1, 265, 1, 265, 1, 265, 1, 266, 1, 266, 1, 266, 1, 266, 1, 267, 1, 267, 1, 267, 1, 267, 1, 268, 1, 268, 1, 268, 1, 268, 1, 269, 1, 269, 1, 269, 1, 269, 1, 270, 1, 270, 1, 270, 1, 270, 1, 270, 1, 271, 1, 271, 1, 271, 1, 271, 1, 272, 1, 272, 1, 272, 1, 272, 1, 273, 1, 273, 1, 273, 1, 273, 1, 274, 1, 274, 1, 274, 1, 274, 1, 275, 1, 275, 1, 275, 1, 275, 1, 276, 1, 276, 1, 276, 1, 276, 1, 277, 1, 277, 1, 277, 1, 277, 1, 278, 1, 278, 1, 278, 1, 278, 1, 279, 1, 279, 1, 279, 1, 279, 1, 280, 1, 280, 1, 280, 1, 280, 1, 281, 1, 281, 1, 281, 1, 281, 1, 282, 1, 282, 1, 282, 1, 282, 1, 283, 1, 283, 1, 283, 1, 283, 1, 284, 1, 284, 1, 284, 1, 284, 1, 285, 1, 285, 1, 285, 1, 285, 1, 286, 1, 286, 1, 286, 1, 286, 1, 287, 1, 287, 1, 287, 1, 287, 1, 288, 1, 288, 1, 288, 1, 288, 1, 289, 1, 289, 1, 289, 1, 289, 1, 289, 1, 290, 1, 290, 1, 290, 1, 290, 1, 290, 1, 291, 1, 291, 1, 291, 1, 291, 1, 292, 1, 292, 1, 292, 1, 292, 1, 293, 1, 293, 1, 293, 1, 293, 2, 630, 1195, 0, 294, 18, 1, 20, 2, 22, 3, 24, 4, 26, 5, 28, 6, 30, 7, 32, 8, 34, 9, 36, 10, 38, 11, 40, 12, 42, 13, 44, 14, 46, 15, 48, 16, 50, 17, 52, 18, 54, 19, 56, 20, 58, 21, 60, 22, 62, 23, 64, 24, 66, 25, 68, 26, 70, 27, 72, 28, 74, 29, 76, 30, 78, 31, 80, 32, 82, 33, 84, 34, 86, 35, 88, 36, 90, 0, 92, 0, 94, 0, 96, 0, 98, 0, 100, 0, 102, 0, 104, 0, 106, 0, 108, 0, 110, 37, 112, 38, 114, 39, 116, 0, 118, 0, 120, 0, 122, 0, 124, 0, 126, 40, 128, 0, 130, 0, 132, 41, 134, 42, 136, 43, 138, 0, 140, 0, 142, 0, 144, 0, 146, 0, 148, 0, 150, 0, 152, 0, 154, 0, 156, 0, 158, 0, 160, 0, 162, 0, 164, 0, 166, 44, 168, 45, 170, 46, 172, 0, 174, 0, 176, 47, 178, 48, 180, 49, 182, 50, 184, 0, 186, 0, 188, 0, 190, 0, 192, 0, 194, 0, 196, 0, 198, 0, 200, 0, 202, 0, 204, 51, 206, 52, 208, 53, 210, 54, 212, 55, 214, 56, 216, 57, 218, 58, 220, 59, 222, 60, 224, 61, 226, 62, 228, 63, 230, 64, 232, 65, 234, 66, 236, 67, 238, 68, 240, 69, 242, 70, 244, 71, 246, 72, 248, 73, 250, 74, 252, 75, 254, 76, 256, 77, 258, 78, 260, 79, 262, 80, 264, 81, 266, 82, 268, 83, 270, 84, 272, 85, 274, 86, 276, 87, 278, 88, 280, 89, 282, 90, 284, 91, 286, 92, 288, 93, 290, 0, 292, 94, 294, 95, 296, 96, 298, 97, 300, 98, 302, 99, 304, 100, 306, 0, 308, 101, 310, 102, 312, 103, 314, 104, 316, 0, 318, 0, 320, 0, 322, 0, 324, 0, 326, 105, 328, 0, 330, 0, 332, 106, 334, 0, 336, 0, 338, 107, 340, 108, 342, 109, 344, 0, 346, 0, 348, 0, 350, 110, 352, 111, 354, 112, 356, 0, 358, 0, 360, 113, 362, 114, 364, 115, 366, 0, 368, 0, 370, 0, 372, 0, 374, 0, 376, 0, 378, 0, 380, 0, 382, 0, 384, 0, 386, 116, 388, 117, 390, 118, 392, 119, 394, 120, 396, 121, 398, 122, 400, 0, 402, 123, 404, 0, 406, 0, 408, 124, 410, 0, 412, 0, 414, 0, 416, 125, 418, 126, 420, 127, 422, 0, 424, 0, 426, 0, 428, 0, 430, 0, 432, 0, 434, 0, 436, 0, 438, 128, 440, 129, 442, 130, 444, 0, 446, 0, 448, 0, 450, 0, 452, 0, 454, 131, 456, 132, 458, 133, 460, 0, 462, 0, 464, 0, 466, 0, 468, 0, 470, 0, 472, 0, 474, 0, 476, 0, 478, 0, 480, 0, 482, 134, 484, 135, 486, 136, 488, 0, 490, 0, 492, 0, 494, 0, 496, 0, 498, 0, 500, 0, 502, 0, 504, 0, 506, 0, 508, 0, 510, 0, 512, 137, 514, 138, 516, 139, 518, 140, 520, 0, 522, 0, 524, 0, 526, 0, 528, 0, 530, 0, 532, 0, 534, 0, 536, 0, 538, 0, 540, 0, 542, 141, 544, 0, 546, 142, 548, 143, 550, 144, 552, 0, 554, 0, 556, 0, 558, 0, 560, 0, 562, 0, 564, 0, 566, 0, 568, 0, 570, 0, 572, 0, 574, 0, 576, 0, 578, 0, 580, 0, 582, 0, 584, 0, 586, 0, 588, 0, 590, 145, 592, 146, 594, 147, 596, 0, 598, 148, 600, 149, 602, 150, 604, 151, 18, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 36, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 2, 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 65, 65, 97, 97, 2, 0, 78, 78, 110, 110, 2, 0, 71, 71, 103, 103, 2, 0, 69, 69, 101, 101, 2, 0, 80, 80, 112, 112, 2, 0, 79, 79, 111, 111, 2, 0, 73, 73, 105, 105, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 88, 88, 120, 120, 2, 0, 76, 76, 108, 108, 2, 0, 77, 77, 109, 109, 2, 0, 68, 68, 100, 100, 2, 0, 83, 83, 115, 115, 2, 0, 86, 86, 118, 118, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 70, 70, 102, 102, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 12, 0, 9, 10, 13, 13, 32, 32, 34, 35, 40, 41, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 12, 0, 9, 10, 13, 13, 32, 32, 34, 34, 40, 41, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 2, 0, 74, 74, 106, 106, 2174, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 66, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 70, 1, 0, 0, 0, 0, 72, 1, 0, 0, 0, 0, 74, 1, 0, 0, 0, 0, 76, 1, 0, 0, 0, 0, 78, 1, 0, 0, 0, 0, 80, 1, 0, 0, 0, 0, 82, 1, 0, 0, 0, 0, 84, 1, 0, 0, 0, 0, 86, 1, 0, 0, 0, 0, 88, 1, 0, 0, 0, 1, 90, 1, 0, 0, 0, 1, 92, 1, 0, 0, 0, 1, 94, 1, 0, 0, 0, 1, 96, 1, 0, 0, 0, 1, 98, 1, 0, 0, 0, 1, 100, 1, 0, 0, 0, 1, 102, 1, 0, 0, 0, 1, 104, 1, 0, 0, 0, 1, 106, 1, 0, 0, 0, 1, 108, 1, 0, 0, 0, 1, 110, 1, 0, 0, 0, 1, 112, 1, 0, 0, 0, 1, 114, 1, 0, 0, 0, 2, 116, 1, 0, 0, 0, 2, 118, 1, 0, 0, 0, 2, 120, 1, 0, 0, 0, 2, 122, 1, 0, 0, 0, 2, 126, 1, 0, 0, 0, 2, 128, 1, 0, 0, 0, 2, 130, 1, 0, 0, 0, 2, 132, 1, 0, 0, 0, 2, 134, 1, 0, 0, 0, 2, 136, 1, 0, 0, 0, 3, 138, 1, 0, 0, 0, 3, 140, 1, 0, 0, 0, 3, 142, 1, 0, 0, 0, 3, 144, 1, 0, 0, 0, 3, 146, 1, 0, 0, 0, 3, 148, 1, 0, 0, 0, 3, 150, 1, 0, 0, 0, 3, 152, 1, 0, 0, 0, 3, 154, 1, 0, 0, 0, 3, 156, 1, 0, 0, 0, 3, 158, 1, 0, 0, 0, 3, 160, 1, 0, 0, 0, 3, 162, 1, 0, 0, 0, 3, 164, 1, 0, 0, 0, 3, 166, 1, 0, 0, 0, 3, 168, 1, 0, 0, 0, 3, 170, 1, 0, 0, 0, 4, 172, 1, 0, 0, 0, 4, 174, 1, 0, 0, 0, 4, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 4, 180, 1, 0, 0, 0, 5, 182, 1, 0, 0, 0, 5, 204, 1, 0, 0, 0, 5, 206, 1, 0, 0, 0, 5, 208, 1, 0, 0, 0, 5, 210, 1, 0, 0, 0, 5, 212, 1, 0, 0, 0, 5, 214, 1, 0, 0, 0, 5, 216, 1, 0, 0, 0, 5, 218, 1, 0, 0, 0, 5, 220, 1, 0, 0, 0, 5, 222, 1, 0, 0, 0, 5, 224, 1, 0, 0, 0, 5, 226, 1, 0, 0, 0, 5, 228, 1, 0, 0, 0, 5, 230, 1, 0, 0, 0, 5, 232, 1, 0, 0, 0, 5, 234, 1, 0, 0, 0, 5, 236, 1, 0, 0, 0, 5, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 5, 246, 1, 0, 0, 0, 5, 248, 1, 0, 0, 0, 5, 250, 1, 0, 0, 0, 5, 252, 1, 0, 0, 0, 5, 254, 1, 0, 0, 0, 5, 256, 1, 0, 0, 0, 5, 258, 1, 0, 0, 0, 5, 260, 1, 0, 0, 0, 5, 262, 1, 0, 0, 0, 5, 264, 1, 0, 0, 0, 5, 266, 1, 0, 0, 0, 5, 268, 1, 0, 0, 0, 5, 270, 1, 0, 0, 0, 5, 272, 1, 0, 0, 0, 5, 274, 1, 0, 0, 0, 5, 276, 1, 0, 0, 0, 5, 278, 1, 0, 0, 0, 5, 280, 1, 0, 0, 0, 5, 282, 1, 0, 0, 0, 5, 284, 1, 0, 0, 0, 5, 286, 1, 0, 0, 0, 5, 288, 1, 0, 0, 0, 5, 290, 1, 0, 0, 0, 5, 292, 1, 0, 0, 0, 5, 294, 1, 0, 0, 0, 5, 296, 1, 0, 0, 0, 5, 298, 1, 0, 0, 0, 5, 300, 1, 0, 0, 0, 5, 302, 1, 0, 0, 0, 5, 304, 1, 0, 0, 0, 5, 308, 1, 0, 0, 0, 5, 310, 1, 0, 0, 0, 5, 312, 1, 0, 0, 0, 5, 314, 1, 0, 0, 0, 6, 316, 1, 0, 0, 0, 6, 318, 1, 0, 0, 0, 6, 320, 1, 0, 0, 0, 6, 322, 1, 0, 0, 0, 6, 324, 1, 0, 0, 0, 6, 326, 1, 0, 0, 0, 6, 328, 1, 0, 0, 0, 6, 332, 1, 0, 0, 0, 6, 334, 1, 0, 0, 0, 6, 336, 1, 0, 0, 0, 6, 338, 1, 0, 0, 0, 6, 340, 1, 0, 0, 0, 6, 342, 1, 0, 0, 0, 7, 344, 1, 0, 0, 0, 7, 346, 1, 0, 0, 0, 7, 348, 1, 0, 0, 0, 7, 350, 1, 0, 0, 0, 7, 352, 1, 0, 0, 0, 7, 354, 1, 0, 0, 0, 8, 356, 1, 0, 0, 0, 8, 358, 1, 0, 0, 0, 8, 360, 1, 0, 0, 0, 8, 362, 1, 0, 0, 0, 8, 364, 1, 0, 0, 0, 8, 366, 1, 0, 0, 0, 8, 368, 1, 0, 0, 0, 8, 370, 1, 0, 0, 0, 8, 372, 1, 0, 0, 0, 8, 374, 1, 0, 0, 0, 8, 376, 1, 0, 0, 0, 8, 378, 1, 0, 0, 0, 8, 380, 1, 0, 0, 0, 8, 382, 1, 0, 0, 0, 8, 384, 1, 0, 0, 0, 8, 386, 1, 0, 0, 0, 8, 388, 1, 0, 0, 0, 8, 390, 1, 0, 0, 0, 9, 392, 1, 0, 0, 0, 9, 394, 1, 0, 0, 0, 9, 396, 1, 0, 0, 0, 9, 398, 1, 0, 0, 0, 10, 400, 1, 0, 0, 0, 10, 402, 1, 0, 0, 0, 10, 404, 1, 0, 0, 0, 10, 406, 1, 0, 0, 0, 10, 408, 1, 0, 0, 0, 10, 410, 1, 0, 0, 0, 10, 412, 1, 0, 0, 0, 10, 414, 1, 0, 0, 0, 10, 416, 1, 0, 0, 0, 10, 418, 1, 0, 0, 0, 10, 420, 1, 0, 0, 0, 11, 422, 1, 0, 0, 0, 11, 424, 1, 0, 0, 0, 11, 426, 1, 0, 0, 0, 11, 428, 1, 0, 0, 0, 11, 430, 1, 0, 0, 0, 11, 432, 1, 0, 0, 0, 11, 434, 1, 0, 0, 0, 11, 436, 1, 0, 0, 0, 11, 438, 1, 0, 0, 0, 11, 440, 1, 0, 0, 0, 11, 442, 1, 0, 0, 0, 12, 444, 1, 0, 0, 0, 12, 446, 1, 0, 0, 0, 12, 448, 1, 0, 0, 0, 12, 450, 1, 0, 0, 0, 12, 452, 1, 0, 0, 0, 12, 454, 1, 0, 0, 0, 12, 456, 1, 0, 0, 0, 12, 458, 1, 0, 0, 0, 13, 460, 1, 0, 0, 0, 13, 462, 1, 0, 0, 0, 13, 464, 1, 0, 0, 0, 13, 466, 1, 0, 0, 0, 13, 468, 1, 0, 0, 0, 13, 470, 1, 0, 0, 0, 13, 472, 1, 0, 0, 0, 13, 474, 1, 0, 0, 0, 13, 476, 1, 0, 0, 0, 13, 478, 1, 0, 0, 0, 13, 480, 1, 0, 0, 0, 13, 482, 1, 0, 0, 0, 13, 484, 1, 0, 0, 0, 13, 486, 1, 0, 0, 0, 14, 488, 1, 0, 0, 0, 14, 490, 1, 0, 0, 0, 14, 492, 1, 0, 0, 0, 14, 494, 1, 0, 0, 0, 14, 496, 1, 0, 0, 0, 14, 498, 1, 0, 0, 0, 14, 500, 1, 0, 0, 0, 14, 502, 1, 0, 0, 0, 14, 504, 1, 0, 0, 0, 14, 506, 1, 0, 0, 0, 14, 512, 1, 0, 0, 0, 14, 514, 1, 0, 0, 0, 14, 516, 1, 0, 0, 0, 14, 518, 1, 0, 0, 0, 15, 520, 1, 0, 0, 0, 15, 522, 1, 0, 0, 0, 15, 524, 1, 0, 0, 0, 15, 526, 1, 0, 0, 0, 15, 528, 1, 0, 0, 0, 15, 530, 1, 0, 0, 0, 15, 532, 1, 0, 0, 0, 15, 534, 1, 0, 0, 0, 15, 536, 1, 0, 0, 0, 15, 538, 1, 0, 0, 0, 15, 540, 1, 0, 0, 0, 15, 542, 1, 0, 0, 0, 15, 544, 1, 0, 0, 0, 15, 546, 1, 0, 0, 0, 15, 548, 1, 0, 0, 0, 15, 550, 1, 0, 0, 0, 16, 552, 1, 0, 0, 0, 16, 554, 1, 0, 0, 0, 16, 556, 1, 0, 0, 0, 16, 558, 1, 0, 0, 0, 16, 560, 1, 0, 0, 0, 16, 562, 1, 0, 0, 0, 16, 564, 1, 0, 0, 0, 16, 566, 1, 0, 0, 0, 16, 568, 1, 0, 0, 0, 16, 570, 1, 0, 0, 0, 16, 572, 1, 0, 0, 0, 16, 574, 1, 0, 0, 0, 16, 576, 1, 0, 0, 0, 16, 578, 1, 0, 0, 0, 16, 580, 1, 0, 0, 0, 16, 582, 1, 0, 0, 0, 16, 584, 1, 0, 0, 0, 16, 586, 1, 0, 0, 0, 16, 588, 1, 0, 0, 0, 16, 590, 1, 0, 0, 0, 16, 592, 1, 0, 0, 0, 16, 594, 1, 0, 0, 0, 17, 596, 1, 0, 0, 0, 17, 598, 1, 0, 0, 0, 17, 600, 1, 0, 0, 0, 17, 602, 1, 0, 0, 0, 17, 604, 1, 0, 0, 0, 18, 606, 1, 0, 0, 0, 20, 623, 1, 0, 0, 0, 22, 639, 1, 0, 0, 0, 24, 645, 1, 0, 0, 0, 26, 660, 1, 0, 0, 0, 28, 669, 1, 0, 0, 0, 30, 680, 1, 0, 0, 0, 32, 693, 1, 0, 0, 0, 34, 703, 1, 0, 0, 0, 36, 710, 1, 0, 0, 0, 38, 717, 1, 0, 0, 0, 40, 725, 1, 0, 0, 0, 42, 734, 1, 0, 0, 0, 44, 740, 1, 0, 0, 0, 46, 749, 1, 0, 0, 0, 48, 756, 1, 0, 0, 0, 50, 764, 1, 0, 0, 0, 52, 772, 1, 0, 0, 0, 54, 779, 1, 0, 0, 0, 56, 784, 1, 0, 0, 0, 58, 791, 1, 0, 0, 0, 60, 798, 1, 0, 0, 0, 62, 807, 1, 0, 0, 0, 64, 821, 1, 0, 0, 0, 66, 830, 1, 0, 0, 0, 68, 838, 1, 0, 0, 0, 70, 846, 1, 0, 0, 0, 72, 855, 1, 0, 0, 0, 74, 867, 1, 0, 0, 0, 76, 879, 1, 0, 0, 0, 78, 886, 1, 0, 0, 0, 80, 893, 1, 0, 0, 0, 82, 905, 1, 0, 0, 0, 84, 914, 1, 0, 0, 0, 86, 920, 1, 0, 0, 0, 88, 928, 1, 0, 0, 0, 90, 934, 1, 0, 0, 0, 92, 939, 1, 0, 0, 0, 94, 945, 1, 0, 0, 0, 96, 949, 1, 0, 0, 0, 98, 953, 1, 0, 0, 0, 100, 957, 1, 0, 0, 0, 102, 961, 1, 0, 0, 0, 104, 965, 1, 0, 0, 0, 106, 969, 1, 0, 0, 0, 108, 973, 1, 0, 0, 0, 110, 977, 1, 0, 0, 0, 112, 981, 1, 0, 0, 0, 114, 985, 1, 0, 0, 0, 116, 989, 1, 0, 0, 0, 118, 994, 1, 0, 0, 0, 120, 1000, 1, 0, 0, 0, 122, 1005, 1, 0, 0, 0, 124, 1010, 1, 0, 0, 0, 126, 1019, 1, 0, 0, 0, 128, 1026, 1, 0, 0, 0, 130, 1030, 1, 0, 0, 0, 132, 1034, 1, 0, 0, 0, 134, 1038, 1, 0, 0, 0, 136, 1042, 1, 0, 0, 0, 138, 1046, 1, 0, 0, 0, 140, 1052, 1, 0, 0, 0, 142, 1059, 1, 0, 0, 0, 144, 1063, 1, 0, 0, 0, 146, 1067, 1, 0, 0, 0, 148, 1071, 1, 0, 0, 0, 150, 1075, 1, 0, 0, 0, 152, 1079, 1, 0, 0, 0, 154, 1083, 1, 0, 0, 0, 156, 1087, 1, 0, 0, 0, 158, 1091, 1, 0, 0, 0, 160, 1095, 1, 0, 0, 0, 162, 1099, 1, 0, 0, 0, 164, 1103, 1, 0, 0, 0, 166, 1107, 1, 0, 0, 0, 168, 1111, 1, 0, 0, 0, 170, 1115, 1, 0, 0, 0, 172, 1119, 1, 0, 0, 0, 174, 1124, 1, 0, 0, 0, 176, 1129, 1, 0, 0, 0, 178, 1133, 1, 0, 0, 0, 180, 1137, 1, 0, 0, 0, 182, 1141, 1, 0, 0, 0, 184, 1145, 1, 0, 0, 0, 186, 1147, 1, 0, 0, 0, 188, 1149, 1, 0, 0, 0, 190, 1152, 1, 0, 0, 0, 192, 1154, 1, 0, 0, 0, 194, 1163, 1, 0, 0, 0, 196, 1165, 1, 0, 0, 0, 198, 1170, 1, 0, 0, 0, 200, 1172, 1, 0, 0, 0, 202, 1177, 1, 0, 0, 0, 204, 1208, 1, 0, 0, 0, 206, 1211, 1, 0, 0, 0, 208, 1257, 1, 0, 0, 0, 210, 1259, 1, 0, 0, 0, 212, 1263, 1, 0, 0, 0, 214, 1267, 1, 0, 0, 0, 216, 1269, 1, 0, 0, 0, 218, 1272, 1, 0, 0, 0, 220, 1275, 1, 0, 0, 0, 222, 1277, 1, 0, 0, 0, 224, 1279, 1, 0, 0, 0, 226, 1281, 1, 0, 0, 0, 228, 1286, 1, 0, 0, 0, 230, 1288, 1, 0, 0, 0, 232, 1294, 1, 0, 0, 0, 234, 1300, 1, 0, 0, 0, 236, 1303, 1, 0, 0, 0, 238, 1306, 1, 0, 0, 0, 240, 1311, 1, 0, 0, 0, 242, 1316, 1, 0, 0, 0, 244, 1320, 1, 0, 0, 0, 246, 1325, 1, 0, 0, 0, 248, 1331, 1, 0, 0, 0, 250, 1334, 1, 0, 0, 0, 252, 1337, 1, 0, 0, 0, 254, 1339, 1, 0, 0, 0, 256, 1345, 1, 0, 0, 0, 258, 1350, 1, 0, 0, 0, 260, 1355, 1, 0, 0, 0, 262, 1358, 1, 0, 0, 0, 264, 1361, 1, 0, 0, 0, 266, 1364, 1, 0, 0, 0, 268, 1366, 1, 0, 0, 0, 270, 1369, 1, 0, 0, 0, 272, 1371, 1, 0, 0, 0, 274, 1374, 1, 0, 0, 0, 276, 1376, 1, 0, 0, 0, 278, 1378, 1, 0, 0, 0, 280, 1380, 1, 0, 0, 0, 282, 1382, 1, 0, 0, 0, 284, 1384, 1, 0, 0, 0, 286, 1386, 1, 0, 0, 0, 288, 1388, 1, 0, 0, 0, 290, 1391, 1, 0, 0, 0, 292, 1412, 1, 0, 0, 0, 294, 1431, 1, 0, 0, 0, 296, 1433, 1, 0, 0, 0, 298, 1438, 1, 0, 0, 0, 300, 1443, 1, 0, 0, 0, 302, 1448, 1, 0, 0, 0, 304, 1469, 1, 0, 0, 0, 306, 1471, 1, 0, 0, 0, 308, 1479, 1, 0, 0, 0, 310, 1481, 1, 0, 0, 0, 312, 1485, 1, 0, 0, 0, 314, 1489, 1, 0, 0, 0, 316, 1493, 1, 0, 0, 0, 318, 1498, 1, 0, 0, 0, 320, 1502, 1, 0, 0, 0, 322, 1506, 1, 0, 0, 0, 324, 1510, 1, 0, 0, 0, 326, 1514, 1, 0, 0, 0, 328, 1523, 1, 0, 0, 0, 330, 1531, 1, 0, 0, 0, 332, 1534, 1, 0, 0, 0, 334, 1538, 1, 0, 0, 0, 336, 1542, 1, 0, 0, 0, 338, 1546, 1, 0, 0, 0, 340, 1550, 1, 0, 0, 0, 342, 1554, 1, 0, 0, 0, 344, 1558, 1, 0, 0, 0, 346, 1563, 1, 0, 0, 0, 348, 1569, 1, 0, 0, 0, 350, 1574, 1, 0, 0, 0, 352, 1578, 1, 0, 0, 0, 354, 1582, 1, 0, 0, 0, 356, 1586, 1, 0, 0, 0, 358, 1591, 1, 0, 0, 0, 360, 1597, 1, 0, 0, 0, 362, 1603, 1, 0, 0, 0, 364, 1609, 1, 0, 0, 0, 366, 1613, 1, 0, 0, 0, 368, 1619, 1, 0, 0, 0, 370, 1623, 1, 0, 0, 0, 372, 1627, 1, 0, 0, 0, 374, 1631, 1, 0, 0, 0, 376, 1635, 1, 0, 0, 0, 378, 1639, 1, 0, 0, 0, 380, 1643, 1, 0, 0, 0, 382, 1647, 1, 0, 0, 0, 384, 1651, 1, 0, 0, 0, 386, 1655, 1, 0, 0, 0, 388, 1659, 1, 0, 0, 0, 390, 1663, 1, 0, 0, 0, 392, 1667, 1, 0, 0, 0, 394, 1676, 1, 0, 0, 0, 396, 1680, 1, 0, 0, 0, 398, 1684, 1, 0, 0, 0, 400, 1688, 1, 0, 0, 0, 402, 1693, 1, 0, 0, 0, 404, 1698, 1, 0, 0, 0, 406, 1702, 1, 0, 0, 0, 408, 1708, 1, 0, 0, 0, 410, 1717, 1, 0, 0, 0, 412, 1721, 1, 0, 0, 0, 414, 1725, 1, 0, 0, 0, 416, 1729, 1, 0, 0, 0, 418, 1733, 1, 0, 0, 0, 420, 1737, 1, 0, 0, 0, 422, 1741, 1, 0, 0, 0, 424, 1746, 1, 0, 0, 0, 426, 1752, 1, 0, 0, 0, 428, 1756, 1, 0, 0, 0, 430, 1760, 1, 0, 0, 0, 432, 1764, 1, 0, 0, 0, 434, 1769, 1, 0, 0, 0, 436, 1773, 1, 0, 0, 0, 438, 1777, 1, 0, 0, 0, 440, 1781, 1, 0, 0, 0, 442, 1785, 1, 0, 0, 0, 444, 1789, 1, 0, 0, 0, 446, 1795, 1, 0, 0, 0, 448, 1802, 1, 0, 0, 0, 450, 1806, 1, 0, 0, 0, 452, 1810, 1, 0, 0, 0, 454, 1814, 1, 0, 0, 0, 456, 1818, 1, 0, 0, 0, 458, 1822, 1, 0, 0, 0, 460, 1826, 1, 0, 0, 0, 462, 1831, 1, 0, 0, 0, 464, 1837, 1, 0, 0, 0, 466, 1841, 1, 0, 0, 0, 468, 1845, 1, 0, 0, 0, 470, 1849, 1, 0, 0, 0, 472, 1853, 1, 0, 0, 0, 474, 1857, 1, 0, 0, 0, 476, 1861, 1, 0, 0, 0, 478, 1865, 1, 0, 0, 0, 480, 1869, 1, 0, 0, 0, 482, 1873, 1, 0, 0, 0, 484, 1877, 1, 0, 0, 0, 486, 1881, 1, 0, 0, 0, 488, 1885, 1, 0, 0, 0, 490, 1890, 1, 0, 0, 0, 492, 1896, 1, 0, 0, 0, 494, 1900, 1, 0, 0, 0, 496, 1904, 1, 0, 0, 0, 498, 1908, 1, 0, 0, 0, 500, 1912, 1, 0, 0, 0, 502, 1916, 1, 0, 0, 0, 504, 1920, 1, 0, 0, 0, 506, 1924, 1, 0, 0, 0, 508, 1932, 1, 0, 0, 0, 510, 1953, 1, 0, 0, 0, 512, 1957, 1, 0, 0, 0, 514, 1961, 1, 0, 0, 0, 516, 1965, 1, 0, 0, 0, 518, 1969, 1, 0, 0, 0, 520, 1973, 1, 0, 0, 0, 522, 1978, 1, 0, 0, 0, 524, 1984, 1, 0, 0, 0, 526, 1988, 1, 0, 0, 0, 528, 1992, 1, 0, 0, 0, 530, 1996, 1, 0, 0, 0, 532, 2000, 1, 0, 0, 0, 534, 2004, 1, 0, 0, 0, 536, 2008, 1, 0, 0, 0, 538, 2012, 1, 0, 0, 0, 540, 2016, 1, 0, 0, 0, 542, 2020, 1, 0, 0, 0, 544, 2023, 1, 0, 0, 0, 546, 2027, 1, 0, 0, 0, 548, 2031, 1, 0, 0, 0, 550, 2035, 1, 0, 0, 0, 552, 2039, 1, 0, 0, 0, 554, 2043, 1, 0, 0, 0, 556, 2047, 1, 0, 0, 0, 558, 2051, 1, 0, 0, 0, 560, 2056, 1, 0, 0, 0, 562, 2060, 1, 0, 0, 0, 564, 2064, 1, 0, 0, 0, 566, 2068, 1, 0, 0, 0, 568, 2072, 1, 0, 0, 0, 570, 2076, 1, 0, 0, 0, 572, 2080, 1, 0, 0, 0, 574, 2084, 1, 0, 0, 0, 576, 2088, 1, 0, 0, 0, 578, 2092, 1, 0, 0, 0, 580, 2096, 1, 0, 0, 0, 582, 2100, 1, 0, 0, 0, 584, 2104, 1, 0, 0, 0, 586, 2108, 1, 0, 0, 0, 588, 2112, 1, 0, 0, 0, 590, 2116, 1, 0, 0, 0, 592, 2120, 1, 0, 0, 0, 594, 2124, 1, 0, 0, 0, 596, 2128, 1, 0, 0, 0, 598, 2133, 1, 0, 0, 0, 600, 2138, 1, 0, 0, 0, 602, 2142, 1, 0, 0, 0, 604, 2146, 1, 0, 0, 0, 606, 607, 5, 47, 0, 0, 607, 608, 5, 47, 0, 0, 608, 612, 1, 0, 0, 0, 609, 611, 8, 0, 0, 0, 610, 609, 1, 0, 0, 0, 611, 614, 1, 0, 0, 0, 612, 610, 1, 0, 0, 0, 612, 613, 1, 0, 0, 0, 613, 616, 1, 0, 0, 0, 614, 612, 1, 0, 0, 0, 615, 617, 5, 13, 0, 0, 616, 615, 1, 0, 0, 0, 616, 617, 1, 0, 0, 0, 617, 619, 1, 0, 0, 0, 618, 620, 5, 10, 0, 0, 619, 618, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 622, 6, 0, 0, 0, 622, 19, 1, 0, 0, 0, 623, 624, 5, 47, 0, 0, 624, 625, 5, 42, 0, 0, 625, 630, 1, 0, 0, 0, 626, 629, 3, 20, 1, 0, 627, 629, 9, 0, 0, 0, 628, 626, 1, 0, 0, 0, 628, 627, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 631, 633, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 5, 42, 0, 0, 634, 635, 5, 47, 0, 0, 635, 636, 1, 0, 0, 0, 636, 637, 6, 1, 0, 0, 637, 21, 1, 0, 0, 0, 638, 640, 7, 1, 0, 0, 639, 638, 1, 0, 0, 0, 640, 641, 1, 0, 0, 0, 641, 639, 1, 0, 0, 0, 641, 642, 1, 0, 0, 0, 642, 643, 1, 0, 0, 0, 643, 644, 6, 2, 0, 0, 644, 23, 1, 0, 0, 0, 645, 646, 7, 2, 0, 0, 646, 647, 7, 3, 0, 0, 647, 648, 7, 4, 0, 0, 648, 649, 7, 5, 0, 0, 649, 650, 7, 6, 0, 0, 650, 651, 7, 7, 0, 0, 651, 652, 5, 95, 0, 0, 652, 653, 7, 8, 0, 0, 653, 654, 7, 9, 0, 0, 654, 655, 7, 10, 0, 0, 655, 656, 7, 5, 0, 0, 656, 657, 7, 11, 0, 0, 657, 658, 1, 0, 0, 0, 658, 659, 6, 3, 1, 0, 659, 25, 1, 0, 0, 0, 660, 661, 7, 7, 0, 0, 661, 662, 7, 5, 0, 0, 662, 663, 7, 12, 0, 0, 663, 664, 7, 10, 0, 0, 664, 665, 7, 2, 0, 0, 665, 666, 7, 3, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 6, 4, 2, 0, 668, 27, 1, 0, 0, 0, 669, 670, 4, 5, 0, 0, 670, 671, 7, 7, 0, 0, 671, 672, 7, 13, 0, 0, 672, 673, 7, 8, 0, 0, 673, 674, 7, 14, 0, 0, 674, 675, 7, 4, 0, 0, 675, 676, 7, 10, 0, 0, 676, 677, 7, 5, 0, 0, 677, 678, 1, 0, 0, 0, 678, 679, 6, 5, 3, 0, 679, 29, 1, 0, 0, 0, 680, 681, 7, 2, 0, 0, 681, 682, 7, 9, 0, 0, 682, 683, 7, 15, 0, 0, 683, 684, 7, 8, 0, 0, 684, 685, 7, 14, 0, 0, 685, 686, 7, 7, 0, 0, 686, 687, 7, 11, 0, 0, 687, 688, 7, 10, 0, 0, 688, 689, 7, 9, 0, 0, 689, 690, 7, 5, 0, 0, 690, 691, 1, 0, 0, 0, 691, 692, 6, 6, 4, 0, 692, 31, 1, 0, 0, 0, 693, 694, 7, 16, 0, 0, 694, 695, 7, 10, 0, 0, 695, 696, 7, 17, 0, 0, 696, 697, 7, 17, 0, 0, 697, 698, 7, 7, 0, 0, 698, 699, 7, 2, 0, 0, 699, 700, 7, 11, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 6, 7, 4, 0, 702, 33, 1, 0, 0, 0, 703, 704, 7, 7, 0, 0, 704, 705, 7, 18, 0, 0, 705, 706, 7, 4, 0, 0, 706, 707, 7, 14, 0, 0, 707, 708, 1, 0, 0, 0, 708, 709, 6, 8, 4, 0, 709, 35, 1, 0, 0, 0, 710, 711, 7, 6, 0, 0, 711, 712, 7, 12, 0, 0, 712, 713, 7, 9, 0, 0, 713, 714, 7, 19, 0, 0, 714, 715, 1, 0, 0, 0, 715, 716, 6, 9, 4, 0, 716, 37, 1, 0, 0, 0, 717, 718, 7, 14, 0, 0, 718, 719, 7, 10, 0, 0, 719, 720, 7, 15, 0, 0, 720, 721, 7, 10, 0, 0, 721, 722, 7, 11, 0, 0, 722, 723, 1, 0, 0, 0, 723, 724, 6, 10, 4, 0, 724, 39, 1, 0, 0, 0, 725, 726, 7, 12, 0, 0, 726, 727, 7, 7, 0, 0, 727, 728, 7, 12, 0, 0, 728, 729, 7, 4, 0, 0, 729, 730, 7, 5, 0, 0, 730, 731, 7, 19, 0, 0, 731, 732, 1, 0, 0, 0, 732, 733, 6, 11, 4, 0, 733, 41, 1, 0, 0, 0, 734, 735, 7, 12, 0, 0, 735, 736, 7, 9, 0, 0, 736, 737, 7, 20, 0, 0, 737, 738, 1, 0, 0, 0, 738, 739, 6, 12, 4, 0, 739, 43, 1, 0, 0, 0, 740, 741, 7, 17, 0, 0, 741, 742, 7, 4, 0, 0, 742, 743, 7, 15, 0, 0, 743, 744, 7, 8, 0, 0, 744, 745, 7, 14, 0, 0, 745, 746, 7, 7, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 6, 13, 4, 0, 748, 45, 1, 0, 0, 0, 749, 750, 7, 17, 0, 0, 750, 751, 7, 9, 0, 0, 751, 752, 7, 12, 0, 0, 752, 753, 7, 11, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 6, 14, 4, 0, 755, 47, 1, 0, 0, 0, 756, 757, 7, 17, 0, 0, 757, 758, 7, 11, 0, 0, 758, 759, 7, 4, 0, 0, 759, 760, 7, 11, 0, 0, 760, 761, 7, 17, 0, 0, 761, 762, 1, 0, 0, 0, 762, 763, 6, 15, 4, 0, 763, 49, 1, 0, 0, 0, 764, 765, 7, 20, 0, 0, 765, 766, 7, 3, 0, 0, 766, 767, 7, 7, 0, 0, 767, 768, 7, 12, 0, 0, 768, 769, 7, 7, 0, 0, 769, 770, 1, 0, 0, 0, 770, 771, 6, 16, 4, 0, 771, 51, 1, 0, 0, 0, 772, 773, 7, 21, 0, 0, 773, 774, 7, 12, 0, 0, 774, 775, 7, 9, 0, 0, 775, 776, 7, 15, 0, 0, 776, 777, 1, 0, 0, 0, 777, 778, 6, 17, 5, 0, 778, 53, 1, 0, 0, 0, 779, 780, 7, 11, 0, 0, 780, 781, 7, 17, 0, 0, 781, 782, 1, 0, 0, 0, 782, 783, 6, 18, 5, 0, 783, 55, 1, 0, 0, 0, 784, 785, 7, 21, 0, 0, 785, 786, 7, 9, 0, 0, 786, 787, 7, 12, 0, 0, 787, 788, 7, 19, 0, 0, 788, 789, 1, 0, 0, 0, 789, 790, 6, 19, 6, 0, 790, 57, 1, 0, 0, 0, 791, 792, 7, 21, 0, 0, 792, 793, 7, 22, 0, 0, 793, 794, 7, 17, 0, 0, 794, 795, 7, 7, 0, 0, 795, 796, 1, 0, 0, 0, 796, 797, 6, 20, 7, 0, 797, 59, 1, 0, 0, 0, 798, 799, 7, 10, 0, 0, 799, 800, 7, 5, 0, 0, 800, 801, 7, 14, 0, 0, 801, 802, 7, 10, 0, 0, 802, 803, 7, 5, 0, 0, 803, 804, 7, 7, 0, 0, 804, 805, 1, 0, 0, 0, 805, 806, 6, 21, 8, 0, 806, 61, 1, 0, 0, 0, 807, 808, 7, 10, 0, 0, 808, 809, 7, 5, 0, 0, 809, 810, 7, 14, 0, 0, 810, 811, 7, 10, 0, 0, 811, 812, 7, 5, 0, 0, 812, 813, 7, 7, 0, 0, 813, 814, 7, 17, 0, 0, 814, 815, 7, 11, 0, 0, 815, 816, 7, 4, 0, 0, 816, 817, 7, 11, 0, 0, 817, 818, 7, 17, 0, 0, 818, 819, 1, 0, 0, 0, 819, 820, 6, 22, 4, 0, 820, 63, 1, 0, 0, 0, 821, 822, 7, 14, 0, 0, 822, 823, 7, 9, 0, 0, 823, 824, 7, 9, 0, 0, 824, 825, 7, 19, 0, 0, 825, 826, 7, 22, 0, 0, 826, 827, 7, 8, 0, 0, 827, 828, 1, 0, 0, 0, 828, 829, 6, 23, 9, 0, 829, 65, 1, 0, 0, 0, 830, 831, 4, 24, 1, 0, 831, 832, 7, 21, 0, 0, 832, 833, 7, 22, 0, 0, 833, 834, 7, 14, 0, 0, 834, 835, 7, 14, 0, 0, 835, 836, 1, 0, 0, 0, 836, 837, 6, 24, 9, 0, 837, 67, 1, 0, 0, 0, 838, 839, 4, 25, 2, 0, 839, 840, 7, 14, 0, 0, 840, 841, 7, 7, 0, 0, 841, 842, 7, 21, 0, 0, 842, 843, 7, 11, 0, 0, 843, 844, 1, 0, 0, 0, 844, 845, 6, 25, 9, 0, 845, 69, 1, 0, 0, 0, 846, 847, 4, 26, 3, 0, 847, 848, 7, 12, 0, 0, 848, 849, 7, 10, 0, 0, 849, 850, 7, 6, 0, 0, 850, 851, 7, 3, 0, 0, 851, 852, 7, 11, 0, 0, 852, 853, 1, 0, 0, 0, 853, 854, 6, 26, 9, 0, 854, 71, 1, 0, 0, 0, 855, 856, 4, 27, 4, 0, 856, 857, 7, 14, 0, 0, 857, 858, 7, 9, 0, 0, 858, 859, 7, 9, 0, 0, 859, 860, 7, 19, 0, 0, 860, 861, 7, 22, 0, 0, 861, 862, 7, 8, 0, 0, 862, 863, 5, 95, 0, 0, 863, 864, 5, 128020, 0, 0, 864, 865, 1, 0, 0, 0, 865, 866, 6, 27, 10, 0, 866, 73, 1, 0, 0, 0, 867, 868, 7, 15, 0, 0, 868, 869, 7, 18, 0, 0, 869, 870, 5, 95, 0, 0, 870, 871, 7, 7, 0, 0, 871, 872, 7, 13, 0, 0, 872, 873, 7, 8, 0, 0, 873, 874, 7, 4, 0, 0, 874, 875, 7, 5, 0, 0, 875, 876, 7, 16, 0, 0, 876, 877, 1, 0, 0, 0, 877, 878, 6, 28, 11, 0, 878, 75, 1, 0, 0, 0, 879, 880, 7, 16, 0, 0, 880, 881, 7, 12, 0, 0, 881, 882, 7, 9, 0, 0, 882, 883, 7, 8, 0, 0, 883, 884, 1, 0, 0, 0, 884, 885, 6, 29, 12, 0, 885, 77, 1, 0, 0, 0, 886, 887, 7, 19, 0, 0, 887, 888, 7, 7, 0, 0, 888, 889, 7, 7, 0, 0, 889, 890, 7, 8, 0, 0, 890, 891, 1, 0, 0, 0, 891, 892, 6, 30, 12, 0, 892, 79, 1, 0, 0, 0, 893, 894, 4, 31, 5, 0, 894, 895, 7, 10, 0, 0, 895, 896, 7, 5, 0, 0, 896, 897, 7, 17, 0, 0, 897, 898, 7, 10, 0, 0, 898, 899, 7, 17, 0, 0, 899, 900, 7, 11, 0, 0, 900, 901, 5, 95, 0, 0, 901, 902, 5, 128020, 0, 0, 902, 903, 1, 0, 0, 0, 903, 904, 6, 31, 12, 0, 904, 81, 1, 0, 0, 0, 905, 906, 7, 12, 0, 0, 906, 907, 7, 7, 0, 0, 907, 908, 7, 5, 0, 0, 908, 909, 7, 4, 0, 0, 909, 910, 7, 15, 0, 0, 910, 911, 7, 7, 0, 0, 911, 912, 1, 0, 0, 0, 912, 913, 6, 32, 13, 0, 913, 83, 1, 0, 0, 0, 914, 915, 7, 17, 0, 0, 915, 916, 7, 7, 0, 0, 916, 917, 7, 11, 0, 0, 917, 918, 1, 0, 0, 0, 918, 919, 6, 33, 14, 0, 919, 85, 1, 0, 0, 0, 920, 921, 7, 17, 0, 0, 921, 922, 7, 3, 0, 0, 922, 923, 7, 9, 0, 0, 923, 924, 7, 20, 0, 0, 924, 925, 1, 0, 0, 0, 925, 926, 6, 34, 15, 0, 926, 87, 1, 0, 0, 0, 927, 929, 8, 23, 0, 0, 928, 927, 1, 0, 0, 0, 929, 930, 1, 0, 0, 0, 930, 928, 1, 0, 0, 0, 930, 931, 1, 0, 0, 0, 931, 932, 1, 0, 0, 0, 932, 933, 6, 35, 4, 0, 933, 89, 1, 0, 0, 0, 934, 935, 3, 182, 82, 0, 935, 936, 1, 0, 0, 0, 936, 937, 6, 36, 16, 0, 937, 938, 6, 36, 17, 0, 938, 91, 1, 0, 0, 0, 939, 940, 3, 302, 142, 0, 940, 941, 1, 0, 0, 0, 941, 942, 6, 37, 18, 0, 942, 943, 6, 37, 17, 0, 943, 944, 6, 37, 17, 0, 944, 93, 1, 0, 0, 0, 945, 946, 3, 248, 115, 0, 946, 947, 1, 0, 0, 0, 947, 948, 6, 38, 19, 0, 948, 95, 1, 0, 0, 0, 949, 950, 3, 542, 262, 0, 950, 951, 1, 0, 0, 0, 951, 952, 6, 39, 20, 0, 952, 97, 1, 0, 0, 0, 953, 954, 3, 228, 105, 0, 954, 955, 1, 0, 0, 0, 955, 956, 6, 40, 21, 0, 956, 99, 1, 0, 0, 0, 957, 958, 3, 224, 103, 0, 958, 959, 1, 0, 0, 0, 959, 960, 6, 41, 22, 0, 960, 101, 1, 0, 0, 0, 961, 962, 3, 296, 139, 0, 962, 963, 1, 0, 0, 0, 963, 964, 6, 42, 23, 0, 964, 103, 1, 0, 0, 0, 965, 966, 3, 298, 140, 0, 966, 967, 1, 0, 0, 0, 967, 968, 6, 43, 24, 0, 968, 105, 1, 0, 0, 0, 969, 970, 3, 308, 145, 0, 970, 971, 1, 0, 0, 0, 971, 972, 6, 44, 25, 0, 972, 107, 1, 0, 0, 0, 973, 974, 3, 304, 143, 0, 974, 975, 1, 0, 0, 0, 975, 976, 6, 45, 26, 0, 976, 109, 1, 0, 0, 0, 977, 978, 3, 18, 0, 0, 978, 979, 1, 0, 0, 0, 979, 980, 6, 46, 0, 0, 980, 111, 1, 0, 0, 0, 981, 982, 3, 20, 1, 0, 982, 983, 1, 0, 0, 0, 983, 984, 6, 47, 0, 0, 984, 113, 1, 0, 0, 0, 985, 986, 3, 22, 2, 0, 986, 987, 1, 0, 0, 0, 987, 988, 6, 48, 0, 0, 988, 115, 1, 0, 0, 0, 989, 990, 3, 182, 82, 0, 990, 991, 1, 0, 0, 0, 991, 992, 6, 49, 16, 0, 992, 993, 6, 49, 17, 0, 993, 117, 1, 0, 0, 0, 994, 995, 3, 302, 142, 0, 995, 996, 1, 0, 0, 0, 996, 997, 6, 50, 18, 0, 997, 998, 6, 50, 17, 0, 998, 999, 6, 50, 17, 0, 999, 119, 1, 0, 0, 0, 1000, 1001, 3, 248, 115, 0, 1001, 1002, 1, 0, 0, 0, 1002, 1003, 6, 51, 19, 0, 1003, 1004, 6, 51, 27, 0, 1004, 121, 1, 0, 0, 0, 1005, 1006, 3, 258, 120, 0, 1006, 1007, 1, 0, 0, 0, 1007, 1008, 6, 52, 28, 0, 1008, 1009, 6, 52, 27, 0, 1009, 123, 1, 0, 0, 0, 1010, 1011, 8, 24, 0, 0, 1011, 125, 1, 0, 0, 0, 1012, 1014, 3, 124, 53, 0, 1013, 1012, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1013, 1, 0, 0, 0, 1015, 1016, 1, 0, 0, 0, 1016, 1017, 1, 0, 0, 0, 1017, 1018, 3, 220, 101, 0, 1018, 1020, 1, 0, 0, 0, 1019, 1013, 1, 0, 0, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1022, 1, 0, 0, 0, 1021, 1023, 3, 124, 53, 0, 1022, 1021, 1, 0, 0, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1022, 1, 0, 0, 0, 1024, 1025, 1, 0, 0, 0, 1025, 127, 1, 0, 0, 0, 1026, 1027, 3, 126, 54, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1029, 6, 55, 29, 0, 1029, 129, 1, 0, 0, 0, 1030, 1031, 3, 204, 93, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 56, 30, 0, 1033, 131, 1, 0, 0, 0, 1034, 1035, 3, 18, 0, 0, 1035, 1036, 1, 0, 0, 0, 1036, 1037, 6, 57, 0, 0, 1037, 133, 1, 0, 0, 0, 1038, 1039, 3, 20, 1, 0, 1039, 1040, 1, 0, 0, 0, 1040, 1041, 6, 58, 0, 0, 1041, 135, 1, 0, 0, 0, 1042, 1043, 3, 22, 2, 0, 1043, 1044, 1, 0, 0, 0, 1044, 1045, 6, 59, 0, 0, 1045, 137, 1, 0, 0, 0, 1046, 1047, 3, 182, 82, 0, 1047, 1048, 1, 0, 0, 0, 1048, 1049, 6, 60, 16, 0, 1049, 1050, 6, 60, 17, 0, 1050, 1051, 6, 60, 17, 0, 1051, 139, 1, 0, 0, 0, 1052, 1053, 3, 302, 142, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1055, 6, 61, 18, 0, 1055, 1056, 6, 61, 17, 0, 1056, 1057, 6, 61, 17, 0, 1057, 1058, 6, 61, 17, 0, 1058, 141, 1, 0, 0, 0, 1059, 1060, 3, 296, 139, 0, 1060, 1061, 1, 0, 0, 0, 1061, 1062, 6, 62, 23, 0, 1062, 143, 1, 0, 0, 0, 1063, 1064, 3, 298, 140, 0, 1064, 1065, 1, 0, 0, 0, 1065, 1066, 6, 63, 24, 0, 1066, 145, 1, 0, 0, 0, 1067, 1068, 3, 214, 98, 0, 1068, 1069, 1, 0, 0, 0, 1069, 1070, 6, 64, 31, 0, 1070, 147, 1, 0, 0, 0, 1071, 1072, 3, 224, 103, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1074, 6, 65, 22, 0, 1074, 149, 1, 0, 0, 0, 1075, 1076, 3, 228, 105, 0, 1076, 1077, 1, 0, 0, 0, 1077, 1078, 6, 66, 21, 0, 1078, 151, 1, 0, 0, 0, 1079, 1080, 3, 258, 120, 0, 1080, 1081, 1, 0, 0, 0, 1081, 1082, 6, 67, 28, 0, 1082, 153, 1, 0, 0, 0, 1083, 1084, 3, 512, 247, 0, 1084, 1085, 1, 0, 0, 0, 1085, 1086, 6, 68, 32, 0, 1086, 155, 1, 0, 0, 0, 1087, 1088, 3, 308, 145, 0, 1088, 1089, 1, 0, 0, 0, 1089, 1090, 6, 69, 25, 0, 1090, 157, 1, 0, 0, 0, 1091, 1092, 3, 252, 117, 0, 1092, 1093, 1, 0, 0, 0, 1093, 1094, 6, 70, 33, 0, 1094, 159, 1, 0, 0, 0, 1095, 1096, 3, 292, 137, 0, 1096, 1097, 1, 0, 0, 0, 1097, 1098, 6, 71, 34, 0, 1098, 161, 1, 0, 0, 0, 1099, 1100, 3, 288, 135, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1102, 6, 72, 35, 0, 1102, 163, 1, 0, 0, 0, 1103, 1104, 3, 294, 138, 0, 1104, 1105, 1, 0, 0, 0, 1105, 1106, 6, 73, 36, 0, 1106, 165, 1, 0, 0, 0, 1107, 1108, 3, 18, 0, 0, 1108, 1109, 1, 0, 0, 0, 1109, 1110, 6, 74, 0, 0, 1110, 167, 1, 0, 0, 0, 1111, 1112, 3, 20, 1, 0, 1112, 1113, 1, 0, 0, 0, 1113, 1114, 6, 75, 0, 0, 1114, 169, 1, 0, 0, 0, 1115, 1116, 3, 22, 2, 0, 1116, 1117, 1, 0, 0, 0, 1117, 1118, 6, 76, 0, 0, 1118, 171, 1, 0, 0, 0, 1119, 1120, 3, 300, 141, 0, 1120, 1121, 1, 0, 0, 0, 1121, 1122, 6, 77, 37, 0, 1122, 1123, 6, 77, 38, 0, 1123, 173, 1, 0, 0, 0, 1124, 1125, 3, 182, 82, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 6, 78, 16, 0, 1127, 1128, 6, 78, 17, 0, 1128, 175, 1, 0, 0, 0, 1129, 1130, 3, 22, 2, 0, 1130, 1131, 1, 0, 0, 0, 1131, 1132, 6, 79, 0, 0, 1132, 177, 1, 0, 0, 0, 1133, 1134, 3, 18, 0, 0, 1134, 1135, 1, 0, 0, 0, 1135, 1136, 6, 80, 0, 0, 1136, 179, 1, 0, 0, 0, 1137, 1138, 3, 20, 1, 0, 1138, 1139, 1, 0, 0, 0, 1139, 1140, 6, 81, 0, 0, 1140, 181, 1, 0, 0, 0, 1141, 1142, 5, 124, 0, 0, 1142, 1143, 1, 0, 0, 0, 1143, 1144, 6, 82, 17, 0, 1144, 183, 1, 0, 0, 0, 1145, 1146, 7, 25, 0, 0, 1146, 185, 1, 0, 0, 0, 1147, 1148, 7, 26, 0, 0, 1148, 187, 1, 0, 0, 0, 1149, 1150, 5, 92, 0, 0, 1150, 1151, 7, 27, 0, 0, 1151, 189, 1, 0, 0, 0, 1152, 1153, 8, 28, 0, 0, 1153, 191, 1, 0, 0, 0, 1154, 1156, 7, 7, 0, 0, 1155, 1157, 7, 29, 0, 0, 1156, 1155, 1, 0, 0, 0, 1156, 1157, 1, 0, 0, 0, 1157, 1159, 1, 0, 0, 0, 1158, 1160, 3, 184, 83, 0, 1159, 1158, 1, 0, 0, 0, 1160, 1161, 1, 0, 0, 0, 1161, 1159, 1, 0, 0, 0, 1161, 1162, 1, 0, 0, 0, 1162, 193, 1, 0, 0, 0, 1163, 1164, 5, 64, 0, 0, 1164, 195, 1, 0, 0, 0, 1165, 1166, 5, 96, 0, 0, 1166, 197, 1, 0, 0, 0, 1167, 1171, 8, 30, 0, 0, 1168, 1169, 5, 96, 0, 0, 1169, 1171, 5, 96, 0, 0, 1170, 1167, 1, 0, 0, 0, 1170, 1168, 1, 0, 0, 0, 1171, 199, 1, 0, 0, 0, 1172, 1173, 5, 95, 0, 0, 1173, 201, 1, 0, 0, 0, 1174, 1178, 3, 186, 84, 0, 1175, 1178, 3, 184, 83, 0, 1176, 1178, 3, 200, 91, 0, 1177, 1174, 1, 0, 0, 0, 1177, 1175, 1, 0, 0, 0, 1177, 1176, 1, 0, 0, 0, 1178, 203, 1, 0, 0, 0, 1179, 1184, 5, 34, 0, 0, 1180, 1183, 3, 188, 85, 0, 1181, 1183, 3, 190, 86, 0, 1182, 1180, 1, 0, 0, 0, 1182, 1181, 1, 0, 0, 0, 1183, 1186, 1, 0, 0, 0, 1184, 1182, 1, 0, 0, 0, 1184, 1185, 1, 0, 0, 0, 1185, 1187, 1, 0, 0, 0, 1186, 1184, 1, 0, 0, 0, 1187, 1209, 5, 34, 0, 0, 1188, 1189, 5, 34, 0, 0, 1189, 1190, 5, 34, 0, 0, 1190, 1191, 5, 34, 0, 0, 1191, 1195, 1, 0, 0, 0, 1192, 1194, 8, 0, 0, 0, 1193, 1192, 1, 0, 0, 0, 1194, 1197, 1, 0, 0, 0, 1195, 1196, 1, 0, 0, 0, 1195, 1193, 1, 0, 0, 0, 1196, 1198, 1, 0, 0, 0, 1197, 1195, 1, 0, 0, 0, 1198, 1199, 5, 34, 0, 0, 1199, 1200, 5, 34, 0, 0, 1200, 1201, 5, 34, 0, 0, 1201, 1203, 1, 0, 0, 0, 1202, 1204, 5, 34, 0, 0, 1203, 1202, 1, 0, 0, 0, 1203, 1204, 1, 0, 0, 0, 1204, 1206, 1, 0, 0, 0, 1205, 1207, 5, 34, 0, 0, 1206, 1205, 1, 0, 0, 0, 1206, 1207, 1, 0, 0, 0, 1207, 1209, 1, 0, 0, 0, 1208, 1179, 1, 0, 0, 0, 1208, 1188, 1, 0, 0, 0, 1209, 205, 1, 0, 0, 0, 1210, 1212, 3, 184, 83, 0, 1211, 1210, 1, 0, 0, 0, 1212, 1213, 1, 0, 0, 0, 1213, 1211, 1, 0, 0, 0, 1213, 1214, 1, 0, 0, 0, 1214, 207, 1, 0, 0, 0, 1215, 1217, 3, 184, 83, 0, 1216, 1215, 1, 0, 0, 0, 1217, 1218, 1, 0, 0, 0, 1218, 1216, 1, 0, 0, 0, 1218, 1219, 1, 0, 0, 0, 1219, 1220, 1, 0, 0, 0, 1220, 1224, 3, 228, 105, 0, 1221, 1223, 3, 184, 83, 0, 1222, 1221, 1, 0, 0, 0, 1223, 1226, 1, 0, 0, 0, 1224, 1222, 1, 0, 0, 0, 1224, 1225, 1, 0, 0, 0, 1225, 1258, 1, 0, 0, 0, 1226, 1224, 1, 0, 0, 0, 1227, 1229, 3, 228, 105, 0, 1228, 1230, 3, 184, 83, 0, 1229, 1228, 1, 0, 0, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1229, 1, 0, 0, 0, 1231, 1232, 1, 0, 0, 0, 1232, 1258, 1, 0, 0, 0, 1233, 1235, 3, 184, 83, 0, 1234, 1233, 1, 0, 0, 0, 1235, 1236, 1, 0, 0, 0, 1236, 1234, 1, 0, 0, 0, 1236, 1237, 1, 0, 0, 0, 1237, 1245, 1, 0, 0, 0, 1238, 1242, 3, 228, 105, 0, 1239, 1241, 3, 184, 83, 0, 1240, 1239, 1, 0, 0, 0, 1241, 1244, 1, 0, 0, 0, 1242, 1240, 1, 0, 0, 0, 1242, 1243, 1, 0, 0, 0, 1243, 1246, 1, 0, 0, 0, 1244, 1242, 1, 0, 0, 0, 1245, 1238, 1, 0, 0, 0, 1245, 1246, 1, 0, 0, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1248, 3, 192, 87, 0, 1248, 1258, 1, 0, 0, 0, 1249, 1251, 3, 228, 105, 0, 1250, 1252, 3, 184, 83, 0, 1251, 1250, 1, 0, 0, 0, 1252, 1253, 1, 0, 0, 0, 1253, 1251, 1, 0, 0, 0, 1253, 1254, 1, 0, 0, 0, 1254, 1255, 1, 0, 0, 0, 1255, 1256, 3, 192, 87, 0, 1256, 1258, 1, 0, 0, 0, 1257, 1216, 1, 0, 0, 0, 1257, 1227, 1, 0, 0, 0, 1257, 1234, 1, 0, 0, 0, 1257, 1249, 1, 0, 0, 0, 1258, 209, 1, 0, 0, 0, 1259, 1260, 7, 4, 0, 0, 1260, 1261, 7, 5, 0, 0, 1261, 1262, 7, 16, 0, 0, 1262, 211, 1, 0, 0, 0, 1263, 1264, 7, 4, 0, 0, 1264, 1265, 7, 17, 0, 0, 1265, 1266, 7, 2, 0, 0, 1266, 213, 1, 0, 0, 0, 1267, 1268, 5, 61, 0, 0, 1268, 215, 1, 0, 0, 0, 1269, 1270, 7, 31, 0, 0, 1270, 1271, 7, 32, 0, 0, 1271, 217, 1, 0, 0, 0, 1272, 1273, 5, 58, 0, 0, 1273, 1274, 5, 58, 0, 0, 1274, 219, 1, 0, 0, 0, 1275, 1276, 5, 58, 0, 0, 1276, 221, 1, 0, 0, 0, 1277, 1278, 5, 59, 0, 0, 1278, 223, 1, 0, 0, 0, 1279, 1280, 5, 44, 0, 0, 1280, 225, 1, 0, 0, 0, 1281, 1282, 7, 16, 0, 0, 1282, 1283, 7, 7, 0, 0, 1283, 1284, 7, 17, 0, 0, 1284, 1285, 7, 2, 0, 0, 1285, 227, 1, 0, 0, 0, 1286, 1287, 5, 46, 0, 0, 1287, 229, 1, 0, 0, 0, 1288, 1289, 7, 21, 0, 0, 1289, 1290, 7, 4, 0, 0, 1290, 1291, 7, 14, 0, 0, 1291, 1292, 7, 17, 0, 0, 1292, 1293, 7, 7, 0, 0, 1293, 231, 1, 0, 0, 0, 1294, 1295, 7, 21, 0, 0, 1295, 1296, 7, 10, 0, 0, 1296, 1297, 7, 12, 0, 0, 1297, 1298, 7, 17, 0, 0, 1298, 1299, 7, 11, 0, 0, 1299, 233, 1, 0, 0, 0, 1300, 1301, 7, 10, 0, 0, 1301, 1302, 7, 5, 0, 0, 1302, 235, 1, 0, 0, 0, 1303, 1304, 7, 10, 0, 0, 1304, 1305, 7, 17, 0, 0, 1305, 237, 1, 0, 0, 0, 1306, 1307, 7, 14, 0, 0, 1307, 1308, 7, 4, 0, 0, 1308, 1309, 7, 17, 0, 0, 1309, 1310, 7, 11, 0, 0, 1310, 239, 1, 0, 0, 0, 1311, 1312, 7, 14, 0, 0, 1312, 1313, 7, 10, 0, 0, 1313, 1314, 7, 19, 0, 0, 1314, 1315, 7, 7, 0, 0, 1315, 241, 1, 0, 0, 0, 1316, 1317, 7, 5, 0, 0, 1317, 1318, 7, 9, 0, 0, 1318, 1319, 7, 11, 0, 0, 1319, 243, 1, 0, 0, 0, 1320, 1321, 7, 5, 0, 0, 1321, 1322, 7, 22, 0, 0, 1322, 1323, 7, 14, 0, 0, 1323, 1324, 7, 14, 0, 0, 1324, 245, 1, 0, 0, 0, 1325, 1326, 7, 5, 0, 0, 1326, 1327, 7, 22, 0, 0, 1327, 1328, 7, 14, 0, 0, 1328, 1329, 7, 14, 0, 0, 1329, 1330, 7, 17, 0, 0, 1330, 247, 1, 0, 0, 0, 1331, 1332, 7, 9, 0, 0, 1332, 1333, 7, 5, 0, 0, 1333, 249, 1, 0, 0, 0, 1334, 1335, 7, 9, 0, 0, 1335, 1336, 7, 12, 0, 0, 1336, 251, 1, 0, 0, 0, 1337, 1338, 5, 63, 0, 0, 1338, 253, 1, 0, 0, 0, 1339, 1340, 7, 12, 0, 0, 1340, 1341, 7, 14, 0, 0, 1341, 1342, 7, 10, 0, 0, 1342, 1343, 7, 19, 0, 0, 1343, 1344, 7, 7, 0, 0, 1344, 255, 1, 0, 0, 0, 1345, 1346, 7, 11, 0, 0, 1346, 1347, 7, 12, 0, 0, 1347, 1348, 7, 22, 0, 0, 1348, 1349, 7, 7, 0, 0, 1349, 257, 1, 0, 0, 0, 1350, 1351, 7, 20, 0, 0, 1351, 1352, 7, 10, 0, 0, 1352, 1353, 7, 11, 0, 0, 1353, 1354, 7, 3, 0, 0, 1354, 259, 1, 0, 0, 0, 1355, 1356, 5, 61, 0, 0, 1356, 1357, 5, 61, 0, 0, 1357, 261, 1, 0, 0, 0, 1358, 1359, 5, 61, 0, 0, 1359, 1360, 5, 126, 0, 0, 1360, 263, 1, 0, 0, 0, 1361, 1362, 5, 33, 0, 0, 1362, 1363, 5, 61, 0, 0, 1363, 265, 1, 0, 0, 0, 1364, 1365, 5, 60, 0, 0, 1365, 267, 1, 0, 0, 0, 1366, 1367, 5, 60, 0, 0, 1367, 1368, 5, 61, 0, 0, 1368, 269, 1, 0, 0, 0, 1369, 1370, 5, 62, 0, 0, 1370, 271, 1, 0, 0, 0, 1371, 1372, 5, 62, 0, 0, 1372, 1373, 5, 61, 0, 0, 1373, 273, 1, 0, 0, 0, 1374, 1375, 5, 43, 0, 0, 1375, 275, 1, 0, 0, 0, 1376, 1377, 5, 45, 0, 0, 1377, 277, 1, 0, 0, 0, 1378, 1379, 5, 42, 0, 0, 1379, 279, 1, 0, 0, 0, 1380, 1381, 5, 47, 0, 0, 1381, 281, 1, 0, 0, 0, 1382, 1383, 5, 37, 0, 0, 1383, 283, 1, 0, 0, 0, 1384, 1385, 5, 123, 0, 0, 1385, 285, 1, 0, 0, 0, 1386, 1387, 5, 125, 0, 0, 1387, 287, 1, 0, 0, 0, 1388, 1389, 5, 63, 0, 0, 1389, 1390, 5, 63, 0, 0, 1390, 289, 1, 0, 0, 0, 1391, 1392, 3, 50, 16, 0, 1392, 1393, 1, 0, 0, 0, 1393, 1394, 6, 136, 39, 0, 1394, 291, 1, 0, 0, 0, 1395, 1398, 3, 252, 117, 0, 1396, 1399, 3, 186, 84, 0, 1397, 1399, 3, 200, 91, 0, 1398, 1396, 1, 0, 0, 0, 1398, 1397, 1, 0, 0, 0, 1399, 1403, 1, 0, 0, 0, 1400, 1402, 3, 202, 92, 0, 1401, 1400, 1, 0, 0, 0, 1402, 1405, 1, 0, 0, 0, 1403, 1401, 1, 0, 0, 0, 1403, 1404, 1, 0, 0, 0, 1404, 1413, 1, 0, 0, 0, 1405, 1403, 1, 0, 0, 0, 1406, 1408, 3, 252, 117, 0, 1407, 1409, 3, 184, 83, 0, 1408, 1407, 1, 0, 0, 0, 1409, 1410, 1, 0, 0, 0, 1410, 1408, 1, 0, 0, 0, 1410, 1411, 1, 0, 0, 0, 1411, 1413, 1, 0, 0, 0, 1412, 1395, 1, 0, 0, 0, 1412, 1406, 1, 0, 0, 0, 1413, 293, 1, 0, 0, 0, 1414, 1417, 3, 288, 135, 0, 1415, 1418, 3, 186, 84, 0, 1416, 1418, 3, 200, 91, 0, 1417, 1415, 1, 0, 0, 0, 1417, 1416, 1, 0, 0, 0, 1418, 1422, 1, 0, 0, 0, 1419, 1421, 3, 202, 92, 0, 1420, 1419, 1, 0, 0, 0, 1421, 1424, 1, 0, 0, 0, 1422, 1420, 1, 0, 0, 0, 1422, 1423, 1, 0, 0, 0, 1423, 1432, 1, 0, 0, 0, 1424, 1422, 1, 0, 0, 0, 1425, 1427, 3, 288, 135, 0, 1426, 1428, 3, 184, 83, 0, 1427, 1426, 1, 0, 0, 0, 1428, 1429, 1, 0, 0, 0, 1429, 1427, 1, 0, 0, 0, 1429, 1430, 1, 0, 0, 0, 1430, 1432, 1, 0, 0, 0, 1431, 1414, 1, 0, 0, 0, 1431, 1425, 1, 0, 0, 0, 1432, 295, 1, 0, 0, 0, 1433, 1434, 5, 91, 0, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 139, 4, 0, 1436, 1437, 6, 139, 4, 0, 1437, 297, 1, 0, 0, 0, 1438, 1439, 5, 93, 0, 0, 1439, 1440, 1, 0, 0, 0, 1440, 1441, 6, 140, 17, 0, 1441, 1442, 6, 140, 17, 0, 1442, 299, 1, 0, 0, 0, 1443, 1444, 5, 40, 0, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 141, 4, 0, 1446, 1447, 6, 141, 4, 0, 1447, 301, 1, 0, 0, 0, 1448, 1449, 5, 41, 0, 0, 1449, 1450, 1, 0, 0, 0, 1450, 1451, 6, 142, 17, 0, 1451, 1452, 6, 142, 17, 0, 1452, 303, 1, 0, 0, 0, 1453, 1457, 3, 186, 84, 0, 1454, 1456, 3, 202, 92, 0, 1455, 1454, 1, 0, 0, 0, 1456, 1459, 1, 0, 0, 0, 1457, 1455, 1, 0, 0, 0, 1457, 1458, 1, 0, 0, 0, 1458, 1470, 1, 0, 0, 0, 1459, 1457, 1, 0, 0, 0, 1460, 1463, 3, 200, 91, 0, 1461, 1463, 3, 194, 88, 0, 1462, 1460, 1, 0, 0, 0, 1462, 1461, 1, 0, 0, 0, 1463, 1465, 1, 0, 0, 0, 1464, 1466, 3, 202, 92, 0, 1465, 1464, 1, 0, 0, 0, 1466, 1467, 1, 0, 0, 0, 1467, 1465, 1, 0, 0, 0, 1467, 1468, 1, 0, 0, 0, 1468, 1470, 1, 0, 0, 0, 1469, 1453, 1, 0, 0, 0, 1469, 1462, 1, 0, 0, 0, 1470, 305, 1, 0, 0, 0, 1471, 1473, 3, 196, 89, 0, 1472, 1474, 3, 198, 90, 0, 1473, 1472, 1, 0, 0, 0, 1474, 1475, 1, 0, 0, 0, 1475, 1473, 1, 0, 0, 0, 1475, 1476, 1, 0, 0, 0, 1476, 1477, 1, 0, 0, 0, 1477, 1478, 3, 196, 89, 0, 1478, 307, 1, 0, 0, 0, 1479, 1480, 3, 306, 144, 0, 1480, 309, 1, 0, 0, 0, 1481, 1482, 3, 18, 0, 0, 1482, 1483, 1, 0, 0, 0, 1483, 1484, 6, 146, 0, 0, 1484, 311, 1, 0, 0, 0, 1485, 1486, 3, 20, 1, 0, 1486, 1487, 1, 0, 0, 0, 1487, 1488, 6, 147, 0, 0, 1488, 313, 1, 0, 0, 0, 1489, 1490, 3, 22, 2, 0, 1490, 1491, 1, 0, 0, 0, 1491, 1492, 6, 148, 0, 0, 1492, 315, 1, 0, 0, 0, 1493, 1494, 3, 182, 82, 0, 1494, 1495, 1, 0, 0, 0, 1495, 1496, 6, 149, 16, 0, 1496, 1497, 6, 149, 17, 0, 1497, 317, 1, 0, 0, 0, 1498, 1499, 3, 220, 101, 0, 1499, 1500, 1, 0, 0, 0, 1500, 1501, 6, 150, 40, 0, 1501, 319, 1, 0, 0, 0, 1502, 1503, 3, 218, 100, 0, 1503, 1504, 1, 0, 0, 0, 1504, 1505, 6, 151, 41, 0, 1505, 321, 1, 0, 0, 0, 1506, 1507, 3, 224, 103, 0, 1507, 1508, 1, 0, 0, 0, 1508, 1509, 6, 152, 22, 0, 1509, 323, 1, 0, 0, 0, 1510, 1511, 3, 214, 98, 0, 1511, 1512, 1, 0, 0, 0, 1512, 1513, 6, 153, 31, 0, 1513, 325, 1, 0, 0, 0, 1514, 1515, 7, 15, 0, 0, 1515, 1516, 7, 7, 0, 0, 1516, 1517, 7, 11, 0, 0, 1517, 1518, 7, 4, 0, 0, 1518, 1519, 7, 16, 0, 0, 1519, 1520, 7, 4, 0, 0, 1520, 1521, 7, 11, 0, 0, 1521, 1522, 7, 4, 0, 0, 1522, 327, 1, 0, 0, 0, 1523, 1524, 3, 302, 142, 0, 1524, 1525, 1, 0, 0, 0, 1525, 1526, 6, 155, 18, 0, 1526, 1527, 6, 155, 17, 0, 1527, 329, 1, 0, 0, 0, 1528, 1532, 8, 33, 0, 0, 1529, 1530, 5, 47, 0, 0, 1530, 1532, 8, 34, 0, 0, 1531, 1528, 1, 0, 0, 0, 1531, 1529, 1, 0, 0, 0, 1532, 331, 1, 0, 0, 0, 1533, 1535, 3, 330, 156, 0, 1534, 1533, 1, 0, 0, 0, 1535, 1536, 1, 0, 0, 0, 1536, 1534, 1, 0, 0, 0, 1536, 1537, 1, 0, 0, 0, 1537, 333, 1, 0, 0, 0, 1538, 1539, 3, 332, 157, 0, 1539, 1540, 1, 0, 0, 0, 1540, 1541, 6, 158, 42, 0, 1541, 335, 1, 0, 0, 0, 1542, 1543, 3, 204, 93, 0, 1543, 1544, 1, 0, 0, 0, 1544, 1545, 6, 159, 30, 0, 1545, 337, 1, 0, 0, 0, 1546, 1547, 3, 18, 0, 0, 1547, 1548, 1, 0, 0, 0, 1548, 1549, 6, 160, 0, 0, 1549, 339, 1, 0, 0, 0, 1550, 1551, 3, 20, 1, 0, 1551, 1552, 1, 0, 0, 0, 1552, 1553, 6, 161, 0, 0, 1553, 341, 1, 0, 0, 0, 1554, 1555, 3, 22, 2, 0, 1555, 1556, 1, 0, 0, 0, 1556, 1557, 6, 162, 0, 0, 1557, 343, 1, 0, 0, 0, 1558, 1559, 3, 300, 141, 0, 1559, 1560, 1, 0, 0, 0, 1560, 1561, 6, 163, 37, 0, 1561, 1562, 6, 163, 38, 0, 1562, 345, 1, 0, 0, 0, 1563, 1564, 3, 302, 142, 0, 1564, 1565, 1, 0, 0, 0, 1565, 1566, 6, 164, 18, 0, 1566, 1567, 6, 164, 17, 0, 1567, 1568, 6, 164, 17, 0, 1568, 347, 1, 0, 0, 0, 1569, 1570, 3, 182, 82, 0, 1570, 1571, 1, 0, 0, 0, 1571, 1572, 6, 165, 16, 0, 1572, 1573, 6, 165, 17, 0, 1573, 349, 1, 0, 0, 0, 1574, 1575, 3, 22, 2, 0, 1575, 1576, 1, 0, 0, 0, 1576, 1577, 6, 166, 0, 0, 1577, 351, 1, 0, 0, 0, 1578, 1579, 3, 18, 0, 0, 1579, 1580, 1, 0, 0, 0, 1580, 1581, 6, 167, 0, 0, 1581, 353, 1, 0, 0, 0, 1582, 1583, 3, 20, 1, 0, 1583, 1584, 1, 0, 0, 0, 1584, 1585, 6, 168, 0, 0, 1585, 355, 1, 0, 0, 0, 1586, 1587, 3, 182, 82, 0, 1587, 1588, 1, 0, 0, 0, 1588, 1589, 6, 169, 16, 0, 1589, 1590, 6, 169, 17, 0, 1590, 357, 1, 0, 0, 0, 1591, 1592, 3, 302, 142, 0, 1592, 1593, 1, 0, 0, 0, 1593, 1594, 6, 170, 18, 0, 1594, 1595, 6, 170, 17, 0, 1595, 1596, 6, 170, 17, 0, 1596, 359, 1, 0, 0, 0, 1597, 1598, 7, 6, 0, 0, 1598, 1599, 7, 12, 0, 0, 1599, 1600, 7, 9, 0, 0, 1600, 1601, 7, 22, 0, 0, 1601, 1602, 7, 8, 0, 0, 1602, 361, 1, 0, 0, 0, 1603, 1604, 7, 17, 0, 0, 1604, 1605, 7, 2, 0, 0, 1605, 1606, 7, 9, 0, 0, 1606, 1607, 7, 12, 0, 0, 1607, 1608, 7, 7, 0, 0, 1608, 363, 1, 0, 0, 0, 1609, 1610, 7, 19, 0, 0, 1610, 1611, 7, 7, 0, 0, 1611, 1612, 7, 32, 0, 0, 1612, 365, 1, 0, 0, 0, 1613, 1614, 3, 258, 120, 0, 1614, 1615, 1, 0, 0, 0, 1615, 1616, 6, 174, 28, 0, 1616, 1617, 6, 174, 17, 0, 1617, 1618, 6, 174, 4, 0, 1618, 367, 1, 0, 0, 0, 1619, 1620, 3, 224, 103, 0, 1620, 1621, 1, 0, 0, 0, 1621, 1622, 6, 175, 22, 0, 1622, 369, 1, 0, 0, 0, 1623, 1624, 3, 228, 105, 0, 1624, 1625, 1, 0, 0, 0, 1625, 1626, 6, 176, 21, 0, 1626, 371, 1, 0, 0, 0, 1627, 1628, 3, 252, 117, 0, 1628, 1629, 1, 0, 0, 0, 1629, 1630, 6, 177, 33, 0, 1630, 373, 1, 0, 0, 0, 1631, 1632, 3, 292, 137, 0, 1632, 1633, 1, 0, 0, 0, 1633, 1634, 6, 178, 34, 0, 1634, 375, 1, 0, 0, 0, 1635, 1636, 3, 288, 135, 0, 1636, 1637, 1, 0, 0, 0, 1637, 1638, 6, 179, 35, 0, 1638, 377, 1, 0, 0, 0, 1639, 1640, 3, 294, 138, 0, 1640, 1641, 1, 0, 0, 0, 1641, 1642, 6, 180, 36, 0, 1642, 379, 1, 0, 0, 0, 1643, 1644, 3, 216, 99, 0, 1644, 1645, 1, 0, 0, 0, 1645, 1646, 6, 181, 43, 0, 1646, 381, 1, 0, 0, 0, 1647, 1648, 3, 308, 145, 0, 1648, 1649, 1, 0, 0, 0, 1649, 1650, 6, 182, 25, 0, 1650, 383, 1, 0, 0, 0, 1651, 1652, 3, 304, 143, 0, 1652, 1653, 1, 0, 0, 0, 1653, 1654, 6, 183, 26, 0, 1654, 385, 1, 0, 0, 0, 1655, 1656, 3, 18, 0, 0, 1656, 1657, 1, 0, 0, 0, 1657, 1658, 6, 184, 0, 0, 1658, 387, 1, 0, 0, 0, 1659, 1660, 3, 20, 1, 0, 1660, 1661, 1, 0, 0, 0, 1661, 1662, 6, 185, 0, 0, 1662, 389, 1, 0, 0, 0, 1663, 1664, 3, 22, 2, 0, 1664, 1665, 1, 0, 0, 0, 1665, 1666, 6, 186, 0, 0, 1666, 391, 1, 0, 0, 0, 1667, 1668, 7, 17, 0, 0, 1668, 1669, 7, 11, 0, 0, 1669, 1670, 7, 4, 0, 0, 1670, 1671, 7, 11, 0, 0, 1671, 1672, 7, 17, 0, 0, 1672, 1673, 1, 0, 0, 0, 1673, 1674, 6, 187, 17, 0, 1674, 1675, 6, 187, 4, 0, 1675, 393, 1, 0, 0, 0, 1676, 1677, 3, 18, 0, 0, 1677, 1678, 1, 0, 0, 0, 1678, 1679, 6, 188, 0, 0, 1679, 395, 1, 0, 0, 0, 1680, 1681, 3, 20, 1, 0, 1681, 1682, 1, 0, 0, 0, 1682, 1683, 6, 189, 0, 0, 1683, 397, 1, 0, 0, 0, 1684, 1685, 3, 22, 2, 0, 1685, 1686, 1, 0, 0, 0, 1686, 1687, 6, 190, 0, 0, 1687, 399, 1, 0, 0, 0, 1688, 1689, 3, 182, 82, 0, 1689, 1690, 1, 0, 0, 0, 1690, 1691, 6, 191, 16, 0, 1691, 1692, 6, 191, 17, 0, 1692, 401, 1, 0, 0, 0, 1693, 1694, 7, 35, 0, 0, 1694, 1695, 7, 9, 0, 0, 1695, 1696, 7, 10, 0, 0, 1696, 1697, 7, 5, 0, 0, 1697, 403, 1, 0, 0, 0, 1698, 1699, 3, 542, 262, 0, 1699, 1700, 1, 0, 0, 0, 1700, 1701, 6, 193, 20, 0, 1701, 405, 1, 0, 0, 0, 1702, 1703, 3, 248, 115, 0, 1703, 1704, 1, 0, 0, 0, 1704, 1705, 6, 194, 19, 0, 1705, 1706, 6, 194, 17, 0, 1706, 1707, 6, 194, 4, 0, 1707, 407, 1, 0, 0, 0, 1708, 1709, 7, 22, 0, 0, 1709, 1710, 7, 17, 0, 0, 1710, 1711, 7, 10, 0, 0, 1711, 1712, 7, 5, 0, 0, 1712, 1713, 7, 6, 0, 0, 1713, 1714, 1, 0, 0, 0, 1714, 1715, 6, 195, 17, 0, 1715, 1716, 6, 195, 4, 0, 1716, 409, 1, 0, 0, 0, 1717, 1718, 3, 332, 157, 0, 1718, 1719, 1, 0, 0, 0, 1719, 1720, 6, 196, 42, 0, 1720, 411, 1, 0, 0, 0, 1721, 1722, 3, 204, 93, 0, 1722, 1723, 1, 0, 0, 0, 1723, 1724, 6, 197, 30, 0, 1724, 413, 1, 0, 0, 0, 1725, 1726, 3, 220, 101, 0, 1726, 1727, 1, 0, 0, 0, 1727, 1728, 6, 198, 40, 0, 1728, 415, 1, 0, 0, 0, 1729, 1730, 3, 18, 0, 0, 1730, 1731, 1, 0, 0, 0, 1731, 1732, 6, 199, 0, 0, 1732, 417, 1, 0, 0, 0, 1733, 1734, 3, 20, 1, 0, 1734, 1735, 1, 0, 0, 0, 1735, 1736, 6, 200, 0, 0, 1736, 419, 1, 0, 0, 0, 1737, 1738, 3, 22, 2, 0, 1738, 1739, 1, 0, 0, 0, 1739, 1740, 6, 201, 0, 0, 1740, 421, 1, 0, 0, 0, 1741, 1742, 3, 182, 82, 0, 1742, 1743, 1, 0, 0, 0, 1743, 1744, 6, 202, 16, 0, 1744, 1745, 6, 202, 17, 0, 1745, 423, 1, 0, 0, 0, 1746, 1747, 3, 302, 142, 0, 1747, 1748, 1, 0, 0, 0, 1748, 1749, 6, 203, 18, 0, 1749, 1750, 6, 203, 17, 0, 1750, 1751, 6, 203, 17, 0, 1751, 425, 1, 0, 0, 0, 1752, 1753, 3, 220, 101, 0, 1753, 1754, 1, 0, 0, 0, 1754, 1755, 6, 204, 40, 0, 1755, 427, 1, 0, 0, 0, 1756, 1757, 3, 224, 103, 0, 1757, 1758, 1, 0, 0, 0, 1758, 1759, 6, 205, 22, 0, 1759, 429, 1, 0, 0, 0, 1760, 1761, 3, 228, 105, 0, 1761, 1762, 1, 0, 0, 0, 1762, 1763, 6, 206, 21, 0, 1763, 431, 1, 0, 0, 0, 1764, 1765, 3, 248, 115, 0, 1765, 1766, 1, 0, 0, 0, 1766, 1767, 6, 207, 19, 0, 1767, 1768, 6, 207, 44, 0, 1768, 433, 1, 0, 0, 0, 1769, 1770, 3, 332, 157, 0, 1770, 1771, 1, 0, 0, 0, 1771, 1772, 6, 208, 42, 0, 1772, 435, 1, 0, 0, 0, 1773, 1774, 3, 204, 93, 0, 1774, 1775, 1, 0, 0, 0, 1775, 1776, 6, 209, 30, 0, 1776, 437, 1, 0, 0, 0, 1777, 1778, 3, 18, 0, 0, 1778, 1779, 1, 0, 0, 0, 1779, 1780, 6, 210, 0, 0, 1780, 439, 1, 0, 0, 0, 1781, 1782, 3, 20, 1, 0, 1782, 1783, 1, 0, 0, 0, 1783, 1784, 6, 211, 0, 0, 1784, 441, 1, 0, 0, 0, 1785, 1786, 3, 22, 2, 0, 1786, 1787, 1, 0, 0, 0, 1787, 1788, 6, 212, 0, 0, 1788, 443, 1, 0, 0, 0, 1789, 1790, 3, 182, 82, 0, 1790, 1791, 1, 0, 0, 0, 1791, 1792, 6, 213, 16, 0, 1792, 1793, 6, 213, 17, 0, 1793, 1794, 6, 213, 17, 0, 1794, 445, 1, 0, 0, 0, 1795, 1796, 3, 302, 142, 0, 1796, 1797, 1, 0, 0, 0, 1797, 1798, 6, 214, 18, 0, 1798, 1799, 6, 214, 17, 0, 1799, 1800, 6, 214, 17, 0, 1800, 1801, 6, 214, 17, 0, 1801, 447, 1, 0, 0, 0, 1802, 1803, 3, 224, 103, 0, 1803, 1804, 1, 0, 0, 0, 1804, 1805, 6, 215, 22, 0, 1805, 449, 1, 0, 0, 0, 1806, 1807, 3, 228, 105, 0, 1807, 1808, 1, 0, 0, 0, 1808, 1809, 6, 216, 21, 0, 1809, 451, 1, 0, 0, 0, 1810, 1811, 3, 512, 247, 0, 1811, 1812, 1, 0, 0, 0, 1812, 1813, 6, 217, 32, 0, 1813, 453, 1, 0, 0, 0, 1814, 1815, 3, 18, 0, 0, 1815, 1816, 1, 0, 0, 0, 1816, 1817, 6, 218, 0, 0, 1817, 455, 1, 0, 0, 0, 1818, 1819, 3, 20, 1, 0, 1819, 1820, 1, 0, 0, 0, 1820, 1821, 6, 219, 0, 0, 1821, 457, 1, 0, 0, 0, 1822, 1823, 3, 22, 2, 0, 1823, 1824, 1, 0, 0, 0, 1824, 1825, 6, 220, 0, 0, 1825, 459, 1, 0, 0, 0, 1826, 1827, 3, 182, 82, 0, 1827, 1828, 1, 0, 0, 0, 1828, 1829, 6, 221, 16, 0, 1829, 1830, 6, 221, 17, 0, 1830, 461, 1, 0, 0, 0, 1831, 1832, 3, 302, 142, 0, 1832, 1833, 1, 0, 0, 0, 1833, 1834, 6, 222, 18, 0, 1834, 1835, 6, 222, 17, 0, 1835, 1836, 6, 222, 17, 0, 1836, 463, 1, 0, 0, 0, 1837, 1838, 3, 296, 139, 0, 1838, 1839, 1, 0, 0, 0, 1839, 1840, 6, 223, 23, 0, 1840, 465, 1, 0, 0, 0, 1841, 1842, 3, 298, 140, 0, 1842, 1843, 1, 0, 0, 0, 1843, 1844, 6, 224, 24, 0, 1844, 467, 1, 0, 0, 0, 1845, 1846, 3, 228, 105, 0, 1846, 1847, 1, 0, 0, 0, 1847, 1848, 6, 225, 21, 0, 1848, 469, 1, 0, 0, 0, 1849, 1850, 3, 252, 117, 0, 1850, 1851, 1, 0, 0, 0, 1851, 1852, 6, 226, 33, 0, 1852, 471, 1, 0, 0, 0, 1853, 1854, 3, 292, 137, 0, 1854, 1855, 1, 0, 0, 0, 1855, 1856, 6, 227, 34, 0, 1856, 473, 1, 0, 0, 0, 1857, 1858, 3, 288, 135, 0, 1858, 1859, 1, 0, 0, 0, 1859, 1860, 6, 228, 35, 0, 1860, 475, 1, 0, 0, 0, 1861, 1862, 3, 294, 138, 0, 1862, 1863, 1, 0, 0, 0, 1863, 1864, 6, 229, 36, 0, 1864, 477, 1, 0, 0, 0, 1865, 1866, 3, 308, 145, 0, 1866, 1867, 1, 0, 0, 0, 1867, 1868, 6, 230, 25, 0, 1868, 479, 1, 0, 0, 0, 1869, 1870, 3, 304, 143, 0, 1870, 1871, 1, 0, 0, 0, 1871, 1872, 6, 231, 26, 0, 1872, 481, 1, 0, 0, 0, 1873, 1874, 3, 18, 0, 0, 1874, 1875, 1, 0, 0, 0, 1875, 1876, 6, 232, 0, 0, 1876, 483, 1, 0, 0, 0, 1877, 1878, 3, 20, 1, 0, 1878, 1879, 1, 0, 0, 0, 1879, 1880, 6, 233, 0, 0, 1880, 485, 1, 0, 0, 0, 1881, 1882, 3, 22, 2, 0, 1882, 1883, 1, 0, 0, 0, 1883, 1884, 6, 234, 0, 0, 1884, 487, 1, 0, 0, 0, 1885, 1886, 3, 182, 82, 0, 1886, 1887, 1, 0, 0, 0, 1887, 1888, 6, 235, 16, 0, 1888, 1889, 6, 235, 17, 0, 1889, 489, 1, 0, 0, 0, 1890, 1891, 3, 302, 142, 0, 1891, 1892, 1, 0, 0, 0, 1892, 1893, 6, 236, 18, 0, 1893, 1894, 6, 236, 17, 0, 1894, 1895, 6, 236, 17, 0, 1895, 491, 1, 0, 0, 0, 1896, 1897, 3, 228, 105, 0, 1897, 1898, 1, 0, 0, 0, 1898, 1899, 6, 237, 21, 0, 1899, 493, 1, 0, 0, 0, 1900, 1901, 3, 296, 139, 0, 1901, 1902, 1, 0, 0, 0, 1902, 1903, 6, 238, 23, 0, 1903, 495, 1, 0, 0, 0, 1904, 1905, 3, 298, 140, 0, 1905, 1906, 1, 0, 0, 0, 1906, 1907, 6, 239, 24, 0, 1907, 497, 1, 0, 0, 0, 1908, 1909, 3, 224, 103, 0, 1909, 1910, 1, 0, 0, 0, 1910, 1911, 6, 240, 22, 0, 1911, 499, 1, 0, 0, 0, 1912, 1913, 3, 252, 117, 0, 1913, 1914, 1, 0, 0, 0, 1914, 1915, 6, 241, 33, 0, 1915, 501, 1, 0, 0, 0, 1916, 1917, 3, 292, 137, 0, 1917, 1918, 1, 0, 0, 0, 1918, 1919, 6, 242, 34, 0, 1919, 503, 1, 0, 0, 0, 1920, 1921, 3, 288, 135, 0, 1921, 1922, 1, 0, 0, 0, 1922, 1923, 6, 243, 35, 0, 1923, 505, 1, 0, 0, 0, 1924, 1925, 3, 294, 138, 0, 1925, 1926, 1, 0, 0, 0, 1926, 1927, 6, 244, 36, 0, 1927, 507, 1, 0, 0, 0, 1928, 1933, 3, 186, 84, 0, 1929, 1933, 3, 184, 83, 0, 1930, 1933, 3, 200, 91, 0, 1931, 1933, 3, 278, 130, 0, 1932, 1928, 1, 0, 0, 0, 1932, 1929, 1, 0, 0, 0, 1932, 1930, 1, 0, 0, 0, 1932, 1931, 1, 0, 0, 0, 1933, 509, 1, 0, 0, 0, 1934, 1937, 3, 186, 84, 0, 1935, 1937, 3, 278, 130, 0, 1936, 1934, 1, 0, 0, 0, 1936, 1935, 1, 0, 0, 0, 1937, 1941, 1, 0, 0, 0, 1938, 1940, 3, 508, 245, 0, 1939, 1938, 1, 0, 0, 0, 1940, 1943, 1, 0, 0, 0, 1941, 1939, 1, 0, 0, 0, 1941, 1942, 1, 0, 0, 0, 1942, 1954, 1, 0, 0, 0, 1943, 1941, 1, 0, 0, 0, 1944, 1947, 3, 200, 91, 0, 1945, 1947, 3, 194, 88, 0, 1946, 1944, 1, 0, 0, 0, 1946, 1945, 1, 0, 0, 0, 1947, 1949, 1, 0, 0, 0, 1948, 1950, 3, 508, 245, 0, 1949, 1948, 1, 0, 0, 0, 1950, 1951, 1, 0, 0, 0, 1951, 1949, 1, 0, 0, 0, 1951, 1952, 1, 0, 0, 0, 1952, 1954, 1, 0, 0, 0, 1953, 1936, 1, 0, 0, 0, 1953, 1946, 1, 0, 0, 0, 1954, 511, 1, 0, 0, 0, 1955, 1958, 3, 510, 246, 0, 1956, 1958, 3, 306, 144, 0, 1957, 1955, 1, 0, 0, 0, 1957, 1956, 1, 0, 0, 0, 1958, 1959, 1, 0, 0, 0, 1959, 1957, 1, 0, 0, 0, 1959, 1960, 1, 0, 0, 0, 1960, 513, 1, 0, 0, 0, 1961, 1962, 3, 18, 0, 0, 1962, 1963, 1, 0, 0, 0, 1963, 1964, 6, 248, 0, 0, 1964, 515, 1, 0, 0, 0, 1965, 1966, 3, 20, 1, 0, 1966, 1967, 1, 0, 0, 0, 1967, 1968, 6, 249, 0, 0, 1968, 517, 1, 0, 0, 0, 1969, 1970, 3, 22, 2, 0, 1970, 1971, 1, 0, 0, 0, 1971, 1972, 6, 250, 0, 0, 1972, 519, 1, 0, 0, 0, 1973, 1974, 3, 182, 82, 0, 1974, 1975, 1, 0, 0, 0, 1975, 1976, 6, 251, 16, 0, 1976, 1977, 6, 251, 17, 0, 1977, 521, 1, 0, 0, 0, 1978, 1979, 3, 302, 142, 0, 1979, 1980, 1, 0, 0, 0, 1980, 1981, 6, 252, 18, 0, 1981, 1982, 6, 252, 17, 0, 1982, 1983, 6, 252, 17, 0, 1983, 523, 1, 0, 0, 0, 1984, 1985, 3, 296, 139, 0, 1985, 1986, 1, 0, 0, 0, 1986, 1987, 6, 253, 23, 0, 1987, 525, 1, 0, 0, 0, 1988, 1989, 3, 298, 140, 0, 1989, 1990, 1, 0, 0, 0, 1990, 1991, 6, 254, 24, 0, 1991, 527, 1, 0, 0, 0, 1992, 1993, 3, 214, 98, 0, 1993, 1994, 1, 0, 0, 0, 1994, 1995, 6, 255, 31, 0, 1995, 529, 1, 0, 0, 0, 1996, 1997, 3, 224, 103, 0, 1997, 1998, 1, 0, 0, 0, 1998, 1999, 6, 256, 22, 0, 1999, 531, 1, 0, 0, 0, 2000, 2001, 3, 228, 105, 0, 2001, 2002, 1, 0, 0, 0, 2002, 2003, 6, 257, 21, 0, 2003, 533, 1, 0, 0, 0, 2004, 2005, 3, 252, 117, 0, 2005, 2006, 1, 0, 0, 0, 2006, 2007, 6, 258, 33, 0, 2007, 535, 1, 0, 0, 0, 2008, 2009, 3, 292, 137, 0, 2009, 2010, 1, 0, 0, 0, 2010, 2011, 6, 259, 34, 0, 2011, 537, 1, 0, 0, 0, 2012, 2013, 3, 288, 135, 0, 2013, 2014, 1, 0, 0, 0, 2014, 2015, 6, 260, 35, 0, 2015, 539, 1, 0, 0, 0, 2016, 2017, 3, 294, 138, 0, 2017, 2018, 1, 0, 0, 0, 2018, 2019, 6, 261, 36, 0, 2019, 541, 1, 0, 0, 0, 2020, 2021, 7, 4, 0, 0, 2021, 2022, 7, 17, 0, 0, 2022, 543, 1, 0, 0, 0, 2023, 2024, 3, 512, 247, 0, 2024, 2025, 1, 0, 0, 0, 2025, 2026, 6, 263, 32, 0, 2026, 545, 1, 0, 0, 0, 2027, 2028, 3, 18, 0, 0, 2028, 2029, 1, 0, 0, 0, 2029, 2030, 6, 264, 0, 0, 2030, 547, 1, 0, 0, 0, 2031, 2032, 3, 20, 1, 0, 2032, 2033, 1, 0, 0, 0, 2033, 2034, 6, 265, 0, 0, 2034, 549, 1, 0, 0, 0, 2035, 2036, 3, 22, 2, 0, 2036, 2037, 1, 0, 0, 0, 2037, 2038, 6, 266, 0, 0, 2038, 551, 1, 0, 0, 0, 2039, 2040, 3, 256, 119, 0, 2040, 2041, 1, 0, 0, 0, 2041, 2042, 6, 267, 45, 0, 2042, 553, 1, 0, 0, 0, 2043, 2044, 3, 230, 106, 0, 2044, 2045, 1, 0, 0, 0, 2045, 2046, 6, 268, 46, 0, 2046, 555, 1, 0, 0, 0, 2047, 2048, 3, 244, 113, 0, 2048, 2049, 1, 0, 0, 0, 2049, 2050, 6, 269, 47, 0, 2050, 557, 1, 0, 0, 0, 2051, 2052, 3, 222, 102, 0, 2052, 2053, 1, 0, 0, 0, 2053, 2054, 6, 270, 48, 0, 2054, 2055, 6, 270, 17, 0, 2055, 559, 1, 0, 0, 0, 2056, 2057, 3, 214, 98, 0, 2057, 2058, 1, 0, 0, 0, 2058, 2059, 6, 271, 31, 0, 2059, 561, 1, 0, 0, 0, 2060, 2061, 3, 204, 93, 0, 2061, 2062, 1, 0, 0, 0, 2062, 2063, 6, 272, 30, 0, 2063, 563, 1, 0, 0, 0, 2064, 2065, 3, 304, 143, 0, 2065, 2066, 1, 0, 0, 0, 2066, 2067, 6, 273, 26, 0, 2067, 565, 1, 0, 0, 0, 2068, 2069, 3, 308, 145, 0, 2069, 2070, 1, 0, 0, 0, 2070, 2071, 6, 274, 25, 0, 2071, 567, 1, 0, 0, 0, 2072, 2073, 3, 208, 95, 0, 2073, 2074, 1, 0, 0, 0, 2074, 2075, 6, 275, 49, 0, 2075, 569, 1, 0, 0, 0, 2076, 2077, 3, 206, 94, 0, 2077, 2078, 1, 0, 0, 0, 2078, 2079, 6, 276, 50, 0, 2079, 571, 1, 0, 0, 0, 2080, 2081, 3, 224, 103, 0, 2081, 2082, 1, 0, 0, 0, 2082, 2083, 6, 277, 22, 0, 2083, 573, 1, 0, 0, 0, 2084, 2085, 3, 228, 105, 0, 2085, 2086, 1, 0, 0, 0, 2086, 2087, 6, 278, 21, 0, 2087, 575, 1, 0, 0, 0, 2088, 2089, 3, 252, 117, 0, 2089, 2090, 1, 0, 0, 0, 2090, 2091, 6, 279, 33, 0, 2091, 577, 1, 0, 0, 0, 2092, 2093, 3, 292, 137, 0, 2093, 2094, 1, 0, 0, 0, 2094, 2095, 6, 280, 34, 0, 2095, 579, 1, 0, 0, 0, 2096, 2097, 3, 288, 135, 0, 2097, 2098, 1, 0, 0, 0, 2098, 2099, 6, 281, 35, 0, 2099, 581, 1, 0, 0, 0, 2100, 2101, 3, 294, 138, 0, 2101, 2102, 1, 0, 0, 0, 2102, 2103, 6, 282, 36, 0, 2103, 583, 1, 0, 0, 0, 2104, 2105, 3, 296, 139, 0, 2105, 2106, 1, 0, 0, 0, 2106, 2107, 6, 283, 23, 0, 2107, 585, 1, 0, 0, 0, 2108, 2109, 3, 298, 140, 0, 2109, 2110, 1, 0, 0, 0, 2110, 2111, 6, 284, 24, 0, 2111, 587, 1, 0, 0, 0, 2112, 2113, 3, 512, 247, 0, 2113, 2114, 1, 0, 0, 0, 2114, 2115, 6, 285, 32, 0, 2115, 589, 1, 0, 0, 0, 2116, 2117, 3, 18, 0, 0, 2117, 2118, 1, 0, 0, 0, 2118, 2119, 6, 286, 0, 0, 2119, 591, 1, 0, 0, 0, 2120, 2121, 3, 20, 1, 0, 2121, 2122, 1, 0, 0, 0, 2122, 2123, 6, 287, 0, 0, 2123, 593, 1, 0, 0, 0, 2124, 2125, 3, 22, 2, 0, 2125, 2126, 1, 0, 0, 0, 2126, 2127, 6, 288, 0, 0, 2127, 595, 1, 0, 0, 0, 2128, 2129, 3, 182, 82, 0, 2129, 2130, 1, 0, 0, 0, 2130, 2131, 6, 289, 16, 0, 2131, 2132, 6, 289, 17, 0, 2132, 597, 1, 0, 0, 0, 2133, 2134, 7, 10, 0, 0, 2134, 2135, 7, 5, 0, 0, 2135, 2136, 7, 21, 0, 0, 2136, 2137, 7, 9, 0, 0, 2137, 599, 1, 0, 0, 0, 2138, 2139, 3, 18, 0, 0, 2139, 2140, 1, 0, 0, 0, 2140, 2141, 6, 291, 0, 0, 2141, 601, 1, 0, 0, 0, 2142, 2143, 3, 20, 1, 0, 2143, 2144, 1, 0, 0, 0, 2144, 2145, 6, 292, 0, 0, 2145, 603, 1, 0, 0, 0, 2146, 2147, 3, 22, 2, 0, 2147, 2148, 1, 0, 0, 0, 2148, 2149, 6, 293, 0, 0, 2149, 605, 1, 0, 0, 0, 70, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 612, 616, 619, 628, 630, 641, 930, 1015, 1019, 1024, 1156, 1161, 1170, 1177, 1182, 1184, 1195, 1203, 1206, 1208, 1213, 1218, 1224, 1231, 1236, 1242, 1245, 1253, 1257, 1398, 1403, 1410, 1412, 1417, 1422, 1429, 1431, 1457, 1462, 1467, 1469, 1475, 1531, 1536, 1932, 1936, 1941, 1946, 1951, 1953, 1957, 1959, 51, 0, 1, 0, 5, 1, 0, 5, 2, 0, 5, 4, 0, 5, 5, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 5, 9, 0, 5, 10, 0, 5, 11, 0, 5, 13, 0, 5, 14, 0, 5, 15, 0, 5, 16, 0, 5, 17, 0, 7, 50, 0, 4, 0, 0, 7, 99, 0, 7, 73, 0, 7, 141, 0, 7, 63, 0, 7, 61, 0, 7, 96, 0, 7, 97, 0, 7, 101, 0, 7, 100, 0, 5, 3, 0, 7, 78, 0, 7, 40, 0, 7, 51, 0, 7, 56, 0, 7, 137, 0, 7, 75, 0, 7, 94, 0, 7, 93, 0, 7, 95, 0, 7, 98, 0, 5, 0, 0, 7, 17, 0, 7, 59, 0, 7, 58, 0, 7, 106, 0, 7, 57, 0, 5, 12, 0, 7, 77, 0, 7, 64, 0, 7, 71, 0, 7, 60, 0, 7, 53, 0, 7, 52, 0] \ No newline at end of file +[4, 0, 151, 2150, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 2, 199, 7, 199, 2, 200, 7, 200, 2, 201, 7, 201, 2, 202, 7, 202, 2, 203, 7, 203, 2, 204, 7, 204, 2, 205, 7, 205, 2, 206, 7, 206, 2, 207, 7, 207, 2, 208, 7, 208, 2, 209, 7, 209, 2, 210, 7, 210, 2, 211, 7, 211, 2, 212, 7, 212, 2, 213, 7, 213, 2, 214, 7, 214, 2, 215, 7, 215, 2, 216, 7, 216, 2, 217, 7, 217, 2, 218, 7, 218, 2, 219, 7, 219, 2, 220, 7, 220, 2, 221, 7, 221, 2, 222, 7, 222, 2, 223, 7, 223, 2, 224, 7, 224, 2, 225, 7, 225, 2, 226, 7, 226, 2, 227, 7, 227, 2, 228, 7, 228, 2, 229, 7, 229, 2, 230, 7, 230, 2, 231, 7, 231, 2, 232, 7, 232, 2, 233, 7, 233, 2, 234, 7, 234, 2, 235, 7, 235, 2, 236, 7, 236, 2, 237, 7, 237, 2, 238, 7, 238, 2, 239, 7, 239, 2, 240, 7, 240, 2, 241, 7, 241, 2, 242, 7, 242, 2, 243, 7, 243, 2, 244, 7, 244, 2, 245, 7, 245, 2, 246, 7, 246, 2, 247, 7, 247, 2, 248, 7, 248, 2, 249, 7, 249, 2, 250, 7, 250, 2, 251, 7, 251, 2, 252, 7, 252, 2, 253, 7, 253, 2, 254, 7, 254, 2, 255, 7, 255, 2, 256, 7, 256, 2, 257, 7, 257, 2, 258, 7, 258, 2, 259, 7, 259, 2, 260, 7, 260, 2, 261, 7, 261, 2, 262, 7, 262, 2, 263, 7, 263, 2, 264, 7, 264, 2, 265, 7, 265, 2, 266, 7, 266, 2, 267, 7, 267, 2, 268, 7, 268, 2, 269, 7, 269, 2, 270, 7, 270, 2, 271, 7, 271, 2, 272, 7, 272, 2, 273, 7, 273, 2, 274, 7, 274, 2, 275, 7, 275, 2, 276, 7, 276, 2, 277, 7, 277, 2, 278, 7, 278, 2, 279, 7, 279, 2, 280, 7, 280, 2, 281, 7, 281, 2, 282, 7, 282, 2, 283, 7, 283, 2, 284, 7, 284, 2, 285, 7, 285, 2, 286, 7, 286, 2, 287, 7, 287, 2, 288, 7, 288, 2, 289, 7, 289, 2, 290, 7, 290, 2, 291, 7, 291, 2, 292, 7, 292, 2, 293, 7, 293, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 611, 8, 0, 10, 0, 12, 0, 614, 9, 0, 1, 0, 3, 0, 617, 8, 0, 1, 0, 3, 0, 620, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 629, 8, 1, 10, 1, 12, 1, 632, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 640, 8, 2, 11, 2, 12, 2, 641, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 4, 35, 929, 8, 35, 11, 35, 12, 35, 930, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 4, 54, 1014, 8, 54, 11, 54, 12, 54, 1015, 1, 54, 1, 54, 3, 54, 1020, 8, 54, 1, 54, 4, 54, 1023, 8, 54, 11, 54, 12, 54, 1024, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 87, 1, 87, 3, 87, 1157, 8, 87, 1, 87, 4, 87, 1160, 8, 87, 11, 87, 12, 87, 1161, 1, 88, 1, 88, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 3, 90, 1171, 8, 90, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 3, 92, 1178, 8, 92, 1, 93, 1, 93, 1, 93, 5, 93, 1183, 8, 93, 10, 93, 12, 93, 1186, 9, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 5, 93, 1194, 8, 93, 10, 93, 12, 93, 1197, 9, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 3, 93, 1204, 8, 93, 1, 93, 3, 93, 1207, 8, 93, 3, 93, 1209, 8, 93, 1, 94, 4, 94, 1212, 8, 94, 11, 94, 12, 94, 1213, 1, 95, 4, 95, 1217, 8, 95, 11, 95, 12, 95, 1218, 1, 95, 1, 95, 5, 95, 1223, 8, 95, 10, 95, 12, 95, 1226, 9, 95, 1, 95, 1, 95, 4, 95, 1230, 8, 95, 11, 95, 12, 95, 1231, 1, 95, 4, 95, 1235, 8, 95, 11, 95, 12, 95, 1236, 1, 95, 1, 95, 5, 95, 1241, 8, 95, 10, 95, 12, 95, 1244, 9, 95, 3, 95, 1246, 8, 95, 1, 95, 1, 95, 1, 95, 1, 95, 4, 95, 1252, 8, 95, 11, 95, 12, 95, 1253, 1, 95, 1, 95, 3, 95, 1258, 8, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 102, 1, 102, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 129, 1, 129, 1, 130, 1, 130, 1, 131, 1, 131, 1, 132, 1, 132, 1, 133, 1, 133, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 3, 137, 1399, 8, 137, 1, 137, 5, 137, 1402, 8, 137, 10, 137, 12, 137, 1405, 9, 137, 1, 137, 1, 137, 4, 137, 1409, 8, 137, 11, 137, 12, 137, 1410, 3, 137, 1413, 8, 137, 1, 138, 1, 138, 1, 138, 3, 138, 1418, 8, 138, 1, 138, 5, 138, 1421, 8, 138, 10, 138, 12, 138, 1424, 9, 138, 1, 138, 1, 138, 4, 138, 1428, 8, 138, 11, 138, 12, 138, 1429, 3, 138, 1432, 8, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 5, 143, 1456, 8, 143, 10, 143, 12, 143, 1459, 9, 143, 1, 143, 1, 143, 3, 143, 1463, 8, 143, 1, 143, 4, 143, 1466, 8, 143, 11, 143, 12, 143, 1467, 3, 143, 1470, 8, 143, 1, 144, 1, 144, 4, 144, 1474, 8, 144, 11, 144, 12, 144, 1475, 1, 144, 1, 144, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 3, 156, 1532, 8, 156, 1, 157, 4, 157, 1535, 8, 157, 11, 157, 12, 157, 1536, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 199, 1, 199, 1, 199, 1, 199, 1, 200, 1, 200, 1, 200, 1, 200, 1, 201, 1, 201, 1, 201, 1, 201, 1, 202, 1, 202, 1, 202, 1, 202, 1, 202, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 203, 1, 204, 1, 204, 1, 204, 1, 204, 1, 205, 1, 205, 1, 205, 1, 205, 1, 206, 1, 206, 1, 206, 1, 206, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 208, 1, 208, 1, 208, 1, 208, 1, 209, 1, 209, 1, 209, 1, 209, 1, 210, 1, 210, 1, 210, 1, 210, 1, 211, 1, 211, 1, 211, 1, 211, 1, 212, 1, 212, 1, 212, 1, 212, 1, 213, 1, 213, 1, 213, 1, 213, 1, 213, 1, 213, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 214, 1, 215, 1, 215, 1, 215, 1, 215, 1, 216, 1, 216, 1, 216, 1, 216, 1, 217, 1, 217, 1, 217, 1, 217, 1, 218, 1, 218, 1, 218, 1, 218, 1, 219, 1, 219, 1, 219, 1, 219, 1, 220, 1, 220, 1, 220, 1, 220, 1, 221, 1, 221, 1, 221, 1, 221, 1, 221, 1, 222, 1, 222, 1, 222, 1, 222, 1, 222, 1, 222, 1, 223, 1, 223, 1, 223, 1, 223, 1, 224, 1, 224, 1, 224, 1, 224, 1, 225, 1, 225, 1, 225, 1, 225, 1, 226, 1, 226, 1, 226, 1, 226, 1, 227, 1, 227, 1, 227, 1, 227, 1, 228, 1, 228, 1, 228, 1, 228, 1, 229, 1, 229, 1, 229, 1, 229, 1, 230, 1, 230, 1, 230, 1, 230, 1, 231, 1, 231, 1, 231, 1, 231, 1, 232, 1, 232, 1, 232, 1, 232, 1, 233, 1, 233, 1, 233, 1, 233, 1, 234, 1, 234, 1, 234, 1, 234, 1, 235, 1, 235, 1, 235, 1, 235, 1, 235, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 236, 1, 237, 1, 237, 1, 237, 1, 237, 1, 238, 1, 238, 1, 238, 1, 238, 1, 239, 1, 239, 1, 239, 1, 239, 1, 240, 1, 240, 1, 240, 1, 240, 1, 241, 1, 241, 1, 241, 1, 241, 1, 242, 1, 242, 1, 242, 1, 242, 1, 243, 1, 243, 1, 243, 1, 243, 1, 244, 1, 244, 1, 244, 1, 244, 1, 245, 1, 245, 1, 245, 1, 245, 3, 245, 1933, 8, 245, 1, 246, 1, 246, 3, 246, 1937, 8, 246, 1, 246, 5, 246, 1940, 8, 246, 10, 246, 12, 246, 1943, 9, 246, 1, 246, 1, 246, 3, 246, 1947, 8, 246, 1, 246, 4, 246, 1950, 8, 246, 11, 246, 12, 246, 1951, 3, 246, 1954, 8, 246, 1, 247, 1, 247, 4, 247, 1958, 8, 247, 11, 247, 12, 247, 1959, 1, 248, 1, 248, 1, 248, 1, 248, 1, 249, 1, 249, 1, 249, 1, 249, 1, 250, 1, 250, 1, 250, 1, 250, 1, 251, 1, 251, 1, 251, 1, 251, 1, 251, 1, 252, 1, 252, 1, 252, 1, 252, 1, 252, 1, 252, 1, 253, 1, 253, 1, 253, 1, 253, 1, 254, 1, 254, 1, 254, 1, 254, 1, 255, 1, 255, 1, 255, 1, 255, 1, 256, 1, 256, 1, 256, 1, 256, 1, 257, 1, 257, 1, 257, 1, 257, 1, 258, 1, 258, 1, 258, 1, 258, 1, 259, 1, 259, 1, 259, 1, 259, 1, 260, 1, 260, 1, 260, 1, 260, 1, 261, 1, 261, 1, 261, 1, 261, 1, 262, 1, 262, 1, 262, 1, 263, 1, 263, 1, 263, 1, 263, 1, 264, 1, 264, 1, 264, 1, 264, 1, 265, 1, 265, 1, 265, 1, 265, 1, 266, 1, 266, 1, 266, 1, 266, 1, 267, 1, 267, 1, 267, 1, 267, 1, 268, 1, 268, 1, 268, 1, 268, 1, 269, 1, 269, 1, 269, 1, 269, 1, 270, 1, 270, 1, 270, 1, 270, 1, 270, 1, 271, 1, 271, 1, 271, 1, 271, 1, 272, 1, 272, 1, 272, 1, 272, 1, 273, 1, 273, 1, 273, 1, 273, 1, 274, 1, 274, 1, 274, 1, 274, 1, 275, 1, 275, 1, 275, 1, 275, 1, 276, 1, 276, 1, 276, 1, 276, 1, 277, 1, 277, 1, 277, 1, 277, 1, 278, 1, 278, 1, 278, 1, 278, 1, 279, 1, 279, 1, 279, 1, 279, 1, 280, 1, 280, 1, 280, 1, 280, 1, 281, 1, 281, 1, 281, 1, 281, 1, 282, 1, 282, 1, 282, 1, 282, 1, 283, 1, 283, 1, 283, 1, 283, 1, 284, 1, 284, 1, 284, 1, 284, 1, 285, 1, 285, 1, 285, 1, 285, 1, 286, 1, 286, 1, 286, 1, 286, 1, 287, 1, 287, 1, 287, 1, 287, 1, 288, 1, 288, 1, 288, 1, 288, 1, 289, 1, 289, 1, 289, 1, 289, 1, 289, 1, 290, 1, 290, 1, 290, 1, 290, 1, 290, 1, 291, 1, 291, 1, 291, 1, 291, 1, 292, 1, 292, 1, 292, 1, 292, 1, 293, 1, 293, 1, 293, 1, 293, 2, 630, 1195, 0, 294, 18, 1, 20, 2, 22, 3, 24, 4, 26, 5, 28, 6, 30, 7, 32, 8, 34, 9, 36, 10, 38, 11, 40, 12, 42, 13, 44, 14, 46, 15, 48, 16, 50, 17, 52, 18, 54, 19, 56, 20, 58, 21, 60, 22, 62, 23, 64, 24, 66, 25, 68, 26, 70, 27, 72, 28, 74, 29, 76, 30, 78, 31, 80, 32, 82, 33, 84, 34, 86, 35, 88, 36, 90, 0, 92, 0, 94, 0, 96, 0, 98, 0, 100, 0, 102, 0, 104, 0, 106, 0, 108, 0, 110, 37, 112, 38, 114, 39, 116, 0, 118, 0, 120, 0, 122, 0, 124, 0, 126, 40, 128, 0, 130, 0, 132, 41, 134, 42, 136, 43, 138, 0, 140, 0, 142, 0, 144, 0, 146, 0, 148, 0, 150, 0, 152, 0, 154, 0, 156, 0, 158, 0, 160, 0, 162, 0, 164, 0, 166, 44, 168, 45, 170, 46, 172, 0, 174, 0, 176, 47, 178, 48, 180, 49, 182, 50, 184, 0, 186, 0, 188, 0, 190, 0, 192, 0, 194, 0, 196, 0, 198, 0, 200, 0, 202, 0, 204, 51, 206, 52, 208, 53, 210, 54, 212, 55, 214, 56, 216, 57, 218, 58, 220, 59, 222, 60, 224, 61, 226, 62, 228, 63, 230, 64, 232, 65, 234, 66, 236, 67, 238, 68, 240, 69, 242, 70, 244, 71, 246, 72, 248, 73, 250, 74, 252, 75, 254, 76, 256, 77, 258, 78, 260, 79, 262, 80, 264, 81, 266, 82, 268, 83, 270, 84, 272, 85, 274, 86, 276, 87, 278, 88, 280, 89, 282, 90, 284, 91, 286, 92, 288, 93, 290, 0, 292, 94, 294, 95, 296, 96, 298, 97, 300, 98, 302, 99, 304, 100, 306, 0, 308, 101, 310, 102, 312, 103, 314, 104, 316, 0, 318, 0, 320, 0, 322, 0, 324, 0, 326, 105, 328, 0, 330, 0, 332, 106, 334, 0, 336, 0, 338, 107, 340, 108, 342, 109, 344, 0, 346, 0, 348, 0, 350, 110, 352, 111, 354, 112, 356, 0, 358, 0, 360, 113, 362, 114, 364, 115, 366, 0, 368, 0, 370, 0, 372, 0, 374, 0, 376, 0, 378, 0, 380, 0, 382, 0, 384, 0, 386, 116, 388, 117, 390, 118, 392, 119, 394, 120, 396, 121, 398, 122, 400, 0, 402, 123, 404, 0, 406, 0, 408, 124, 410, 0, 412, 0, 414, 0, 416, 125, 418, 126, 420, 127, 422, 0, 424, 0, 426, 0, 428, 0, 430, 0, 432, 0, 434, 0, 436, 0, 438, 128, 440, 129, 442, 130, 444, 0, 446, 0, 448, 0, 450, 0, 452, 0, 454, 131, 456, 132, 458, 133, 460, 0, 462, 0, 464, 0, 466, 0, 468, 0, 470, 0, 472, 0, 474, 0, 476, 0, 478, 0, 480, 0, 482, 134, 484, 135, 486, 136, 488, 0, 490, 0, 492, 0, 494, 0, 496, 0, 498, 0, 500, 0, 502, 0, 504, 0, 506, 0, 508, 0, 510, 0, 512, 137, 514, 138, 516, 139, 518, 140, 520, 0, 522, 0, 524, 0, 526, 0, 528, 0, 530, 0, 532, 0, 534, 0, 536, 0, 538, 0, 540, 0, 542, 141, 544, 0, 546, 142, 548, 143, 550, 144, 552, 0, 554, 0, 556, 0, 558, 0, 560, 0, 562, 0, 564, 0, 566, 0, 568, 0, 570, 0, 572, 0, 574, 0, 576, 0, 578, 0, 580, 0, 582, 0, 584, 0, 586, 0, 588, 0, 590, 145, 592, 146, 594, 147, 596, 0, 598, 148, 600, 149, 602, 150, 604, 151, 18, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 36, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 2, 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 65, 65, 97, 97, 2, 0, 78, 78, 110, 110, 2, 0, 71, 71, 103, 103, 2, 0, 69, 69, 101, 101, 2, 0, 80, 80, 112, 112, 2, 0, 79, 79, 111, 111, 2, 0, 73, 73, 105, 105, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 88, 88, 120, 120, 2, 0, 76, 76, 108, 108, 2, 0, 77, 77, 109, 109, 2, 0, 68, 68, 100, 100, 2, 0, 83, 83, 115, 115, 2, 0, 86, 86, 118, 118, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 70, 70, 102, 102, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 12, 0, 9, 10, 13, 13, 32, 32, 34, 35, 40, 41, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 12, 0, 9, 10, 13, 13, 32, 32, 34, 34, 40, 41, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 2, 0, 74, 74, 106, 106, 2174, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 66, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 70, 1, 0, 0, 0, 0, 72, 1, 0, 0, 0, 0, 74, 1, 0, 0, 0, 0, 76, 1, 0, 0, 0, 0, 78, 1, 0, 0, 0, 0, 80, 1, 0, 0, 0, 0, 82, 1, 0, 0, 0, 0, 84, 1, 0, 0, 0, 0, 86, 1, 0, 0, 0, 0, 88, 1, 0, 0, 0, 1, 90, 1, 0, 0, 0, 1, 92, 1, 0, 0, 0, 1, 94, 1, 0, 0, 0, 1, 96, 1, 0, 0, 0, 1, 98, 1, 0, 0, 0, 1, 100, 1, 0, 0, 0, 1, 102, 1, 0, 0, 0, 1, 104, 1, 0, 0, 0, 1, 106, 1, 0, 0, 0, 1, 108, 1, 0, 0, 0, 1, 110, 1, 0, 0, 0, 1, 112, 1, 0, 0, 0, 1, 114, 1, 0, 0, 0, 2, 116, 1, 0, 0, 0, 2, 118, 1, 0, 0, 0, 2, 120, 1, 0, 0, 0, 2, 122, 1, 0, 0, 0, 2, 126, 1, 0, 0, 0, 2, 128, 1, 0, 0, 0, 2, 130, 1, 0, 0, 0, 2, 132, 1, 0, 0, 0, 2, 134, 1, 0, 0, 0, 2, 136, 1, 0, 0, 0, 3, 138, 1, 0, 0, 0, 3, 140, 1, 0, 0, 0, 3, 142, 1, 0, 0, 0, 3, 144, 1, 0, 0, 0, 3, 146, 1, 0, 0, 0, 3, 148, 1, 0, 0, 0, 3, 150, 1, 0, 0, 0, 3, 152, 1, 0, 0, 0, 3, 154, 1, 0, 0, 0, 3, 156, 1, 0, 0, 0, 3, 158, 1, 0, 0, 0, 3, 160, 1, 0, 0, 0, 3, 162, 1, 0, 0, 0, 3, 164, 1, 0, 0, 0, 3, 166, 1, 0, 0, 0, 3, 168, 1, 0, 0, 0, 3, 170, 1, 0, 0, 0, 4, 172, 1, 0, 0, 0, 4, 174, 1, 0, 0, 0, 4, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 4, 180, 1, 0, 0, 0, 5, 182, 1, 0, 0, 0, 5, 204, 1, 0, 0, 0, 5, 206, 1, 0, 0, 0, 5, 208, 1, 0, 0, 0, 5, 210, 1, 0, 0, 0, 5, 212, 1, 0, 0, 0, 5, 214, 1, 0, 0, 0, 5, 216, 1, 0, 0, 0, 5, 218, 1, 0, 0, 0, 5, 220, 1, 0, 0, 0, 5, 222, 1, 0, 0, 0, 5, 224, 1, 0, 0, 0, 5, 226, 1, 0, 0, 0, 5, 228, 1, 0, 0, 0, 5, 230, 1, 0, 0, 0, 5, 232, 1, 0, 0, 0, 5, 234, 1, 0, 0, 0, 5, 236, 1, 0, 0, 0, 5, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 5, 246, 1, 0, 0, 0, 5, 248, 1, 0, 0, 0, 5, 250, 1, 0, 0, 0, 5, 252, 1, 0, 0, 0, 5, 254, 1, 0, 0, 0, 5, 256, 1, 0, 0, 0, 5, 258, 1, 0, 0, 0, 5, 260, 1, 0, 0, 0, 5, 262, 1, 0, 0, 0, 5, 264, 1, 0, 0, 0, 5, 266, 1, 0, 0, 0, 5, 268, 1, 0, 0, 0, 5, 270, 1, 0, 0, 0, 5, 272, 1, 0, 0, 0, 5, 274, 1, 0, 0, 0, 5, 276, 1, 0, 0, 0, 5, 278, 1, 0, 0, 0, 5, 280, 1, 0, 0, 0, 5, 282, 1, 0, 0, 0, 5, 284, 1, 0, 0, 0, 5, 286, 1, 0, 0, 0, 5, 288, 1, 0, 0, 0, 5, 290, 1, 0, 0, 0, 5, 292, 1, 0, 0, 0, 5, 294, 1, 0, 0, 0, 5, 296, 1, 0, 0, 0, 5, 298, 1, 0, 0, 0, 5, 300, 1, 0, 0, 0, 5, 302, 1, 0, 0, 0, 5, 304, 1, 0, 0, 0, 5, 308, 1, 0, 0, 0, 5, 310, 1, 0, 0, 0, 5, 312, 1, 0, 0, 0, 5, 314, 1, 0, 0, 0, 6, 316, 1, 0, 0, 0, 6, 318, 1, 0, 0, 0, 6, 320, 1, 0, 0, 0, 6, 322, 1, 0, 0, 0, 6, 324, 1, 0, 0, 0, 6, 326, 1, 0, 0, 0, 6, 328, 1, 0, 0, 0, 6, 332, 1, 0, 0, 0, 6, 334, 1, 0, 0, 0, 6, 336, 1, 0, 0, 0, 6, 338, 1, 0, 0, 0, 6, 340, 1, 0, 0, 0, 6, 342, 1, 0, 0, 0, 7, 344, 1, 0, 0, 0, 7, 346, 1, 0, 0, 0, 7, 348, 1, 0, 0, 0, 7, 350, 1, 0, 0, 0, 7, 352, 1, 0, 0, 0, 7, 354, 1, 0, 0, 0, 8, 356, 1, 0, 0, 0, 8, 358, 1, 0, 0, 0, 8, 360, 1, 0, 0, 0, 8, 362, 1, 0, 0, 0, 8, 364, 1, 0, 0, 0, 8, 366, 1, 0, 0, 0, 8, 368, 1, 0, 0, 0, 8, 370, 1, 0, 0, 0, 8, 372, 1, 0, 0, 0, 8, 374, 1, 0, 0, 0, 8, 376, 1, 0, 0, 0, 8, 378, 1, 0, 0, 0, 8, 380, 1, 0, 0, 0, 8, 382, 1, 0, 0, 0, 8, 384, 1, 0, 0, 0, 8, 386, 1, 0, 0, 0, 8, 388, 1, 0, 0, 0, 8, 390, 1, 0, 0, 0, 9, 392, 1, 0, 0, 0, 9, 394, 1, 0, 0, 0, 9, 396, 1, 0, 0, 0, 9, 398, 1, 0, 0, 0, 10, 400, 1, 0, 0, 0, 10, 402, 1, 0, 0, 0, 10, 404, 1, 0, 0, 0, 10, 406, 1, 0, 0, 0, 10, 408, 1, 0, 0, 0, 10, 410, 1, 0, 0, 0, 10, 412, 1, 0, 0, 0, 10, 414, 1, 0, 0, 0, 10, 416, 1, 0, 0, 0, 10, 418, 1, 0, 0, 0, 10, 420, 1, 0, 0, 0, 11, 422, 1, 0, 0, 0, 11, 424, 1, 0, 0, 0, 11, 426, 1, 0, 0, 0, 11, 428, 1, 0, 0, 0, 11, 430, 1, 0, 0, 0, 11, 432, 1, 0, 0, 0, 11, 434, 1, 0, 0, 0, 11, 436, 1, 0, 0, 0, 11, 438, 1, 0, 0, 0, 11, 440, 1, 0, 0, 0, 11, 442, 1, 0, 0, 0, 12, 444, 1, 0, 0, 0, 12, 446, 1, 0, 0, 0, 12, 448, 1, 0, 0, 0, 12, 450, 1, 0, 0, 0, 12, 452, 1, 0, 0, 0, 12, 454, 1, 0, 0, 0, 12, 456, 1, 0, 0, 0, 12, 458, 1, 0, 0, 0, 13, 460, 1, 0, 0, 0, 13, 462, 1, 0, 0, 0, 13, 464, 1, 0, 0, 0, 13, 466, 1, 0, 0, 0, 13, 468, 1, 0, 0, 0, 13, 470, 1, 0, 0, 0, 13, 472, 1, 0, 0, 0, 13, 474, 1, 0, 0, 0, 13, 476, 1, 0, 0, 0, 13, 478, 1, 0, 0, 0, 13, 480, 1, 0, 0, 0, 13, 482, 1, 0, 0, 0, 13, 484, 1, 0, 0, 0, 13, 486, 1, 0, 0, 0, 14, 488, 1, 0, 0, 0, 14, 490, 1, 0, 0, 0, 14, 492, 1, 0, 0, 0, 14, 494, 1, 0, 0, 0, 14, 496, 1, 0, 0, 0, 14, 498, 1, 0, 0, 0, 14, 500, 1, 0, 0, 0, 14, 502, 1, 0, 0, 0, 14, 504, 1, 0, 0, 0, 14, 506, 1, 0, 0, 0, 14, 512, 1, 0, 0, 0, 14, 514, 1, 0, 0, 0, 14, 516, 1, 0, 0, 0, 14, 518, 1, 0, 0, 0, 15, 520, 1, 0, 0, 0, 15, 522, 1, 0, 0, 0, 15, 524, 1, 0, 0, 0, 15, 526, 1, 0, 0, 0, 15, 528, 1, 0, 0, 0, 15, 530, 1, 0, 0, 0, 15, 532, 1, 0, 0, 0, 15, 534, 1, 0, 0, 0, 15, 536, 1, 0, 0, 0, 15, 538, 1, 0, 0, 0, 15, 540, 1, 0, 0, 0, 15, 542, 1, 0, 0, 0, 15, 544, 1, 0, 0, 0, 15, 546, 1, 0, 0, 0, 15, 548, 1, 0, 0, 0, 15, 550, 1, 0, 0, 0, 16, 552, 1, 0, 0, 0, 16, 554, 1, 0, 0, 0, 16, 556, 1, 0, 0, 0, 16, 558, 1, 0, 0, 0, 16, 560, 1, 0, 0, 0, 16, 562, 1, 0, 0, 0, 16, 564, 1, 0, 0, 0, 16, 566, 1, 0, 0, 0, 16, 568, 1, 0, 0, 0, 16, 570, 1, 0, 0, 0, 16, 572, 1, 0, 0, 0, 16, 574, 1, 0, 0, 0, 16, 576, 1, 0, 0, 0, 16, 578, 1, 0, 0, 0, 16, 580, 1, 0, 0, 0, 16, 582, 1, 0, 0, 0, 16, 584, 1, 0, 0, 0, 16, 586, 1, 0, 0, 0, 16, 588, 1, 0, 0, 0, 16, 590, 1, 0, 0, 0, 16, 592, 1, 0, 0, 0, 16, 594, 1, 0, 0, 0, 17, 596, 1, 0, 0, 0, 17, 598, 1, 0, 0, 0, 17, 600, 1, 0, 0, 0, 17, 602, 1, 0, 0, 0, 17, 604, 1, 0, 0, 0, 18, 606, 1, 0, 0, 0, 20, 623, 1, 0, 0, 0, 22, 639, 1, 0, 0, 0, 24, 645, 1, 0, 0, 0, 26, 660, 1, 0, 0, 0, 28, 669, 1, 0, 0, 0, 30, 680, 1, 0, 0, 0, 32, 693, 1, 0, 0, 0, 34, 703, 1, 0, 0, 0, 36, 710, 1, 0, 0, 0, 38, 717, 1, 0, 0, 0, 40, 725, 1, 0, 0, 0, 42, 734, 1, 0, 0, 0, 44, 740, 1, 0, 0, 0, 46, 749, 1, 0, 0, 0, 48, 756, 1, 0, 0, 0, 50, 764, 1, 0, 0, 0, 52, 772, 1, 0, 0, 0, 54, 779, 1, 0, 0, 0, 56, 784, 1, 0, 0, 0, 58, 791, 1, 0, 0, 0, 60, 798, 1, 0, 0, 0, 62, 807, 1, 0, 0, 0, 64, 821, 1, 0, 0, 0, 66, 830, 1, 0, 0, 0, 68, 838, 1, 0, 0, 0, 70, 846, 1, 0, 0, 0, 72, 855, 1, 0, 0, 0, 74, 867, 1, 0, 0, 0, 76, 879, 1, 0, 0, 0, 78, 886, 1, 0, 0, 0, 80, 893, 1, 0, 0, 0, 82, 905, 1, 0, 0, 0, 84, 914, 1, 0, 0, 0, 86, 920, 1, 0, 0, 0, 88, 928, 1, 0, 0, 0, 90, 934, 1, 0, 0, 0, 92, 939, 1, 0, 0, 0, 94, 945, 1, 0, 0, 0, 96, 949, 1, 0, 0, 0, 98, 953, 1, 0, 0, 0, 100, 957, 1, 0, 0, 0, 102, 961, 1, 0, 0, 0, 104, 965, 1, 0, 0, 0, 106, 969, 1, 0, 0, 0, 108, 973, 1, 0, 0, 0, 110, 977, 1, 0, 0, 0, 112, 981, 1, 0, 0, 0, 114, 985, 1, 0, 0, 0, 116, 989, 1, 0, 0, 0, 118, 994, 1, 0, 0, 0, 120, 1000, 1, 0, 0, 0, 122, 1005, 1, 0, 0, 0, 124, 1010, 1, 0, 0, 0, 126, 1019, 1, 0, 0, 0, 128, 1026, 1, 0, 0, 0, 130, 1030, 1, 0, 0, 0, 132, 1034, 1, 0, 0, 0, 134, 1038, 1, 0, 0, 0, 136, 1042, 1, 0, 0, 0, 138, 1046, 1, 0, 0, 0, 140, 1052, 1, 0, 0, 0, 142, 1059, 1, 0, 0, 0, 144, 1063, 1, 0, 0, 0, 146, 1067, 1, 0, 0, 0, 148, 1071, 1, 0, 0, 0, 150, 1075, 1, 0, 0, 0, 152, 1079, 1, 0, 0, 0, 154, 1083, 1, 0, 0, 0, 156, 1087, 1, 0, 0, 0, 158, 1091, 1, 0, 0, 0, 160, 1095, 1, 0, 0, 0, 162, 1099, 1, 0, 0, 0, 164, 1103, 1, 0, 0, 0, 166, 1107, 1, 0, 0, 0, 168, 1111, 1, 0, 0, 0, 170, 1115, 1, 0, 0, 0, 172, 1119, 1, 0, 0, 0, 174, 1124, 1, 0, 0, 0, 176, 1129, 1, 0, 0, 0, 178, 1133, 1, 0, 0, 0, 180, 1137, 1, 0, 0, 0, 182, 1141, 1, 0, 0, 0, 184, 1145, 1, 0, 0, 0, 186, 1147, 1, 0, 0, 0, 188, 1149, 1, 0, 0, 0, 190, 1152, 1, 0, 0, 0, 192, 1154, 1, 0, 0, 0, 194, 1163, 1, 0, 0, 0, 196, 1165, 1, 0, 0, 0, 198, 1170, 1, 0, 0, 0, 200, 1172, 1, 0, 0, 0, 202, 1177, 1, 0, 0, 0, 204, 1208, 1, 0, 0, 0, 206, 1211, 1, 0, 0, 0, 208, 1257, 1, 0, 0, 0, 210, 1259, 1, 0, 0, 0, 212, 1263, 1, 0, 0, 0, 214, 1267, 1, 0, 0, 0, 216, 1269, 1, 0, 0, 0, 218, 1272, 1, 0, 0, 0, 220, 1275, 1, 0, 0, 0, 222, 1277, 1, 0, 0, 0, 224, 1279, 1, 0, 0, 0, 226, 1281, 1, 0, 0, 0, 228, 1286, 1, 0, 0, 0, 230, 1288, 1, 0, 0, 0, 232, 1294, 1, 0, 0, 0, 234, 1300, 1, 0, 0, 0, 236, 1303, 1, 0, 0, 0, 238, 1306, 1, 0, 0, 0, 240, 1311, 1, 0, 0, 0, 242, 1316, 1, 0, 0, 0, 244, 1320, 1, 0, 0, 0, 246, 1325, 1, 0, 0, 0, 248, 1331, 1, 0, 0, 0, 250, 1334, 1, 0, 0, 0, 252, 1337, 1, 0, 0, 0, 254, 1339, 1, 0, 0, 0, 256, 1345, 1, 0, 0, 0, 258, 1350, 1, 0, 0, 0, 260, 1355, 1, 0, 0, 0, 262, 1358, 1, 0, 0, 0, 264, 1361, 1, 0, 0, 0, 266, 1364, 1, 0, 0, 0, 268, 1366, 1, 0, 0, 0, 270, 1369, 1, 0, 0, 0, 272, 1371, 1, 0, 0, 0, 274, 1374, 1, 0, 0, 0, 276, 1376, 1, 0, 0, 0, 278, 1378, 1, 0, 0, 0, 280, 1380, 1, 0, 0, 0, 282, 1382, 1, 0, 0, 0, 284, 1384, 1, 0, 0, 0, 286, 1386, 1, 0, 0, 0, 288, 1388, 1, 0, 0, 0, 290, 1391, 1, 0, 0, 0, 292, 1412, 1, 0, 0, 0, 294, 1431, 1, 0, 0, 0, 296, 1433, 1, 0, 0, 0, 298, 1438, 1, 0, 0, 0, 300, 1443, 1, 0, 0, 0, 302, 1448, 1, 0, 0, 0, 304, 1469, 1, 0, 0, 0, 306, 1471, 1, 0, 0, 0, 308, 1479, 1, 0, 0, 0, 310, 1481, 1, 0, 0, 0, 312, 1485, 1, 0, 0, 0, 314, 1489, 1, 0, 0, 0, 316, 1493, 1, 0, 0, 0, 318, 1498, 1, 0, 0, 0, 320, 1502, 1, 0, 0, 0, 322, 1506, 1, 0, 0, 0, 324, 1510, 1, 0, 0, 0, 326, 1514, 1, 0, 0, 0, 328, 1523, 1, 0, 0, 0, 330, 1531, 1, 0, 0, 0, 332, 1534, 1, 0, 0, 0, 334, 1538, 1, 0, 0, 0, 336, 1542, 1, 0, 0, 0, 338, 1546, 1, 0, 0, 0, 340, 1550, 1, 0, 0, 0, 342, 1554, 1, 0, 0, 0, 344, 1558, 1, 0, 0, 0, 346, 1563, 1, 0, 0, 0, 348, 1569, 1, 0, 0, 0, 350, 1574, 1, 0, 0, 0, 352, 1578, 1, 0, 0, 0, 354, 1582, 1, 0, 0, 0, 356, 1586, 1, 0, 0, 0, 358, 1591, 1, 0, 0, 0, 360, 1597, 1, 0, 0, 0, 362, 1603, 1, 0, 0, 0, 364, 1609, 1, 0, 0, 0, 366, 1613, 1, 0, 0, 0, 368, 1619, 1, 0, 0, 0, 370, 1623, 1, 0, 0, 0, 372, 1627, 1, 0, 0, 0, 374, 1631, 1, 0, 0, 0, 376, 1635, 1, 0, 0, 0, 378, 1639, 1, 0, 0, 0, 380, 1643, 1, 0, 0, 0, 382, 1647, 1, 0, 0, 0, 384, 1651, 1, 0, 0, 0, 386, 1655, 1, 0, 0, 0, 388, 1659, 1, 0, 0, 0, 390, 1663, 1, 0, 0, 0, 392, 1667, 1, 0, 0, 0, 394, 1676, 1, 0, 0, 0, 396, 1680, 1, 0, 0, 0, 398, 1684, 1, 0, 0, 0, 400, 1688, 1, 0, 0, 0, 402, 1693, 1, 0, 0, 0, 404, 1698, 1, 0, 0, 0, 406, 1702, 1, 0, 0, 0, 408, 1708, 1, 0, 0, 0, 410, 1717, 1, 0, 0, 0, 412, 1721, 1, 0, 0, 0, 414, 1725, 1, 0, 0, 0, 416, 1729, 1, 0, 0, 0, 418, 1733, 1, 0, 0, 0, 420, 1737, 1, 0, 0, 0, 422, 1741, 1, 0, 0, 0, 424, 1746, 1, 0, 0, 0, 426, 1752, 1, 0, 0, 0, 428, 1756, 1, 0, 0, 0, 430, 1760, 1, 0, 0, 0, 432, 1764, 1, 0, 0, 0, 434, 1769, 1, 0, 0, 0, 436, 1773, 1, 0, 0, 0, 438, 1777, 1, 0, 0, 0, 440, 1781, 1, 0, 0, 0, 442, 1785, 1, 0, 0, 0, 444, 1789, 1, 0, 0, 0, 446, 1795, 1, 0, 0, 0, 448, 1802, 1, 0, 0, 0, 450, 1806, 1, 0, 0, 0, 452, 1810, 1, 0, 0, 0, 454, 1814, 1, 0, 0, 0, 456, 1818, 1, 0, 0, 0, 458, 1822, 1, 0, 0, 0, 460, 1826, 1, 0, 0, 0, 462, 1831, 1, 0, 0, 0, 464, 1837, 1, 0, 0, 0, 466, 1841, 1, 0, 0, 0, 468, 1845, 1, 0, 0, 0, 470, 1849, 1, 0, 0, 0, 472, 1853, 1, 0, 0, 0, 474, 1857, 1, 0, 0, 0, 476, 1861, 1, 0, 0, 0, 478, 1865, 1, 0, 0, 0, 480, 1869, 1, 0, 0, 0, 482, 1873, 1, 0, 0, 0, 484, 1877, 1, 0, 0, 0, 486, 1881, 1, 0, 0, 0, 488, 1885, 1, 0, 0, 0, 490, 1890, 1, 0, 0, 0, 492, 1896, 1, 0, 0, 0, 494, 1900, 1, 0, 0, 0, 496, 1904, 1, 0, 0, 0, 498, 1908, 1, 0, 0, 0, 500, 1912, 1, 0, 0, 0, 502, 1916, 1, 0, 0, 0, 504, 1920, 1, 0, 0, 0, 506, 1924, 1, 0, 0, 0, 508, 1932, 1, 0, 0, 0, 510, 1953, 1, 0, 0, 0, 512, 1957, 1, 0, 0, 0, 514, 1961, 1, 0, 0, 0, 516, 1965, 1, 0, 0, 0, 518, 1969, 1, 0, 0, 0, 520, 1973, 1, 0, 0, 0, 522, 1978, 1, 0, 0, 0, 524, 1984, 1, 0, 0, 0, 526, 1988, 1, 0, 0, 0, 528, 1992, 1, 0, 0, 0, 530, 1996, 1, 0, 0, 0, 532, 2000, 1, 0, 0, 0, 534, 2004, 1, 0, 0, 0, 536, 2008, 1, 0, 0, 0, 538, 2012, 1, 0, 0, 0, 540, 2016, 1, 0, 0, 0, 542, 2020, 1, 0, 0, 0, 544, 2023, 1, 0, 0, 0, 546, 2027, 1, 0, 0, 0, 548, 2031, 1, 0, 0, 0, 550, 2035, 1, 0, 0, 0, 552, 2039, 1, 0, 0, 0, 554, 2043, 1, 0, 0, 0, 556, 2047, 1, 0, 0, 0, 558, 2051, 1, 0, 0, 0, 560, 2056, 1, 0, 0, 0, 562, 2060, 1, 0, 0, 0, 564, 2064, 1, 0, 0, 0, 566, 2068, 1, 0, 0, 0, 568, 2072, 1, 0, 0, 0, 570, 2076, 1, 0, 0, 0, 572, 2080, 1, 0, 0, 0, 574, 2084, 1, 0, 0, 0, 576, 2088, 1, 0, 0, 0, 578, 2092, 1, 0, 0, 0, 580, 2096, 1, 0, 0, 0, 582, 2100, 1, 0, 0, 0, 584, 2104, 1, 0, 0, 0, 586, 2108, 1, 0, 0, 0, 588, 2112, 1, 0, 0, 0, 590, 2116, 1, 0, 0, 0, 592, 2120, 1, 0, 0, 0, 594, 2124, 1, 0, 0, 0, 596, 2128, 1, 0, 0, 0, 598, 2133, 1, 0, 0, 0, 600, 2138, 1, 0, 0, 0, 602, 2142, 1, 0, 0, 0, 604, 2146, 1, 0, 0, 0, 606, 607, 5, 47, 0, 0, 607, 608, 5, 47, 0, 0, 608, 612, 1, 0, 0, 0, 609, 611, 8, 0, 0, 0, 610, 609, 1, 0, 0, 0, 611, 614, 1, 0, 0, 0, 612, 610, 1, 0, 0, 0, 612, 613, 1, 0, 0, 0, 613, 616, 1, 0, 0, 0, 614, 612, 1, 0, 0, 0, 615, 617, 5, 13, 0, 0, 616, 615, 1, 0, 0, 0, 616, 617, 1, 0, 0, 0, 617, 619, 1, 0, 0, 0, 618, 620, 5, 10, 0, 0, 619, 618, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 622, 6, 0, 0, 0, 622, 19, 1, 0, 0, 0, 623, 624, 5, 47, 0, 0, 624, 625, 5, 42, 0, 0, 625, 630, 1, 0, 0, 0, 626, 629, 3, 20, 1, 0, 627, 629, 9, 0, 0, 0, 628, 626, 1, 0, 0, 0, 628, 627, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 631, 633, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 5, 42, 0, 0, 634, 635, 5, 47, 0, 0, 635, 636, 1, 0, 0, 0, 636, 637, 6, 1, 0, 0, 637, 21, 1, 0, 0, 0, 638, 640, 7, 1, 0, 0, 639, 638, 1, 0, 0, 0, 640, 641, 1, 0, 0, 0, 641, 639, 1, 0, 0, 0, 641, 642, 1, 0, 0, 0, 642, 643, 1, 0, 0, 0, 643, 644, 6, 2, 0, 0, 644, 23, 1, 0, 0, 0, 645, 646, 7, 2, 0, 0, 646, 647, 7, 3, 0, 0, 647, 648, 7, 4, 0, 0, 648, 649, 7, 5, 0, 0, 649, 650, 7, 6, 0, 0, 650, 651, 7, 7, 0, 0, 651, 652, 5, 95, 0, 0, 652, 653, 7, 8, 0, 0, 653, 654, 7, 9, 0, 0, 654, 655, 7, 10, 0, 0, 655, 656, 7, 5, 0, 0, 656, 657, 7, 11, 0, 0, 657, 658, 1, 0, 0, 0, 658, 659, 6, 3, 1, 0, 659, 25, 1, 0, 0, 0, 660, 661, 7, 7, 0, 0, 661, 662, 7, 5, 0, 0, 662, 663, 7, 12, 0, 0, 663, 664, 7, 10, 0, 0, 664, 665, 7, 2, 0, 0, 665, 666, 7, 3, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 6, 4, 2, 0, 668, 27, 1, 0, 0, 0, 669, 670, 4, 5, 0, 0, 670, 671, 7, 7, 0, 0, 671, 672, 7, 13, 0, 0, 672, 673, 7, 8, 0, 0, 673, 674, 7, 14, 0, 0, 674, 675, 7, 4, 0, 0, 675, 676, 7, 10, 0, 0, 676, 677, 7, 5, 0, 0, 677, 678, 1, 0, 0, 0, 678, 679, 6, 5, 3, 0, 679, 29, 1, 0, 0, 0, 680, 681, 7, 2, 0, 0, 681, 682, 7, 9, 0, 0, 682, 683, 7, 15, 0, 0, 683, 684, 7, 8, 0, 0, 684, 685, 7, 14, 0, 0, 685, 686, 7, 7, 0, 0, 686, 687, 7, 11, 0, 0, 687, 688, 7, 10, 0, 0, 688, 689, 7, 9, 0, 0, 689, 690, 7, 5, 0, 0, 690, 691, 1, 0, 0, 0, 691, 692, 6, 6, 4, 0, 692, 31, 1, 0, 0, 0, 693, 694, 7, 16, 0, 0, 694, 695, 7, 10, 0, 0, 695, 696, 7, 17, 0, 0, 696, 697, 7, 17, 0, 0, 697, 698, 7, 7, 0, 0, 698, 699, 7, 2, 0, 0, 699, 700, 7, 11, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 6, 7, 4, 0, 702, 33, 1, 0, 0, 0, 703, 704, 7, 7, 0, 0, 704, 705, 7, 18, 0, 0, 705, 706, 7, 4, 0, 0, 706, 707, 7, 14, 0, 0, 707, 708, 1, 0, 0, 0, 708, 709, 6, 8, 4, 0, 709, 35, 1, 0, 0, 0, 710, 711, 7, 6, 0, 0, 711, 712, 7, 12, 0, 0, 712, 713, 7, 9, 0, 0, 713, 714, 7, 19, 0, 0, 714, 715, 1, 0, 0, 0, 715, 716, 6, 9, 4, 0, 716, 37, 1, 0, 0, 0, 717, 718, 7, 14, 0, 0, 718, 719, 7, 10, 0, 0, 719, 720, 7, 15, 0, 0, 720, 721, 7, 10, 0, 0, 721, 722, 7, 11, 0, 0, 722, 723, 1, 0, 0, 0, 723, 724, 6, 10, 4, 0, 724, 39, 1, 0, 0, 0, 725, 726, 7, 12, 0, 0, 726, 727, 7, 7, 0, 0, 727, 728, 7, 12, 0, 0, 728, 729, 7, 4, 0, 0, 729, 730, 7, 5, 0, 0, 730, 731, 7, 19, 0, 0, 731, 732, 1, 0, 0, 0, 732, 733, 6, 11, 4, 0, 733, 41, 1, 0, 0, 0, 734, 735, 7, 12, 0, 0, 735, 736, 7, 9, 0, 0, 736, 737, 7, 20, 0, 0, 737, 738, 1, 0, 0, 0, 738, 739, 6, 12, 4, 0, 739, 43, 1, 0, 0, 0, 740, 741, 7, 17, 0, 0, 741, 742, 7, 4, 0, 0, 742, 743, 7, 15, 0, 0, 743, 744, 7, 8, 0, 0, 744, 745, 7, 14, 0, 0, 745, 746, 7, 7, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 6, 13, 4, 0, 748, 45, 1, 0, 0, 0, 749, 750, 7, 17, 0, 0, 750, 751, 7, 9, 0, 0, 751, 752, 7, 12, 0, 0, 752, 753, 7, 11, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 6, 14, 4, 0, 755, 47, 1, 0, 0, 0, 756, 757, 7, 17, 0, 0, 757, 758, 7, 11, 0, 0, 758, 759, 7, 4, 0, 0, 759, 760, 7, 11, 0, 0, 760, 761, 7, 17, 0, 0, 761, 762, 1, 0, 0, 0, 762, 763, 6, 15, 4, 0, 763, 49, 1, 0, 0, 0, 764, 765, 7, 20, 0, 0, 765, 766, 7, 3, 0, 0, 766, 767, 7, 7, 0, 0, 767, 768, 7, 12, 0, 0, 768, 769, 7, 7, 0, 0, 769, 770, 1, 0, 0, 0, 770, 771, 6, 16, 4, 0, 771, 51, 1, 0, 0, 0, 772, 773, 7, 21, 0, 0, 773, 774, 7, 12, 0, 0, 774, 775, 7, 9, 0, 0, 775, 776, 7, 15, 0, 0, 776, 777, 1, 0, 0, 0, 777, 778, 6, 17, 5, 0, 778, 53, 1, 0, 0, 0, 779, 780, 7, 11, 0, 0, 780, 781, 7, 17, 0, 0, 781, 782, 1, 0, 0, 0, 782, 783, 6, 18, 5, 0, 783, 55, 1, 0, 0, 0, 784, 785, 7, 21, 0, 0, 785, 786, 7, 9, 0, 0, 786, 787, 7, 12, 0, 0, 787, 788, 7, 19, 0, 0, 788, 789, 1, 0, 0, 0, 789, 790, 6, 19, 6, 0, 790, 57, 1, 0, 0, 0, 791, 792, 7, 21, 0, 0, 792, 793, 7, 22, 0, 0, 793, 794, 7, 17, 0, 0, 794, 795, 7, 7, 0, 0, 795, 796, 1, 0, 0, 0, 796, 797, 6, 20, 7, 0, 797, 59, 1, 0, 0, 0, 798, 799, 7, 10, 0, 0, 799, 800, 7, 5, 0, 0, 800, 801, 7, 14, 0, 0, 801, 802, 7, 10, 0, 0, 802, 803, 7, 5, 0, 0, 803, 804, 7, 7, 0, 0, 804, 805, 1, 0, 0, 0, 805, 806, 6, 21, 8, 0, 806, 61, 1, 0, 0, 0, 807, 808, 7, 10, 0, 0, 808, 809, 7, 5, 0, 0, 809, 810, 7, 14, 0, 0, 810, 811, 7, 10, 0, 0, 811, 812, 7, 5, 0, 0, 812, 813, 7, 7, 0, 0, 813, 814, 7, 17, 0, 0, 814, 815, 7, 11, 0, 0, 815, 816, 7, 4, 0, 0, 816, 817, 7, 11, 0, 0, 817, 818, 7, 17, 0, 0, 818, 819, 1, 0, 0, 0, 819, 820, 6, 22, 4, 0, 820, 63, 1, 0, 0, 0, 821, 822, 7, 14, 0, 0, 822, 823, 7, 9, 0, 0, 823, 824, 7, 9, 0, 0, 824, 825, 7, 19, 0, 0, 825, 826, 7, 22, 0, 0, 826, 827, 7, 8, 0, 0, 827, 828, 1, 0, 0, 0, 828, 829, 6, 23, 9, 0, 829, 65, 1, 0, 0, 0, 830, 831, 4, 24, 1, 0, 831, 832, 7, 21, 0, 0, 832, 833, 7, 22, 0, 0, 833, 834, 7, 14, 0, 0, 834, 835, 7, 14, 0, 0, 835, 836, 1, 0, 0, 0, 836, 837, 6, 24, 9, 0, 837, 67, 1, 0, 0, 0, 838, 839, 4, 25, 2, 0, 839, 840, 7, 14, 0, 0, 840, 841, 7, 7, 0, 0, 841, 842, 7, 21, 0, 0, 842, 843, 7, 11, 0, 0, 843, 844, 1, 0, 0, 0, 844, 845, 6, 25, 9, 0, 845, 69, 1, 0, 0, 0, 846, 847, 4, 26, 3, 0, 847, 848, 7, 12, 0, 0, 848, 849, 7, 10, 0, 0, 849, 850, 7, 6, 0, 0, 850, 851, 7, 3, 0, 0, 851, 852, 7, 11, 0, 0, 852, 853, 1, 0, 0, 0, 853, 854, 6, 26, 9, 0, 854, 71, 1, 0, 0, 0, 855, 856, 4, 27, 4, 0, 856, 857, 7, 14, 0, 0, 857, 858, 7, 9, 0, 0, 858, 859, 7, 9, 0, 0, 859, 860, 7, 19, 0, 0, 860, 861, 7, 22, 0, 0, 861, 862, 7, 8, 0, 0, 862, 863, 5, 95, 0, 0, 863, 864, 5, 128020, 0, 0, 864, 865, 1, 0, 0, 0, 865, 866, 6, 27, 10, 0, 866, 73, 1, 0, 0, 0, 867, 868, 7, 15, 0, 0, 868, 869, 7, 18, 0, 0, 869, 870, 5, 95, 0, 0, 870, 871, 7, 7, 0, 0, 871, 872, 7, 13, 0, 0, 872, 873, 7, 8, 0, 0, 873, 874, 7, 4, 0, 0, 874, 875, 7, 5, 0, 0, 875, 876, 7, 16, 0, 0, 876, 877, 1, 0, 0, 0, 877, 878, 6, 28, 11, 0, 878, 75, 1, 0, 0, 0, 879, 880, 7, 16, 0, 0, 880, 881, 7, 12, 0, 0, 881, 882, 7, 9, 0, 0, 882, 883, 7, 8, 0, 0, 883, 884, 1, 0, 0, 0, 884, 885, 6, 29, 12, 0, 885, 77, 1, 0, 0, 0, 886, 887, 7, 19, 0, 0, 887, 888, 7, 7, 0, 0, 888, 889, 7, 7, 0, 0, 889, 890, 7, 8, 0, 0, 890, 891, 1, 0, 0, 0, 891, 892, 6, 30, 12, 0, 892, 79, 1, 0, 0, 0, 893, 894, 4, 31, 5, 0, 894, 895, 7, 10, 0, 0, 895, 896, 7, 5, 0, 0, 896, 897, 7, 17, 0, 0, 897, 898, 7, 10, 0, 0, 898, 899, 7, 17, 0, 0, 899, 900, 7, 11, 0, 0, 900, 901, 5, 95, 0, 0, 901, 902, 5, 128020, 0, 0, 902, 903, 1, 0, 0, 0, 903, 904, 6, 31, 12, 0, 904, 81, 1, 0, 0, 0, 905, 906, 7, 12, 0, 0, 906, 907, 7, 7, 0, 0, 907, 908, 7, 5, 0, 0, 908, 909, 7, 4, 0, 0, 909, 910, 7, 15, 0, 0, 910, 911, 7, 7, 0, 0, 911, 912, 1, 0, 0, 0, 912, 913, 6, 32, 13, 0, 913, 83, 1, 0, 0, 0, 914, 915, 7, 17, 0, 0, 915, 916, 7, 7, 0, 0, 916, 917, 7, 11, 0, 0, 917, 918, 1, 0, 0, 0, 918, 919, 6, 33, 14, 0, 919, 85, 1, 0, 0, 0, 920, 921, 7, 17, 0, 0, 921, 922, 7, 3, 0, 0, 922, 923, 7, 9, 0, 0, 923, 924, 7, 20, 0, 0, 924, 925, 1, 0, 0, 0, 925, 926, 6, 34, 15, 0, 926, 87, 1, 0, 0, 0, 927, 929, 8, 23, 0, 0, 928, 927, 1, 0, 0, 0, 929, 930, 1, 0, 0, 0, 930, 928, 1, 0, 0, 0, 930, 931, 1, 0, 0, 0, 931, 932, 1, 0, 0, 0, 932, 933, 6, 35, 4, 0, 933, 89, 1, 0, 0, 0, 934, 935, 3, 182, 82, 0, 935, 936, 1, 0, 0, 0, 936, 937, 6, 36, 16, 0, 937, 938, 6, 36, 17, 0, 938, 91, 1, 0, 0, 0, 939, 940, 3, 302, 142, 0, 940, 941, 1, 0, 0, 0, 941, 942, 6, 37, 18, 0, 942, 943, 6, 37, 17, 0, 943, 944, 6, 37, 17, 0, 944, 93, 1, 0, 0, 0, 945, 946, 3, 248, 115, 0, 946, 947, 1, 0, 0, 0, 947, 948, 6, 38, 19, 0, 948, 95, 1, 0, 0, 0, 949, 950, 3, 542, 262, 0, 950, 951, 1, 0, 0, 0, 951, 952, 6, 39, 20, 0, 952, 97, 1, 0, 0, 0, 953, 954, 3, 228, 105, 0, 954, 955, 1, 0, 0, 0, 955, 956, 6, 40, 21, 0, 956, 99, 1, 0, 0, 0, 957, 958, 3, 224, 103, 0, 958, 959, 1, 0, 0, 0, 959, 960, 6, 41, 22, 0, 960, 101, 1, 0, 0, 0, 961, 962, 3, 296, 139, 0, 962, 963, 1, 0, 0, 0, 963, 964, 6, 42, 23, 0, 964, 103, 1, 0, 0, 0, 965, 966, 3, 298, 140, 0, 966, 967, 1, 0, 0, 0, 967, 968, 6, 43, 24, 0, 968, 105, 1, 0, 0, 0, 969, 970, 3, 308, 145, 0, 970, 971, 1, 0, 0, 0, 971, 972, 6, 44, 25, 0, 972, 107, 1, 0, 0, 0, 973, 974, 3, 304, 143, 0, 974, 975, 1, 0, 0, 0, 975, 976, 6, 45, 26, 0, 976, 109, 1, 0, 0, 0, 977, 978, 3, 18, 0, 0, 978, 979, 1, 0, 0, 0, 979, 980, 6, 46, 0, 0, 980, 111, 1, 0, 0, 0, 981, 982, 3, 20, 1, 0, 982, 983, 1, 0, 0, 0, 983, 984, 6, 47, 0, 0, 984, 113, 1, 0, 0, 0, 985, 986, 3, 22, 2, 0, 986, 987, 1, 0, 0, 0, 987, 988, 6, 48, 0, 0, 988, 115, 1, 0, 0, 0, 989, 990, 3, 182, 82, 0, 990, 991, 1, 0, 0, 0, 991, 992, 6, 49, 16, 0, 992, 993, 6, 49, 17, 0, 993, 117, 1, 0, 0, 0, 994, 995, 3, 302, 142, 0, 995, 996, 1, 0, 0, 0, 996, 997, 6, 50, 18, 0, 997, 998, 6, 50, 17, 0, 998, 999, 6, 50, 17, 0, 999, 119, 1, 0, 0, 0, 1000, 1001, 3, 248, 115, 0, 1001, 1002, 1, 0, 0, 0, 1002, 1003, 6, 51, 19, 0, 1003, 1004, 6, 51, 27, 0, 1004, 121, 1, 0, 0, 0, 1005, 1006, 3, 258, 120, 0, 1006, 1007, 1, 0, 0, 0, 1007, 1008, 6, 52, 28, 0, 1008, 1009, 6, 52, 27, 0, 1009, 123, 1, 0, 0, 0, 1010, 1011, 8, 24, 0, 0, 1011, 125, 1, 0, 0, 0, 1012, 1014, 3, 124, 53, 0, 1013, 1012, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1013, 1, 0, 0, 0, 1015, 1016, 1, 0, 0, 0, 1016, 1017, 1, 0, 0, 0, 1017, 1018, 3, 220, 101, 0, 1018, 1020, 1, 0, 0, 0, 1019, 1013, 1, 0, 0, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1022, 1, 0, 0, 0, 1021, 1023, 3, 124, 53, 0, 1022, 1021, 1, 0, 0, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1022, 1, 0, 0, 0, 1024, 1025, 1, 0, 0, 0, 1025, 127, 1, 0, 0, 0, 1026, 1027, 3, 126, 54, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1029, 6, 55, 29, 0, 1029, 129, 1, 0, 0, 0, 1030, 1031, 3, 204, 93, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 56, 30, 0, 1033, 131, 1, 0, 0, 0, 1034, 1035, 3, 18, 0, 0, 1035, 1036, 1, 0, 0, 0, 1036, 1037, 6, 57, 0, 0, 1037, 133, 1, 0, 0, 0, 1038, 1039, 3, 20, 1, 0, 1039, 1040, 1, 0, 0, 0, 1040, 1041, 6, 58, 0, 0, 1041, 135, 1, 0, 0, 0, 1042, 1043, 3, 22, 2, 0, 1043, 1044, 1, 0, 0, 0, 1044, 1045, 6, 59, 0, 0, 1045, 137, 1, 0, 0, 0, 1046, 1047, 3, 182, 82, 0, 1047, 1048, 1, 0, 0, 0, 1048, 1049, 6, 60, 16, 0, 1049, 1050, 6, 60, 17, 0, 1050, 1051, 6, 60, 17, 0, 1051, 139, 1, 0, 0, 0, 1052, 1053, 3, 302, 142, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1055, 6, 61, 18, 0, 1055, 1056, 6, 61, 17, 0, 1056, 1057, 6, 61, 17, 0, 1057, 1058, 6, 61, 17, 0, 1058, 141, 1, 0, 0, 0, 1059, 1060, 3, 296, 139, 0, 1060, 1061, 1, 0, 0, 0, 1061, 1062, 6, 62, 23, 0, 1062, 143, 1, 0, 0, 0, 1063, 1064, 3, 298, 140, 0, 1064, 1065, 1, 0, 0, 0, 1065, 1066, 6, 63, 24, 0, 1066, 145, 1, 0, 0, 0, 1067, 1068, 3, 214, 98, 0, 1068, 1069, 1, 0, 0, 0, 1069, 1070, 6, 64, 31, 0, 1070, 147, 1, 0, 0, 0, 1071, 1072, 3, 224, 103, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1074, 6, 65, 22, 0, 1074, 149, 1, 0, 0, 0, 1075, 1076, 3, 228, 105, 0, 1076, 1077, 1, 0, 0, 0, 1077, 1078, 6, 66, 21, 0, 1078, 151, 1, 0, 0, 0, 1079, 1080, 3, 258, 120, 0, 1080, 1081, 1, 0, 0, 0, 1081, 1082, 6, 67, 28, 0, 1082, 153, 1, 0, 0, 0, 1083, 1084, 3, 512, 247, 0, 1084, 1085, 1, 0, 0, 0, 1085, 1086, 6, 68, 32, 0, 1086, 155, 1, 0, 0, 0, 1087, 1088, 3, 308, 145, 0, 1088, 1089, 1, 0, 0, 0, 1089, 1090, 6, 69, 25, 0, 1090, 157, 1, 0, 0, 0, 1091, 1092, 3, 252, 117, 0, 1092, 1093, 1, 0, 0, 0, 1093, 1094, 6, 70, 33, 0, 1094, 159, 1, 0, 0, 0, 1095, 1096, 3, 292, 137, 0, 1096, 1097, 1, 0, 0, 0, 1097, 1098, 6, 71, 34, 0, 1098, 161, 1, 0, 0, 0, 1099, 1100, 3, 288, 135, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1102, 6, 72, 35, 0, 1102, 163, 1, 0, 0, 0, 1103, 1104, 3, 294, 138, 0, 1104, 1105, 1, 0, 0, 0, 1105, 1106, 6, 73, 36, 0, 1106, 165, 1, 0, 0, 0, 1107, 1108, 3, 18, 0, 0, 1108, 1109, 1, 0, 0, 0, 1109, 1110, 6, 74, 0, 0, 1110, 167, 1, 0, 0, 0, 1111, 1112, 3, 20, 1, 0, 1112, 1113, 1, 0, 0, 0, 1113, 1114, 6, 75, 0, 0, 1114, 169, 1, 0, 0, 0, 1115, 1116, 3, 22, 2, 0, 1116, 1117, 1, 0, 0, 0, 1117, 1118, 6, 76, 0, 0, 1118, 171, 1, 0, 0, 0, 1119, 1120, 3, 300, 141, 0, 1120, 1121, 1, 0, 0, 0, 1121, 1122, 6, 77, 37, 0, 1122, 1123, 6, 77, 38, 0, 1123, 173, 1, 0, 0, 0, 1124, 1125, 3, 182, 82, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 6, 78, 16, 0, 1127, 1128, 6, 78, 17, 0, 1128, 175, 1, 0, 0, 0, 1129, 1130, 3, 22, 2, 0, 1130, 1131, 1, 0, 0, 0, 1131, 1132, 6, 79, 0, 0, 1132, 177, 1, 0, 0, 0, 1133, 1134, 3, 18, 0, 0, 1134, 1135, 1, 0, 0, 0, 1135, 1136, 6, 80, 0, 0, 1136, 179, 1, 0, 0, 0, 1137, 1138, 3, 20, 1, 0, 1138, 1139, 1, 0, 0, 0, 1139, 1140, 6, 81, 0, 0, 1140, 181, 1, 0, 0, 0, 1141, 1142, 5, 124, 0, 0, 1142, 1143, 1, 0, 0, 0, 1143, 1144, 6, 82, 17, 0, 1144, 183, 1, 0, 0, 0, 1145, 1146, 7, 25, 0, 0, 1146, 185, 1, 0, 0, 0, 1147, 1148, 7, 26, 0, 0, 1148, 187, 1, 0, 0, 0, 1149, 1150, 5, 92, 0, 0, 1150, 1151, 7, 27, 0, 0, 1151, 189, 1, 0, 0, 0, 1152, 1153, 8, 28, 0, 0, 1153, 191, 1, 0, 0, 0, 1154, 1156, 7, 7, 0, 0, 1155, 1157, 7, 29, 0, 0, 1156, 1155, 1, 0, 0, 0, 1156, 1157, 1, 0, 0, 0, 1157, 1159, 1, 0, 0, 0, 1158, 1160, 3, 184, 83, 0, 1159, 1158, 1, 0, 0, 0, 1160, 1161, 1, 0, 0, 0, 1161, 1159, 1, 0, 0, 0, 1161, 1162, 1, 0, 0, 0, 1162, 193, 1, 0, 0, 0, 1163, 1164, 5, 64, 0, 0, 1164, 195, 1, 0, 0, 0, 1165, 1166, 5, 96, 0, 0, 1166, 197, 1, 0, 0, 0, 1167, 1171, 8, 30, 0, 0, 1168, 1169, 5, 96, 0, 0, 1169, 1171, 5, 96, 0, 0, 1170, 1167, 1, 0, 0, 0, 1170, 1168, 1, 0, 0, 0, 1171, 199, 1, 0, 0, 0, 1172, 1173, 5, 95, 0, 0, 1173, 201, 1, 0, 0, 0, 1174, 1178, 3, 186, 84, 0, 1175, 1178, 3, 184, 83, 0, 1176, 1178, 3, 200, 91, 0, 1177, 1174, 1, 0, 0, 0, 1177, 1175, 1, 0, 0, 0, 1177, 1176, 1, 0, 0, 0, 1178, 203, 1, 0, 0, 0, 1179, 1184, 5, 34, 0, 0, 1180, 1183, 3, 188, 85, 0, 1181, 1183, 3, 190, 86, 0, 1182, 1180, 1, 0, 0, 0, 1182, 1181, 1, 0, 0, 0, 1183, 1186, 1, 0, 0, 0, 1184, 1182, 1, 0, 0, 0, 1184, 1185, 1, 0, 0, 0, 1185, 1187, 1, 0, 0, 0, 1186, 1184, 1, 0, 0, 0, 1187, 1209, 5, 34, 0, 0, 1188, 1189, 5, 34, 0, 0, 1189, 1190, 5, 34, 0, 0, 1190, 1191, 5, 34, 0, 0, 1191, 1195, 1, 0, 0, 0, 1192, 1194, 8, 0, 0, 0, 1193, 1192, 1, 0, 0, 0, 1194, 1197, 1, 0, 0, 0, 1195, 1196, 1, 0, 0, 0, 1195, 1193, 1, 0, 0, 0, 1196, 1198, 1, 0, 0, 0, 1197, 1195, 1, 0, 0, 0, 1198, 1199, 5, 34, 0, 0, 1199, 1200, 5, 34, 0, 0, 1200, 1201, 5, 34, 0, 0, 1201, 1203, 1, 0, 0, 0, 1202, 1204, 5, 34, 0, 0, 1203, 1202, 1, 0, 0, 0, 1203, 1204, 1, 0, 0, 0, 1204, 1206, 1, 0, 0, 0, 1205, 1207, 5, 34, 0, 0, 1206, 1205, 1, 0, 0, 0, 1206, 1207, 1, 0, 0, 0, 1207, 1209, 1, 0, 0, 0, 1208, 1179, 1, 0, 0, 0, 1208, 1188, 1, 0, 0, 0, 1209, 205, 1, 0, 0, 0, 1210, 1212, 3, 184, 83, 0, 1211, 1210, 1, 0, 0, 0, 1212, 1213, 1, 0, 0, 0, 1213, 1211, 1, 0, 0, 0, 1213, 1214, 1, 0, 0, 0, 1214, 207, 1, 0, 0, 0, 1215, 1217, 3, 184, 83, 0, 1216, 1215, 1, 0, 0, 0, 1217, 1218, 1, 0, 0, 0, 1218, 1216, 1, 0, 0, 0, 1218, 1219, 1, 0, 0, 0, 1219, 1220, 1, 0, 0, 0, 1220, 1224, 3, 228, 105, 0, 1221, 1223, 3, 184, 83, 0, 1222, 1221, 1, 0, 0, 0, 1223, 1226, 1, 0, 0, 0, 1224, 1222, 1, 0, 0, 0, 1224, 1225, 1, 0, 0, 0, 1225, 1258, 1, 0, 0, 0, 1226, 1224, 1, 0, 0, 0, 1227, 1229, 3, 228, 105, 0, 1228, 1230, 3, 184, 83, 0, 1229, 1228, 1, 0, 0, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1229, 1, 0, 0, 0, 1231, 1232, 1, 0, 0, 0, 1232, 1258, 1, 0, 0, 0, 1233, 1235, 3, 184, 83, 0, 1234, 1233, 1, 0, 0, 0, 1235, 1236, 1, 0, 0, 0, 1236, 1234, 1, 0, 0, 0, 1236, 1237, 1, 0, 0, 0, 1237, 1245, 1, 0, 0, 0, 1238, 1242, 3, 228, 105, 0, 1239, 1241, 3, 184, 83, 0, 1240, 1239, 1, 0, 0, 0, 1241, 1244, 1, 0, 0, 0, 1242, 1240, 1, 0, 0, 0, 1242, 1243, 1, 0, 0, 0, 1243, 1246, 1, 0, 0, 0, 1244, 1242, 1, 0, 0, 0, 1245, 1238, 1, 0, 0, 0, 1245, 1246, 1, 0, 0, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1248, 3, 192, 87, 0, 1248, 1258, 1, 0, 0, 0, 1249, 1251, 3, 228, 105, 0, 1250, 1252, 3, 184, 83, 0, 1251, 1250, 1, 0, 0, 0, 1252, 1253, 1, 0, 0, 0, 1253, 1251, 1, 0, 0, 0, 1253, 1254, 1, 0, 0, 0, 1254, 1255, 1, 0, 0, 0, 1255, 1256, 3, 192, 87, 0, 1256, 1258, 1, 0, 0, 0, 1257, 1216, 1, 0, 0, 0, 1257, 1227, 1, 0, 0, 0, 1257, 1234, 1, 0, 0, 0, 1257, 1249, 1, 0, 0, 0, 1258, 209, 1, 0, 0, 0, 1259, 1260, 7, 4, 0, 0, 1260, 1261, 7, 5, 0, 0, 1261, 1262, 7, 16, 0, 0, 1262, 211, 1, 0, 0, 0, 1263, 1264, 7, 4, 0, 0, 1264, 1265, 7, 17, 0, 0, 1265, 1266, 7, 2, 0, 0, 1266, 213, 1, 0, 0, 0, 1267, 1268, 5, 61, 0, 0, 1268, 215, 1, 0, 0, 0, 1269, 1270, 7, 31, 0, 0, 1270, 1271, 7, 32, 0, 0, 1271, 217, 1, 0, 0, 0, 1272, 1273, 5, 58, 0, 0, 1273, 1274, 5, 58, 0, 0, 1274, 219, 1, 0, 0, 0, 1275, 1276, 5, 58, 0, 0, 1276, 221, 1, 0, 0, 0, 1277, 1278, 5, 59, 0, 0, 1278, 223, 1, 0, 0, 0, 1279, 1280, 5, 44, 0, 0, 1280, 225, 1, 0, 0, 0, 1281, 1282, 7, 16, 0, 0, 1282, 1283, 7, 7, 0, 0, 1283, 1284, 7, 17, 0, 0, 1284, 1285, 7, 2, 0, 0, 1285, 227, 1, 0, 0, 0, 1286, 1287, 5, 46, 0, 0, 1287, 229, 1, 0, 0, 0, 1288, 1289, 7, 21, 0, 0, 1289, 1290, 7, 4, 0, 0, 1290, 1291, 7, 14, 0, 0, 1291, 1292, 7, 17, 0, 0, 1292, 1293, 7, 7, 0, 0, 1293, 231, 1, 0, 0, 0, 1294, 1295, 7, 21, 0, 0, 1295, 1296, 7, 10, 0, 0, 1296, 1297, 7, 12, 0, 0, 1297, 1298, 7, 17, 0, 0, 1298, 1299, 7, 11, 0, 0, 1299, 233, 1, 0, 0, 0, 1300, 1301, 7, 10, 0, 0, 1301, 1302, 7, 5, 0, 0, 1302, 235, 1, 0, 0, 0, 1303, 1304, 7, 10, 0, 0, 1304, 1305, 7, 17, 0, 0, 1305, 237, 1, 0, 0, 0, 1306, 1307, 7, 14, 0, 0, 1307, 1308, 7, 4, 0, 0, 1308, 1309, 7, 17, 0, 0, 1309, 1310, 7, 11, 0, 0, 1310, 239, 1, 0, 0, 0, 1311, 1312, 7, 14, 0, 0, 1312, 1313, 7, 10, 0, 0, 1313, 1314, 7, 19, 0, 0, 1314, 1315, 7, 7, 0, 0, 1315, 241, 1, 0, 0, 0, 1316, 1317, 7, 5, 0, 0, 1317, 1318, 7, 9, 0, 0, 1318, 1319, 7, 11, 0, 0, 1319, 243, 1, 0, 0, 0, 1320, 1321, 7, 5, 0, 0, 1321, 1322, 7, 22, 0, 0, 1322, 1323, 7, 14, 0, 0, 1323, 1324, 7, 14, 0, 0, 1324, 245, 1, 0, 0, 0, 1325, 1326, 7, 5, 0, 0, 1326, 1327, 7, 22, 0, 0, 1327, 1328, 7, 14, 0, 0, 1328, 1329, 7, 14, 0, 0, 1329, 1330, 7, 17, 0, 0, 1330, 247, 1, 0, 0, 0, 1331, 1332, 7, 9, 0, 0, 1332, 1333, 7, 5, 0, 0, 1333, 249, 1, 0, 0, 0, 1334, 1335, 7, 9, 0, 0, 1335, 1336, 7, 12, 0, 0, 1336, 251, 1, 0, 0, 0, 1337, 1338, 5, 63, 0, 0, 1338, 253, 1, 0, 0, 0, 1339, 1340, 7, 12, 0, 0, 1340, 1341, 7, 14, 0, 0, 1341, 1342, 7, 10, 0, 0, 1342, 1343, 7, 19, 0, 0, 1343, 1344, 7, 7, 0, 0, 1344, 255, 1, 0, 0, 0, 1345, 1346, 7, 11, 0, 0, 1346, 1347, 7, 12, 0, 0, 1347, 1348, 7, 22, 0, 0, 1348, 1349, 7, 7, 0, 0, 1349, 257, 1, 0, 0, 0, 1350, 1351, 7, 20, 0, 0, 1351, 1352, 7, 10, 0, 0, 1352, 1353, 7, 11, 0, 0, 1353, 1354, 7, 3, 0, 0, 1354, 259, 1, 0, 0, 0, 1355, 1356, 5, 61, 0, 0, 1356, 1357, 5, 61, 0, 0, 1357, 261, 1, 0, 0, 0, 1358, 1359, 5, 61, 0, 0, 1359, 1360, 5, 126, 0, 0, 1360, 263, 1, 0, 0, 0, 1361, 1362, 5, 33, 0, 0, 1362, 1363, 5, 61, 0, 0, 1363, 265, 1, 0, 0, 0, 1364, 1365, 5, 60, 0, 0, 1365, 267, 1, 0, 0, 0, 1366, 1367, 5, 60, 0, 0, 1367, 1368, 5, 61, 0, 0, 1368, 269, 1, 0, 0, 0, 1369, 1370, 5, 62, 0, 0, 1370, 271, 1, 0, 0, 0, 1371, 1372, 5, 62, 0, 0, 1372, 1373, 5, 61, 0, 0, 1373, 273, 1, 0, 0, 0, 1374, 1375, 5, 43, 0, 0, 1375, 275, 1, 0, 0, 0, 1376, 1377, 5, 45, 0, 0, 1377, 277, 1, 0, 0, 0, 1378, 1379, 5, 42, 0, 0, 1379, 279, 1, 0, 0, 0, 1380, 1381, 5, 47, 0, 0, 1381, 281, 1, 0, 0, 0, 1382, 1383, 5, 37, 0, 0, 1383, 283, 1, 0, 0, 0, 1384, 1385, 5, 123, 0, 0, 1385, 285, 1, 0, 0, 0, 1386, 1387, 5, 125, 0, 0, 1387, 287, 1, 0, 0, 0, 1388, 1389, 5, 63, 0, 0, 1389, 1390, 5, 63, 0, 0, 1390, 289, 1, 0, 0, 0, 1391, 1392, 3, 50, 16, 0, 1392, 1393, 1, 0, 0, 0, 1393, 1394, 6, 136, 39, 0, 1394, 291, 1, 0, 0, 0, 1395, 1398, 3, 252, 117, 0, 1396, 1399, 3, 186, 84, 0, 1397, 1399, 3, 200, 91, 0, 1398, 1396, 1, 0, 0, 0, 1398, 1397, 1, 0, 0, 0, 1399, 1403, 1, 0, 0, 0, 1400, 1402, 3, 202, 92, 0, 1401, 1400, 1, 0, 0, 0, 1402, 1405, 1, 0, 0, 0, 1403, 1401, 1, 0, 0, 0, 1403, 1404, 1, 0, 0, 0, 1404, 1413, 1, 0, 0, 0, 1405, 1403, 1, 0, 0, 0, 1406, 1408, 3, 252, 117, 0, 1407, 1409, 3, 184, 83, 0, 1408, 1407, 1, 0, 0, 0, 1409, 1410, 1, 0, 0, 0, 1410, 1408, 1, 0, 0, 0, 1410, 1411, 1, 0, 0, 0, 1411, 1413, 1, 0, 0, 0, 1412, 1395, 1, 0, 0, 0, 1412, 1406, 1, 0, 0, 0, 1413, 293, 1, 0, 0, 0, 1414, 1417, 3, 288, 135, 0, 1415, 1418, 3, 186, 84, 0, 1416, 1418, 3, 200, 91, 0, 1417, 1415, 1, 0, 0, 0, 1417, 1416, 1, 0, 0, 0, 1418, 1422, 1, 0, 0, 0, 1419, 1421, 3, 202, 92, 0, 1420, 1419, 1, 0, 0, 0, 1421, 1424, 1, 0, 0, 0, 1422, 1420, 1, 0, 0, 0, 1422, 1423, 1, 0, 0, 0, 1423, 1432, 1, 0, 0, 0, 1424, 1422, 1, 0, 0, 0, 1425, 1427, 3, 288, 135, 0, 1426, 1428, 3, 184, 83, 0, 1427, 1426, 1, 0, 0, 0, 1428, 1429, 1, 0, 0, 0, 1429, 1427, 1, 0, 0, 0, 1429, 1430, 1, 0, 0, 0, 1430, 1432, 1, 0, 0, 0, 1431, 1414, 1, 0, 0, 0, 1431, 1425, 1, 0, 0, 0, 1432, 295, 1, 0, 0, 0, 1433, 1434, 5, 91, 0, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 139, 4, 0, 1436, 1437, 6, 139, 4, 0, 1437, 297, 1, 0, 0, 0, 1438, 1439, 5, 93, 0, 0, 1439, 1440, 1, 0, 0, 0, 1440, 1441, 6, 140, 17, 0, 1441, 1442, 6, 140, 17, 0, 1442, 299, 1, 0, 0, 0, 1443, 1444, 5, 40, 0, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 141, 4, 0, 1446, 1447, 6, 141, 4, 0, 1447, 301, 1, 0, 0, 0, 1448, 1449, 5, 41, 0, 0, 1449, 1450, 1, 0, 0, 0, 1450, 1451, 6, 142, 17, 0, 1451, 1452, 6, 142, 17, 0, 1452, 303, 1, 0, 0, 0, 1453, 1457, 3, 186, 84, 0, 1454, 1456, 3, 202, 92, 0, 1455, 1454, 1, 0, 0, 0, 1456, 1459, 1, 0, 0, 0, 1457, 1455, 1, 0, 0, 0, 1457, 1458, 1, 0, 0, 0, 1458, 1470, 1, 0, 0, 0, 1459, 1457, 1, 0, 0, 0, 1460, 1463, 3, 200, 91, 0, 1461, 1463, 3, 194, 88, 0, 1462, 1460, 1, 0, 0, 0, 1462, 1461, 1, 0, 0, 0, 1463, 1465, 1, 0, 0, 0, 1464, 1466, 3, 202, 92, 0, 1465, 1464, 1, 0, 0, 0, 1466, 1467, 1, 0, 0, 0, 1467, 1465, 1, 0, 0, 0, 1467, 1468, 1, 0, 0, 0, 1468, 1470, 1, 0, 0, 0, 1469, 1453, 1, 0, 0, 0, 1469, 1462, 1, 0, 0, 0, 1470, 305, 1, 0, 0, 0, 1471, 1473, 3, 196, 89, 0, 1472, 1474, 3, 198, 90, 0, 1473, 1472, 1, 0, 0, 0, 1474, 1475, 1, 0, 0, 0, 1475, 1473, 1, 0, 0, 0, 1475, 1476, 1, 0, 0, 0, 1476, 1477, 1, 0, 0, 0, 1477, 1478, 3, 196, 89, 0, 1478, 307, 1, 0, 0, 0, 1479, 1480, 3, 306, 144, 0, 1480, 309, 1, 0, 0, 0, 1481, 1482, 3, 18, 0, 0, 1482, 1483, 1, 0, 0, 0, 1483, 1484, 6, 146, 0, 0, 1484, 311, 1, 0, 0, 0, 1485, 1486, 3, 20, 1, 0, 1486, 1487, 1, 0, 0, 0, 1487, 1488, 6, 147, 0, 0, 1488, 313, 1, 0, 0, 0, 1489, 1490, 3, 22, 2, 0, 1490, 1491, 1, 0, 0, 0, 1491, 1492, 6, 148, 0, 0, 1492, 315, 1, 0, 0, 0, 1493, 1494, 3, 182, 82, 0, 1494, 1495, 1, 0, 0, 0, 1495, 1496, 6, 149, 16, 0, 1496, 1497, 6, 149, 17, 0, 1497, 317, 1, 0, 0, 0, 1498, 1499, 3, 220, 101, 0, 1499, 1500, 1, 0, 0, 0, 1500, 1501, 6, 150, 40, 0, 1501, 319, 1, 0, 0, 0, 1502, 1503, 3, 218, 100, 0, 1503, 1504, 1, 0, 0, 0, 1504, 1505, 6, 151, 41, 0, 1505, 321, 1, 0, 0, 0, 1506, 1507, 3, 224, 103, 0, 1507, 1508, 1, 0, 0, 0, 1508, 1509, 6, 152, 22, 0, 1509, 323, 1, 0, 0, 0, 1510, 1511, 3, 214, 98, 0, 1511, 1512, 1, 0, 0, 0, 1512, 1513, 6, 153, 31, 0, 1513, 325, 1, 0, 0, 0, 1514, 1515, 7, 15, 0, 0, 1515, 1516, 7, 7, 0, 0, 1516, 1517, 7, 11, 0, 0, 1517, 1518, 7, 4, 0, 0, 1518, 1519, 7, 16, 0, 0, 1519, 1520, 7, 4, 0, 0, 1520, 1521, 7, 11, 0, 0, 1521, 1522, 7, 4, 0, 0, 1522, 327, 1, 0, 0, 0, 1523, 1524, 3, 302, 142, 0, 1524, 1525, 1, 0, 0, 0, 1525, 1526, 6, 155, 18, 0, 1526, 1527, 6, 155, 17, 0, 1527, 329, 1, 0, 0, 0, 1528, 1532, 8, 33, 0, 0, 1529, 1530, 5, 47, 0, 0, 1530, 1532, 8, 34, 0, 0, 1531, 1528, 1, 0, 0, 0, 1531, 1529, 1, 0, 0, 0, 1532, 331, 1, 0, 0, 0, 1533, 1535, 3, 330, 156, 0, 1534, 1533, 1, 0, 0, 0, 1535, 1536, 1, 0, 0, 0, 1536, 1534, 1, 0, 0, 0, 1536, 1537, 1, 0, 0, 0, 1537, 333, 1, 0, 0, 0, 1538, 1539, 3, 332, 157, 0, 1539, 1540, 1, 0, 0, 0, 1540, 1541, 6, 158, 42, 0, 1541, 335, 1, 0, 0, 0, 1542, 1543, 3, 204, 93, 0, 1543, 1544, 1, 0, 0, 0, 1544, 1545, 6, 159, 30, 0, 1545, 337, 1, 0, 0, 0, 1546, 1547, 3, 18, 0, 0, 1547, 1548, 1, 0, 0, 0, 1548, 1549, 6, 160, 0, 0, 1549, 339, 1, 0, 0, 0, 1550, 1551, 3, 20, 1, 0, 1551, 1552, 1, 0, 0, 0, 1552, 1553, 6, 161, 0, 0, 1553, 341, 1, 0, 0, 0, 1554, 1555, 3, 22, 2, 0, 1555, 1556, 1, 0, 0, 0, 1556, 1557, 6, 162, 0, 0, 1557, 343, 1, 0, 0, 0, 1558, 1559, 3, 300, 141, 0, 1559, 1560, 1, 0, 0, 0, 1560, 1561, 6, 163, 37, 0, 1561, 1562, 6, 163, 38, 0, 1562, 345, 1, 0, 0, 0, 1563, 1564, 3, 302, 142, 0, 1564, 1565, 1, 0, 0, 0, 1565, 1566, 6, 164, 18, 0, 1566, 1567, 6, 164, 17, 0, 1567, 1568, 6, 164, 17, 0, 1568, 347, 1, 0, 0, 0, 1569, 1570, 3, 182, 82, 0, 1570, 1571, 1, 0, 0, 0, 1571, 1572, 6, 165, 16, 0, 1572, 1573, 6, 165, 17, 0, 1573, 349, 1, 0, 0, 0, 1574, 1575, 3, 22, 2, 0, 1575, 1576, 1, 0, 0, 0, 1576, 1577, 6, 166, 0, 0, 1577, 351, 1, 0, 0, 0, 1578, 1579, 3, 18, 0, 0, 1579, 1580, 1, 0, 0, 0, 1580, 1581, 6, 167, 0, 0, 1581, 353, 1, 0, 0, 0, 1582, 1583, 3, 20, 1, 0, 1583, 1584, 1, 0, 0, 0, 1584, 1585, 6, 168, 0, 0, 1585, 355, 1, 0, 0, 0, 1586, 1587, 3, 182, 82, 0, 1587, 1588, 1, 0, 0, 0, 1588, 1589, 6, 169, 16, 0, 1589, 1590, 6, 169, 17, 0, 1590, 357, 1, 0, 0, 0, 1591, 1592, 3, 302, 142, 0, 1592, 1593, 1, 0, 0, 0, 1593, 1594, 6, 170, 18, 0, 1594, 1595, 6, 170, 17, 0, 1595, 1596, 6, 170, 17, 0, 1596, 359, 1, 0, 0, 0, 1597, 1598, 7, 6, 0, 0, 1598, 1599, 7, 12, 0, 0, 1599, 1600, 7, 9, 0, 0, 1600, 1601, 7, 22, 0, 0, 1601, 1602, 7, 8, 0, 0, 1602, 361, 1, 0, 0, 0, 1603, 1604, 7, 17, 0, 0, 1604, 1605, 7, 2, 0, 0, 1605, 1606, 7, 9, 0, 0, 1606, 1607, 7, 12, 0, 0, 1607, 1608, 7, 7, 0, 0, 1608, 363, 1, 0, 0, 0, 1609, 1610, 7, 19, 0, 0, 1610, 1611, 7, 7, 0, 0, 1611, 1612, 7, 32, 0, 0, 1612, 365, 1, 0, 0, 0, 1613, 1614, 3, 258, 120, 0, 1614, 1615, 1, 0, 0, 0, 1615, 1616, 6, 174, 28, 0, 1616, 1617, 6, 174, 17, 0, 1617, 1618, 6, 174, 4, 0, 1618, 367, 1, 0, 0, 0, 1619, 1620, 3, 224, 103, 0, 1620, 1621, 1, 0, 0, 0, 1621, 1622, 6, 175, 22, 0, 1622, 369, 1, 0, 0, 0, 1623, 1624, 3, 228, 105, 0, 1624, 1625, 1, 0, 0, 0, 1625, 1626, 6, 176, 21, 0, 1626, 371, 1, 0, 0, 0, 1627, 1628, 3, 252, 117, 0, 1628, 1629, 1, 0, 0, 0, 1629, 1630, 6, 177, 33, 0, 1630, 373, 1, 0, 0, 0, 1631, 1632, 3, 292, 137, 0, 1632, 1633, 1, 0, 0, 0, 1633, 1634, 6, 178, 34, 0, 1634, 375, 1, 0, 0, 0, 1635, 1636, 3, 288, 135, 0, 1636, 1637, 1, 0, 0, 0, 1637, 1638, 6, 179, 35, 0, 1638, 377, 1, 0, 0, 0, 1639, 1640, 3, 294, 138, 0, 1640, 1641, 1, 0, 0, 0, 1641, 1642, 6, 180, 36, 0, 1642, 379, 1, 0, 0, 0, 1643, 1644, 3, 216, 99, 0, 1644, 1645, 1, 0, 0, 0, 1645, 1646, 6, 181, 43, 0, 1646, 381, 1, 0, 0, 0, 1647, 1648, 3, 308, 145, 0, 1648, 1649, 1, 0, 0, 0, 1649, 1650, 6, 182, 25, 0, 1650, 383, 1, 0, 0, 0, 1651, 1652, 3, 304, 143, 0, 1652, 1653, 1, 0, 0, 0, 1653, 1654, 6, 183, 26, 0, 1654, 385, 1, 0, 0, 0, 1655, 1656, 3, 18, 0, 0, 1656, 1657, 1, 0, 0, 0, 1657, 1658, 6, 184, 0, 0, 1658, 387, 1, 0, 0, 0, 1659, 1660, 3, 20, 1, 0, 1660, 1661, 1, 0, 0, 0, 1661, 1662, 6, 185, 0, 0, 1662, 389, 1, 0, 0, 0, 1663, 1664, 3, 22, 2, 0, 1664, 1665, 1, 0, 0, 0, 1665, 1666, 6, 186, 0, 0, 1666, 391, 1, 0, 0, 0, 1667, 1668, 7, 17, 0, 0, 1668, 1669, 7, 11, 0, 0, 1669, 1670, 7, 4, 0, 0, 1670, 1671, 7, 11, 0, 0, 1671, 1672, 7, 17, 0, 0, 1672, 1673, 1, 0, 0, 0, 1673, 1674, 6, 187, 17, 0, 1674, 1675, 6, 187, 4, 0, 1675, 393, 1, 0, 0, 0, 1676, 1677, 3, 18, 0, 0, 1677, 1678, 1, 0, 0, 0, 1678, 1679, 6, 188, 0, 0, 1679, 395, 1, 0, 0, 0, 1680, 1681, 3, 20, 1, 0, 1681, 1682, 1, 0, 0, 0, 1682, 1683, 6, 189, 0, 0, 1683, 397, 1, 0, 0, 0, 1684, 1685, 3, 22, 2, 0, 1685, 1686, 1, 0, 0, 0, 1686, 1687, 6, 190, 0, 0, 1687, 399, 1, 0, 0, 0, 1688, 1689, 3, 182, 82, 0, 1689, 1690, 1, 0, 0, 0, 1690, 1691, 6, 191, 16, 0, 1691, 1692, 6, 191, 17, 0, 1692, 401, 1, 0, 0, 0, 1693, 1694, 7, 35, 0, 0, 1694, 1695, 7, 9, 0, 0, 1695, 1696, 7, 10, 0, 0, 1696, 1697, 7, 5, 0, 0, 1697, 403, 1, 0, 0, 0, 1698, 1699, 3, 542, 262, 0, 1699, 1700, 1, 0, 0, 0, 1700, 1701, 6, 193, 20, 0, 1701, 405, 1, 0, 0, 0, 1702, 1703, 3, 248, 115, 0, 1703, 1704, 1, 0, 0, 0, 1704, 1705, 6, 194, 19, 0, 1705, 1706, 6, 194, 17, 0, 1706, 1707, 6, 194, 4, 0, 1707, 407, 1, 0, 0, 0, 1708, 1709, 7, 22, 0, 0, 1709, 1710, 7, 17, 0, 0, 1710, 1711, 7, 10, 0, 0, 1711, 1712, 7, 5, 0, 0, 1712, 1713, 7, 6, 0, 0, 1713, 1714, 1, 0, 0, 0, 1714, 1715, 6, 195, 17, 0, 1715, 1716, 6, 195, 4, 0, 1716, 409, 1, 0, 0, 0, 1717, 1718, 3, 332, 157, 0, 1718, 1719, 1, 0, 0, 0, 1719, 1720, 6, 196, 42, 0, 1720, 411, 1, 0, 0, 0, 1721, 1722, 3, 204, 93, 0, 1722, 1723, 1, 0, 0, 0, 1723, 1724, 6, 197, 30, 0, 1724, 413, 1, 0, 0, 0, 1725, 1726, 3, 220, 101, 0, 1726, 1727, 1, 0, 0, 0, 1727, 1728, 6, 198, 40, 0, 1728, 415, 1, 0, 0, 0, 1729, 1730, 3, 18, 0, 0, 1730, 1731, 1, 0, 0, 0, 1731, 1732, 6, 199, 0, 0, 1732, 417, 1, 0, 0, 0, 1733, 1734, 3, 20, 1, 0, 1734, 1735, 1, 0, 0, 0, 1735, 1736, 6, 200, 0, 0, 1736, 419, 1, 0, 0, 0, 1737, 1738, 3, 22, 2, 0, 1738, 1739, 1, 0, 0, 0, 1739, 1740, 6, 201, 0, 0, 1740, 421, 1, 0, 0, 0, 1741, 1742, 3, 182, 82, 0, 1742, 1743, 1, 0, 0, 0, 1743, 1744, 6, 202, 16, 0, 1744, 1745, 6, 202, 17, 0, 1745, 423, 1, 0, 0, 0, 1746, 1747, 3, 302, 142, 0, 1747, 1748, 1, 0, 0, 0, 1748, 1749, 6, 203, 18, 0, 1749, 1750, 6, 203, 17, 0, 1750, 1751, 6, 203, 17, 0, 1751, 425, 1, 0, 0, 0, 1752, 1753, 3, 220, 101, 0, 1753, 1754, 1, 0, 0, 0, 1754, 1755, 6, 204, 40, 0, 1755, 427, 1, 0, 0, 0, 1756, 1757, 3, 224, 103, 0, 1757, 1758, 1, 0, 0, 0, 1758, 1759, 6, 205, 22, 0, 1759, 429, 1, 0, 0, 0, 1760, 1761, 3, 228, 105, 0, 1761, 1762, 1, 0, 0, 0, 1762, 1763, 6, 206, 21, 0, 1763, 431, 1, 0, 0, 0, 1764, 1765, 3, 248, 115, 0, 1765, 1766, 1, 0, 0, 0, 1766, 1767, 6, 207, 19, 0, 1767, 1768, 6, 207, 44, 0, 1768, 433, 1, 0, 0, 0, 1769, 1770, 3, 332, 157, 0, 1770, 1771, 1, 0, 0, 0, 1771, 1772, 6, 208, 42, 0, 1772, 435, 1, 0, 0, 0, 1773, 1774, 3, 204, 93, 0, 1774, 1775, 1, 0, 0, 0, 1775, 1776, 6, 209, 30, 0, 1776, 437, 1, 0, 0, 0, 1777, 1778, 3, 18, 0, 0, 1778, 1779, 1, 0, 0, 0, 1779, 1780, 6, 210, 0, 0, 1780, 439, 1, 0, 0, 0, 1781, 1782, 3, 20, 1, 0, 1782, 1783, 1, 0, 0, 0, 1783, 1784, 6, 211, 0, 0, 1784, 441, 1, 0, 0, 0, 1785, 1786, 3, 22, 2, 0, 1786, 1787, 1, 0, 0, 0, 1787, 1788, 6, 212, 0, 0, 1788, 443, 1, 0, 0, 0, 1789, 1790, 3, 182, 82, 0, 1790, 1791, 1, 0, 0, 0, 1791, 1792, 6, 213, 16, 0, 1792, 1793, 6, 213, 17, 0, 1793, 1794, 6, 213, 17, 0, 1794, 445, 1, 0, 0, 0, 1795, 1796, 3, 302, 142, 0, 1796, 1797, 1, 0, 0, 0, 1797, 1798, 6, 214, 18, 0, 1798, 1799, 6, 214, 17, 0, 1799, 1800, 6, 214, 17, 0, 1800, 1801, 6, 214, 17, 0, 1801, 447, 1, 0, 0, 0, 1802, 1803, 3, 224, 103, 0, 1803, 1804, 1, 0, 0, 0, 1804, 1805, 6, 215, 22, 0, 1805, 449, 1, 0, 0, 0, 1806, 1807, 3, 228, 105, 0, 1807, 1808, 1, 0, 0, 0, 1808, 1809, 6, 216, 21, 0, 1809, 451, 1, 0, 0, 0, 1810, 1811, 3, 512, 247, 0, 1811, 1812, 1, 0, 0, 0, 1812, 1813, 6, 217, 32, 0, 1813, 453, 1, 0, 0, 0, 1814, 1815, 3, 18, 0, 0, 1815, 1816, 1, 0, 0, 0, 1816, 1817, 6, 218, 0, 0, 1817, 455, 1, 0, 0, 0, 1818, 1819, 3, 20, 1, 0, 1819, 1820, 1, 0, 0, 0, 1820, 1821, 6, 219, 0, 0, 1821, 457, 1, 0, 0, 0, 1822, 1823, 3, 22, 2, 0, 1823, 1824, 1, 0, 0, 0, 1824, 1825, 6, 220, 0, 0, 1825, 459, 1, 0, 0, 0, 1826, 1827, 3, 182, 82, 0, 1827, 1828, 1, 0, 0, 0, 1828, 1829, 6, 221, 16, 0, 1829, 1830, 6, 221, 17, 0, 1830, 461, 1, 0, 0, 0, 1831, 1832, 3, 302, 142, 0, 1832, 1833, 1, 0, 0, 0, 1833, 1834, 6, 222, 18, 0, 1834, 1835, 6, 222, 17, 0, 1835, 1836, 6, 222, 17, 0, 1836, 463, 1, 0, 0, 0, 1837, 1838, 3, 296, 139, 0, 1838, 1839, 1, 0, 0, 0, 1839, 1840, 6, 223, 23, 0, 1840, 465, 1, 0, 0, 0, 1841, 1842, 3, 298, 140, 0, 1842, 1843, 1, 0, 0, 0, 1843, 1844, 6, 224, 24, 0, 1844, 467, 1, 0, 0, 0, 1845, 1846, 3, 228, 105, 0, 1846, 1847, 1, 0, 0, 0, 1847, 1848, 6, 225, 21, 0, 1848, 469, 1, 0, 0, 0, 1849, 1850, 3, 252, 117, 0, 1850, 1851, 1, 0, 0, 0, 1851, 1852, 6, 226, 33, 0, 1852, 471, 1, 0, 0, 0, 1853, 1854, 3, 292, 137, 0, 1854, 1855, 1, 0, 0, 0, 1855, 1856, 6, 227, 34, 0, 1856, 473, 1, 0, 0, 0, 1857, 1858, 3, 288, 135, 0, 1858, 1859, 1, 0, 0, 0, 1859, 1860, 6, 228, 35, 0, 1860, 475, 1, 0, 0, 0, 1861, 1862, 3, 294, 138, 0, 1862, 1863, 1, 0, 0, 0, 1863, 1864, 6, 229, 36, 0, 1864, 477, 1, 0, 0, 0, 1865, 1866, 3, 308, 145, 0, 1866, 1867, 1, 0, 0, 0, 1867, 1868, 6, 230, 25, 0, 1868, 479, 1, 0, 0, 0, 1869, 1870, 3, 304, 143, 0, 1870, 1871, 1, 0, 0, 0, 1871, 1872, 6, 231, 26, 0, 1872, 481, 1, 0, 0, 0, 1873, 1874, 3, 18, 0, 0, 1874, 1875, 1, 0, 0, 0, 1875, 1876, 6, 232, 0, 0, 1876, 483, 1, 0, 0, 0, 1877, 1878, 3, 20, 1, 0, 1878, 1879, 1, 0, 0, 0, 1879, 1880, 6, 233, 0, 0, 1880, 485, 1, 0, 0, 0, 1881, 1882, 3, 22, 2, 0, 1882, 1883, 1, 0, 0, 0, 1883, 1884, 6, 234, 0, 0, 1884, 487, 1, 0, 0, 0, 1885, 1886, 3, 182, 82, 0, 1886, 1887, 1, 0, 0, 0, 1887, 1888, 6, 235, 16, 0, 1888, 1889, 6, 235, 17, 0, 1889, 489, 1, 0, 0, 0, 1890, 1891, 3, 302, 142, 0, 1891, 1892, 1, 0, 0, 0, 1892, 1893, 6, 236, 18, 0, 1893, 1894, 6, 236, 17, 0, 1894, 1895, 6, 236, 17, 0, 1895, 491, 1, 0, 0, 0, 1896, 1897, 3, 228, 105, 0, 1897, 1898, 1, 0, 0, 0, 1898, 1899, 6, 237, 21, 0, 1899, 493, 1, 0, 0, 0, 1900, 1901, 3, 296, 139, 0, 1901, 1902, 1, 0, 0, 0, 1902, 1903, 6, 238, 23, 0, 1903, 495, 1, 0, 0, 0, 1904, 1905, 3, 298, 140, 0, 1905, 1906, 1, 0, 0, 0, 1906, 1907, 6, 239, 24, 0, 1907, 497, 1, 0, 0, 0, 1908, 1909, 3, 224, 103, 0, 1909, 1910, 1, 0, 0, 0, 1910, 1911, 6, 240, 22, 0, 1911, 499, 1, 0, 0, 0, 1912, 1913, 3, 252, 117, 0, 1913, 1914, 1, 0, 0, 0, 1914, 1915, 6, 241, 33, 0, 1915, 501, 1, 0, 0, 0, 1916, 1917, 3, 292, 137, 0, 1917, 1918, 1, 0, 0, 0, 1918, 1919, 6, 242, 34, 0, 1919, 503, 1, 0, 0, 0, 1920, 1921, 3, 288, 135, 0, 1921, 1922, 1, 0, 0, 0, 1922, 1923, 6, 243, 35, 0, 1923, 505, 1, 0, 0, 0, 1924, 1925, 3, 294, 138, 0, 1925, 1926, 1, 0, 0, 0, 1926, 1927, 6, 244, 36, 0, 1927, 507, 1, 0, 0, 0, 1928, 1933, 3, 186, 84, 0, 1929, 1933, 3, 184, 83, 0, 1930, 1933, 3, 200, 91, 0, 1931, 1933, 3, 278, 130, 0, 1932, 1928, 1, 0, 0, 0, 1932, 1929, 1, 0, 0, 0, 1932, 1930, 1, 0, 0, 0, 1932, 1931, 1, 0, 0, 0, 1933, 509, 1, 0, 0, 0, 1934, 1937, 3, 186, 84, 0, 1935, 1937, 3, 278, 130, 0, 1936, 1934, 1, 0, 0, 0, 1936, 1935, 1, 0, 0, 0, 1937, 1941, 1, 0, 0, 0, 1938, 1940, 3, 508, 245, 0, 1939, 1938, 1, 0, 0, 0, 1940, 1943, 1, 0, 0, 0, 1941, 1939, 1, 0, 0, 0, 1941, 1942, 1, 0, 0, 0, 1942, 1954, 1, 0, 0, 0, 1943, 1941, 1, 0, 0, 0, 1944, 1947, 3, 200, 91, 0, 1945, 1947, 3, 194, 88, 0, 1946, 1944, 1, 0, 0, 0, 1946, 1945, 1, 0, 0, 0, 1947, 1949, 1, 0, 0, 0, 1948, 1950, 3, 508, 245, 0, 1949, 1948, 1, 0, 0, 0, 1950, 1951, 1, 0, 0, 0, 1951, 1949, 1, 0, 0, 0, 1951, 1952, 1, 0, 0, 0, 1952, 1954, 1, 0, 0, 0, 1953, 1936, 1, 0, 0, 0, 1953, 1946, 1, 0, 0, 0, 1954, 511, 1, 0, 0, 0, 1955, 1958, 3, 510, 246, 0, 1956, 1958, 3, 306, 144, 0, 1957, 1955, 1, 0, 0, 0, 1957, 1956, 1, 0, 0, 0, 1958, 1959, 1, 0, 0, 0, 1959, 1957, 1, 0, 0, 0, 1959, 1960, 1, 0, 0, 0, 1960, 513, 1, 0, 0, 0, 1961, 1962, 3, 18, 0, 0, 1962, 1963, 1, 0, 0, 0, 1963, 1964, 6, 248, 0, 0, 1964, 515, 1, 0, 0, 0, 1965, 1966, 3, 20, 1, 0, 1966, 1967, 1, 0, 0, 0, 1967, 1968, 6, 249, 0, 0, 1968, 517, 1, 0, 0, 0, 1969, 1970, 3, 22, 2, 0, 1970, 1971, 1, 0, 0, 0, 1971, 1972, 6, 250, 0, 0, 1972, 519, 1, 0, 0, 0, 1973, 1974, 3, 182, 82, 0, 1974, 1975, 1, 0, 0, 0, 1975, 1976, 6, 251, 16, 0, 1976, 1977, 6, 251, 17, 0, 1977, 521, 1, 0, 0, 0, 1978, 1979, 3, 302, 142, 0, 1979, 1980, 1, 0, 0, 0, 1980, 1981, 6, 252, 18, 0, 1981, 1982, 6, 252, 17, 0, 1982, 1983, 6, 252, 17, 0, 1983, 523, 1, 0, 0, 0, 1984, 1985, 3, 296, 139, 0, 1985, 1986, 1, 0, 0, 0, 1986, 1987, 6, 253, 23, 0, 1987, 525, 1, 0, 0, 0, 1988, 1989, 3, 298, 140, 0, 1989, 1990, 1, 0, 0, 0, 1990, 1991, 6, 254, 24, 0, 1991, 527, 1, 0, 0, 0, 1992, 1993, 3, 214, 98, 0, 1993, 1994, 1, 0, 0, 0, 1994, 1995, 6, 255, 31, 0, 1995, 529, 1, 0, 0, 0, 1996, 1997, 3, 224, 103, 0, 1997, 1998, 1, 0, 0, 0, 1998, 1999, 6, 256, 22, 0, 1999, 531, 1, 0, 0, 0, 2000, 2001, 3, 228, 105, 0, 2001, 2002, 1, 0, 0, 0, 2002, 2003, 6, 257, 21, 0, 2003, 533, 1, 0, 0, 0, 2004, 2005, 3, 252, 117, 0, 2005, 2006, 1, 0, 0, 0, 2006, 2007, 6, 258, 33, 0, 2007, 535, 1, 0, 0, 0, 2008, 2009, 3, 292, 137, 0, 2009, 2010, 1, 0, 0, 0, 2010, 2011, 6, 259, 34, 0, 2011, 537, 1, 0, 0, 0, 2012, 2013, 3, 288, 135, 0, 2013, 2014, 1, 0, 0, 0, 2014, 2015, 6, 260, 35, 0, 2015, 539, 1, 0, 0, 0, 2016, 2017, 3, 294, 138, 0, 2017, 2018, 1, 0, 0, 0, 2018, 2019, 6, 261, 36, 0, 2019, 541, 1, 0, 0, 0, 2020, 2021, 7, 4, 0, 0, 2021, 2022, 7, 17, 0, 0, 2022, 543, 1, 0, 0, 0, 2023, 2024, 3, 512, 247, 0, 2024, 2025, 1, 0, 0, 0, 2025, 2026, 6, 263, 32, 0, 2026, 545, 1, 0, 0, 0, 2027, 2028, 3, 18, 0, 0, 2028, 2029, 1, 0, 0, 0, 2029, 2030, 6, 264, 0, 0, 2030, 547, 1, 0, 0, 0, 2031, 2032, 3, 20, 1, 0, 2032, 2033, 1, 0, 0, 0, 2033, 2034, 6, 265, 0, 0, 2034, 549, 1, 0, 0, 0, 2035, 2036, 3, 22, 2, 0, 2036, 2037, 1, 0, 0, 0, 2037, 2038, 6, 266, 0, 0, 2038, 551, 1, 0, 0, 0, 2039, 2040, 3, 256, 119, 0, 2040, 2041, 1, 0, 0, 0, 2041, 2042, 6, 267, 45, 0, 2042, 553, 1, 0, 0, 0, 2043, 2044, 3, 230, 106, 0, 2044, 2045, 1, 0, 0, 0, 2045, 2046, 6, 268, 46, 0, 2046, 555, 1, 0, 0, 0, 2047, 2048, 3, 244, 113, 0, 2048, 2049, 1, 0, 0, 0, 2049, 2050, 6, 269, 47, 0, 2050, 557, 1, 0, 0, 0, 2051, 2052, 3, 222, 102, 0, 2052, 2053, 1, 0, 0, 0, 2053, 2054, 6, 270, 48, 0, 2054, 2055, 6, 270, 17, 0, 2055, 559, 1, 0, 0, 0, 2056, 2057, 3, 214, 98, 0, 2057, 2058, 1, 0, 0, 0, 2058, 2059, 6, 271, 31, 0, 2059, 561, 1, 0, 0, 0, 2060, 2061, 3, 204, 93, 0, 2061, 2062, 1, 0, 0, 0, 2062, 2063, 6, 272, 30, 0, 2063, 563, 1, 0, 0, 0, 2064, 2065, 3, 304, 143, 0, 2065, 2066, 1, 0, 0, 0, 2066, 2067, 6, 273, 26, 0, 2067, 565, 1, 0, 0, 0, 2068, 2069, 3, 308, 145, 0, 2069, 2070, 1, 0, 0, 0, 2070, 2071, 6, 274, 25, 0, 2071, 567, 1, 0, 0, 0, 2072, 2073, 3, 208, 95, 0, 2073, 2074, 1, 0, 0, 0, 2074, 2075, 6, 275, 49, 0, 2075, 569, 1, 0, 0, 0, 2076, 2077, 3, 206, 94, 0, 2077, 2078, 1, 0, 0, 0, 2078, 2079, 6, 276, 50, 0, 2079, 571, 1, 0, 0, 0, 2080, 2081, 3, 224, 103, 0, 2081, 2082, 1, 0, 0, 0, 2082, 2083, 6, 277, 22, 0, 2083, 573, 1, 0, 0, 0, 2084, 2085, 3, 228, 105, 0, 2085, 2086, 1, 0, 0, 0, 2086, 2087, 6, 278, 21, 0, 2087, 575, 1, 0, 0, 0, 2088, 2089, 3, 252, 117, 0, 2089, 2090, 1, 0, 0, 0, 2090, 2091, 6, 279, 33, 0, 2091, 577, 1, 0, 0, 0, 2092, 2093, 3, 292, 137, 0, 2093, 2094, 1, 0, 0, 0, 2094, 2095, 6, 280, 34, 0, 2095, 579, 1, 0, 0, 0, 2096, 2097, 3, 288, 135, 0, 2097, 2098, 1, 0, 0, 0, 2098, 2099, 6, 281, 35, 0, 2099, 581, 1, 0, 0, 0, 2100, 2101, 3, 294, 138, 0, 2101, 2102, 1, 0, 0, 0, 2102, 2103, 6, 282, 36, 0, 2103, 583, 1, 0, 0, 0, 2104, 2105, 3, 296, 139, 0, 2105, 2106, 1, 0, 0, 0, 2106, 2107, 6, 283, 23, 0, 2107, 585, 1, 0, 0, 0, 2108, 2109, 3, 298, 140, 0, 2109, 2110, 1, 0, 0, 0, 2110, 2111, 6, 284, 24, 0, 2111, 587, 1, 0, 0, 0, 2112, 2113, 3, 512, 247, 0, 2113, 2114, 1, 0, 0, 0, 2114, 2115, 6, 285, 32, 0, 2115, 589, 1, 0, 0, 0, 2116, 2117, 3, 18, 0, 0, 2117, 2118, 1, 0, 0, 0, 2118, 2119, 6, 286, 0, 0, 2119, 591, 1, 0, 0, 0, 2120, 2121, 3, 20, 1, 0, 2121, 2122, 1, 0, 0, 0, 2122, 2123, 6, 287, 0, 0, 2123, 593, 1, 0, 0, 0, 2124, 2125, 3, 22, 2, 0, 2125, 2126, 1, 0, 0, 0, 2126, 2127, 6, 288, 0, 0, 2127, 595, 1, 0, 0, 0, 2128, 2129, 3, 182, 82, 0, 2129, 2130, 1, 0, 0, 0, 2130, 2131, 6, 289, 16, 0, 2131, 2132, 6, 289, 17, 0, 2132, 597, 1, 0, 0, 0, 2133, 2134, 7, 10, 0, 0, 2134, 2135, 7, 5, 0, 0, 2135, 2136, 7, 21, 0, 0, 2136, 2137, 7, 9, 0, 0, 2137, 599, 1, 0, 0, 0, 2138, 2139, 3, 18, 0, 0, 2139, 2140, 1, 0, 0, 0, 2140, 2141, 6, 291, 0, 0, 2141, 601, 1, 0, 0, 0, 2142, 2143, 3, 20, 1, 0, 2143, 2144, 1, 0, 0, 0, 2144, 2145, 6, 292, 0, 0, 2145, 603, 1, 0, 0, 0, 2146, 2147, 3, 22, 2, 0, 2147, 2148, 1, 0, 0, 0, 2148, 2149, 6, 293, 0, 0, 2149, 605, 1, 0, 0, 0, 70, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 612, 616, 619, 628, 630, 641, 930, 1015, 1019, 1024, 1156, 1161, 1170, 1177, 1182, 1184, 1195, 1203, 1206, 1208, 1213, 1218, 1224, 1231, 1236, 1242, 1245, 1253, 1257, 1398, 1403, 1410, 1412, 1417, 1422, 1429, 1431, 1457, 1462, 1467, 1469, 1475, 1531, 1536, 1932, 1936, 1941, 1946, 1951, 1953, 1957, 1959, 51, 0, 1, 0, 5, 1, 0, 5, 2, 0, 5, 4, 0, 5, 5, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 5, 9, 0, 5, 10, 0, 5, 11, 0, 5, 13, 0, 5, 14, 0, 5, 15, 0, 5, 16, 0, 5, 17, 0, 7, 50, 0, 4, 0, 0, 7, 99, 0, 7, 73, 0, 7, 141, 0, 7, 63, 0, 7, 61, 0, 7, 96, 0, 7, 97, 0, 7, 101, 0, 7, 100, 0, 5, 3, 0, 7, 78, 0, 7, 40, 0, 7, 51, 0, 7, 56, 0, 7, 137, 0, 7, 75, 0, 7, 94, 0, 7, 93, 0, 7, 95, 0, 7, 98, 0, 5, 0, 0, 7, 17, 0, 7, 59, 0, 7, 58, 0, 7, 106, 0, 7, 57, 0, 5, 12, 0, 7, 77, 0, 7, 64, 0, 7, 71, 0, 7, 60, 0, 7, 53, 0, 7, 52, 0] diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java index 4f34f977c99f1..2b96b8d56a8cb 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java @@ -25,122 +25,122 @@ public class EsqlBaseLexer extends LexerConfig { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - LINE_COMMENT=1, MULTILINE_COMMENT=2, WS=3, CHANGE_POINT=4, ENRICH=5, DEV_EXPLAIN=6, - COMPLETION=7, DISSECT=8, EVAL=9, GROK=10, LIMIT=11, RERANK=12, ROW=13, - SAMPLE=14, SORT=15, STATS=16, WHERE=17, FROM=18, TS=19, FORK=20, FUSE=21, - INLINE=22, INLINESTATS=23, JOIN_LOOKUP=24, DEV_JOIN_FULL=25, DEV_JOIN_LEFT=26, - DEV_JOIN_RIGHT=27, DEV_LOOKUP=28, MV_EXPAND=29, DROP=30, KEEP=31, DEV_INSIST=32, - RENAME=33, SET=34, SHOW=35, UNKNOWN_CMD=36, CHANGE_POINT_LINE_COMMENT=37, - CHANGE_POINT_MULTILINE_COMMENT=38, CHANGE_POINT_WS=39, ENRICH_POLICY_NAME=40, - ENRICH_LINE_COMMENT=41, ENRICH_MULTILINE_COMMENT=42, ENRICH_WS=43, ENRICH_FIELD_LINE_COMMENT=44, - ENRICH_FIELD_MULTILINE_COMMENT=45, ENRICH_FIELD_WS=46, EXPLAIN_WS=47, - EXPLAIN_LINE_COMMENT=48, EXPLAIN_MULTILINE_COMMENT=49, PIPE=50, QUOTED_STRING=51, - INTEGER_LITERAL=52, DECIMAL_LITERAL=53, AND=54, ASC=55, ASSIGN=56, BY=57, - CAST_OP=58, COLON=59, SEMICOLON=60, COMMA=61, DESC=62, DOT=63, FALSE=64, - FIRST=65, IN=66, IS=67, LAST=68, LIKE=69, NOT=70, NULL=71, NULLS=72, ON=73, - OR=74, PARAM=75, RLIKE=76, TRUE=77, WITH=78, EQ=79, CIEQ=80, NEQ=81, LT=82, - LTE=83, GT=84, GTE=85, PLUS=86, MINUS=87, ASTERISK=88, SLASH=89, PERCENT=90, - LEFT_BRACES=91, RIGHT_BRACES=92, DOUBLE_PARAMS=93, NAMED_OR_POSITIONAL_PARAM=94, - NAMED_OR_POSITIONAL_DOUBLE_PARAMS=95, OPENING_BRACKET=96, CLOSING_BRACKET=97, - LP=98, RP=99, UNQUOTED_IDENTIFIER=100, QUOTED_IDENTIFIER=101, EXPR_LINE_COMMENT=102, - EXPR_MULTILINE_COMMENT=103, EXPR_WS=104, METADATA=105, UNQUOTED_SOURCE=106, - FROM_LINE_COMMENT=107, FROM_MULTILINE_COMMENT=108, FROM_WS=109, FORK_WS=110, - FORK_LINE_COMMENT=111, FORK_MULTILINE_COMMENT=112, GROUP=113, SCORE=114, - KEY=115, FUSE_LINE_COMMENT=116, FUSE_MULTILINE_COMMENT=117, FUSE_WS=118, - INLINE_STATS=119, INLINE_LINE_COMMENT=120, INLINE_MULTILINE_COMMENT=121, - INLINE_WS=122, JOIN=123, USING=124, JOIN_LINE_COMMENT=125, JOIN_MULTILINE_COMMENT=126, - JOIN_WS=127, LOOKUP_LINE_COMMENT=128, LOOKUP_MULTILINE_COMMENT=129, LOOKUP_WS=130, - LOOKUP_FIELD_LINE_COMMENT=131, LOOKUP_FIELD_MULTILINE_COMMENT=132, LOOKUP_FIELD_WS=133, - MVEXPAND_LINE_COMMENT=134, MVEXPAND_MULTILINE_COMMENT=135, MVEXPAND_WS=136, - ID_PATTERN=137, PROJECT_LINE_COMMENT=138, PROJECT_MULTILINE_COMMENT=139, - PROJECT_WS=140, AS=141, RENAME_LINE_COMMENT=142, RENAME_MULTILINE_COMMENT=143, - RENAME_WS=144, SET_LINE_COMMENT=145, SET_MULTILINE_COMMENT=146, SET_WS=147, + LINE_COMMENT=1, MULTILINE_COMMENT=2, WS=3, CHANGE_POINT=4, ENRICH=5, DEV_EXPLAIN=6, + COMPLETION=7, DISSECT=8, EVAL=9, GROK=10, LIMIT=11, RERANK=12, ROW=13, + SAMPLE=14, SORT=15, STATS=16, WHERE=17, FROM=18, TS=19, FORK=20, FUSE=21, + INLINE=22, INLINESTATS=23, JOIN_LOOKUP=24, DEV_JOIN_FULL=25, DEV_JOIN_LEFT=26, + DEV_JOIN_RIGHT=27, DEV_LOOKUP=28, MV_EXPAND=29, DROP=30, KEEP=31, DEV_INSIST=32, + RENAME=33, SET=34, SHOW=35, UNKNOWN_CMD=36, CHANGE_POINT_LINE_COMMENT=37, + CHANGE_POINT_MULTILINE_COMMENT=38, CHANGE_POINT_WS=39, ENRICH_POLICY_NAME=40, + ENRICH_LINE_COMMENT=41, ENRICH_MULTILINE_COMMENT=42, ENRICH_WS=43, ENRICH_FIELD_LINE_COMMENT=44, + ENRICH_FIELD_MULTILINE_COMMENT=45, ENRICH_FIELD_WS=46, EXPLAIN_WS=47, + EXPLAIN_LINE_COMMENT=48, EXPLAIN_MULTILINE_COMMENT=49, PIPE=50, QUOTED_STRING=51, + INTEGER_LITERAL=52, DECIMAL_LITERAL=53, AND=54, ASC=55, ASSIGN=56, BY=57, + CAST_OP=58, COLON=59, SEMICOLON=60, COMMA=61, DESC=62, DOT=63, FALSE=64, + FIRST=65, IN=66, IS=67, LAST=68, LIKE=69, NOT=70, NULL=71, NULLS=72, ON=73, + OR=74, PARAM=75, RLIKE=76, TRUE=77, WITH=78, EQ=79, CIEQ=80, NEQ=81, LT=82, + LTE=83, GT=84, GTE=85, PLUS=86, MINUS=87, ASTERISK=88, SLASH=89, PERCENT=90, + LEFT_BRACES=91, RIGHT_BRACES=92, DOUBLE_PARAMS=93, NAMED_OR_POSITIONAL_PARAM=94, + NAMED_OR_POSITIONAL_DOUBLE_PARAMS=95, OPENING_BRACKET=96, CLOSING_BRACKET=97, + LP=98, RP=99, UNQUOTED_IDENTIFIER=100, QUOTED_IDENTIFIER=101, EXPR_LINE_COMMENT=102, + EXPR_MULTILINE_COMMENT=103, EXPR_WS=104, METADATA=105, UNQUOTED_SOURCE=106, + FROM_LINE_COMMENT=107, FROM_MULTILINE_COMMENT=108, FROM_WS=109, FORK_WS=110, + FORK_LINE_COMMENT=111, FORK_MULTILINE_COMMENT=112, GROUP=113, SCORE=114, + KEY=115, FUSE_LINE_COMMENT=116, FUSE_MULTILINE_COMMENT=117, FUSE_WS=118, + INLINE_STATS=119, INLINE_LINE_COMMENT=120, INLINE_MULTILINE_COMMENT=121, + INLINE_WS=122, JOIN=123, USING=124, JOIN_LINE_COMMENT=125, JOIN_MULTILINE_COMMENT=126, + JOIN_WS=127, LOOKUP_LINE_COMMENT=128, LOOKUP_MULTILINE_COMMENT=129, LOOKUP_WS=130, + LOOKUP_FIELD_LINE_COMMENT=131, LOOKUP_FIELD_MULTILINE_COMMENT=132, LOOKUP_FIELD_WS=133, + MVEXPAND_LINE_COMMENT=134, MVEXPAND_MULTILINE_COMMENT=135, MVEXPAND_WS=136, + ID_PATTERN=137, PROJECT_LINE_COMMENT=138, PROJECT_MULTILINE_COMMENT=139, + PROJECT_WS=140, AS=141, RENAME_LINE_COMMENT=142, RENAME_MULTILINE_COMMENT=143, + RENAME_WS=144, SET_LINE_COMMENT=145, SET_MULTILINE_COMMENT=146, SET_WS=147, INFO=148, SHOW_LINE_COMMENT=149, SHOW_MULTILINE_COMMENT=150, SHOW_WS=151; public static final int - CHANGE_POINT_MODE=1, ENRICH_MODE=2, ENRICH_FIELD_MODE=3, EXPLAIN_MODE=4, - EXPRESSION_MODE=5, FROM_MODE=6, FORK_MODE=7, FUSE_MODE=8, INLINE_MODE=9, - JOIN_MODE=10, LOOKUP_MODE=11, LOOKUP_FIELD_MODE=12, MVEXPAND_MODE=13, + CHANGE_POINT_MODE=1, ENRICH_MODE=2, ENRICH_FIELD_MODE=3, EXPLAIN_MODE=4, + EXPRESSION_MODE=5, FROM_MODE=6, FORK_MODE=7, FUSE_MODE=8, INLINE_MODE=9, + JOIN_MODE=10, LOOKUP_MODE=11, LOOKUP_FIELD_MODE=12, MVEXPAND_MODE=13, PROJECT_MODE=14, RENAME_MODE=15, SET_MODE=16, SHOW_MODE=17; public static String[] channelNames = { "DEFAULT_TOKEN_CHANNEL", "HIDDEN" }; public static String[] modeNames = { - "DEFAULT_MODE", "CHANGE_POINT_MODE", "ENRICH_MODE", "ENRICH_FIELD_MODE", - "EXPLAIN_MODE", "EXPRESSION_MODE", "FROM_MODE", "FORK_MODE", "FUSE_MODE", - "INLINE_MODE", "JOIN_MODE", "LOOKUP_MODE", "LOOKUP_FIELD_MODE", "MVEXPAND_MODE", + "DEFAULT_MODE", "CHANGE_POINT_MODE", "ENRICH_MODE", "ENRICH_FIELD_MODE", + "EXPLAIN_MODE", "EXPRESSION_MODE", "FROM_MODE", "FORK_MODE", "FUSE_MODE", + "INLINE_MODE", "JOIN_MODE", "LOOKUP_MODE", "LOOKUP_FIELD_MODE", "MVEXPAND_MODE", "PROJECT_MODE", "RENAME_MODE", "SET_MODE", "SHOW_MODE" }; private static String[] makeRuleNames() { return new String[] { - "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "CHANGE_POINT", "ENRICH", - "DEV_EXPLAIN", "COMPLETION", "DISSECT", "EVAL", "GROK", "LIMIT", "RERANK", - "ROW", "SAMPLE", "SORT", "STATS", "WHERE", "FROM", "TS", "FORK", "FUSE", - "INLINE", "INLINESTATS", "JOIN_LOOKUP", "DEV_JOIN_FULL", "DEV_JOIN_LEFT", - "DEV_JOIN_RIGHT", "DEV_LOOKUP", "MV_EXPAND", "DROP", "KEEP", "DEV_INSIST", - "RENAME", "SET", "SHOW", "UNKNOWN_CMD", "CHANGE_POINT_PIPE", "CHANGE_POINT_RP", - "CHANGE_POINT_ON", "CHANGE_POINT_AS", "CHANGE_POINT_DOT", "CHANGE_POINT_COMMA", - "CHANGE_POINT_OPENING_BRACKET", "CHANGE_POINT_CLOSING_BRACKET", "CHANGE_POINT_QUOTED_IDENTIFIER", - "CHANGE_POINT_UNQUOTED_IDENTIFIER", "CHANGE_POINT_LINE_COMMENT", "CHANGE_POINT_MULTILINE_COMMENT", - "CHANGE_POINT_WS", "ENRICH_PIPE", "ENRICH_RP", "ENRICH_ON", "ENRICH_WITH", - "ENRICH_POLICY_NAME_BODY", "ENRICH_POLICY_NAME", "ENRICH_MODE_UNQUOTED_VALUE", - "ENRICH_QUOTED_POLICY_NAME", "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", - "ENRICH_WS", "ENRICH_FIELD_PIPE", "ENRICH_FIELD_RP", "ENRICH_FIELD_OPENING_BRACKET", - "ENRICH_FIELD_CLOSING_BRACKET", "ENRICH_FIELD_ASSIGN", "ENRICH_FIELD_COMMA", - "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", "ENRICH_FIELD_ID_PATTERN", "ENRICH_FIELD_QUOTED_IDENTIFIER", - "ENRICH_FIELD_PARAM", "ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM", "ENRICH_FIELD_DOUBLE_PARAMS", - "ENRICH_FIELD_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "ENRICH_FIELD_LINE_COMMENT", - "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "EXPLAIN_LP", "EXPLAIN_PIPE", - "EXPLAIN_WS", "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", "PIPE", - "DIGIT", "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", - "ASPERAND", "BACKQUOTE", "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", - "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "AND", "ASC", - "ASSIGN", "BY", "CAST_OP", "COLON", "SEMICOLON", "COMMA", "DESC", "DOT", - "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "NOT", "NULL", "NULLS", - "ON", "OR", "PARAM", "RLIKE", "TRUE", "WITH", "EQ", "CIEQ", "NEQ", "LT", - "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", - "LEFT_BRACES", "RIGHT_BRACES", "DOUBLE_PARAMS", "NESTED_WHERE", "NAMED_OR_POSITIONAL_PARAM", - "NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "OPENING_BRACKET", "CLOSING_BRACKET", - "LP", "RP", "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", - "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "FROM_PIPE", - "FROM_COLON", "FROM_SELECTOR", "FROM_COMMA", "FROM_ASSIGN", "METADATA", - "FROM_RP", "UNQUOTED_SOURCE_PART", "UNQUOTED_SOURCE", "FROM_UNQUOTED_SOURCE", - "FROM_QUOTED_SOURCE", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", - "FROM_WS", "FORK_LP", "FORK_RP", "FORK_PIPE", "FORK_WS", "FORK_LINE_COMMENT", - "FORK_MULTILINE_COMMENT", "FUSE_PIPE", "FUSE_RP", "GROUP", "SCORE", "KEY", - "FUSE_WITH", "FUSE_COMMA", "FUSE_DOT", "FUSE_PARAM", "FUSE_NAMED_OR_POSITIONAL_PARAM", - "FUSE_DOUBLE_PARAMS", "FUSE_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "FUSE_BY", - "FUSE_QUOTED_IDENTIFIER", "FUSE_UNQUOTED_IDENTIFIER", "FUSE_LINE_COMMENT", - "FUSE_MULTILINE_COMMENT", "FUSE_WS", "INLINE_STATS", "INLINE_LINE_COMMENT", - "INLINE_MULTILINE_COMMENT", "INLINE_WS", "JOIN_PIPE", "JOIN", "JOIN_AS", - "JOIN_ON", "USING", "JOIN_UNQUOTED_SOURCE", "JOIN_QUOTED_SOURCE", "JOIN_COLON", - "JOIN_LINE_COMMENT", "JOIN_MULTILINE_COMMENT", "JOIN_WS", "LOOKUP_PIPE", - "LOOKUP_RP", "LOOKUP_COLON", "LOOKUP_COMMA", "LOOKUP_DOT", "LOOKUP_ON", - "LOOKUP_UNQUOTED_SOURCE", "LOOKUP_QUOTED_SOURCE", "LOOKUP_LINE_COMMENT", - "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_PIPE", "LOOK_FIELD_RP", - "LOOKUP_FIELD_COMMA", "LOOKUP_FIELD_DOT", "LOOKUP_FIELD_ID_PATTERN", - "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", - "MVEXPAND_PIPE", "MVEXPAND_RP", "MV_EXPAND_OPENING_BRACKET", "MV_EXPAND_CLOSING_BRACKET", - "MVEXPAND_DOT", "MVEXPAND_PARAM", "MVEXPAND_NAMED_OR_POSITIONAL_PARAM", - "MVEXPAND_DOUBLE_PARAMS", "MVEXPAND_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", - "MVEXPAND_QUOTED_IDENTIFIER", "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", - "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "PROJECT_PIPE", "PROJECT_RP", - "PROJECT_DOT", "PROJECT_OPENING_BRACKET", "PROJECT_CLOSING_BRACKET", - "PROJECT_COMMA", "PROJECT_PARAM", "PROJECT_NAMED_OR_POSITIONAL_PARAM", - "PROJECT_DOUBLE_PARAMS", "PROJECT_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", - "UNQUOTED_ID_BODY_WITH_PATTERN", "UNQUOTED_ID_PATTERN", "ID_PATTERN", - "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", "RENAME_PIPE", - "RENAME_RP", "RENAME_OPENING_BRACKET", "RENAME_CLOSING_BRACKET", "RENAME_ASSIGN", - "RENAME_COMMA", "RENAME_DOT", "RENAME_PARAM", "RENAME_NAMED_OR_POSITIONAL_PARAM", - "RENAME_DOUBLE_PARAMS", "RENAME_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "AS", - "RENAME_ID_PATTERN", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", - "RENAME_WS", "SET_TRUE", "SET_FALSE", "SET_NULL", "SET_SEMICOLON", "SET_ASSIGN", - "SET_QUOTED_STRING", "SET_UNQUOTED_IDENTIFIER", "SET_QUOTED_IDENTIFIER", - "SET_DECIMAL_LITERAL", "SET_INTEGER_LITERAL", "SET_COMMA", "SET_DOT", - "SET_PARAM", "SET_NAMED_OR_POSITIONAL_PARAM", "SET_DOUBLE_PARAMS", "SET_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", - "SET_OPENING_BRACKET", "SET_CLOSING_BRACKET", "SET_ID_PATTERN", "SET_LINE_COMMENT", - "SET_MULTILINE_COMMENT", "SET_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", + "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "CHANGE_POINT", "ENRICH", + "DEV_EXPLAIN", "COMPLETION", "DISSECT", "EVAL", "GROK", "LIMIT", "RERANK", + "ROW", "SAMPLE", "SORT", "STATS", "WHERE", "FROM", "TS", "FORK", "FUSE", + "INLINE", "INLINESTATS", "JOIN_LOOKUP", "DEV_JOIN_FULL", "DEV_JOIN_LEFT", + "DEV_JOIN_RIGHT", "DEV_LOOKUP", "MV_EXPAND", "DROP", "KEEP", "DEV_INSIST", + "RENAME", "SET", "SHOW", "UNKNOWN_CMD", "CHANGE_POINT_PIPE", "CHANGE_POINT_RP", + "CHANGE_POINT_ON", "CHANGE_POINT_AS", "CHANGE_POINT_DOT", "CHANGE_POINT_COMMA", + "CHANGE_POINT_OPENING_BRACKET", "CHANGE_POINT_CLOSING_BRACKET", "CHANGE_POINT_QUOTED_IDENTIFIER", + "CHANGE_POINT_UNQUOTED_IDENTIFIER", "CHANGE_POINT_LINE_COMMENT", "CHANGE_POINT_MULTILINE_COMMENT", + "CHANGE_POINT_WS", "ENRICH_PIPE", "ENRICH_RP", "ENRICH_ON", "ENRICH_WITH", + "ENRICH_POLICY_NAME_BODY", "ENRICH_POLICY_NAME", "ENRICH_MODE_UNQUOTED_VALUE", + "ENRICH_QUOTED_POLICY_NAME", "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", + "ENRICH_WS", "ENRICH_FIELD_PIPE", "ENRICH_FIELD_RP", "ENRICH_FIELD_OPENING_BRACKET", + "ENRICH_FIELD_CLOSING_BRACKET", "ENRICH_FIELD_ASSIGN", "ENRICH_FIELD_COMMA", + "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", "ENRICH_FIELD_ID_PATTERN", "ENRICH_FIELD_QUOTED_IDENTIFIER", + "ENRICH_FIELD_PARAM", "ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM", "ENRICH_FIELD_DOUBLE_PARAMS", + "ENRICH_FIELD_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "ENRICH_FIELD_LINE_COMMENT", + "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "EXPLAIN_LP", "EXPLAIN_PIPE", + "EXPLAIN_WS", "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", "PIPE", + "DIGIT", "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", + "ASPERAND", "BACKQUOTE", "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", + "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "AND", "ASC", + "ASSIGN", "BY", "CAST_OP", "COLON", "SEMICOLON", "COMMA", "DESC", "DOT", + "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "NOT", "NULL", "NULLS", + "ON", "OR", "PARAM", "RLIKE", "TRUE", "WITH", "EQ", "CIEQ", "NEQ", "LT", + "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", + "LEFT_BRACES", "RIGHT_BRACES", "DOUBLE_PARAMS", "NESTED_WHERE", "NAMED_OR_POSITIONAL_PARAM", + "NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "OPENING_BRACKET", "CLOSING_BRACKET", + "LP", "RP", "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", + "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "FROM_PIPE", + "FROM_COLON", "FROM_SELECTOR", "FROM_COMMA", "FROM_ASSIGN", "METADATA", + "FROM_RP", "UNQUOTED_SOURCE_PART", "UNQUOTED_SOURCE", "FROM_UNQUOTED_SOURCE", + "FROM_QUOTED_SOURCE", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", + "FROM_WS", "FORK_LP", "FORK_RP", "FORK_PIPE", "FORK_WS", "FORK_LINE_COMMENT", + "FORK_MULTILINE_COMMENT", "FUSE_PIPE", "FUSE_RP", "GROUP", "SCORE", "KEY", + "FUSE_WITH", "FUSE_COMMA", "FUSE_DOT", "FUSE_PARAM", "FUSE_NAMED_OR_POSITIONAL_PARAM", + "FUSE_DOUBLE_PARAMS", "FUSE_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "FUSE_BY", + "FUSE_QUOTED_IDENTIFIER", "FUSE_UNQUOTED_IDENTIFIER", "FUSE_LINE_COMMENT", + "FUSE_MULTILINE_COMMENT", "FUSE_WS", "INLINE_STATS", "INLINE_LINE_COMMENT", + "INLINE_MULTILINE_COMMENT", "INLINE_WS", "JOIN_PIPE", "JOIN", "JOIN_AS", + "JOIN_ON", "USING", "JOIN_UNQUOTED_SOURCE", "JOIN_QUOTED_SOURCE", "JOIN_COLON", + "JOIN_LINE_COMMENT", "JOIN_MULTILINE_COMMENT", "JOIN_WS", "LOOKUP_PIPE", + "LOOKUP_RP", "LOOKUP_COLON", "LOOKUP_COMMA", "LOOKUP_DOT", "LOOKUP_ON", + "LOOKUP_UNQUOTED_SOURCE", "LOOKUP_QUOTED_SOURCE", "LOOKUP_LINE_COMMENT", + "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_PIPE", "LOOK_FIELD_RP", + "LOOKUP_FIELD_COMMA", "LOOKUP_FIELD_DOT", "LOOKUP_FIELD_ID_PATTERN", + "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", + "MVEXPAND_PIPE", "MVEXPAND_RP", "MV_EXPAND_OPENING_BRACKET", "MV_EXPAND_CLOSING_BRACKET", + "MVEXPAND_DOT", "MVEXPAND_PARAM", "MVEXPAND_NAMED_OR_POSITIONAL_PARAM", + "MVEXPAND_DOUBLE_PARAMS", "MVEXPAND_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", + "MVEXPAND_QUOTED_IDENTIFIER", "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", + "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "PROJECT_PIPE", "PROJECT_RP", + "PROJECT_DOT", "PROJECT_OPENING_BRACKET", "PROJECT_CLOSING_BRACKET", + "PROJECT_COMMA", "PROJECT_PARAM", "PROJECT_NAMED_OR_POSITIONAL_PARAM", + "PROJECT_DOUBLE_PARAMS", "PROJECT_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", + "UNQUOTED_ID_BODY_WITH_PATTERN", "UNQUOTED_ID_PATTERN", "ID_PATTERN", + "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", "RENAME_PIPE", + "RENAME_RP", "RENAME_OPENING_BRACKET", "RENAME_CLOSING_BRACKET", "RENAME_ASSIGN", + "RENAME_COMMA", "RENAME_DOT", "RENAME_PARAM", "RENAME_NAMED_OR_POSITIONAL_PARAM", + "RENAME_DOUBLE_PARAMS", "RENAME_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "AS", + "RENAME_ID_PATTERN", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", + "RENAME_WS", "SET_TRUE", "SET_FALSE", "SET_NULL", "SET_SEMICOLON", "SET_ASSIGN", + "SET_QUOTED_STRING", "SET_UNQUOTED_IDENTIFIER", "SET_QUOTED_IDENTIFIER", + "SET_DECIMAL_LITERAL", "SET_INTEGER_LITERAL", "SET_COMMA", "SET_DOT", + "SET_PARAM", "SET_NAMED_OR_POSITIONAL_PARAM", "SET_DOUBLE_PARAMS", "SET_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", + "SET_OPENING_BRACKET", "SET_CLOSING_BRACKET", "SET_ID_PATTERN", "SET_LINE_COMMENT", + "SET_MULTILINE_COMMENT", "SET_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS" }; } @@ -148,55 +148,55 @@ private static String[] makeRuleNames() { private static String[] makeLiteralNames() { return new String[] { - null, null, null, null, "'change_point'", "'enrich'", null, "'completion'", - "'dissect'", "'eval'", "'grok'", "'limit'", "'rerank'", "'row'", "'sample'", - "'sort'", null, "'where'", "'from'", "'ts'", "'fork'", "'fuse'", "'inline'", - "'inlinestats'", "'lookup'", null, null, null, null, "'mv_expand'", "'drop'", - "'keep'", null, "'rename'", "'set'", "'show'", null, null, null, null, - null, null, null, null, null, null, null, null, null, null, "'|'", null, - null, null, "'and'", "'asc'", "'='", "'by'", "'::'", "':'", "';'", "','", - "'desc'", "'.'", "'false'", "'first'", "'in'", "'is'", "'last'", "'like'", - "'not'", "'null'", "'nulls'", "'on'", "'or'", "'?'", "'rlike'", "'true'", - "'with'", "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", - "'-'", "'*'", "'/'", "'%'", "'{'", "'}'", "'??'", null, null, null, "']'", - null, "')'", null, null, null, null, null, "'metadata'", null, null, - null, null, null, null, null, "'group'", "'score'", "'key'", null, null, - null, null, null, null, null, "'join'", "'USING'", null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, "'change_point'", "'enrich'", null, "'completion'", + "'dissect'", "'eval'", "'grok'", "'limit'", "'rerank'", "'row'", "'sample'", + "'sort'", null, "'where'", "'from'", "'ts'", "'fork'", "'fuse'", "'inline'", + "'inlinestats'", "'lookup'", null, null, null, null, "'mv_expand'", "'drop'", + "'keep'", null, "'rename'", "'set'", "'show'", null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "'|'", null, + null, null, "'and'", "'asc'", "'='", "'by'", "'::'", "':'", "';'", "','", + "'desc'", "'.'", "'false'", "'first'", "'in'", "'is'", "'last'", "'like'", + "'not'", "'null'", "'nulls'", "'on'", "'or'", "'?'", "'rlike'", "'true'", + "'with'", "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", + "'-'", "'*'", "'/'", "'%'", "'{'", "'}'", "'??'", null, null, null, "']'", + null, "')'", null, null, null, null, null, "'metadata'", null, null, + null, null, null, null, null, "'group'", "'score'", "'key'", null, null, + null, null, null, null, null, "'join'", "'USING'", null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, "'as'", null, null, null, null, null, null, "'info'" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "CHANGE_POINT", "ENRICH", - "DEV_EXPLAIN", "COMPLETION", "DISSECT", "EVAL", "GROK", "LIMIT", "RERANK", - "ROW", "SAMPLE", "SORT", "STATS", "WHERE", "FROM", "TS", "FORK", "FUSE", - "INLINE", "INLINESTATS", "JOIN_LOOKUP", "DEV_JOIN_FULL", "DEV_JOIN_LEFT", - "DEV_JOIN_RIGHT", "DEV_LOOKUP", "MV_EXPAND", "DROP", "KEEP", "DEV_INSIST", - "RENAME", "SET", "SHOW", "UNKNOWN_CMD", "CHANGE_POINT_LINE_COMMENT", - "CHANGE_POINT_MULTILINE_COMMENT", "CHANGE_POINT_WS", "ENRICH_POLICY_NAME", - "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", - "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "EXPLAIN_WS", "EXPLAIN_LINE_COMMENT", - "EXPLAIN_MULTILINE_COMMENT", "PIPE", "QUOTED_STRING", "INTEGER_LITERAL", - "DECIMAL_LITERAL", "AND", "ASC", "ASSIGN", "BY", "CAST_OP", "COLON", - "SEMICOLON", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", - "LIKE", "NOT", "NULL", "NULLS", "ON", "OR", "PARAM", "RLIKE", "TRUE", - "WITH", "EQ", "CIEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", - "ASTERISK", "SLASH", "PERCENT", "LEFT_BRACES", "RIGHT_BRACES", "DOUBLE_PARAMS", - "NAMED_OR_POSITIONAL_PARAM", "NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "OPENING_BRACKET", - "CLOSING_BRACKET", "LP", "RP", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", - "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "METADATA", - "UNQUOTED_SOURCE", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", "FROM_WS", - "FORK_WS", "FORK_LINE_COMMENT", "FORK_MULTILINE_COMMENT", "GROUP", "SCORE", - "KEY", "FUSE_LINE_COMMENT", "FUSE_MULTILINE_COMMENT", "FUSE_WS", "INLINE_STATS", - "INLINE_LINE_COMMENT", "INLINE_MULTILINE_COMMENT", "INLINE_WS", "JOIN", - "USING", "JOIN_LINE_COMMENT", "JOIN_MULTILINE_COMMENT", "JOIN_WS", "LOOKUP_LINE_COMMENT", - "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_LINE_COMMENT", - "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "MVEXPAND_LINE_COMMENT", - "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "ID_PATTERN", "PROJECT_LINE_COMMENT", - "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", "AS", "RENAME_LINE_COMMENT", - "RENAME_MULTILINE_COMMENT", "RENAME_WS", "SET_LINE_COMMENT", "SET_MULTILINE_COMMENT", + null, "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "CHANGE_POINT", "ENRICH", + "DEV_EXPLAIN", "COMPLETION", "DISSECT", "EVAL", "GROK", "LIMIT", "RERANK", + "ROW", "SAMPLE", "SORT", "STATS", "WHERE", "FROM", "TS", "FORK", "FUSE", + "INLINE", "INLINESTATS", "JOIN_LOOKUP", "DEV_JOIN_FULL", "DEV_JOIN_LEFT", + "DEV_JOIN_RIGHT", "DEV_LOOKUP", "MV_EXPAND", "DROP", "KEEP", "DEV_INSIST", + "RENAME", "SET", "SHOW", "UNKNOWN_CMD", "CHANGE_POINT_LINE_COMMENT", + "CHANGE_POINT_MULTILINE_COMMENT", "CHANGE_POINT_WS", "ENRICH_POLICY_NAME", + "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", + "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "EXPLAIN_WS", "EXPLAIN_LINE_COMMENT", + "EXPLAIN_MULTILINE_COMMENT", "PIPE", "QUOTED_STRING", "INTEGER_LITERAL", + "DECIMAL_LITERAL", "AND", "ASC", "ASSIGN", "BY", "CAST_OP", "COLON", + "SEMICOLON", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", + "LIKE", "NOT", "NULL", "NULLS", "ON", "OR", "PARAM", "RLIKE", "TRUE", + "WITH", "EQ", "CIEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", + "ASTERISK", "SLASH", "PERCENT", "LEFT_BRACES", "RIGHT_BRACES", "DOUBLE_PARAMS", + "NAMED_OR_POSITIONAL_PARAM", "NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "OPENING_BRACKET", + "CLOSING_BRACKET", "LP", "RP", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", + "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "METADATA", + "UNQUOTED_SOURCE", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", "FROM_WS", + "FORK_WS", "FORK_LINE_COMMENT", "FORK_MULTILINE_COMMENT", "GROUP", "SCORE", + "KEY", "FUSE_LINE_COMMENT", "FUSE_MULTILINE_COMMENT", "FUSE_WS", "INLINE_STATS", + "INLINE_LINE_COMMENT", "INLINE_MULTILINE_COMMENT", "INLINE_WS", "JOIN", + "USING", "JOIN_LINE_COMMENT", "JOIN_MULTILINE_COMMENT", "JOIN_WS", "LOOKUP_LINE_COMMENT", + "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_LINE_COMMENT", + "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "MVEXPAND_LINE_COMMENT", + "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "ID_PATTERN", "PROJECT_LINE_COMMENT", + "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", "AS", "RENAME_LINE_COMMENT", + "RENAME_MULTILINE_COMMENT", "RENAME_WS", "SET_LINE_COMMENT", "SET_MULTILINE_COMMENT", "SET_WS", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS" }; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/UnionAll.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/UnionAll.java new file mode 100644 index 0000000000000..a291e43151e62 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/UnionAll.java @@ -0,0 +1,177 @@ +/* + * 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.logical; + +import org.elasticsearch.xpack.esql.capabilities.PostOptimizationPlanVerificationAware; +import org.elasticsearch.xpack.esql.common.Failure; +import org.elasticsearch.xpack.esql.common.Failures; +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.core.type.DataType; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +public class UnionAll extends Fork implements PostOptimizationPlanVerificationAware { + + private final List output; + + public UnionAll(Source source, List children, List output) { + super(source, children, output); + this.output = output; + } + + @Override + public LogicalPlan replaceChildren(List newChildren) { + return new UnionAll(source(), newChildren, output); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, UnionAll::new, children(), output); + } + + @Override + public UnionAll replaceSubPlans(List subPlans) { + return new UnionAll(source(), subPlans, output); + } + + @Override + public int hashCode() { + return Objects.hash(UnionAll.class, children()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UnionAll other = (UnionAll) o; + + return Objects.equals(children(), other.children()); + } + + @Override + public BiConsumer postAnalysisPlanVerification() { + return UnionAll::checkUnionAll; + } + + private static void checkUnionAll(LogicalPlan plan, Failures failures) { + if (plan instanceof UnionAll == false) { + return; + } + UnionAll unionAll = (UnionAll) plan; + + Map outputTypes = unionAll.output().stream().collect(Collectors.toMap(Attribute::name, Attribute::dataType)); + Map> childrenTypes = unionAll.children() + .stream() + .flatMap(p -> p.output().stream()) + .collect(Collectors.groupingBy(Attribute::name, Collectors.mapping(Attribute::dataType, Collectors.toList()))); + + unionAll.children().forEach(subPlan -> { + for (Attribute attr : subPlan.output()) { + var expected = outputTypes.get(attr.name()); + + // UnionAll with unsupported types should not be allowed here, otherwise runtime couldn't handle it + // TODO make this a failure + if (expected == DataType.UNSUPPORTED) { + continue; + /* + failures.add( + Failure.fail( + attr, + "Column [{}] has conflicting data types in subqueries: [{}]", + attr.name(), + childrenTypes.get(attr.name()).stream().map(Objects::toString).collect(Collectors.joining(", ")) + ) + ); + */ + } + + var actual = attr.dataType(); + if (actual != expected) { + failures.add( + Failure.fail( + attr, + "Column [{}] has conflicting data types in subqueries: [{}] and [{}]", + attr.name(), + actual, + expected + ) + ); + } + } + }); + } + + private static Map buildOutputTypes(UnionAll unionAll) { + return unionAll.output().stream().collect(Collectors.toMap(Attribute::name, Attribute::dataType)); + } + + private static Map> buildChildrenTypes(UnionAll unionAll) { + return unionAll.children() + .stream() + .flatMap(p -> p.output().stream()) + .collect(Collectors.groupingBy(Attribute::name, Collectors.mapping(Attribute::dataType, Collectors.toList()))); + } + + private static void checkChildAttributes( + LogicalPlan child, + Map outputTypes, + Map> childrenTypes, + Failures failures + ) { + for (Attribute attr : child.output()) { + DataType expected = outputTypes.get(attr.name()); + + if (expected == DataType.UNSUPPORTED) { + reportUnsupportedType(attr, childrenTypes, failures); + continue; + } + + DataType actual = attr.dataType(); + if (actual != expected) { + reportTypeConflict(attr, actual, expected, failures); + } + } + } + + private static void reportUnsupportedType(Attribute attr, Map> childrenTypes, Failures failures) { + String types = childrenTypes.get(attr.name()).stream().map(Objects::toString).collect(Collectors.joining(", ")); + + failures.add(Failure.fail(attr, "Column [{}] has unsupported/conflicting data types in subqueries: [{}]", attr.name(), types)); + } + + private static void reportTypeConflict(Attribute attr, DataType actual, DataType expected, Failures failures) { + failures.add( + Failure.fail(attr, "Column [{}] has conflicting data types in subqueries: [{}] vs [{}]", attr.name(), actual, expected) + ); + } + + @Override + public BiConsumer postOptimizationPlanVerification() { + return UnionAll::checkNestedUnionAlls; + } + + private static void checkNestedUnionAlls(LogicalPlan logicalPlan, Failures failures) { + if (logicalPlan instanceof UnionAll unionAll) { + unionAll.forEachDown(UnionAll.class, otherUnionAll -> { + if (unionAll == otherUnionAll) { + return; + } + failures.add(Failure.fail(otherUnionAll, "Nested subqueries are not supported")); + }); + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java index 7f98ba0e1ed98..d76e8cf664ab4 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java @@ -74,6 +74,7 @@ import org.elasticsearch.xpack.esql.enrich.LookupFromIndexOperator; import org.elasticsearch.xpack.esql.execution.PlanExecutor; import org.elasticsearch.xpack.esql.expression.ExpressionWritables; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.io.stream.ExpressionQueryBuilder; import org.elasticsearch.xpack.esql.io.stream.PlanStreamWrapperQueryBuilder; import org.elasticsearch.xpack.esql.plan.PlanWritables; @@ -82,6 +83,21 @@ import org.elasticsearch.xpack.esql.querydsl.query.SingleValueQuery; import org.elasticsearch.xpack.esql.querylog.EsqlQueryLog; import org.elasticsearch.xpack.esql.session.IndexResolver; +import org.elasticsearch.xpack.esql.view.ClusterViewService; +import org.elasticsearch.xpack.esql.view.DeleteViewAction; +import org.elasticsearch.xpack.esql.view.GetViewAction; +import org.elasticsearch.xpack.esql.view.ListViewsAction; +import org.elasticsearch.xpack.esql.view.PutViewAction; +import org.elasticsearch.xpack.esql.view.RestDeleteViewAction; +import org.elasticsearch.xpack.esql.view.RestGetViewAction; +import org.elasticsearch.xpack.esql.view.RestListViewsAction; +import org.elasticsearch.xpack.esql.view.RestPutViewAction; +import org.elasticsearch.xpack.esql.view.TransportDeleteViewAction; +import org.elasticsearch.xpack.esql.view.TransportGetViewAction; +import org.elasticsearch.xpack.esql.view.TransportListViewsAction; +import org.elasticsearch.xpack.esql.view.TransportPutViewAction; +import org.elasticsearch.xpack.esql.view.ViewMetadata; +import org.elasticsearch.xpack.esql.view.ViewService; import java.lang.invoke.MethodHandles; import java.util.ArrayList; @@ -182,6 +198,12 @@ public Collection createComponents(PluginServices services) { ); BigArrays bigArrays = services.indicesService().getBigArrays().withCircuitBreaking(); var blockFactoryProvider = blockFactoryProvider(circuitBreaker, bigArrays, maxPrimitiveArrayBlockSize); + EsqlFunctionRegistry functionRegistry = new EsqlFunctionRegistry(); + ViewService viewService = new ClusterViewService( + functionRegistry, + services.clusterService(), + ViewService.ViewServiceConfig.fromSettings(settings) + ); setupSharedSecrets(); List> extraCheckers = extraCheckerProviders.stream() .flatMap(p -> p.checkers(services.projectResolver(), services.clusterService()).stream()) @@ -190,6 +212,7 @@ public Collection createComponents(PluginServices services) { return List.of( new PlanExecutor( new IndexResolver(services.client()), + functionRegistry, services.telemetryProvider().getMeterRegistry(), getLicenseState(), new EsqlQueryLog(services.clusterService().getClusterSettings(), services.slowLogFieldProvider()), @@ -201,7 +224,9 @@ public Collection createComponents(PluginServices services) { ThreadPool.Names.SEARCH, blockFactoryProvider.blockFactory() ), - blockFactoryProvider + blockFactoryProvider, + functionRegistry, + viewService ); } @@ -264,7 +289,11 @@ public List getActions() { new ActionHandler(EsqlSearchShardsAction.TYPE, EsqlSearchShardsAction.class), new ActionHandler(EsqlAsyncStopAction.INSTANCE, TransportEsqlAsyncStopAction.class), new ActionHandler(EsqlListQueriesAction.INSTANCE, TransportEsqlListQueriesAction.class), - new ActionHandler(EsqlGetQueryAction.INSTANCE, TransportEsqlGetQueryAction.class) + new ActionHandler(EsqlGetQueryAction.INSTANCE, TransportEsqlGetQueryAction.class), + new ActionHandler(PutViewAction.INSTANCE, TransportPutViewAction.class), + new ActionHandler(DeleteViewAction.INSTANCE, TransportDeleteViewAction.class), + new ActionHandler(GetViewAction.INSTANCE, TransportGetViewAction.class), + new ActionHandler(ListViewsAction.INSTANCE, TransportListViewsAction.class) ); } @@ -286,7 +315,11 @@ public List getRestHandlers( new RestEsqlGetAsyncResultAction(), new RestEsqlStopAsyncAction(), new RestEsqlDeleteAsyncResultAction(), - new RestEsqlListQueriesAction() + new RestEsqlListQueriesAction(), + new RestPutViewAction(), + new RestDeleteViewAction(), + new RestGetViewAction(), + new RestListViewsAction() ); } @@ -315,6 +348,7 @@ public List getNamedWriteables() { entries.add(ExpressionQueryBuilder.ENTRY); entries.add(PlanStreamWrapperQueryBuilder.ENTRY); + entries.add(ViewMetadata.ENTRY); entries.addAll(ExpressionWritables.getNamedWriteables()); entries.addAll(PlanWritables.getNamedWriteables()); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java index f8d24720231e5..077d09a7cf12b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java @@ -55,6 +55,7 @@ import org.elasticsearch.xpack.esql.planner.PlannerSettings; import org.elasticsearch.xpack.esql.session.EsqlSession.PlanRunner; import org.elasticsearch.xpack.esql.session.Result; +import org.elasticsearch.xpack.esql.view.ClusterViewService; import java.io.IOException; import java.util.ArrayList; @@ -77,6 +78,7 @@ public class TransportEsqlQueryAction extends HandledTransportAction asyncTaskManagementService; @@ -98,6 +100,7 @@ public TransportEsqlQueryAction( SearchService searchService, ExchangeService exchangeService, ClusterService clusterService, + ClusterViewService viewService, ProjectResolver projectResolver, ThreadPool threadPool, BigArrays bigArrays, @@ -112,6 +115,7 @@ public TransportEsqlQueryAction( this.threadPool = threadPool; this.planExecutor = planExecutor; this.clusterService = clusterService; + this.viewService = viewService; this.requestExecutor = threadPool.executor(ThreadPool.Names.SEARCH); exchangeService.registerTransportHandler(transportService); this.exchangeService = exchangeService; @@ -262,6 +266,7 @@ private void innerExecute(Task task, EsqlQueryRequest request, ActionListener indexResolutions, boolean usedFilter ) { if (executionInfo.clusterInfo.isEmpty()) { @@ -195,8 +221,10 @@ static void updateExecutionInfoWithClustersWithNoMatchingIndices( // Get the clusters which are still running, and we will check whether they have any matching indices. // NOTE: we assume that updateExecutionInfoWithUnavailableClusters() was already run and took care of unavailable clusters. final Set clustersWithNoMatchingIndices = executionInfo.getRunningClusterAliases().collect(toSet()); - for (String indexName : indexResolution.resolvedIndices()) { - clustersWithNoMatchingIndices.remove(RemoteClusterAware.parseClusterAlias(indexName)); + for (IndexResolution indexResolution : indexResolutions) { + for (String indexName : indexResolution.resolvedIndices()) { + clustersWithNoMatchingIndices.remove(RemoteClusterAware.parseClusterAlias(indexName)); + } } /* * Rules enforced at planning time around non-matching indices @@ -234,20 +262,22 @@ static void updateExecutionInfoWithClustersWithNoMatchingIndices( } } else { // We check for the valid resolution because if we have empty resolution it's still an error. - if (indexResolution.isValid()) { - List failures = indexResolution.failures().getOrDefault(c, List.of()); - // No matching indices, no concrete index requested, and no error in field-caps; just mark as done. - if (failures.isEmpty()) { - markClusterWithFinalStateAndNoShards(executionInfo, c, Cluster.Status.SUCCESSFUL, null); - } else { - // skip reporting index_not_found exceptions to avoid spamming users with such errors - // when queries use a remote cluster wildcard, e.g., `*:my-logs*`. - Exception nonIndexNotFound = failures.stream() - .map(FieldCapabilitiesFailure::getException) - .filter(ex -> ExceptionsHelper.unwrap(ex, IndexNotFoundException.class) == null) - .findAny() - .orElse(null); - markClusterWithFinalStateAndNoShards(executionInfo, c, Cluster.Status.SKIPPED, nonIndexNotFound); + for (IndexResolution indexResolution : indexResolutions) { + if (indexResolution.isValid()) { + List failures = indexResolution.failures().getOrDefault(c, List.of()); + // No matching indices, no concrete index requested, and no error in field-caps; just mark as done. + if (failures.isEmpty()) { + markClusterWithFinalStateAndNoShards(executionInfo, c, Cluster.Status.SUCCESSFUL, null); + } else { + // skip reporting index_not_found exceptions to avoid spamming users with such errors + // when queries use a remote cluster wildcard, e.g., `*:my-logs*`. + Exception nonIndexNotFound = failures.stream() + .map(FieldCapabilitiesFailure::getException) + .filter(ex -> ExceptionsHelper.unwrap(ex, IndexNotFoundException.class) == null) + .findAny() + .orElse(null); + markClusterWithFinalStateAndNoShards(executionInfo, c, Cluster.Status.SKIPPED, nonIndexNotFound); + } } } } @@ -258,8 +288,11 @@ static void updateExecutionInfoWithClustersWithNoMatchingIndices( } // Filter-less version, mainly for testing where we don't need filter support - static void updateExecutionInfoWithClustersWithNoMatchingIndices(EsqlExecutionInfo executionInfo, IndexResolution indexResolution) { - updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, indexResolution, false); + static void updateExecutionInfoWithClustersWithNoMatchingIndices( + EsqlExecutionInfo executionInfo, + Set indexResolutions + ) { + updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, indexResolutions, false); } // visible for testing @@ -304,24 +337,29 @@ static void updateExecutionInfoAtEndOfPlanning(EsqlExecutionInfo execInfo) { /** * Checks the index expression for the presence of remote clusters. * If found, it will ensure that the caller has a valid Enterprise (or Trial) license on the querying cluster - * as well as initialize corresponding cluster state in execution info. + * as well as initialize the corresponding cluster state in execution info. * @throws org.elasticsearch.ElasticsearchStatusException if the license is not valid (or present) for ES|QL CCS search. */ public static void initCrossClusterState( IndicesExpressionGrouper indicesGrouper, XPackLicenseState licenseState, - IndexPattern indexPattern, + Set indexPatterns, EsqlExecutionInfo executionInfo ) throws ElasticsearchStatusException { - if (indexPattern == null) { + if (indexPatterns == null || indexPatterns.isEmpty()) { return; } try { - var groupedIndices = indicesGrouper.groupIndices( - IndicesOptions.DEFAULT, - Strings.splitStringByCommaToArray(indexPattern.indexPattern()), - false - ); + String[] indexExpressions = indexPatterns.stream() + .map(indexPattern -> Strings.splitStringByCommaToArray(indexPattern.indexPattern())) + .reduce((a, b) -> { + String[] combined = new String[a.length + b.length]; + System.arraycopy(a, 0, combined, 0, a.length); + System.arraycopy(b, 0, combined, a.length, b.length); + return combined; + }) + .get(); + var groupedIndices = indicesGrouper.groupIndices(IndicesOptions.DEFAULT, indexExpressions, false); executionInfo.clusterInfoInitializing(true); // initialize the cluster entries in EsqlExecutionInfo before throwing the invalid license error diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java index d694588bf18c4..7c4eaa19910cb 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java @@ -83,6 +83,7 @@ import org.elasticsearch.xpack.esql.planner.premapper.PreMapper; import org.elasticsearch.xpack.esql.plugin.TransportActionServices; import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry; +import org.elasticsearch.xpack.esql.view.ViewService; import java.util.ArrayList; import java.util.Collection; @@ -120,6 +121,7 @@ public interface PlanRunner { private final AnalyzerSettings clusterSettings; private final IndexResolver indexResolver; private final EnrichPolicyResolver enrichPolicyResolver; + private final ViewService viewService; private final PreAnalyzer preAnalyzer; private final Verifier verifier; @@ -144,6 +146,7 @@ public EsqlSession( AnalyzerSettings clusterSettings, IndexResolver indexResolver, EnrichPolicyResolver enrichPolicyResolver, + ViewService viewService, PreAnalyzer preAnalyzer, EsqlFunctionRegistry functionRegistry, Mapper mapper, @@ -156,6 +159,7 @@ public EsqlSession( this.clusterSettings = clusterSettings; this.indexResolver = indexResolver; this.enrichPolicyResolver = enrichPolicyResolver; + this.viewService = viewService; this.preAnalyzer = preAnalyzer; this.verifier = verifier; this.functionRegistry = functionRegistry; @@ -203,7 +207,7 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P ); FoldContext foldContext = configuration.newFoldContext(); - LogicalPlan plan = statement.plan(); + LogicalPlan plan = viewService.replaceViews(statement.plan(), planTelemetry); if (plan instanceof Explain explain) { explainMode = true; plan = explain.query(); @@ -459,9 +463,24 @@ private EsqlStatement parse(String query, QueryParams params) { static void handleFieldCapsFailures( boolean allowPartialResults, EsqlExecutionInfo executionInfo, - Map> failures + Map indexResolutions ) throws Exception { FailureCollector failureCollector = new FailureCollector(); + for (IndexResolution indexResolution : indexResolutions.values()) { + handleFieldCapsFailures(allowPartialResults, executionInfo, indexResolution.failures(), failureCollector); + } + Exception failure = failureCollector.getFailure(); + if (failure != null) { + throw failure; + } + } + + static void handleFieldCapsFailures( + boolean allowPartialResults, + EsqlExecutionInfo executionInfo, + Map> failures, + FailureCollector failureCollector + ) throws Exception { for (var e : failures.entrySet()) { String clusterAlias = e.getKey(); EsqlExecutionInfo.Cluster cluster = executionInfo.getCluster(clusterAlias); @@ -491,10 +510,6 @@ static void handleFieldCapsFailures( ); } } - Exception failure = failureCollector.getFailure(); - if (failure != null) { - throw failure; - } } public void analyzedPlan( @@ -532,21 +547,19 @@ private void resolveIndicesAndAnalyze( PreAnalysisResult result, ActionListener> logicalPlanListener ) { - EsqlCCSUtils.initCrossClusterState(indicesExpressionGrouper, verifier.licenseState(), preAnalysis.indexPattern(), executionInfo); + EsqlCCSUtils.initCrossClusterState( + indicesExpressionGrouper, + verifier.licenseState(), + preAnalysis.indexes().keySet(), + executionInfo + ); // The main index pattern dictates on which nodes the query can be executed, so we use the minimum transport version from this field // caps request. SubscribableListener.newForked( - l -> preAnalyzeMainIndicesAndRetrieveMinTransportVersion(preAnalysis, executionInfo, result, requestFilter, l) - ).andThenApply(r -> { - if (r.indices.isValid() - && executionInfo.isCrossClusterSearch() - && executionInfo.getRunningClusterAliases().findAny().isEmpty()) { - LOGGER.debug("No more clusters to search, ending analysis stage"); - throw new NoClustersToSearchException(); - } - return r; - }) + l -> preAnalyzeMainIndices(preAnalysis.indexes().keySet().iterator(), preAnalysis, executionInfo, result, requestFilter, l) + ) + .andThen((l, r) -> validateClusters(preAnalysis.indexes().keySet().iterator(), executionInfo, r, l)) .andThen((l, r) -> preAnalyzeLookupIndices(preAnalysis.lookupIndices().iterator(), r, executionInfo, l)) .andThen((l, r) -> { enrichPolicyResolver.resolvePolicies(preAnalysis.enriches(), executionInfo, l.map(r::withEnrichResolution)); @@ -779,7 +792,62 @@ private void validateRemoteVersions(EsqlExecutionInfo executionInfo) { }); } - private void preAnalyzeMainIndicesAndRetrieveMinTransportVersion( + private void validateClusters( + Iterator indexPatterns, + EsqlExecutionInfo executionInfo, + PreAnalysisResult result, + ActionListener listener + ) { + if (indexPatterns.hasNext()) { + validateClusters(indexPatterns.next(), executionInfo, result, listener.delegateFailureAndWrap((l, r) -> { + validateClusters(indexPatterns, executionInfo, r, l); + })); + } else { + listener.onResponse(result); + } + } + + private void validateClusters( + IndexPattern indexPattern, + EsqlExecutionInfo executionInfo, + PreAnalysisResult result, + ActionListener listener + ) { + if (result.indexResolutions.get(indexPattern).isValid() + && executionInfo.isCrossClusterSearch() + && executionInfo.getRunningClusterAliases().findAny().isEmpty()) { + LOGGER.debug("No more clusters to search, ending analysis stage"); + throw new NoClustersToSearchException(); + } + listener.onResponse(result); + } + + private void preAnalyzeMainIndices( + Iterator indexPatterns, + PreAnalyzer.PreAnalysis preAnalysis, + EsqlExecutionInfo executionInfo, + PreAnalysisResult result, + QueryBuilder requestFilter, + ActionListener listener + ) { + if (indexPatterns.hasNext()) { + preAnalyzeMainIndices( + indexPatterns.next(), + preAnalysis, + executionInfo, + result, + requestFilter, + listener.delegateFailureAndWrap((l, r) -> { + preAnalyzeMainIndices(indexPatterns, preAnalysis, executionInfo, r, requestFilter, l); + }) + ); + } else { + listener.onResponse(result); + } + } + + private void preAnalyzeMainIndices( + IndexPattern indexPattern, PreAnalyzer.PreAnalysis preAnalysis, EsqlExecutionInfo executionInfo, PreAnalysisResult result, @@ -791,42 +859,37 @@ private void preAnalyzeMainIndicesAndRetrieveMinTransportVersion( ThreadPool.Names.SEARCH_COORDINATION, ThreadPool.Names.SYSTEM_READ ); - if (preAnalysis.indexPattern() != null) { - if (executionInfo.clusterAliases().isEmpty()) { - // return empty resolution if the expression is pure CCS and resolved no remote clusters (like no-such-cluster*:index) - listener.onResponse( - result.withIndices(IndexResolution.valid(new EsIndex(preAnalysis.indexPattern().indexPattern(), Map.of(), Map.of()))) - .withMinimumTransportVersion(TransportVersion.current()) - ); - } else { - indexResolver.resolveAsMergedMappingAndRetrieveMinimumVersion( - preAnalysis.indexPattern().indexPattern(), - result.fieldNames, - // Maybe if no indices are returned, retry without index mode and provide a clearer error message. - switch (preAnalysis.indexMode()) { - case IndexMode.TIME_SERIES -> { - var indexModeFilter = new TermQueryBuilder(IndexModeFieldMapper.NAME, IndexMode.TIME_SERIES.getName()); - yield requestFilter != null - ? new BoolQueryBuilder().filter(requestFilter).filter(indexModeFilter) - : indexModeFilter; - } - default -> requestFilter; - }, - preAnalysis.indexMode() == IndexMode.TIME_SERIES, - preAnalysis.supportsAggregateMetricDouble(), - preAnalysis.supportsDenseVector(), - listener.delegateFailureAndWrap((l, indexResolution) -> { - EsqlCCSUtils.updateExecutionInfoWithUnavailableClusters(executionInfo, indexResolution.inner().failures()); - l.onResponse( - result.withIndices(indexResolution.inner()).withMinimumTransportVersion(indexResolution.minimumVersion()) - ); - }) - ); - } - } else { - // occurs when dealing with local relations (row a = 1) + // TODO: Make this specific to the index pattern + if (executionInfo.clusterAliases().isEmpty()) { + // return empty resolution if the expression is pure CCS and resolved no remote clusters (like no-such-cluster*:index) listener.onResponse( - result.withIndices(IndexResolution.invalid("[none specified]")).withMinimumTransportVersion(TransportVersion.current()) + result.withIndices(indexPattern, IndexResolution.valid(new EsIndex(indexPattern.indexPattern(), Map.of(), Map.of()))) + ); + } else { + IndexMode indexMode = preAnalysis.indexes().get(indexPattern); + indexResolver.resolveAsMergedMappingAndRetrieveMinimumVersion( + indexPattern.indexPattern(), + result.fieldNames, + // Maybe if no indices are returned, retry without index mode and provide a clearer error message. + switch (indexMode) { + case IndexMode.TIME_SERIES -> { + var indexModeFilter = new TermQueryBuilder(IndexModeFieldMapper.NAME, IndexMode.TIME_SERIES.getName()); + yield requestFilter != null + ? new BoolQueryBuilder().filter(requestFilter).filter(indexModeFilter) + : indexModeFilter; + } + default -> requestFilter; + }, + indexMode == IndexMode.TIME_SERIES, + preAnalysis.supportsAggregateMetricDouble(), + preAnalysis.supportsDenseVector(), + listener.delegateFailureAndWrap((l, indexResolution) -> { + EsqlCCSUtils.updateExecutionInfoWithUnavailableClusters(executionInfo, indexResolution.inner().failures()); + l.onResponse( + result.withIndices(indexPattern, indexResolution.inner()) + .withMinimumTransportVersion(indexResolution.minimumVersion()) + ); + }) ); } } @@ -843,10 +906,14 @@ private void analyzeWithRetry( ) { LOGGER.debug("Analyzing the plan ({})", description); try { - if (result.indices.isValid() || requestFilter != null) { + if (result.indexResolutions.values().stream().anyMatch(IndexResolution::isValid) || requestFilter != null) { // We won't run this check with no filter and no valid indices since this may lead to false positive - missing index report // when the resolution result is not valid for a different reason. - EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, result.indices, requestFilter != null); + EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices( + executionInfo, + result.indexResolutions.values(), + requestFilter != null + ); } LogicalPlan plan = analyzedPlan(parsed, configuration, result, executionInfo); LOGGER.debug("Analyzed plan ({}):\n{}", description, plan); @@ -900,7 +967,7 @@ private PhysicalPlan logicalPlanToPhysicalPlan( private LogicalPlan analyzedPlan(LogicalPlan parsed, Configuration configuration, PreAnalysisResult r, EsqlExecutionInfo executionInfo) throws Exception { - handleFieldCapsFailures(configuration.allowPartialResults(), executionInfo, r.indices.failures()); + handleFieldCapsFailures(configuration.allowPartialResults(), executionInfo, r.indexResolutions()); Analyzer analyzer = new Analyzer(new AnalyzerContext(configuration, functionRegistry, r), verifier); LogicalPlan plan = analyzer.analyze(parsed); plan.setAnalyzed(); @@ -946,7 +1013,7 @@ private PhysicalPlan optimizedPhysicalPlan(LogicalPlan optimizedPlan, PhysicalPl public record PreAnalysisResult( Set fieldNames, Set wildcardJoinIndices, - IndexResolution indices, + Map indexResolutions, Map lookupIndices, EnrichResolution enrichResolution, InferenceResolution inferenceResolution, @@ -954,14 +1021,16 @@ public record PreAnalysisResult( ) { public PreAnalysisResult(Set fieldNames, Set wildcardJoinIndices) { - this(fieldNames, wildcardJoinIndices, null, new HashMap<>(), null, InferenceResolution.EMPTY, null); + this(fieldNames, wildcardJoinIndices, Map.of(), new HashMap<>(), null, InferenceResolution.EMPTY, TransportVersion.current()); } - PreAnalysisResult withIndices(IndexResolution indices) { + PreAnalysisResult withIndices(IndexPattern indexPattern, IndexResolution indices) { + Map newIndexResolutions = new HashMap<>(this.indexResolutions); + newIndexResolutions.put(indexPattern, indices); return new PreAnalysisResult( fieldNames, wildcardJoinIndices, - indices, + newIndexResolutions, lookupIndices, enrichResolution, inferenceResolution, @@ -978,7 +1047,7 @@ PreAnalysisResult withEnrichResolution(EnrichResolution enrichResolution) { return new PreAnalysisResult( fieldNames, wildcardJoinIndices, - indices, + indexResolutions, lookupIndices, enrichResolution, inferenceResolution, @@ -990,7 +1059,7 @@ PreAnalysisResult withInferenceResolution(InferenceResolution inferenceResolutio return new PreAnalysisResult( fieldNames, wildcardJoinIndices, - indices, + indexResolutions, lookupIndices, enrichResolution, inferenceResolution, @@ -999,10 +1068,16 @@ PreAnalysisResult withInferenceResolution(InferenceResolution inferenceResolutio } PreAnalysisResult withMinimumTransportVersion(TransportVersion minimumTransportVersion) { + if (this.minimumTransportVersion != null) { + if (this.minimumTransportVersion.equals(minimumTransportVersion)) { + return this; + } + minimumTransportVersion = TransportVersion.min(this.minimumTransportVersion, minimumTransportVersion); + } return new PreAnalysisResult( fieldNames, wildcardJoinIndices, - indices, + indexResolutions, lookupIndices, enrichResolution, inferenceResolution, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ClusterViewService.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ClusterViewService.java new file mode 100644 index 0000000000000..f77272467e1a4 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ClusterViewService.java @@ -0,0 +1,71 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ClusterStateUpdateTask; +import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; + +import java.util.Map; +import java.util.function.Function; + +/** + * Implementation of {@link ViewService} that keeps the views in the cluster state. + */ +public class ClusterViewService extends ViewService { + private final ClusterService clusterService; + + public ClusterViewService(EsqlFunctionRegistry functionRegistry, ClusterService clusterService, ViewServiceConfig config) { + super(functionRegistry, config); + this.clusterService = clusterService; + } + + @Override + protected ViewMetadata getMetadata() { + return clusterService.state().metadata().custom(ViewMetadata.TYPE, ViewMetadata.EMPTY); + } + + @Override + protected void updateViewMetadata(ActionListener callback, Function> function) { + submitUnbatchedTask("update-esql-view-metadata", new ClusterStateUpdateTask() { + @Override + public ClusterState execute(ClusterState currentState) { + var views = currentState.metadata().custom(ViewMetadata.TYPE, ViewMetadata.EMPTY); + Map policies = function.apply(views); + Metadata metadata = Metadata.builder(currentState.metadata()) + .putCustom(ViewMetadata.TYPE, new ViewMetadata(policies)) + .build(); + return ClusterState.builder(currentState).metadata(metadata).build(); + } + + @Override + public void clusterStateProcessed(ClusterState oldState, ClusterState newState) { + callback.onResponse(null); + } + + @Override + public void onFailure(Exception e) { + callback.onFailure(e); + } + }); + } + + @SuppressForbidden(reason = "legacy usage of unbatched task") // TODO add support for batching here + private void submitUnbatchedTask(@SuppressWarnings("SameParameterValue") String source, ClusterStateUpdateTask task) { + clusterService.submitUnbatchedStateUpdateTask(source, task); + } + + @Override + protected void assertMasterNode() { + assert clusterService.localNode().isMasterNode(); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/DeleteViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/DeleteViewAction.java new file mode 100644 index 0000000000000..d0776f58120dd --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/DeleteViewAction.java @@ -0,0 +1,70 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.TimeValue; + +import java.io.IOException; +import java.util.Objects; + +public class DeleteViewAction extends ActionType { + + public static final DeleteViewAction INSTANCE = new DeleteViewAction(); + public static final String NAME = "cluster:admin/xpack/esql/view/delete"; + + private DeleteViewAction() { + super(NAME); + } + + public static class Request extends MasterNodeRequest { + private final String name; + + public Request(TimeValue masterNodeTimeout, String name) { + super(masterNodeTimeout); + this.name = Objects.requireNonNull(name, "name cannot be null"); + } + + public Request(StreamInput in) throws IOException { + super(in); + name = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(name); + } + + public String name() { + return name; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return name.equals(request.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/GetViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/GetViewAction.java new file mode 100644 index 0000000000000..74c7606e4b285 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/GetViewAction.java @@ -0,0 +1,115 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.TransportAction; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +public class GetViewAction extends ActionType { + + public static final GetViewAction INSTANCE = new GetViewAction(); + public static final String NAME = "cluster:admin/xpack/esql/view/get"; + + private GetViewAction() { + super(NAME); + } + + public static class Request extends ActionRequest { + private String name; + + public Request(String name) { + this.name = Objects.requireNonNull(name, "name cannot be null"); + } + + public Request(StreamInput in) throws IOException { + super(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(name); + } + + public String name() { + return name; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return name.equals(request.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } + + public static class Response extends ActionResponse implements ToXContentObject { + + private final View view; + + public Response(final View view) { + this.view = view; + } + + public View getView() { + return view; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + TransportAction.localOnly(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + view.toXContent(builder, params); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return view.equals(((Response) o).view); + } + + @Override + public int hashCode() { + return view.hashCode(); + } + + @Override + public String toString() { + return "GetViewAction.Response{view=" + view.toString() + '}'; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/InMemoryViewService.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/InMemoryViewService.java new file mode 100644 index 0000000000000..0a7334a1026e8 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/InMemoryViewService.java @@ -0,0 +1,48 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; + +import java.util.Map; +import java.util.function.Function; + +/** + * Simple implementation of {@link ClusterViewService} that keeps the views in memory. + * This is useful for testing. + */ +public class InMemoryViewService extends ViewService { + + private ViewMetadata metadata; + + public InMemoryViewService(EsqlFunctionRegistry functionRegistry) { + this(functionRegistry, ViewServiceConfig.DEFAULT); + } + + public InMemoryViewService(EsqlFunctionRegistry functionRegistry, ViewServiceConfig config) { + super(functionRegistry, config); + this.metadata = ViewMetadata.EMPTY; + } + + @Override + protected ViewMetadata getMetadata() { + return metadata; + } + + @Override + protected void updateViewMetadata(ActionListener callback, Function> function) { + Map updated = function.apply(metadata); + this.metadata = new ViewMetadata(updated); + } + + @Override + protected void assertMasterNode() { + // no-op + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ListViewsAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ListViewsAction.java new file mode 100644 index 0000000000000..76c78895a468c --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ListViewsAction.java @@ -0,0 +1,106 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.TransportAction; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Map; + +public class ListViewsAction extends ActionType { + + public static final ListViewsAction INSTANCE = new ListViewsAction(); + public static final String NAME = "cluster:admin/xpack/esql/views"; + + private ListViewsAction() { + super(NAME); + } + + public static class Request extends ActionRequest { + public Request() { + super(); + } + + public Request(StreamInput in) throws IOException { + super(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + return o != null && getClass() == o.getClass(); + } + + @Override + public int hashCode() { + return ListViewsAction.Request.class.hashCode(); + } + } + + public static class Response extends ActionResponse implements ToXContentObject { + + private final Map views; + + public Response(final Map views) { + this.views = views; + } + + public Map getViews() { + return views; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + TransportAction.localOnly(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.value(views); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return views.equals(((Response) o).views); + } + + @Override + public int hashCode() { + return views.hashCode(); + } + + @Override + public String toString() { + return "ListViewsAction.Response{view=" + views.toString() + '}'; + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/PutViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/PutViewAction.java new file mode 100644 index 0000000000000..d783684df492c --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/PutViewAction.java @@ -0,0 +1,78 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.TimeValue; + +import java.io.IOException; +import java.util.Objects; + +public class PutViewAction extends ActionType { + + public static final PutViewAction INSTANCE = new PutViewAction(); + public static final String NAME = "cluster:admin/xpack/esql/view/put"; + + private PutViewAction() { + super(NAME); + } + + public static class Request extends MasterNodeRequest { + private final String name; + private final View view; + + public Request(TimeValue masterNodeTimeout, String name, View view) { + super(masterNodeTimeout); + this.name = Objects.requireNonNull(name, "name cannot be null"); + this.view = view; + } + + public Request(StreamInput in) throws IOException { + super(in); + name = in.readString(); + view = new View(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(name); + view.writeTo(out); + } + + public String name() { + return name; + } + + public View view() { + return view; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return name.equals(request.name) && view.equals(request.view); + } + + @Override + public int hashCode() { + return Objects.hash(name, view); + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestDeleteViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestDeleteViewAction.java new file mode 100644 index 0000000000000..f0ad25b2c0db9 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestDeleteViewAction.java @@ -0,0 +1,39 @@ +/* + * 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.view; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestUtils; +import org.elasticsearch.rest.Scope; +import org.elasticsearch.rest.ServerlessScope; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.DELETE; + +@ServerlessScope(Scope.PUBLIC) +public class RestDeleteViewAction extends BaseRestHandler { + @Override + public List routes() { + return List.of(new Route(DELETE, "/_query/view/{name}")); + } + + @Override + public String getName() { + return "esql_delete_view"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + DeleteViewAction.Request req = new DeleteViewAction.Request(RestUtils.getMasterNodeTimeout(request), request.param("name")); + return channel -> client.execute(DeleteViewAction.INSTANCE, req, new RestToXContentListener<>(channel)); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestGetViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestGetViewAction.java new file mode 100644 index 0000000000000..0e94733822477 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestGetViewAction.java @@ -0,0 +1,39 @@ +/* + * 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.view; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.Scope; +import org.elasticsearch.rest.ServerlessScope; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; + +@ServerlessScope(Scope.PUBLIC) +public class RestGetViewAction extends BaseRestHandler { + @Override + public List routes() { + return List.of(new Route(GET, "/_query/view/{name}")); + } + + @Override + public String getName() { + return "esql_get_view"; + } + + @Override + protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + GetViewAction.Request req = new GetViewAction.Request(request.param("name")); + return channel -> client.execute(TransportGetViewAction.TYPE, req, new RestToXContentListener<>(channel)); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestListViewsAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestListViewsAction.java new file mode 100644 index 0000000000000..5e7033ee3c282 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestListViewsAction.java @@ -0,0 +1,39 @@ +/* + * 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.view; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.Scope; +import org.elasticsearch.rest.ServerlessScope; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; + +@ServerlessScope(Scope.PUBLIC) +public class RestListViewsAction extends BaseRestHandler { + @Override + public List routes() { + return List.of(new Route(GET, "/_query/views")); + } + + @Override + public String getName() { + return "esql_list_views"; + } + + @Override + protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + ListViewsAction.Request req = new ListViewsAction.Request(); + return channel -> client.execute(TransportListViewsAction.TYPE, req, new RestToXContentListener<>(channel)); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestPutViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestPutViewAction.java new file mode 100644 index 0000000000000..c7250cf8f4786 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/RestPutViewAction.java @@ -0,0 +1,47 @@ +/* + * 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.view; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestUtils; +import org.elasticsearch.rest.Scope; +import org.elasticsearch.rest.ServerlessScope; +import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.xcontent.XContentParser; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.PUT; + +@ServerlessScope(Scope.PUBLIC) +public class RestPutViewAction extends BaseRestHandler { + @Override + public List routes() { + return List.of(new Route(PUT, "/_query/view/{name}")); + } + + @Override + public String getName() { + return "esql_put_view"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + try (XContentParser parser = request.contentOrSourceParamParser()) { + PutViewAction.Request req = new PutViewAction.Request( + RestUtils.getMasterNodeTimeout(request), + request.param("name"), + View.PARSER.parse(parser, null) + ); + return channel -> client.execute(PutViewAction.INSTANCE, req, new RestToXContentListener<>(channel)); + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportDeleteViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportDeleteViewAction.java new file mode 100644 index 0000000000000..10fe9de0a5037 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportDeleteViewAction.java @@ -0,0 +1,60 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.AcknowledgedTransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.injection.guice.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +public class TransportDeleteViewAction extends AcknowledgedTransportMasterNodeAction { + private final ClusterViewService viewService; + + @Inject + public TransportDeleteViewAction( + TransportService transportService, + ClusterService clusterService, + ThreadPool threadPool, + ActionFilters actionFilters, + ClusterViewService viewService + ) { + super( + DeleteViewAction.NAME, + transportService, + clusterService, + threadPool, + actionFilters, + DeleteViewAction.Request::new, + EsExecutors.DIRECT_EXECUTOR_SERVICE + ); + this.viewService = viewService; + } + + @Override + protected void masterOperation( + Task task, + DeleteViewAction.Request request, + ClusterState state, + ActionListener listener + ) { + viewService.delete(request.name(), listener.map(v -> AcknowledgedResponse.TRUE)); + } + + @Override + protected ClusterBlockException checkBlock(DeleteViewAction.Request request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportGetViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportGetViewAction.java new file mode 100644 index 0000000000000..68471ff5d887d --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportGetViewAction.java @@ -0,0 +1,38 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.admin.cluster.remote.RemoteInfoResponse; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.injection.guice.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.transport.TransportService; + +public class TransportGetViewAction extends HandledTransportAction { + public static final ActionType TYPE = new ActionType<>(GetViewAction.NAME); + private final ClusterViewService viewService; + + @Inject + public TransportGetViewAction(TransportService transportService, ActionFilters actionFilters, ClusterViewService viewService) { + super(GetViewAction.NAME, transportService, actionFilters, GetViewAction.Request::new, EsExecutors.DIRECT_EXECUTOR_SERVICE); + this.viewService = viewService; + } + + @Override + protected void doExecute(Task task, GetViewAction.Request request, ActionListener listener) { + View view = viewService.get(request.name()); + if (view == null) { + listener.onFailure(new IllegalArgumentException("View [" + request.name() + "] does not exist")); + } else { + listener.onResponse(new GetViewAction.Response(view)); + } + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportListViewsAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportListViewsAction.java new file mode 100644 index 0000000000000..b08865f846a5a --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportListViewsAction.java @@ -0,0 +1,43 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.admin.cluster.remote.RemoteInfoResponse; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.injection.guice.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.transport.TransportService; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class TransportListViewsAction extends HandledTransportAction { + public static final ActionType TYPE = new ActionType<>(ListViewsAction.NAME); + private final ClusterViewService viewService; + + @Inject + public TransportListViewsAction(TransportService transportService, ActionFilters actionFilters, ClusterViewService viewService) { + super(ListViewsAction.NAME, transportService, actionFilters, ListViewsAction.Request::new, EsExecutors.DIRECT_EXECUTOR_SERVICE); + this.viewService = viewService; + } + + @Override + protected void doExecute(Task task, ListViewsAction.Request request, ActionListener listener) { + Map views = new LinkedHashMap<>(); + for (String name : viewService.list()) { + View view = viewService.get(name); + if (view != null) { + views.put(name, viewService.get(name)); + } + } + listener.onResponse(new ListViewsAction.Response(views)); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportPutViewAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportPutViewAction.java new file mode 100644 index 0000000000000..f2dde51ae23ef --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/TransportPutViewAction.java @@ -0,0 +1,60 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.support.master.AcknowledgedTransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.injection.guice.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +public class TransportPutViewAction extends AcknowledgedTransportMasterNodeAction { + private final ClusterViewService viewService; + + @Inject + public TransportPutViewAction( + TransportService transportService, + ClusterService clusterService, + ThreadPool threadPool, + ActionFilters actionFilters, + ClusterViewService viewService + ) { + super( + PutViewAction.NAME, + transportService, + clusterService, + threadPool, + actionFilters, + PutViewAction.Request::new, + EsExecutors.DIRECT_EXECUTOR_SERVICE + ); + this.viewService = viewService; + } + + @Override + protected void masterOperation( + Task task, + PutViewAction.Request request, + ClusterState state, + ActionListener listener + ) { + viewService.put(request.name(), request.view(), listener.map(v -> AcknowledgedResponse.TRUE)); + } + + @Override + protected ClusterBlockException checkBlock(PutViewAction.Request request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/View.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/View.java new file mode 100644 index 0000000000000..38a441481e839 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/View.java @@ -0,0 +1,79 @@ +/* + * 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.view; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.xcontent.ConstructingObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.ToXContentFragment; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +/** + * Represents an enrich policy including its configuration. + */ +public final class View implements Writeable, ToXContentFragment { + private static final ParseField QUERY = new ParseField("query"); + + static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "view", + false, + (args, ctx) -> new View((String) args[0]) + ); + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), QUERY); + } + + private final String query; + + public View(String query) { + this.query = query; + } + + public View(StreamInput in) throws IOException { + this.query = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(query); + } + + public String query() { + return query; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(QUERY.getPreferredName(), query); + return builder.endObject(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + View policy = (View) o; + return Objects.equals(query, policy.query); + } + + @Override + public int hashCode() { + return Objects.hash(query); + } + + public String toString() { + return Strings.toString(this); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ViewMetadata.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ViewMetadata.java new file mode 100644 index 0000000000000..2145b168b3576 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ViewMetadata.java @@ -0,0 +1,106 @@ +/* + * 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.view; + +import org.elasticsearch.TransportVersion; +import org.elasticsearch.cluster.AbstractNamedDiffable; +import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ChunkedToXContentHelper; +import org.elasticsearch.xcontent.ConstructingObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +/** + * Encapsulates view definitions as custom metadata inside cluster state. + */ +public final class ViewMetadata extends AbstractNamedDiffable implements Metadata.ClusterCustom { + public static final String TYPE = "esql_view"; + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(ViewMetadata.class, TYPE, ViewMetadata::new); + private static final TransportVersion ESQL_VIEWS = TransportVersion.fromName("esql_views"); + + static final ParseField VIEWS = new ParseField("views"); + + public static final ViewMetadata EMPTY = new ViewMetadata(Collections.emptyMap()); + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "enrich_metadata", + args -> new ViewMetadata((Map) args[0]) + ); + + static { + PARSER.declareObject(ConstructingObjectParser.constructorArg(), View.PARSER, VIEWS); + } + + public static ViewMetadata fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + private final Map views; + + public ViewMetadata(StreamInput in) throws IOException { + this(in.readMap(View::new)); + } + + public ViewMetadata(Map views) { + this.views = Collections.unmodifiableMap(views); + } + + public Map views() { + return views; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return ESQL_VIEWS; + } + + @Override + public String getWriteableName() { + return TYPE; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap(views, StreamOutput::writeWriteable); + } + + @Override + public Iterator toXContentChunked(ToXContent.Params ignored) { + return ChunkedToXContentHelper.xContentObjectFieldObjects(VIEWS.getPreferredName(), views); + } + + @Override + public EnumSet context() { + return EnumSet.of(Metadata.XContentContext.API); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ViewMetadata that = (ViewMetadata) o; + return views.equals(that.views); + } + + @Override + public int hashCode() { + return Objects.hash(views); + } + +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ViewService.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ViewService.java new file mode 100644 index 0000000000000..caa76fb656be1 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/view/ViewService.java @@ -0,0 +1,211 @@ +/* + * 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.view; + +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xpack.esql.VerificationException; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; +import org.elasticsearch.xpack.esql.parser.EsqlParser; +import org.elasticsearch.xpack.esql.parser.QueryParams; +import org.elasticsearch.xpack.esql.plan.IndexPattern; +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; +import org.elasticsearch.xpack.esql.plan.logical.UnionAll; +import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation; +import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public abstract class ViewService { + private final ViewServiceConfig config; + private final EsqlFunctionRegistry functionRegistry; + + public record ViewServiceConfig(int maxViews, int maxViewSize, int maxViewDepth) { + + public static final String MAX_VIEWS_COUNT_SETTING = "esql.views.max_count"; + public static final String MAX_VIEWS_SIZE_SETTING = "esql.views.max_size"; + public static final String MAX_VIEWS_DEPTH_SETTING = "esql.views.max_depth"; + public static final ViewServiceConfig DEFAULT = new ViewServiceConfig(100, 10_000, 10); + + public static ViewServiceConfig fromSettings(Settings settings) { + return new ViewServiceConfig( + settings.getAsInt(MAX_VIEWS_COUNT_SETTING, DEFAULT.maxViews), + settings.getAsInt(MAX_VIEWS_SIZE_SETTING, DEFAULT.maxViewSize), + settings.getAsInt(MAX_VIEWS_DEPTH_SETTING, DEFAULT.maxViewDepth) + ); + } + } + + public ViewService(EsqlFunctionRegistry functionRegistry, ViewServiceConfig config) { + this.functionRegistry = functionRegistry; + this.config = config; + } + + protected abstract ViewMetadata getMetadata(); + + public LogicalPlan replaceViews(LogicalPlan plan, PlanTelemetry telemetry) { + ViewMetadata views = getMetadata(); + + List seen = new ArrayList<>(); + while (true) { + LogicalPlan prev = plan; + plan = plan.transformUp(UnresolvedRelation.class, ur -> { + List indexes = new ArrayList<>(); + List subqueries = new ArrayList<>(); + for (String name : ur.indexPattern().indexPattern().split(",")) { + name = name.trim(); + if (views.views().containsKey(name)) { + boolean alreadySeen = seen.contains(name); + seen.add(name); + if (alreadySeen) { + throw viewError("circular view reference ", seen); + } + if (seen.size() > config.maxViewDepth) { + throw viewError("The maximum allowed view depth of " + config.maxViewDepth + " has been exceeded: ", seen); + } + View view = views.views().get(name); + subqueries.add(resolve(view, telemetry)); + } else { + indexes.add(name); + } + } + if (subqueries.isEmpty()) { + // No views defined, just return the original plan + return ur; + } + if (indexes.isEmpty()) { + if (subqueries.size() == 1) { + // only one view, no need for union + return subqueries.getFirst(); + } + } else { + subqueries.add( + 0, + new UnresolvedRelation( + ur.source(), + new IndexPattern(ur.indexPattern().source(), String.join(",", indexes)), + ur.frozen(), + ur.metadataFields(), + ur.indexMode(), + ur.unresolvedMessage() + ) + ); + } + return new UnionAll(ur.source(), subqueries, List.of()); + }); + if (plan.equals(prev)) { + return prev; + } + } + } + + private static LogicalPlan resolve(View view, PlanTelemetry telemetry) { + // TODO don't reparse every time. Store parsed? Or cache parsing? dunno + // this will make super-wrong Source. the _source should be the view. + // if there's a `filter` it applies "under" the view. that's weird. right? + // security to create this + // telemetry + // don't allow circular references + return new EsqlParser().createStatement(view.query(), new QueryParams(), telemetry); + } + + private VerificationException viewError(String type, List seen) { + StringBuilder b = new StringBuilder(); + for (String s : seen) { + if (b.isEmpty()) { + b.append(type); + } else { + b.append(" -> "); + } + b.append(s); + } + throw new VerificationException(b.toString()); + } + + /** + * Adds or modifies a view by name. This method can only be invoked on the master node. + */ + public void put(String name, View view, ActionListener callback) { + assertMasterNode(); + validatePutView(name, view); + updateViewMetadata(callback, current -> { + Map original = getMetadata().views(); + Map updated = new HashMap<>(original); + updated.put(name, view); + return updated; + }); + } + + private void validatePutView(String name, View view) { + if (Strings.isNullOrEmpty(name)) { + throw new IllegalArgumentException("name is missing or empty"); + } + if (view == null) { + throw new IllegalArgumentException("view is missing"); + } + if (Strings.isNullOrEmpty(view.query())) { + throw new IllegalArgumentException("view query is missing or empty"); + } + if (view.query().length() > config.maxViewSize) { + throw new IllegalArgumentException( + "view query is too large: " + view.query().length() + " characters, the maximum allowed is " + config.maxViewSize + ); + } + if (getMetadata().views().containsKey(name) == false && getMetadata().views().size() >= config.maxViews) { + throw new IllegalArgumentException("cannot add view, the maximum number of views is reached: " + config.maxViews); + } + new EsqlParser().createStatement(view.query(), new QueryParams(), new PlanTelemetry(functionRegistry)); + // TODO should we validate this in the transport action and make it async? like plan like a query + // TODO postgresql does. + } + + /** + * Gets the view by name. + */ + public View get(String name) { + return getMetadata().views().get(name); + } + + /** + * List current view names. + */ + public Set list() { + return getMetadata().views().keySet(); + } + + /** + * Removes a view from the cluster state. This method can only be invoked on the master node. + */ + public void delete(String name, ActionListener callback) { + assertMasterNode(); + if (Strings.isNullOrEmpty(name)) { + throw new IllegalArgumentException("name is missing or empty"); + } + + updateViewMetadata(callback, current -> { + Map original = current.views(); + if (original.containsKey(name) == false) { + throw new ResourceNotFoundException("policy [{}] not found", name); + } + Map updated = new HashMap<>(original); + updated.remove(name); + return updated; + }); + } + + protected abstract void assertMasterNode(); + + protected abstract void updateViewMetadata(ActionListener callback, Function> function); +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java index f90547f57c0ff..0116d0992aa27 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java @@ -60,6 +60,7 @@ import org.elasticsearch.xpack.esql.analysis.PreAnalyzer; import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.FoldContext; +import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.core.type.InvalidMappedField; @@ -79,6 +80,7 @@ import org.elasticsearch.xpack.esql.optimizer.LogicalPreOptimizerContext; import org.elasticsearch.xpack.esql.optimizer.TestLocalPhysicalPlanOptimizer; import org.elasticsearch.xpack.esql.parser.EsqlParser; +import org.elasticsearch.xpack.esql.plan.IndexPattern; import org.elasticsearch.xpack.esql.plan.logical.Enrich; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.physical.ChangePointExec; @@ -101,6 +103,8 @@ import org.elasticsearch.xpack.esql.session.Result; import org.elasticsearch.xpack.esql.stats.DisabledSearchStats; import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry; +import org.elasticsearch.xpack.esql.view.InMemoryViewService; +import org.elasticsearch.xpack.esql.view.View; import org.junit.After; import org.junit.Before; @@ -124,6 +128,8 @@ import static org.elasticsearch.xpack.esql.CsvTestUtils.loadCsvSpecValues; import static org.elasticsearch.xpack.esql.CsvTestUtils.loadPageFromCsv; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.CSV_DATASET_MAP; +import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.VIEW_CONFIGS; +import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.loadViewQuery; import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_PLANNER_SETTINGS; import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_VERIFIER; import static org.elasticsearch.xpack.esql.EsqlTestUtils.classpathResources; @@ -344,6 +350,14 @@ public final void test() throws Throwable { "CSV tests cannot currently handle multi_match function that depends on Lucene", testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.capabilityName()) ); + assumeFalse( + "CSV tests cannot currently handle subqueries", + testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.capabilityName()) + ); + assumeFalse( + "CSV tests cannot currently handle views with branching", + testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.VIEWS_WITH_BRANCHING.capabilityName()) + ); if (Build.current().isSnapshot()) { assertThat( @@ -414,6 +428,16 @@ protected void assertResults(ExpectedResults expected, ActualResults actual, boo CsvAssert.assertResults(expected, actual, ignoreOrder, logger); } + private static Map loadIndexResolution( + Map datasets + ) { + Map indexResolutions = new HashMap<>(); + for (var entry : datasets.entrySet()) { + indexResolutions.put(entry.getKey(), loadIndexResolution(entry.getValue())); + } + return indexResolutions; + } + private static IndexResolution loadIndexResolution(CsvTestsDataLoader.MultiIndexTestDataset datasets) { var indexNames = datasets.datasets().stream().map(CsvTestsDataLoader.TestDataset::indexName); Map indexModes = indexNames.collect(Collectors.toMap(x -> x, x -> IndexMode.STANDARD)); @@ -429,21 +453,30 @@ private static IndexResolution loadIndexResolution(CsvTestsDataLoader.MultiIndex private static Map createMappingForIndex(CsvTestsDataLoader.TestDataset dataset) { var mapping = new TreeMap<>(loadMapping(dataset.mappingFileName())); - if (dataset.typeMapping() == null) { - return mapping; + if (dataset.typeMapping() != null) { + for (var entry : dataset.typeMapping().entrySet()) { + if (mapping.containsKey(entry.getKey())) { + DataType dataType = DataType.fromTypeName(entry.getValue()); + EsField field = mapping.get(entry.getKey()); + EsField editedField = new EsField( + field.getName(), + dataType, + field.getProperties(), + field.isAggregatable(), + field.getTimeSeriesFieldType() + ); + mapping.put(entry.getKey(), editedField); + } + } } - for (var entry : dataset.typeMapping().entrySet()) { - if (mapping.containsKey(entry.getKey())) { - DataType dataType = DataType.fromTypeName(entry.getValue()); - EsField field = mapping.get(entry.getKey()); - EsField editedField = new EsField( - field.getName(), - dataType, - field.getProperties(), - field.isAggregatable(), - field.getTimeSeriesFieldType() - ); - mapping.put(entry.getKey(), editedField); + // Add dynamic mappings, but only if they are not already mapped + if (dataset.dynamicTypeMapping() != null) { + for (var entry : dataset.dynamicTypeMapping().entrySet()) { + if (mapping.containsKey(entry.getKey()) == false) { + DataType dataType = DataType.fromTypeName(entry.getValue()); + EsField editedField = new EsField(entry.getKey(), dataType, Map.of(), false, EsField.TimeSeriesFieldType.NONE); + mapping.put(entry.getKey(), editedField); + } } } return mapping; @@ -526,7 +559,7 @@ private static EnrichPolicy loadEnrichPolicyMapping(String policyFileName) { private LogicalPlan analyzedPlan( LogicalPlan parsed, - CsvTestsDataLoader.MultiIndexTestDataset datasets, + Map datasets, TransportVersion minimumVersion ) { var indexResolution = loadIndexResolution(datasets); @@ -549,52 +582,85 @@ private LogicalPlan analyzedPlan( return plan; } - private static CsvTestsDataLoader.MultiIndexTestDataset testDatasets(LogicalPlan parsed) { + private LogicalPlan resolveViews(LogicalPlan parsed) { + InMemoryViewService viewService = new InMemoryViewService(functionRegistry); + for (var viewConfig : VIEW_CONFIGS) { + try { + String viewQuery = loadViewQuery(viewConfig.viewName(), viewConfig.viewFileName(), LOGGER); + viewService.put(viewConfig.viewName(), new View(viewQuery), ActionListener.noop()); + } catch (IOException e) { + logger.error("Failed to load view '" + viewConfig + "': " + e.getMessage()); + throw new RuntimeException(e); + } + } + return viewService.replaceViews(parsed, new PlanTelemetry(functionRegistry)); + } + + private Map testDatasets(LogicalPlan parsed) { var preAnalysis = new PreAnalyzer().preAnalyze(parsed); - if (preAnalysis.indexPattern() == null) { + if (preAnalysis.indexes().isEmpty()) { // If the data set doesn't matter we'll just grab one we know works. Employees is fine. - return CsvTestsDataLoader.MultiIndexTestDataset.of(CSV_DATASET_MAP.get("employees")); + return Map.of( + new IndexPattern(Source.EMPTY, "employees"), + CsvTestsDataLoader.MultiIndexTestDataset.of(CSV_DATASET_MAP.get("employees")) + ); } - String indexName = preAnalysis.indexPattern().indexPattern(); - List datasets = new ArrayList<>(); - if (indexName.endsWith("*")) { - String indexPrefix = indexName.substring(0, indexName.length() - 1); - for (var entry : CSV_DATASET_MAP.entrySet()) { - if (entry.getKey().startsWith(indexPrefix)) { - datasets.add(entry.getValue()); + List missing = new ArrayList<>(); + Map all = new HashMap<>(); + for (IndexPattern indexPattern : preAnalysis.indexes().keySet()) { + List datasets = new ArrayList<>(); + String indexName = indexPattern.indexPattern(); + if (indexName.endsWith("*")) { + String indexPrefix = indexName.substring(0, indexName.length() - 1); + for (var entry : CSV_DATASET_MAP.entrySet()) { + if (entry.getKey().startsWith(indexPrefix)) { + datasets.add(entry.getValue()); + } } - } - } else { - for (String index : indexName.split(",")) { - var dataset = CSV_DATASET_MAP.get(index); - if (dataset == null) { - throw new IllegalArgumentException("unknown CSV dataset for table [" + index + "]"); + } else { + for (String index : indexName.split(",")) { + var dataset = CSV_DATASET_MAP.get(index); + if (dataset == null) { + throw new IllegalArgumentException("unknown CSV dataset for table [" + index + "]"); + } + datasets.add(dataset); } - datasets.add(dataset); } + if (datasets.isEmpty() == false) { + all.put(indexPattern, new CsvTestsDataLoader.MultiIndexTestDataset(indexName, datasets)); + } else { + missing.add(indexName); + } + } + if (all.isEmpty()) { + throw new IllegalArgumentException("Found no CSV datasets for table [" + preAnalysis.indexes() + "]"); } - if (datasets.isEmpty()) { - throw new IllegalArgumentException("unknown CSV dataset for table [" + indexName + "]"); + if (missing.isEmpty() == false) { + throw new IllegalArgumentException("Did not find datasets for tables: " + missing); } - return new CsvTestsDataLoader.MultiIndexTestDataset(indexName, datasets); + return all; } private static TestPhysicalOperationProviders testOperationProviders( FoldContext foldCtx, - CsvTestsDataLoader.MultiIndexTestDataset datasets + Map allDatasets ) throws Exception { var indexPages = new ArrayList(); - for (CsvTestsDataLoader.TestDataset dataset : datasets.datasets()) { - var testData = loadPageFromCsv(CsvTests.class.getResource("/data/" + dataset.dataFileName()), dataset.typeMapping()); - Set mappedFields = loadMapping(dataset.mappingFileName()).keySet(); - indexPages.add(new TestPhysicalOperationProviders.IndexPage(dataset.indexName(), testData.v1(), testData.v2(), mappedFields)); + for (CsvTestsDataLoader.MultiIndexTestDataset datasets : allDatasets.values()) { + for (CsvTestsDataLoader.TestDataset dataset : datasets.datasets()) { + var testData = loadPageFromCsv(CsvTests.class.getResource("/data/" + dataset.dataFileName()), dataset.typeMapping()); + Set mappedFields = loadMapping(dataset.mappingFileName()).keySet(); + indexPages.add( + new TestPhysicalOperationProviders.IndexPage(dataset.indexName(), testData.v1(), testData.v2(), mappedFields) + ); + } } return TestPhysicalOperationProviders.create(foldCtx, indexPages); } private ActualResults executePlan(BigArrays bigArrays) throws Exception { - LogicalPlan parsed = parser.createStatement(testCase.query); + LogicalPlan parsed = resolveViews(parser.createStatement(testCase.query)); var testDatasets = testDatasets(parsed); // Specifically use the newest transport version; the csv tests correspond to a single node cluster on the current version. TransportVersion minimumVersion = TransportVersion.current(); @@ -607,6 +673,7 @@ private ActualResults executePlan(BigArrays bigArrays) throws Exception { null, null, null, + null, functionRegistry, mapper, TEST_VERIFIER, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java index a501ee2100bbc..6b0f56f2c3c33 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java @@ -12,6 +12,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.esql.EsqlTestUtils; +import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.core.type.InvalidMappedField; import org.elasticsearch.xpack.esql.enrich.ResolvedEnrichPolicy; @@ -22,18 +23,23 @@ import org.elasticsearch.xpack.esql.inference.ResolvedInference; import org.elasticsearch.xpack.esql.parser.EsqlParser; import org.elasticsearch.xpack.esql.parser.QueryParams; +import org.elasticsearch.xpack.esql.plan.IndexPattern; import org.elasticsearch.xpack.esql.plan.logical.Enrich; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; +import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation; import org.elasticsearch.xpack.esql.session.Configuration; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.elasticsearch.xpack.core.enrich.EnrichPolicy.GEO_MATCH_TYPE; import static org.elasticsearch.xpack.core.enrich.EnrichPolicy.MATCH_TYPE; @@ -55,10 +61,16 @@ public static Analyzer expandedDefaultAnalyzer() { return analyzer(expandedDefaultIndexResolution()); } + /** Simplest analyzer with a single index, which must be valid */ public static Analyzer analyzer(IndexResolution indexResolution) { return analyzer(indexResolution, TEST_VERIFIER); } + /** Simple analyzer with multiple indexes, which may also be invalid */ + public static Analyzer analyzer(Map indexResolutions) { + return analyzer(indexResolutions, defaultLookupResolution(), defaultEnrichResolution(), TEST_VERIFIER, TEST_CFG); + } + public static Analyzer analyzer(IndexResolution indexResolution, Map lookupResolution) { return analyzer(indexResolution, lookupResolution, TEST_VERIFIER); } @@ -77,11 +89,11 @@ public static Analyzer analyzer( EnrichResolution enrichResolution, Verifier verifier ) { - return analyzer(indexResolution, lookupResolution, enrichResolution, verifier, TEST_CFG); + return analyzer(indexResolutions(indexResolution), lookupResolution, enrichResolution, verifier, TEST_CFG); } public static Analyzer analyzer( - IndexResolution indexResolution, + Map indexResolutions, Map lookupResolution, EnrichResolution enrichResolution, Verifier verifier, @@ -91,7 +103,7 @@ public static Analyzer analyzer( testAnalyzerContext( config, new EsqlFunctionRegistry(), - indexResolution, + mergeIndexResolutions(indexResolutions, defaultSubqueryResolution()), lookupResolution, enrichResolution, defaultInferenceResolution() @@ -100,22 +112,25 @@ public static Analyzer analyzer( ); } - public static Analyzer analyzer(IndexResolution indexResolution, Verifier verifier, Configuration config) { - return analyzer(indexResolution, defaultLookupResolution(), defaultEnrichResolution(), verifier, config); + private static Map mergeIndexResolutions( + Map indexResolutions, + Map more + ) { + Map combined = new HashMap<>(indexResolutions); + combined.putAll(more); + return combined; + } + + public static Analyzer analyzer(Map indexResolutions, Verifier verifier, Configuration config) { + return analyzer(indexResolutions, defaultLookupResolution(), defaultEnrichResolution(), verifier, config); } public static Analyzer analyzer(Verifier verifier) { - return new Analyzer( - testAnalyzerContext( - EsqlTestUtils.TEST_CFG, - new EsqlFunctionRegistry(), - analyzerDefaultMapping(), - defaultLookupResolution(), - defaultEnrichResolution(), - defaultInferenceResolution() - ), - verifier - ); + return analyzer(analyzerDefaultMapping(), defaultLookupResolution(), defaultEnrichResolution(), verifier, EsqlTestUtils.TEST_CFG); + } + + public static Analyzer analyzer(Map indexResolutions, Verifier verifier) { + return analyzer(indexResolutions, defaultLookupResolution(), defaultEnrichResolution(), verifier, EsqlTestUtils.TEST_CFG); } public static LogicalPlan analyze(String query) { @@ -123,11 +138,14 @@ public static LogicalPlan analyze(String query) { } public static LogicalPlan analyze(String query, String mapping) { - return analyze(query, "test", mapping); + return analyze(query, indexFromQuery(query), mapping); } public static LogicalPlan analyze(String query, String index, String mapping) { - return analyze(query, analyzer(loadMapping(mapping, index), TEST_VERIFIER, configuration(query))); + Map indexResolutions = index == null + ? Map.of() + : Map.of(new IndexPattern(Source.EMPTY, index), loadMapping(mapping, index)); + return analyze(query, analyzer(indexResolutions, TEST_VERIFIER, configuration(query))); } public static LogicalPlan analyze(String query, Analyzer analyzer) { @@ -138,12 +156,40 @@ public static LogicalPlan analyze(String query, Analyzer analyzer) { return analyzed; } + private static final Pattern indexFromPattern = Pattern.compile("(?i)FROM\\s+([\\w-]+)"); + + private static String indexFromQuery(String query) { + // Extract the index name from the FROM clause of the query using regexp + Matcher matcher = indexFromPattern.matcher(query); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + public static LogicalPlan analyze(String query, String mapping, QueryParams params) { + return analyze(query, indexFromQuery(query), mapping, params); + } + + public static LogicalPlan analyze(String query, String index, String mapping, QueryParams params) { var plan = new EsqlParser().createStatement(query, params); - var analyzer = analyzer(loadMapping(mapping, "test"), TEST_VERIFIER, configuration(query)); + var indexResolutions = Map.of(new IndexPattern(Source.EMPTY, index), loadMapping(mapping, index)); + var analyzer = analyzer(indexResolutions, TEST_VERIFIER, configuration(query)); return analyzer.analyze(plan); } + public static UnresolvedRelation unresolvedRelation(String index) { + return new UnresolvedRelation( + Source.EMPTY, + new IndexPattern(Source.EMPTY, index), + false, + List.of(), + IndexMode.STANDARD, + null, + "FROM" + ); + } + public static IndexResolution loadMapping(String resource, String indexName, IndexMode indexMode) { EsIndex test = new EsIndex(indexName, EsqlTestUtils.loadMapping(resource), Map.of(indexName, indexMode)); return IndexResolution.valid(test); @@ -154,8 +200,30 @@ public static IndexResolution loadMapping(String resource, String indexName) { return IndexResolution.valid(test); } - public static IndexResolution analyzerDefaultMapping() { - return loadMapping("mapping-basic.json", "test"); + public static Map analyzerDefaultMapping() { + // Most tests use either "test" or "employees" as the index name, but for the same mapping + return Map.of( + new IndexPattern(Source.EMPTY, "test"), + loadMapping("mapping-basic.json", "test"), + new IndexPattern(Source.EMPTY, "employees"), + loadMapping("mapping-basic.json", "employees") + ); + } + + public static Map indexResolutions(EsIndex... indexes) { + Map map = new HashMap<>(); + for (EsIndex index : indexes) { + map.put(new IndexPattern(Source.EMPTY, index.name()), IndexResolution.valid(index)); + } + return map; + } + + public static Map indexResolutions(IndexResolution... indexes) { + Map map = new HashMap<>(); + for (IndexResolution index : indexes) { + map.put(new IndexPattern(Source.EMPTY, index.get().name()), index); + } + return map; } public static IndexResolution expandedDefaultIndexResolution() { @@ -223,6 +291,17 @@ public static InferenceResolution defaultInferenceResolution() { .build(); } + public static Map defaultSubqueryResolution() { + return Map.of( + new IndexPattern(Source.EMPTY, "languages"), + loadMapping("mapping-languages.json", "languages"), + new IndexPattern(Source.EMPTY, "sample_data"), + loadMapping("mapping-sample_data.json", "sample_data"), + new IndexPattern(Source.EMPTY, "test_mixed_types"), + loadMapping("mapping-default-incompatible.json", "test_mixed_types") + ); + } + public static String randomInferenceId() { return ESTestCase.randomFrom(VALID_INFERENCE_IDS); } @@ -299,7 +378,7 @@ public static IndexResolution indexWithDateDateNanosUnionType() { EsField dateDateNanosField = new InvalidMappedField(dateDateNanos, typesToIndices1); EsField dateDateNanosLongField = new InvalidMappedField(dateDateNanosLong, typesToIndices2); EsIndex index = new EsIndex( - "test*", + "index*", Map.of(dateDateNanos, dateDateNanosField, dateDateNanosLong, dateDateNanosLongField), Map.of("index1", IndexMode.STANDARD, "index2", IndexMode.STANDARD, "index3", IndexMode.STANDARD) ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index 1423699ab7b45..c7b39226c0a1e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -44,6 +44,7 @@ import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField; import org.elasticsearch.xpack.esql.core.type.PotentiallyUnmappedKeywordEsField; import org.elasticsearch.xpack.esql.enrich.ResolvedEnrichPolicy; +import org.elasticsearch.xpack.esql.expression.Order; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute; import org.elasticsearch.xpack.esql.expression.function.aggregate.Count; @@ -87,9 +88,11 @@ import org.elasticsearch.xpack.esql.plan.logical.Limit; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.logical.Lookup; +import org.elasticsearch.xpack.esql.plan.logical.MvExpand; import org.elasticsearch.xpack.esql.plan.logical.OrderBy; import org.elasticsearch.xpack.esql.plan.logical.Project; import org.elasticsearch.xpack.esql.plan.logical.Row; +import org.elasticsearch.xpack.esql.plan.logical.UnionAll; import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation; import org.elasticsearch.xpack.esql.plan.logical.fuse.FuseScoreEval; import org.elasticsearch.xpack.esql.plan.logical.inference.Completion; @@ -130,10 +133,12 @@ import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyzerDefaultMapping; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultEnrichResolution; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultInferenceResolution; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexWithDateDateNanosUnionType; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.loadMapping; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.randomInferenceIdOtherThan; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.tsdbIndexResolution; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.unresolvedRelation; import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY; import static org.elasticsearch.xpack.esql.core.type.DataType.AGGREGATE_METRIC_DOUBLE; import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME; @@ -141,6 +146,7 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_PERIOD; import static org.elasticsearch.xpack.esql.core.type.DataType.DENSE_VECTOR; import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; +import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED; @@ -161,16 +167,7 @@ //@TestLogging(value = "org.elasticsearch.xpack.esql.analysis:TRACE", reason = "debug") public class AnalyzerTests extends ESTestCase { - private static final UnresolvedRelation UNRESOLVED_RELATION = new UnresolvedRelation( - EMPTY, - new IndexPattern(EMPTY, "idx"), - false, - List.of(), - IndexMode.STANDARD, - null, - "FROM" - ); - + private static final UnresolvedRelation UNRESOLVED_RELATION = unresolvedRelation("idx"); private static final int MAX_LIMIT = AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY); private static final int DEFAULT_LIMIT = AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY); private static final int DEFAULT_TIMESERIES_LIMIT = AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault( @@ -187,7 +184,7 @@ public void testIndexResolution() { } public void testFailOnUnresolvedIndex() { - Analyzer analyzer = analyzer(IndexResolution.invalid("Unknown index [idx]")); + Analyzer analyzer = analyzer(Map.of(new IndexPattern(Source.EMPTY, "idx"), IndexResolution.invalid("Unknown index [idx]"))); VerificationException e = expectThrows(VerificationException.class, () -> analyzer.analyze(UNRESOLVED_RELATION)); @@ -198,7 +195,7 @@ public void testIndexWithClusterResolution() { EsIndex idx = new EsIndex("cluster:idx", Map.of()); Analyzer analyzer = analyzer(IndexResolution.valid(idx)); - var plan = analyzer.analyze(UNRESOLVED_RELATION); + var plan = analyzer.analyze(unresolvedRelation("cluster:idx")); var limit = as(plan, Limit.class); assertEquals(new EsRelation(EMPTY, idx.name(), IndexMode.STANDARD, idx.indexNameWithModes(), NO_FIELDS), limit.child()); @@ -264,7 +261,7 @@ public void testRowAttributeResolution() { var plan = analyzer.analyze( new Eval( EMPTY, - new Row(EMPTY, List.of(new Alias(EMPTY, "emp_no", new Literal(EMPTY, 1, DataType.INTEGER)))), + new Row(EMPTY, List.of(new Alias(EMPTY, "emp_no", new Literal(EMPTY, 1, INTEGER)))), List.of(new Alias(EMPTY, "e", new UnresolvedAttribute(EMPTY, "emp_no"))) ) ); @@ -272,7 +269,7 @@ public void testRowAttributeResolution() { var limit = as(plan, Limit.class); var eval = as(limit.child(), Eval.class); assertEquals(1, eval.fields().size()); - assertEquals(new Alias(EMPTY, "e", new ReferenceAttribute(EMPTY, "emp_no", DataType.INTEGER)), eval.fields().get(0)); + assertEquals(new Alias(EMPTY, "e", new ReferenceAttribute(EMPTY, "emp_no", INTEGER)), eval.fields().get(0)); assertEquals(2, eval.output().size()); Attribute empNo = eval.output().get(0); @@ -403,16 +400,16 @@ public void testNoProjection() { from test """, DataType.KEYWORD, - DataType.INTEGER, + INTEGER, DataType.KEYWORD, DataType.TEXT, DATETIME, DataType.TEXT, DataType.KEYWORD, - DataType.INTEGER, + INTEGER, DataType.KEYWORD, DataType.LONG, - DataType.INTEGER + INTEGER ); } @@ -1680,7 +1677,7 @@ public void testEnrichPolicyWithError() { AnalyzerContext context = testAnalyzerContext( configuration("from test"), new EsqlFunctionRegistry(), - testIndex, + indexResolutions(testIndex), enrichResolution, emptyInferenceResolution() ); @@ -1836,7 +1833,7 @@ public void testEnrichFieldsIncludeMatchField() { AnalyzerContext context = testAnalyzerContext( configuration(query), new EsqlFunctionRegistry(), - testIndex, + indexResolutions(testIndex), enrichResolution, emptyInferenceResolution() ); @@ -1917,7 +1914,7 @@ public void testUnresolvedMvExpand() { public void testRegularStats() { var plan = analyze(""" - from tests + from test | stats by salary """); @@ -2637,7 +2634,7 @@ private void validateConditionalFunctions(LogicalPlan plan) { assertEquals(projection.dataType(), DataType.DOUBLE); projection = as(projections.get(1), ReferenceAttribute.class); assertEquals(projection.name(), "y"); - assertEquals(projection.dataType(), DataType.INTEGER); + assertEquals(projection.dataType(), INTEGER); projection = as(projections.get(2), ReferenceAttribute.class); assertEquals(projection.name(), "z"); assertEquals(projection.dataType(), DataType.LONG); @@ -3046,7 +3043,7 @@ public void testFromEnrichAndMatchColonUsage() { | EVAL x = to_string(languages) | ENRICH _any:languages ON x | WHERE first_name: "Anna" - """, "mapping-default.json"); + """, "*:test", "mapping-default.json"); var limit = as(plan, Limit.class); var filter = as(limit.child(), Filter.class); var match = as(filter.condition(), MatchOperator.class); @@ -3055,7 +3052,7 @@ public void testFromEnrichAndMatchColonUsage() { assertEquals(enrich.policy().getMatchField(), "language_code"); var eval = as(enrich.child(), Eval.class); var esRelation = as(eval.child(), EsRelation.class); - assertEquals(esRelation.indexPattern(), "test"); + assertEquals(esRelation.indexPattern(), "*:test"); // This tests nothing, as whatever appears here comes from the test itself } public void testFunctionNamedParamsAsFunctionArgument() { @@ -3113,7 +3110,7 @@ public void testResolveInsist_fieldExists_insistedOutputContainsNoUnmappedFields Attribute last = plan.output().getLast(); assertThat(last.name(), is("emp_no")); - assertThat(last.dataType(), is(DataType.INTEGER)); + assertThat(last.dataType(), is(INTEGER)); assertThat( plan.output() .stream() @@ -3150,7 +3147,7 @@ public void testResolveInsist_multiIndexFieldPartiallyMappedWithSingleKeywordTyp assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); IndexResolution resolution = IndexResolver.mergedMappings( - "foo, bar", + "foo,bar", new IndexResolver.FieldsInfo( new FieldCapabilitiesResponse( List.of( @@ -3165,7 +3162,7 @@ public void testResolveInsist_multiIndexFieldPartiallyMappedWithSingleKeywordTyp ); String query = "FROM foo, bar | INSIST_🐔 message"; - var plan = analyze(query, analyzer(resolution, TEST_VERIFIER, configuration(query))); + var plan = analyze(query, analyzer(indexResolutions(resolution), TEST_VERIFIER, configuration(query))); var limit = as(plan, Limit.class); var insist = as(limit.child(), Insist.class); var attribute = (FieldAttribute) EsqlTestUtils.singleValue(insist.output()); @@ -3177,7 +3174,7 @@ public void testResolveInsist_multiIndexFieldExistsWithSingleTypeButIsNotKeyword assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); IndexResolution resolution = IndexResolver.mergedMappings( - "foo, bar", + "foo,bar", new IndexResolver.FieldsInfo( new FieldCapabilitiesResponse( List.of( @@ -3205,7 +3202,7 @@ public void testResolveInsist_multiIndexFieldPartiallyExistsWithMultiTypesNoKeyw assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); IndexResolution resolution = IndexResolver.mergedMappings( - "foo, bar", + "foo,bar", new IndexResolver.FieldsInfo( new FieldCapabilitiesResponse( List.of( @@ -3233,7 +3230,7 @@ public void testResolveInsist_multiIndexSameMapping_fieldIsMapped() { assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); IndexResolution resolution = IndexResolver.mergedMappings( - "foo, bar", + "foo,bar", new IndexResolver.FieldsInfo( new FieldCapabilitiesResponse( List.of( @@ -3258,7 +3255,7 @@ public void testResolveInsist_multiIndexFieldPartiallyExistsWithMultiTypesWithKe assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); IndexResolution resolution = IndexResolver.mergedMappings( - "foo, bar", + "foo,bar", new IndexResolver.FieldsInfo( new FieldCapabilitiesResponse( List.of( @@ -3287,7 +3284,7 @@ public void testResolveInsist_multiIndexFieldPartiallyExistsWithMultiTypesWithCa assumeTrue("Requires UNMAPPED FIELDS", EsqlCapabilities.Cap.UNMAPPED_FIELDS.isEnabled()); IndexResolution resolution = IndexResolver.mergedMappings( - "foo, bar", + "foo,bar", new IndexResolver.FieldsInfo( new FieldCapabilitiesResponse( List.of( @@ -3303,7 +3300,7 @@ public void testResolveInsist_multiIndexFieldPartiallyExistsWithMultiTypesWithCa ); VerificationException e = expectThrows( VerificationException.class, - () -> analyze("FROM multi_index | INSIST_🐔 message | EVAL message = message :: keyword", analyzer(resolution, TEST_VERIFIER)) + () -> analyze("FROM foo, bar | INSIST_🐔 message | EVAL message = message :: keyword", analyzer(resolution, TEST_VERIFIER)) ); // This isn't the most informative error, but it'll do for now. assertThat( @@ -3803,7 +3800,7 @@ private static LogicalPlan analyzeWithEmptyFieldCapsResponse(String query) throw ); IndexResolver.FieldsInfo caps = new IndexResolver.FieldsInfo(new FieldCapabilitiesResponse(idxResponses, List.of()), true, true); IndexResolution resolution = IndexResolver.mergedMappings("test*", caps); - var analyzer = analyzer(resolution, TEST_VERIFIER, configuration(query)); + var analyzer = analyzer(indexResolutions(resolution), TEST_VERIFIER, configuration(query)); return analyze(query, analyzer); } @@ -4420,7 +4417,7 @@ public void testImplicitCastingForDateAndDateNanosFields() { // Validate if a union typed field is cast to a type explicitly, implicit casting won't be applied again, and include some cases of // nested casting as well. LogicalPlan plan = analyze(""" - FROM tests + FROM index* | Eval a = date_and_date_nanos, b = date_and_date_nanos::datetime, c = date_and_date_nanos::date_nanos, d = date_and_date_nanos::datetime::datetime, e = date_and_date_nanos::datetime::date_nanos, f = date_and_date_nanos::date_nanos::datetime, g = date_and_date_nanos::date_nanos::date_nanos, @@ -4528,7 +4525,7 @@ public void testImplicitCastingForDateAndDateNanosFields() { fa = as(toDateNanos.field(), FieldAttribute.class); verifyNameAndTypeAndMultiTypeEsField(fa.name(), fa.dataType(), "$$date_and_date_nanos$converted_to$long", LONG, fa); EsRelation esRelation = as(eval.child(), EsRelation.class); - assertEquals("test*", esRelation.indexPattern()); + assertEquals("index*", esRelation.indexPattern()); } public void testGroupingOverridesInStats() { @@ -4596,6 +4593,413 @@ public void testTBucketWithDatePeriodInBothAggregationAndGrouping() { assertEquals(oneWeek, literal); } + public void testSubqueryInFrom() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("disabled", false); + LogicalPlan plan = analyze(""" + FROM test, (FROM languages | WHERE language_code > 1) + | WHERE emp_no > 10000 + | SORT emp_no, language_code + """); + + Limit limit = as(plan, Limit.class); + OrderBy orderBy = as(limit.child(), OrderBy.class); + List order = orderBy.order(); + assertEquals(2, order.size()); + ReferenceAttribute empNo = as(order.get(0).child(), ReferenceAttribute.class); + assertEquals("emp_no", empNo.name()); + ReferenceAttribute languageCode = as(order.get(1).child(), ReferenceAttribute.class); + assertEquals("language_code", languageCode.name()); + Filter filter = as(orderBy.child(), Filter.class); + GreaterThan greaterThan = as(filter.condition(), GreaterThan.class); + empNo = as(greaterThan.left(), ReferenceAttribute.class); + assertEquals("emp_no", empNo.name()); + Literal literal = as(greaterThan.right(), Literal.class); + assertEquals(10000, literal.value()); + UnionAll unionAll = as(filter.child(), UnionAll.class); + assertEquals(2, unionAll.children().size()); + // leg1 + Limit subqueryLimit = as(unionAll.children().get(0), Limit.class); + Literal limitLiteral = as(subqueryLimit.limit(), Literal.class); + assertEquals(1000, limitLiteral.value()); + EsqlProject subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + List projections = subqueryProject.projections(); + assertEquals(13, projections.size()); // all fields from the two indices + Eval subqueryEval = as(subqueryProject.child(), Eval.class); + List aliases = subqueryEval.fields(); // nullEvals from languages index + assertEquals(2, aliases.size()); + assertEquals("language_code", aliases.get(0).name()); + Literal nullLiteral = as(aliases.get(0).child(), Literal.class); + assertNull(nullLiteral.value()); + assertEquals(INTEGER, nullLiteral.dataType()); + assertEquals("language_name", aliases.get(1).name()); + nullLiteral = as(aliases.get(1).child(), Literal.class); + assertNull(nullLiteral.value()); + assertEquals(KEYWORD, nullLiteral.dataType()); + EsRelation subqueryIndex = as(subqueryEval.child(), EsRelation.class); + assertEquals("test", subqueryIndex.indexPattern()); + // leg2 + subqueryLimit = as(unionAll.children().get(1), Limit.class); + limitLiteral = as(subqueryLimit.limit(), Literal.class); + assertEquals(1000, limitLiteral.value()); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + projections = subqueryProject.projections(); + assertEquals(13, projections.size()); // all fields from the two indices + subqueryEval = as(subqueryProject.child(), Eval.class); + aliases = subqueryEval.fields(); // nullEvals from test index + assertEquals(11, aliases.size()); + /* + Subquery subquery = as(subqueryEval.child(), Subquery.class); + Filter subqueryFilter = as(subquery.child(), Filter.class); + subqueryIndex = as(subqueryFilter.child(), EsRelation.class); + assertEquals("languages", subqueryIndex.indexPattern()); + */ + } + + public void testMultipleSubqueriesInFrom() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("disabled", false); + LogicalPlan plan = analyze(""" + FROM test + , (FROM languages | WHERE language_code > 10 | RENAME language_name as languageName) + , (FROM sample_data | STATS max(@timestamp)) + , (FROM test | EVAL language_code = languages | LOOKUP JOIN languages_lookup ON language_code) + | WHERE emp_no > 10000 + | STATS count(*) by emp_no, language_code + | RENAME emp_no AS empNo, language_code AS languageCode + | MV_EXPAND languageCode + """); + + Limit limit = as(plan, Limit.class); + MvExpand mvExpand = as(limit.child(), MvExpand.class); + NamedExpression mvExpandTarget = as(mvExpand.target(), NamedExpression.class); + assertEquals("languageCode", mvExpandTarget.name()); + ReferenceAttribute mvExpandExpanded = as(mvExpand.expanded(), ReferenceAttribute.class); + assertEquals("languageCode", mvExpandExpanded.name()); + EsqlProject rename = as(mvExpand.child(), EsqlProject.class); + List projections = rename.projections(); + assertEquals(3, projections.size()); + Alias a = as(projections.get(1), Alias.class); + assertEquals("empNo", a.name()); + ReferenceAttribute ra = as(a.child(), ReferenceAttribute.class); + assertEquals("emp_no", ra.name()); + a = as(projections.get(2), Alias.class); + assertEquals("languageCode", a.name()); + ra = as(a.child(), ReferenceAttribute.class); + assertEquals("language_code", ra.name()); + Aggregate aggregate = as(rename.child(), Aggregate.class); + List aggregates = aggregate.aggregates(); + assertEquals(3, aggregates.size()); + a = as(aggregates.get(0), Alias.class); + assertEquals("count(*)", a.name()); + List groupings = aggregate.groupings(); + assertEquals(2, groupings.size()); + ra = as(groupings.get(0), ReferenceAttribute.class); + assertEquals("emp_no", ra.name()); + ra = as(groupings.get(1), ReferenceAttribute.class); + assertEquals("language_code", ra.name()); + Filter filter = as(aggregate.child(), Filter.class); + GreaterThan greaterThan = as(filter.condition(), GreaterThan.class); + ReferenceAttribute empNo = as(greaterThan.left(), ReferenceAttribute.class); + assertEquals("emp_no", empNo.name()); + Literal literal = as(greaterThan.right(), Literal.class); + assertEquals(10000, literal.value()); + UnionAll unionAll = as(filter.child(), UnionAll.class); + assertEquals(4, unionAll.children().size()); + // leg1 + Limit subqueryLimit = as(unionAll.children().get(0), Limit.class); + Literal limitLiteral = as(subqueryLimit.limit(), Literal.class); + assertEquals(1000, limitLiteral.value()); + EsqlProject subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + projections = subqueryProject.projections(); + assertEquals(15, projections.size()); // all fields from the other legs + Eval subqueryEval = as(subqueryProject.child(), Eval.class); + List aliases = subqueryEval.fields(); // nullEvals from the other legs + assertEquals(4, aliases.size()); + EsRelation subqueryIndex = as(subqueryEval.child(), EsRelation.class); + assertEquals("test", subqueryIndex.indexPattern()); + // leg2 + subqueryLimit = as(unionAll.children().get(1), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + projections = subqueryProject.projections(); + assertEquals(15, projections.size()); // all fields from the other legs + subqueryEval = as(subqueryProject.child(), Eval.class); + aliases = subqueryEval.fields(); // nullEvals from the other legs + assertEquals(13, aliases.size()); + /* + Subquery subquery = as(subqueryEval.child(), Subquery.class); + rename = as(subquery.child(), EsqlProject.class); + */ + List renameProjections = rename.projections(); + assertEquals(2, renameProjections.size()); + FieldAttribute language_code = as(renameProjections.get(0), FieldAttribute.class); + assertEquals("language_code", language_code.name()); + a = as(renameProjections.get(1), Alias.class); + assertEquals("languageName", a.name()); + FieldAttribute language_name = as(a.child(), FieldAttribute.class); + assertEquals("language_name", language_name.name()); + Filter subqueryFilter = as(rename.child(), Filter.class); + greaterThan = as(subqueryFilter.condition(), GreaterThan.class); + language_code = as(greaterThan.left(), FieldAttribute.class); + assertEquals("language_code", language_code.name()); + literal = as(greaterThan.right(), Literal.class); + assertEquals(10, literal.value()); + subqueryIndex = as(subqueryFilter.child(), EsRelation.class); + assertEquals("languages", subqueryIndex.indexPattern()); + // leg3 + subqueryLimit = as(unionAll.children().get(2), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + projections = subqueryProject.projections(); + assertEquals(15, projections.size()); // all fields from the other legs + subqueryEval = as(subqueryProject.child(), Eval.class); + aliases = subqueryEval.fields(); // nullEvals from the other legs + assertEquals(14, aliases.size()); + /* + subquery = as(subqueryEval.child(), Subquery.class); + Aggregate subqueryAggregate = as(subquery.child(), Aggregate.class); + subqueryIndex = as(subqueryAggregate.child(), EsRelation.class); + assertEquals("sample_data", subqueryIndex.indexPattern()); + */ + // leg4 + subqueryLimit = as(unionAll.children().get(3), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + projections = subqueryProject.projections(); + assertEquals(15, projections.size()); // all fields from the other legs + subqueryEval = as(subqueryProject.child(), Eval.class); + aliases = subqueryEval.fields(); // nullEvals from the other legs + assertEquals(2, aliases.size()); + /* + subquery = as(subqueryEval.child(), Subquery.class); + LookupJoin lookupJoin = as(subquery.child(), LookupJoin.class); + subqueryIndex = as(lookupJoin.right(), EsRelation.class); + assertEquals("languages_lookup", subqueryIndex.indexPattern()); + subqueryEval = as(lookupJoin.left(), Eval.class); + subqueryIndex = as(subqueryEval.child(), EsRelation.class); + assertEquals("test", subqueryIndex.indexPattern()); + */ + } + + /** + * Nested fork/subquery is not supported, it passes Analyzer, but the query will + * fail in logical planner verifier. We may add a rule in logical planner to flatten nested + * subqueries in the future, so leave the check of nested subqueries after logical planner. + */ + public void testNestedSubqueryInFrom() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("disabled", false); + LogicalPlan plan = analyze(""" + FROM test, (FROM languages, (FROM sample_data | STATS count(*)) | WHERE language_code > 10) + | WHERE emp_no > 10000 + | SORT emp_no, language_code + """); + + Limit limit = as(plan, Limit.class); + OrderBy orderBy = as(limit.child(), OrderBy.class); + Filter filter = as(orderBy.child(), Filter.class); + UnionAll unionAll = as(filter.child(), UnionAll.class); + assertEquals(2, unionAll.children().size()); + // leg1 + Limit subqueryLimit = as(unionAll.children().get(0), Limit.class); + EsqlProject subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + Eval subqueryEval = as(subqueryProject.child(), Eval.class); + EsRelation subqueryIndex = as(subqueryEval.child(), EsRelation.class); + assertEquals("test", subqueryIndex.indexPattern()); + subqueryLimit = as(unionAll.children().get(1), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + subqueryEval = as(subqueryProject.child(), Eval.class); + /* + Subquery subquery = as(subqueryEval.child(), Subquery.class); + Filter subqueryFilter = as(subquery.child(), Filter.class); + unionAll = as(subqueryFilter.child(), UnionAll.class); + assertEquals(2, unionAll.children().size()); + */ + // leg2 + subqueryLimit = as(unionAll.children().get(0), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + subqueryEval = as(subqueryProject.child(), Eval.class); + subqueryIndex = as(subqueryEval.child(), EsRelation.class); + assertEquals("languages", subqueryIndex.indexPattern()); + // leg3 + subqueryLimit = as(unionAll.children().get(1), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + subqueryEval = as(subqueryProject.child(), Eval.class); + /* + subquery = as(subqueryEval.child(), Subquery.class); + Aggregate subqueryAggregate = as(subquery.child(), Aggregate.class); + subqueryIndex = as(subqueryAggregate.child(), EsRelation.class); + assertEquals("sample_data", subqueryIndex.indexPattern()); + */ + } + + // nested fork/subquery is not supported, it passes Analyzer and the query will fail in logical planner verifier + public void testNestedSubqueryInFromWithMetadata() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("disabled", false); + LogicalPlan plan = analyze(""" + FROM test, (FROM languages, (FROM sample_data | STATS count(*)) | WHERE language_code > 10) metadata _index + | WHERE emp_no > 10000 + | SORT emp_no, language_code + """); + + Limit limit = as(plan, Limit.class); + OrderBy orderBy = as(limit.child(), OrderBy.class); + Filter filter = as(orderBy.child(), Filter.class); + UnionAll unionAll = as(filter.child(), UnionAll.class); + assertEquals(2, unionAll.children().size()); + // leg1 + Limit subqueryLimit = as(unionAll.children().get(0), Limit.class); + EsqlProject subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + Eval subqueryEval = as(subqueryProject.child(), Eval.class); + EsRelation subqueryIndex = as(subqueryEval.child(), EsRelation.class); + assertEquals("test", subqueryIndex.indexPattern()); + List output = subqueryIndex.output(); + assertEquals(12, output.size()); + MetadataAttribute metadataAttribute = as(output.get(11), MetadataAttribute.class); + assertEquals("_index", metadataAttribute.name()); + + subqueryLimit = as(unionAll.children().get(1), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + subqueryEval = as(subqueryProject.child(), Eval.class); + /* + Subquery subquery = as(subqueryEval.child(), Subquery.class); + Filter subqueryFilter = as(subquery.child(), Filter.class); + unionAll = as(subqueryFilter.child(), UnionAll.class); + assertEquals(2, unionAll.children().size()); + */ + // leg2 + subqueryLimit = as(unionAll.children().get(0), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + subqueryEval = as(subqueryProject.child(), Eval.class); + subqueryIndex = as(subqueryEval.child(), EsRelation.class); + assertEquals("languages", subqueryIndex.indexPattern()); + output = subqueryIndex.output(); + assertEquals(2, output.size()); + // leg3 + subqueryLimit = as(unionAll.children().get(1), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + subqueryEval = as(subqueryProject.child(), Eval.class); + /* + subquery = as(subqueryEval.child(), Subquery.class); + Aggregate subqueryAggregate = as(subquery.child(), Aggregate.class); + subqueryIndex = as(subqueryAggregate.child(), EsRelation.class); + assertEquals("sample_data", subqueryIndex.indexPattern()); + output = subqueryIndex.output(); + assertEquals(4, output.size()); + */ + } + + /** + * When there are mixed date types between the main query and the subquery, + * the fields/references are casted to the common types in the UnionAll legs, otherwise + * FORK's postAnalysisPlanVerification will fail. + */ + public void testMixedDataTypesInSubquery() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("disabled", false); + LogicalPlan plan = analyze(""" + FROM test, (FROM test_mixed_types | WHERE languages > 0) + | WHERE emp_no > 10000 + | SORT emp_no + """, "mapping-default.json"); + + Limit limit = as(plan, Limit.class); + OrderBy orderBy = as(limit.child(), OrderBy.class); + Filter filter = as(orderBy.child(), Filter.class); + UnionAll unionAll = as(filter.child(), UnionAll.class); + List output = unionAll.output(); + // all fields from the two indices + assertEquals(25, output.size()); + assertEquals(2, unionAll.children().size()); + // leg1 + Limit subqueryLimit = as(unionAll.children().get(0), Limit.class); + EsqlProject subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + Eval castingEval = as(subqueryProject.child(), Eval.class); + Eval nullEval = as(castingEval.child(), Eval.class); + EsRelation subqueryIndex = as(nullEval.child(), EsRelation.class); + assertEquals("test", subqueryIndex.indexPattern()); + // leg2 + subqueryLimit = as(unionAll.children().get(1), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + castingEval = as(subqueryProject.child(), Eval.class); + nullEval = as(castingEval.child(), Eval.class); + /* + Subquery subquery = as(nullEval.child(), Subquery.class); + Filter subqueryFilter = as(subquery.child(), Filter.class); + GreaterThan greaterThan = as(subqueryFilter.condition(), GreaterThan.class); + FieldAttribute fa = as(greaterThan.left(), FieldAttribute.class); + assertEquals("languages", fa.name()); + assertEquals(INTEGER, fa.dataType()); + Literal literal = as(greaterThan.right(), Literal.class); + assertEquals(0, literal.value()); + assertEquals(INTEGER, literal.dataType()); + subqueryIndex = as(subqueryFilter.child(), EsRelation.class); + assertEquals("test_mixed_types", subqueryIndex.indexPattern()); + */ + } + + public void testMixedDataTypesWithExplicitCastingInSubquery() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("disabled", false); + LogicalPlan plan = analyze(""" + FROM test, (FROM test_mixed_types | WHERE languages > 0) + | WHERE emp_no > 10000 + | EVAL still_hired = still_hired::string, is_rehired = is_rehired::string + | SORT still_hired, is_rehired + """, "mapping-default.json"); + + Limit limit = as(plan, Limit.class); + OrderBy orderBy = as(limit.child(), OrderBy.class); + Eval eval = as(orderBy.child(), Eval.class); + List aliases = eval.fields(); + assertEquals(2, aliases.size()); + Alias a = aliases.get(0); + assertEquals("still_hired", a.name()); + ReferenceAttribute still_hired = as(a.child(), ReferenceAttribute.class); + assertEquals("still_hired", still_hired.name()); + a = aliases.get(1); + assertEquals("is_rehired", a.name()); + ReferenceAttribute is_rehired = as(a.child(), ReferenceAttribute.class); + assertEquals("is_rehired", is_rehired.name()); + Filter filter = as(eval.child(), Filter.class); + UnionAll unionAll = as(filter.child(), UnionAll.class); + List output = unionAll.output(); + // all fields from the two indices + assertEquals(25, output.size()); + assertEquals(2, unionAll.children().size()); + // leg1 + Limit subqueryLimit = as(unionAll.children().get(0), Limit.class); + EsqlProject subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + Eval castingEval = as(subqueryProject.child(), Eval.class); + Eval nullEval = as(castingEval.child(), Eval.class); + nullEval = as(nullEval.child(), Eval.class); + EsRelation subqueryIndex = as(nullEval.child(), EsRelation.class); + assertEquals("test", subqueryIndex.indexPattern()); + // leg2 + subqueryLimit = as(unionAll.children().get(1), Limit.class); + subqueryProject = as(subqueryLimit.child(), EsqlProject.class); + castingEval = as(subqueryProject.child(), Eval.class); + nullEval = as(castingEval.child(), Eval.class); + /* + Subquery subquery = as(nullEval.child(), Subquery.class); + Filter subqueryFilter = as(subquery.child(), Filter.class); + GreaterThan greaterThan = as(subqueryFilter.condition(), GreaterThan.class); + FieldAttribute fa = as(greaterThan.left(), FieldAttribute.class); + assertEquals("languages", fa.name()); + assertEquals(INTEGER, fa.dataType()); + Literal literal = as(greaterThan.right(), Literal.class); + assertEquals(0, literal.value()); + assertEquals(INTEGER, literal.dataType()); + subqueryIndex = as(subqueryFilter.child(), EsRelation.class); + assertEquals("test_mixed_types", subqueryIndex.indexPattern()); + */ + } + private void verifyNameAndType(String actualName, DataType actualType, String expectedName, DataType expectedType) { assertEquals(expectedName, actualName); assertEquals(expectedType, actualType); @@ -4621,12 +5025,11 @@ public void testImplicitCastingForAggregateMetricDouble() { Map.of("k8s", IndexMode.TIME_SERIES, "k8s-downsampled", IndexMode.TIME_SERIES), Set.of() ); - var indexResolution = IndexResolution.valid(esIndex); var analyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - indexResolution, + indexResolutions(esIndex), defaultEnrichResolution(), defaultInferenceResolution() ), @@ -4690,6 +5093,6 @@ static Literal string(String value) { } static Literal literal(int value) { - return new Literal(EMPTY, value, DataType.INTEGER); + return new Literal(EMPTY, value, INTEGER); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java index 606db6fb8fb2c..ae3426178e36b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java @@ -45,6 +45,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyPolicyResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; @@ -56,7 +57,13 @@ public class ParsingTests extends ESTestCase { private final IndexResolution defaultIndex = loadIndexResolution("mapping-basic.json"); private final Analyzer defaultAnalyzer = new Analyzer( - testAnalyzerContext(TEST_CFG, new EsqlFunctionRegistry(), defaultIndex, emptyPolicyResolution(), emptyInferenceResolution()), + testAnalyzerContext( + TEST_CFG, + new EsqlFunctionRegistry(), + indexResolutions(defaultIndex), + emptyPolicyResolution(), + emptyInferenceResolution() + ), TEST_VERIFIER ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java index 7a3b294be2ab3..b1004ba3ca06b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java @@ -1005,10 +1005,10 @@ public void testFilterNullField() { query("from test | where null::boolean"); // Provide `NULL` type in `EVAL` - query("from t | EVAL x = null | where x"); + query("from test | EVAL x = null | where x"); // `to_string(null)` is of `KEYWORD` type null, resulting in `to_string(null) == "abc"` being of `BOOLEAN` - query("from t | where to_string(null) == \"abc\""); + query("from test | where to_string(null) == \"abc\""); // Other DataTypes can contain null values assertEquals("1:19: Condition expression needs to be boolean, found [KEYWORD]", error("from test | where null::string")); @@ -1113,27 +1113,27 @@ public void testInlineImpossibleConvert() { public void testAggregateOnCounter() { assertThat( - error("FROM tests | STATS min(network.bytes_in)", tsdb), + error("FROM test | STATS min(network.bytes_in)", tsdb), equalTo( - "1:20: argument of [min(network.bytes_in)] must be" + "1:19: argument of [min(network.bytes_in)] must be" + " [boolean, date, ip, string, version, aggregate_metric_double or numeric except counter types]," + " found value [network.bytes_in] type [counter_long]" ) ); assertThat( - error("FROM tests | STATS max(network.bytes_in)", tsdb), + error("FROM test | STATS max(network.bytes_in)", tsdb), equalTo( - "1:20: argument of [max(network.bytes_in)] must be" + "1:19: argument of [max(network.bytes_in)] must be" + " [boolean, date, ip, string, version, aggregate_metric_double or numeric except counter types]," + " found value [network.bytes_in] type [counter_long]" ) ); assertThat( - error("FROM tests | STATS count(network.bytes_out)", tsdb), + error("FROM test | STATS count(network.bytes_out)", tsdb), equalTo( - "1:20: argument of [count(network.bytes_out)] must be [any type except counter types or dense_vector]," + "1:19: argument of [count(network.bytes_out)] must be [any type except counter types or dense_vector]," + " found value [network.bytes_out] type [counter_long]" ) ); @@ -1156,91 +1156,88 @@ public void testAggsResolutionWithUnresolvedGroupings() { new String[] { "avg", "count", "count_distinct", "min", "max", "median", "median_absolute_deviation", "sum", "values" } ); - assertThat(error("FROM tests | STATS " + agg_func + "(emp_no) by foobar"), matchesRegex("1:\\d+: Unknown column \\[foobar]")); + assertThat(error("FROM test | STATS " + agg_func + "(emp_no) by foobar"), matchesRegex("1:\\d+: Unknown column \\[foobar]")); + assertThat(error("FROM test | STATS " + agg_func + "(x) by foobar, x = emp_no"), matchesRegex("1:\\d+: Unknown column \\[foobar]")); + assertThat(error("FROM test | STATS " + agg_func + "(foobar) by foobar"), matchesRegex("1:\\d+: Unknown column \\[foobar]")); assertThat( - error("FROM tests | STATS " + agg_func + "(x) by foobar, x = emp_no"), - matchesRegex("1:\\d+: Unknown column \\[foobar]") - ); - assertThat(error("FROM tests | STATS " + agg_func + "(foobar) by foobar"), matchesRegex("1:\\d+: Unknown column \\[foobar]")); - assertThat( - error("FROM tests | STATS " + agg_func + "(foobar) by BUCKET(hire_date, 10)"), + error("FROM test | STATS " + agg_func + "(foobar) by BUCKET(hire_date, 10)"), matchesRegex( "1:\\d+: function expects exactly four arguments when the first one is of type \\[DATETIME]" + " and the second of type \\[INTEGER]\n" + "line 1:\\d+: Unknown column \\[foobar]" ) ); - assertThat(error("FROM tests | STATS " + agg_func + "(foobar) by emp_no"), matchesRegex("1:\\d+: Unknown column \\[foobar]")); + assertThat(error("FROM test | STATS " + agg_func + "(foobar) by emp_no"), matchesRegex("1:\\d+: Unknown column \\[foobar]")); // TODO: Ideally, we'd detect that count_distinct(x) doesn't require an error message. assertThat( - error("FROM tests | STATS " + agg_func + "(x) by x = foobar"), + error("FROM test | STATS " + agg_func + "(x) by x = foobar"), matchesRegex("1:\\d+: Unknown column \\[foobar]\n" + "line 1:\\d+: Unknown column \\[x]") ); } public void testNotAllowRateOutsideMetrics() { assertThat( - error("FROM tests | STATS avg(rate(network.bytes_in))", tsdb), + error("FROM test | STATS avg(rate(network.bytes_in))", tsdb), equalTo("1:24: time_series aggregate[rate(network.bytes_in)] can only be used with the TS command") ); assertThat( - error("FROM tests | STATS rate(network.bytes_in)", tsdb), + error("FROM test | STATS rate(network.bytes_in)", tsdb), equalTo("1:20: time_series aggregate[rate(network.bytes_in)] can only be used with the TS command") ); assertThat( - error("FROM tests | STATS max_over_time(network.connections)", tsdb), + error("FROM test | STATS max_over_time(network.connections)", tsdb), equalTo("1:20: time_series aggregate[max_over_time(network.connections)] can only be used with the TS command") ); assertThat( - error("FROM tests | EVAL r = rate(network.bytes_in)", tsdb), + error("FROM test | EVAL r = rate(network.bytes_in)", tsdb), equalTo("1:23: aggregate function [rate(network.bytes_in)] not allowed outside STATS command") ); } public void testTimeseriesAggregate() { assertThat( - error("TS tests | STATS rate(network.bytes_in)", tsdb), + error("TS test | STATS rate(network.bytes_in)", tsdb), equalTo( "1:18: time-series aggregate function [rate(network.bytes_in)] can only be used with the TS command " + "and inside another aggregate function" ) ); assertThat( - error("TS tests | STATS avg_over_time(network.connections)", tsdb), + error("TS test | STATS avg_over_time(network.connections)", tsdb), equalTo( "1:18: time-series aggregate function [avg_over_time(network.connections)] can only be used " + "with the TS command and inside another aggregate function" ) ); assertThat( - error("TS tests | STATS avg(rate(network.bytes_in)), rate(network.bytes_in)", tsdb), + error("TS test | STATS avg(rate(network.bytes_in)), rate(network.bytes_in)", tsdb), equalTo( "1:47: time-series aggregate function [rate(network.bytes_in)] can only be used " + "with the TS command and inside another aggregate function" ) ); - assertThat(error("TS tests | STATS max(avg(rate(network.bytes_in)))", tsdb), equalTo(""" + assertThat(error("TS test | STATS max(avg(rate(network.bytes_in)))", tsdb), equalTo(""" 1:22: nested aggregations [avg(rate(network.bytes_in))] \ not allowed inside other aggregations [max(avg(rate(network.bytes_in)))] line 1:12: cannot use aggregate function [avg(rate(network.bytes_in))] \ inside over-time aggregation function [rate(network.bytes_in)]""")); - assertThat(error("TS tests | STATS max(avg(rate(network.bytes_in)))", tsdb), equalTo(""" + assertThat(error("TS test | STATS max(avg(rate(network.bytes_in)))", tsdb), equalTo(""" 1:22: nested aggregations [avg(rate(network.bytes_in))] \ not allowed inside other aggregations [max(avg(rate(network.bytes_in)))] line 1:12: cannot use aggregate function [avg(rate(network.bytes_in))] \ inside over-time aggregation function [rate(network.bytes_in)]""")); assertThat( - error("TS tests | STATS rate(network.bytes_in) BY bucket(@timestamp, 1 hour)", tsdb), + error("TS test | STATS rate(network.bytes_in) BY bucket(@timestamp, 1 hour)", tsdb), equalTo( "1:18: time-series aggregate function [rate(network.bytes_in)] can only be used " + "with the TS command and inside another aggregate function" ) ); assertThat( - error("TS tests | STATS COUNT(*)", tsdb), + error("TS test | STATS COUNT(*)", tsdb), equalTo("1:18: count_star [COUNT(*)] can't be used with TS command; use count on a field instead") ); } @@ -1732,45 +1729,45 @@ public void testConditionalFunctionsWithMixedNumericTypes() { // counter assertEquals( - "1:23: second argument of [" + "1:22: second argument of [" + functionName + "(network.bytes_in, 0)] must be [counter_long], found value [0] type [integer]", - error("FROM tests | eval x = " + functionName + "(network.bytes_in, 0)", tsdb) + error("FROM test | eval x = " + functionName + "(network.bytes_in, 0)", tsdb) ); assertEquals( - "1:23: second argument of [" + "1:22: second argument of [" + functionName + "(network.bytes_in, to_long(0))] must be [counter_long], " + "found value [to_long(0)] type [long]", - error("FROM tests | eval x = " + functionName + "(network.bytes_in, to_long(0))", tsdb) + error("FROM test | eval x = " + functionName + "(network.bytes_in, to_long(0))", tsdb) ); assertEquals( - "1:23: second argument of [" + "1:22: second argument of [" + functionName + "(network.bytes_in, 0.0)] must be [counter_long], found value [0.0] type [double]", - error("FROM tests | eval x = " + functionName + "(network.bytes_in, 0.0)", tsdb) + error("FROM test | eval x = " + functionName + "(network.bytes_in, 0.0)", tsdb) ); assertEquals( - "1:23: third argument of [" + "1:22: third argument of [" + functionName + "(null, network.bytes_in, 0)] must be [counter_long], found value [0] type [integer]", - error("FROM tests | eval x = " + functionName + "(null, network.bytes_in, 0)", tsdb) + error("FROM test | eval x = " + functionName + "(null, network.bytes_in, 0)", tsdb) ); assertEquals( - "1:23: third argument of [" + "1:22: third argument of [" + functionName + "(null, network.bytes_in, to_long(0))] must be [counter_long], " + "found value [to_long(0)] type [long]", - error("FROM tests | eval x = " + functionName + "(null, network.bytes_in, to_long(0))", tsdb) + error("FROM test | eval x = " + functionName + "(null, network.bytes_in, to_long(0))", tsdb) ); assertEquals( - "1:23: third argument of [" + "1:22: third argument of [" + functionName + "(null, network.bytes_in, 0.0)] must be [counter_long], found value [0.0] type [double]", - error("FROM tests | eval x = " + functionName + "(null, network.bytes_in, 0.0)", tsdb) + error("FROM test | eval x = " + functionName + "(null, network.bytes_in, 0.0)", tsdb) ); } @@ -1780,8 +1777,8 @@ public void testConditionalFunctionsWithMixedNumericTypes() { error("from test | eval x = case(languages == 1, salary, height)") ); assertEquals( - "1:23: third argument of [case(name == \"a\", network.bytes_in, 0)] must be [counter_long], found value [0] type [integer]", - error("FROM tests | eval x = case(name == \"a\", network.bytes_in, 0)", tsdb) + "1:22: third argument of [case(name == \"a\", network.bytes_in, 0)] must be [counter_long], found value [0] type [integer]", + error("FROM test | eval x = case(name == \"a\", network.bytes_in, 0)", tsdb) ); } @@ -1842,35 +1839,35 @@ public void testToDatePeriodTimeDurationInInvalidPosition() { public void testToDatePeriodToTimeDurationWithInvalidType() { assertEquals( "1:36: argument of [1.5::date_period] must be [date_period or string], found value [1.5] type [double]", - error("from types | EVAL x = birth_date + 1.5::date_period") + error("from test | EVAL x = birth_date + 1.5::date_period") ); assertEquals( "1:37: argument of [to_timeduration(1)] must be [time_duration or string], found value [1] type [integer]", - error("from types | EVAL x = birth_date - to_timeduration(1)") + error("from test | EVAL x = birth_date - to_timeduration(1)") ); assertEquals( "1:45: argument of [x::date_period] must be [date_period or string], found value [x] type [double]", - error("from types | EVAL x = 1.5, y = birth_date + x::date_period") + error("from test | EVAL x = 1.5, y = birth_date + x::date_period") ); assertEquals( "1:44: argument of [to_timeduration(x)] must be [time_duration or string], found value [x] type [integer]", - error("from types | EVAL x = 1, y = birth_date - to_timeduration(x)") + error("from test | EVAL x = 1, y = birth_date - to_timeduration(x)") ); assertEquals( "1:64: argument of [x::date_period] must be [date_period or string], found value [x] type [datetime]", - error("from types | EVAL x = \"2024-09-08\"::datetime, y = birth_date + x::date_period") + error("from test | EVAL x = \"2024-09-08\"::datetime, y = birth_date + x::date_period") ); assertEquals( "1:65: argument of [to_timeduration(x)] must be [time_duration or string], found value [x] type [datetime]", - error("from types | EVAL x = \"2024-09-08\"::datetime, y = birth_date - to_timeduration(x)") + error("from test | EVAL x = \"2024-09-08\"::datetime, y = birth_date - to_timeduration(x)") ); assertEquals( "1:58: argument of [x::date_period] must be [date_period or string], found value [x] type [ip]", - error("from types | EVAL x = \"2024-09-08\"::ip, y = birth_date + x::date_period") + error("from test | EVAL x = \"2024-09-08\"::ip, y = birth_date + x::date_period") ); assertEquals( "1:59: argument of [to_timeduration(x)] must be [time_duration or string], found value [x] type [ip]", - error("from types | EVAL x = \"2024-09-08\"::ip, y = birth_date - to_timeduration(x)") + error("from test | EVAL x = \"2024-09-08\"::ip, y = birth_date - to_timeduration(x)") ); } @@ -1878,17 +1875,17 @@ public void testIntervalAsString() { // DateTrunc for (String interval : List.of("1 minu", "1 dy", "1.5 minutes", "0.5 days", "minutes 1", "day 5")) { assertThat( - error("from types | EVAL x = date_trunc(\"" + interval + "\", \"1991-06-26T00:00:00.000Z\")"), + error("from test | EVAL x = date_trunc(\"" + interval + "\", \"1991-06-26T00:00:00.000Z\")"), containsString("1:35: Cannot convert string [" + interval + "] to [DATE_PERIOD or TIME_DURATION]") ); assertThat( - error("from types | EVAL x = \"1991-06-26T00:00:00.000Z\", y = date_trunc(\"" + interval + "\", x::datetime)"), + error("from test | EVAL x = \"1991-06-26T00:00:00.000Z\", y = date_trunc(\"" + interval + "\", x::datetime)"), containsString("1:67: Cannot convert string [" + interval + "] to [DATE_PERIOD or TIME_DURATION]") ); } for (String interval : List.of("1", "0.5", "invalid")) { assertThat( - error("from types | EVAL x = date_trunc(\"" + interval + "\", \"1991-06-26T00:00:00.000Z\")"), + error("from test | EVAL x = date_trunc(\"" + interval + "\", \"1991-06-26T00:00:00.000Z\")"), containsString( "1:24: first argument of [date_trunc(\"" + interval @@ -1898,7 +1895,7 @@ public void testIntervalAsString() { ) ); assertThat( - error("from types | EVAL x = \"1991-06-26T00:00:00.000Z\", y = date_trunc(\"" + interval + "\", x::datetime)"), + error("from test | EVAL x = \"1991-06-26T00:00:00.000Z\", y = date_trunc(\"" + interval + "\", x::datetime)"), containsString( "1:56: first argument of [date_trunc(\"" + interval @@ -2190,8 +2187,8 @@ public void testFilterByAggregate() { ); assertEquals("1:23: aggregate function [max(a)] not allowed outside STATS command", error("ROW a = 1 | WHERE 1 + max(a) > 0")); assertEquals( - "1:24: aggregate function [min(languages)] not allowed outside STATS command", - error("FROM employees | WHERE min(languages) > 2") + "1:19: aggregate function [min(languages)] not allowed outside STATS command", + error("FROM test | WHERE min(languages) > 2") ); assertEquals( "1:19: aggregate function [present(gender)] not allowed outside STATS command", @@ -2813,6 +2810,26 @@ STATS max(max_over_time(network.connections)) BY host, time_bucket = bucket(@tim can only be used after STATS when used with TS command""")); } + /** + * If there is no common data type for a field in a subquery and the main query, {@code VerificationException} is thrown. + */ + public void testMixedDataTypesInSubquery() { + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + // TODO: Replace with a VIEWS example + assumeTrue("Disabled", false); + assertThat( + error(""" + FROM test, (FROM test_mixed_types | WHERE languages > 0) + | WHERE emp_no > 10000 + | SORT is_rehired, still_hired + """), + equalTo( + "1:1: Column [is_rehired] has conflicting data types in subqueries: [boolean, keyword]\n" + + "line 1:1: Column [still_hired] has conflicting data types in subqueries: [boolean, keyword]" + ) + ); + } + private void checkVectorFunctionsNullArgs(String functionInvocation) throws Exception { query("from test | eval similarity = " + functionInvocation, fullTextAnalyzer); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java index 733c23af12966..e70d428e16beb 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java @@ -42,7 +42,7 @@ public class CheckLicenseTests extends ESTestCase { private final EsqlParser parser = new EsqlParser(); - private final String esql = "from tests | eval license() | LIMIT 10"; + private final String esql = "from test | eval license() | LIMIT 10"; public void testLicense() { for (License.OperationMode functionLicense : License.OperationMode.values()) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java index 8c887aca9dcce..d7fc5b9a6009e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java @@ -47,6 +47,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; public class AbstractLocalPhysicalPlanOptimizerTests extends MapperServiceTestCase { protected final Configuration config; @@ -101,7 +102,7 @@ public void init() { testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - timeSeriesIndex, + indexResolutions(timeSeriesIndex), enrichResolution, emptyInferenceResolution() ), @@ -123,7 +124,7 @@ private Analyzer makeAnalyzer(String mappingFileName, EnrichResolution enrichRes testAnalyzerContext( config, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), defaultLookupResolution(), enrichResolution, emptyInferenceResolution() @@ -138,7 +139,13 @@ protected Analyzer makeAnalyzer(String mappingFileName) { protected Analyzer makeAnalyzer(IndexResolution indexResolution) { return new Analyzer( - testAnalyzerContext(config, new EsqlFunctionRegistry(), indexResolution, new EnrichResolution(), emptyInferenceResolution()), + testAnalyzerContext( + config, + new EsqlFunctionRegistry(), + indexResolutions(indexResolution), + new EnrichResolution(), + emptyInferenceResolution() + ), new Verifier(new Metrics(new EsqlFunctionRegistry()), new XPackLicenseState(() -> 0L)) ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java index eb340661a4b78..bd3ee270a0d11 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java @@ -17,7 +17,6 @@ import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.parser.EsqlParser; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.junit.BeforeClass; @@ -35,6 +34,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultInferenceResolution; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; public abstract class AbstractLogicalPlanOptimizerTests extends ESTestCase { @@ -56,6 +56,7 @@ public abstract class AbstractLogicalPlanOptimizerTests extends ESTestCase { protected static Analyzer metricsAnalyzer; protected static Analyzer multiIndexAnalyzer; protected static Analyzer sampleDataIndexAnalyzer; + protected static Analyzer subqueryAnalyzer; protected static EnrichResolution enrichResolution; @@ -83,15 +84,15 @@ public static void init() { enrichResolution = new EnrichResolution(); AnalyzerTestUtils.loadEnrichPolicyResolution(enrichResolution, "languages_idx", "id", "languages_idx", "mapping-languages.json"); - // Most tests used data from the test index, so we load it here, and use it in the plan() function. + // Most tests use either "test" or "employees" as the index name, but for the same mapping mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(test); + EsIndex employees = new EsIndex("employees", mapping, Map.of("employees", IndexMode.STANDARD)); analyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test, employees), defaultLookupResolution(), enrichResolution, emptyInferenceResolution() @@ -99,15 +100,14 @@ public static void init() { TEST_VERIFIER ); - // Some tests use data from the airports index, so we load it here, and use it in the plan_airports() function. + // Some tests use data from the airports index, so we load it here, and use it in the planAirports() function. mappingAirports = loadMapping("mapping-airports.json"); EsIndex airports = new EsIndex("airports", mappingAirports, Map.of("airports", IndexMode.STANDARD)); - IndexResolution getIndexResultAirports = IndexResolution.valid(airports); analyzerAirports = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResultAirports, + indexResolutions(airports), defaultLookupResolution(), enrichResolution, emptyInferenceResolution() @@ -118,12 +118,11 @@ public static void init() { // Some tests need additional types, so we load that index here and use it in the plan_types() function. mappingTypes = loadMapping("mapping-all-types.json"); EsIndex types = new EsIndex("types", mappingTypes, Map.of("types", IndexMode.STANDARD)); - IndexResolution getIndexResultTypes = IndexResolution.valid(types); analyzerTypes = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResultTypes, + indexResolutions(types), enrichResolution, defaultInferenceResolution() ), @@ -133,12 +132,11 @@ public static void init() { // Some tests use mappings from mapping-extra.json to be able to test more types so we load it here mappingExtra = loadMapping("mapping-extra.json"); EsIndex extra = new EsIndex("extra", mappingExtra, Map.of("extra", IndexMode.STANDARD)); - IndexResolution getIndexResultExtra = IndexResolution.valid(extra); analyzerExtra = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResultExtra, + indexResolutions(extra), enrichResolution, emptyInferenceResolution() ), @@ -146,12 +144,12 @@ public static void init() { ); metricMapping = loadMapping("k8s-mappings.json"); - var metricsIndex = IndexResolution.valid(new EsIndex("k8s", metricMapping, Map.of("k8s", IndexMode.TIME_SERIES))); + var metricsIndex = new EsIndex("k8s", metricMapping, Map.of("k8s", IndexMode.TIME_SERIES)); metricsAnalyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - metricsIndex, + indexResolutions(metricsIndex), enrichResolution, emptyInferenceResolution() ), @@ -163,19 +161,17 @@ public static void init() { "partial_type_keyword", new EsField("partial_type_keyword", KEYWORD, emptyMap(), true, EsField.TimeSeriesFieldType.NONE) ); - var multiIndex = IndexResolution.valid( - new EsIndex( - "multi_index", - multiIndexMapping, - Map.of("test1", IndexMode.STANDARD, "test2", IndexMode.STANDARD), - Set.of("partial_type_keyword") - ) + var multiIndex = new EsIndex( + "multi_index", + multiIndexMapping, + Map.of("test1", IndexMode.STANDARD, "test2", IndexMode.STANDARD), + Set.of("partial_type_keyword") ); multiIndexAnalyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - multiIndex, + indexResolutions(multiIndex), enrichResolution, emptyInferenceResolution() ), @@ -183,14 +179,28 @@ public static void init() { ); var sampleDataMapping = loadMapping("mapping-sample_data.json"); - var sampleDataIndex = IndexResolution.valid( - new EsIndex("sample_data", sampleDataMapping, Map.of("sample_data", IndexMode.STANDARD)) - ); + var sampleDataIndex = new EsIndex("sample_data", sampleDataMapping, Map.of("sample_data", IndexMode.STANDARD)); sampleDataIndexAnalyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - sampleDataIndex, + indexResolutions(sampleDataIndex), + enrichResolution, + emptyInferenceResolution() + ), + TEST_VERIFIER + ); + + EsIndex test1 = new EsIndex("test1", mapping, Map.of("test1", IndexMode.STANDARD)); + var mappingLanguages = loadMapping("mapping-languages.json"); + EsIndex languages = new EsIndex("languages", mappingLanguages, Map.of("languages", IndexMode.STANDARD)); + + subqueryAnalyzer = new Analyzer( + testAnalyzerContext( + EsqlTestUtils.TEST_CFG, + new EsqlFunctionRegistry(), + indexResolutions(test1, languages), + emptyMap(), enrichResolution, emptyInferenceResolution() ), @@ -208,25 +218,19 @@ protected LogicalPlan plan(String query) { protected LogicalPlan plan(String query, LogicalPlanOptimizer optimizer) { var analyzed = analyzer.analyze(parser.createStatement(query)); - // System.out.println(analyzed); var optimized = optimizer.optimize(analyzed); - // System.out.println(optimized); return optimized; } protected LogicalPlan planAirports(String query) { var analyzed = analyzerAirports.analyze(parser.createStatement(query)); - // System.out.println(analyzed); var optimized = logicalOptimizer.optimize(analyzed); - // System.out.println(optimized); return optimized; } protected LogicalPlan planExtra(String query) { var analyzed = analyzerExtra.analyze(parser.createStatement(query)); - // System.out.println(analyzed); var optimized = logicalOptimizer.optimize(analyzed); - // System.out.println(optimized); return optimized; } @@ -243,6 +247,11 @@ protected LogicalPlan planSample(String query) { return logicalOptimizer.optimize(analyzed); } + protected LogicalPlan planSubquery(String query) { + var analyzed = subqueryAnalyzer.analyze(parser.createStatement(query)); + return logicalOptimizer.optimize(analyzed); + } + @Override protected List filteredWarnings() { return withDefaultLimitWarning(super.filteredWarnings()); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java index 1ef512a070d14..df7e7189dce0d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java @@ -45,7 +45,6 @@ import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNotNull; import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.optimizer.rules.logical.OptimizerRules; import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferIsNotNull; import org.elasticsearch.xpack.esql.parser.EsqlParser; @@ -97,6 +96,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY; import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; @@ -124,14 +124,13 @@ public static void init() { mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(test); logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); analyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), emptyPolicyResolution(), emptyInferenceResolution() ), @@ -518,14 +517,13 @@ public void testSparseDocument() throws Exception { SearchStats searchStats = statsForExistingField("field000", "field001", "field002", "field003", "field004"); EsIndex index = new EsIndex("large", large, Map.of("large", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(index); var logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); var analyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(index), emptyPolicyResolution(), emptyInferenceResolution() ), @@ -1114,13 +1112,12 @@ private static Analyzer analyzerWithUnionTypeMapping() { Map.of("integer_long_field", unionTypeField), Map.of("test1", IndexMode.STANDARD, "test2", IndexMode.STANDARD) ); - IndexResolution getIndexResult = IndexResolution.valid(test); return new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), emptyPolicyResolution(), emptyInferenceResolution() ), diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java index d45e178b8ff32..8c6cb43b86539 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java @@ -2278,7 +2278,7 @@ public void testToDateNanosPushDown() { plannerOptimizerDateDateNanosUnionTypes = new TestPlannerOptimizer(EsqlTestUtils.TEST_CFG, makeAnalyzer(indexWithUnionTypedFields)); var stats = EsqlTestUtils.statsForExistingField("date_and_date_nanos", "date_and_date_nanos_and_long"); String query = """ - from test* + from index* | where date_and_date_nanos < "2025-01-01" and date_and_date_nanos_and_long::date_nanos >= "2024-01-01\""""; var plan = plannerOptimizerDateDateNanosUnionTypes.plan(query, stats); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java index e10a4cc0dc2d7..52e2245e7cc3b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java @@ -95,7 +95,6 @@ import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.NotEquals; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.optimizer.rules.logical.LiteralsOnTheRight; import org.elasticsearch.xpack.esql.optimizer.rules.logical.OptimizerRules; import org.elasticsearch.xpack.esql.optimizer.rules.logical.PruneRedundantOrderBy; @@ -176,6 +175,7 @@ import static org.elasticsearch.xpack.esql.analysis.Analyzer.NO_FIELDS; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyze; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultAnalyzer; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.elasticsearch.xpack.esql.core.expression.Literal.NULL; import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY; import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; @@ -4263,7 +4263,7 @@ public void testIsNotNullConstraintForAliasedExpressions() { */ public void testSpatialTypesAndStatsUseDocValues() { var plan = planAirports(""" - from test + from airports | stats centroid = st_centroid_agg(location) """); @@ -4290,7 +4290,7 @@ public void testSpatialTypesAndStatsUseDocValues() { */ public void testSpatialTypesAndStatsUseDocValuesWithEval() { var plan = planAirports(""" - from test + from airports | stats centroid = st_centroid_agg(to_geopoint(location)) """); @@ -4326,7 +4326,7 @@ public void testTrivialTypeConversionWrittenAway() { default -> "to_" + type; }; var field = "types." + type; - var plan = planExtra("from test | eval new_" + field + " = " + func + "(" + field + ")"); + var plan = planExtra("from extra | eval new_" + field + " = " + func + "(" + field + ")"); var eval = as(plan, Eval.class); var alias = as(eval.fields().get(0), Alias.class); assertThat(func + "(" + field + ")", alias.name(), equalTo("new_" + field)); @@ -5283,12 +5283,11 @@ private static boolean oneLeaveIsNull(Expression e) { public void testEmptyMappingIndex() { EsIndex empty = new EsIndex("empty_test", emptyMap(), Map.of()); - IndexResolution getIndexResultAirports = IndexResolution.valid(empty); var analyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResultAirports, + indexResolutions(empty), enrichResolution, emptyInferenceResolution() ), @@ -8506,7 +8505,7 @@ public void testSampleMerged() { assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled()); var query = """ - FROM TEST + FROM test | SAMPLE .3 | EVAL irrelevant1 = 1 | SAMPLE .5 @@ -8536,7 +8535,7 @@ public void testSamplePushDown() { "GROK first_name \"%{WORD:bar}\"", "DISSECT first_name \"%{z}\"" )) { - var query = "FROM TEST | " + command + " | SAMPLE .5"; + var query = "FROM test | " + command + " | SAMPLE .5"; var optimized = optimizedPlan(query); var unary = as(optimized, UnaryPlan.class); @@ -8551,7 +8550,7 @@ public void testSamplePushDown() { public void testSamplePushDown_sort() { assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled()); - var query = "FROM TEST | WHERE emp_no > 0 | SAMPLE 0.5 | LIMIT 100"; + var query = "FROM test | WHERE emp_no > 0 | SAMPLE 0.5 | LIMIT 100"; var optimized = optimizedPlan(query); var limit = as(optimized, Limit.class); @@ -8565,7 +8564,7 @@ public void testSamplePushDown_sort() { public void testSamplePushDown_where() { assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled()); - var query = "FROM TEST | SORT emp_no | SAMPLE 0.5 | LIMIT 100"; + var query = "FROM test | SORT emp_no | SAMPLE 0.5 | LIMIT 100"; var optimized = optimizedPlan(query); var topN = as(optimized, TopN.class); @@ -8579,7 +8578,7 @@ public void testSampleNoPushDown() { assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled()); for (var command : List.of("LIMIT 100", "MV_EXPAND languages", "STATS COUNT()")) { - var query = "FROM TEST | " + command + " | SAMPLE .5"; + var query = "FROM test | " + command + " | SAMPLE .5"; var optimized = optimizedPlan(query); var limit = as(optimized, Limit.class); @@ -8603,7 +8602,7 @@ public void testSampleNoPushDownLookupJoin() { assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled()); var query = """ - FROM TEST + FROM test | EVAL language_code = emp_no | LOOKUP JOIN languages_lookup ON language_code | SAMPLE .5 @@ -8631,7 +8630,7 @@ public void testSampleNoPushDownChangePoint() { assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled()); var query = """ - FROM TEST + FROM test | CHANGE_POINT emp_no ON hire_date | SAMPLE .5 """; @@ -8647,7 +8646,7 @@ public void testSampleNoPushDownChangePoint() { public void testPushDownConjunctionsToKnnPrefilter() { var query = """ - from test + from types | where knn(dense_vector, [0, 1, 2]) and integer > 10 """; var optimized = planTypes(query); @@ -8665,7 +8664,7 @@ public void testPushDownConjunctionsToKnnPrefilter() { public void testPushDownMultipleFiltersToKnnPrefilter() { var query = """ - from test + from types | where knn(dense_vector, [0, 1, 2]) | where integer > 10 | where keyword == "test" @@ -8686,7 +8685,7 @@ public void testPushDownMultipleFiltersToKnnPrefilter() { public void testNotPushDownDisjunctionsToKnnPrefilter() { var query = """ - from test + from types | where knn(dense_vector, [0, 1, 2]) or integer > 10 """; var optimized = planTypes(query); @@ -8713,7 +8712,7 @@ public void testPushDownConjunctionsAndNotDisjunctionsToKnnPrefilter() { */ // Both conjunctions are pushed down to knn prefilters, disjunctions are not var query = """ - from test + from types | where ((knn(dense_vector, [0, 1, 2]) or integer > 10) and keyword == "test") and ((short < 5) or (double > 5.0)) """; @@ -8746,7 +8745,7 @@ public void testMorePushDownConjunctionsAndNotDisjunctionsToKnnPrefilter() { */ // Just the conjunction is pushed down to knn prefilters, disjunctions are not var query = """ - from test + from types | where ((knn(dense_vector, [0, 1, 2]) and integer > 10) or keyword == "test") or ((short < 5) and (double > 5.0)) """; @@ -8773,7 +8772,7 @@ public void testMultipleKnnQueriesInPrefilters() { knn(dense_vector, [4, 5, 6], 10) */ var query = """ - from test + from types | where ((knn(dense_vector, [0, 1, 2]) or integer > 10) and ((keyword == "test") or knn(dense_vector, [4, 5, 6]))) """; var optimized = planTypes(query); @@ -8805,7 +8804,7 @@ public void testMultipleKnnQueriesInPrefilters() { public void testKnnImplicitLimit() { var query = """ - from test + from types | where knn(dense_vector, [0, 1, 2]) """; var optimized = planTypes(query); @@ -8818,7 +8817,7 @@ public void testKnnImplicitLimit() { public void testKnnWithLimit() { var query = """ - from test + from types | where knn(dense_vector, [0, 1, 2]) | limit 10 """; @@ -8832,7 +8831,7 @@ public void testKnnWithLimit() { public void testKnnWithTopN() { var query = """ - from test metadata _score + from types metadata _score | where knn(dense_vector, [0, 1, 2]) | sort _score desc | limit 10 @@ -8847,7 +8846,7 @@ public void testKnnWithTopN() { public void testKnnWithMultipleLimitsAfterTopN() { var query = """ - from test metadata _score + from types metadata _score | where knn(dense_vector, [0, 1, 2]) | limit 20 | sort _score desc @@ -8865,7 +8864,7 @@ public void testKnnWithMultipleLimitsAfterTopN() { public void testKnnWithMultipleLimitsCombined() { var query = """ - from test metadata _score + from types metadata _score | where knn(dense_vector, [0, 1, 2]) | limit 20 | limit 10 @@ -8881,7 +8880,7 @@ public void testKnnWithMultipleLimitsCombined() { public void testKnnWithMultipleClauses() { var query = """ - from test metadata _score + from types metadata _score | where knn(dense_vector, [0, 1, 2]) and match(keyword, "test") | where knn(dense_vector, [1, 2, 3]) | sort _score @@ -8902,14 +8901,14 @@ public void testKnnWithMultipleClauses() { public void testKnnWithStats() { assertThat( - typesError("from test | where knn(dense_vector, [0, 1, 2]) | stats c = count(*)"), + typesError("from types | where knn(dense_vector, [0, 1, 2]) | stats c = count(*)"), containsString("Knn function must be used with a LIMIT clause") ); } public void testKnnWithRerankAmdTopN() { assertThat(typesError(""" - from test metadata _score + from types metadata _score | where knn(dense_vector, [0, 1, 2]) | rerank "some text" on text with { "inference_id" : "reranking-inference-id" } | sort _score desc @@ -8919,7 +8918,7 @@ public void testKnnWithRerankAmdTopN() { public void testKnnWithRerankAmdLimit() { var query = """ - from test metadata _score + from types metadata _score | where knn(dense_vector, [0, 1, 2]) | rerank "some text" on text with { "inference_id" : "reranking-inference-id" } | limit 100 diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index 5fdaa2ca2692b..c7c33203fd2f6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -186,6 +186,7 @@ import static org.elasticsearch.xpack.esql.SerializationTestUtils.assertSerialization; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyze; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.elasticsearch.xpack.esql.core.expression.Expressions.name; import static org.elasticsearch.xpack.esql.core.expression.Expressions.names; import static org.elasticsearch.xpack.esql.core.expression.function.scalar.FunctionTestUtils.l; @@ -390,13 +391,23 @@ TestDataSource makeTestDataSource( SearchStats stats ) { Map mapping = loadMapping(mappingFileName); - EsIndex index = new EsIndex(indexName, mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(index); + EsIndex[] indexes = new EsIndex[1 + lookupResolution.size()]; + indexes[0] = new EsIndex(indexName, mapping, Map.of(indexName, IndexMode.STANDARD)); + for (int i = 0; i < lookupResolution.size(); i++) { + indexes[i + 1] = lookupResolution.values().toArray(new IndexResolution[0])[i].get(); + } Analyzer analyzer = new Analyzer( - testAnalyzerContext(config, functionRegistry, getIndexResult, lookupResolution, enrichResolution, emptyInferenceResolution()), + testAnalyzerContext( + config, + functionRegistry, + indexResolutions(indexes), + lookupResolution, + enrichResolution, + emptyInferenceResolution() + ), TEST_VERIFIER ); - return new TestDataSource(mapping, index, analyzer, stats); + return new TestDataSource(mapping, indexes[0], analyzer, stats); } TestDataSource makeTestDataSource( @@ -1209,7 +1220,7 @@ public void testPushMultipleBinaryLogicFilters() { */ public void testPushMultipleFunctions() { var plan = physicalPlan(""" - from airports + from test | where starts_with(first_name, "*Firs") or ends_with(first_name, "irst*") | where ends_with(last_name, "ast") """); @@ -3469,7 +3480,7 @@ public void testSpatialTypesAndStatsCentroidUseDocValues() { for (boolean withDocValues : new boolean[] { false, true }) { var testData = withDocValues ? airports : airportsNoDocValues; var fieldExtractPreference = withDocValues ? FieldExtractPreference.DOC_VALUES : FieldExtractPreference.NONE; - var plan = physicalPlan(query, testData); + var plan = physicalPlan(query.replace("airports", testData.index.name()), testData); var limit = as(plan, LimitExec.class); var agg = as(limit.child(), AggregateExec.class); @@ -3532,7 +3543,7 @@ public void testSpatialTypesAndStatsExtentUseDocValues() { for (boolean withDocValues : new boolean[] { false, true }) { var fieldExtractPreference = withDocValues ? FieldExtractPreference.DOC_VALUES : FieldExtractPreference.NONE; var testData = withDocValues ? airports : airportsNoDocValues; - var plan = physicalPlan(query, testData); + var plan = physicalPlan(query.replace("airports", testData.index.name()), testData); var limit = as(plan, LimitExec.class); var agg = as(limit.child(), AggregateExec.class); @@ -3595,7 +3606,7 @@ public void testSpatialTypesAndStatsExtentAndCentroidUseDocValues() { for (boolean withDocValues : new boolean[] { false, true }) { var fieldExtractPreference = withDocValues ? FieldExtractPreference.DOC_VALUES : FieldExtractPreference.NONE; var testData = withDocValues ? airports : airportsNoDocValues; - var plan = physicalPlan(query, testData); + var plan = physicalPlan(query.replace("airports", testData.index.name()), testData); var limit = as(plan, LimitExec.class); var agg = as(limit.child(), AggregateExec.class); @@ -3644,7 +3655,7 @@ public void testSpatialTypesAndStatsExtentOfGeoShapeUsesBinaryExtraction() { var query = "FROM airports_city_boundaries | STATS extent = ST_EXTENT_AGG(city_boundary)"; for (boolean useDocValues : new Boolean[] { true, false }) { var testData = useDocValues ? airportsCityBoundaries : airportsCityBoundariesNoDocValues; - var plan = physicalPlan(query, testData); + var plan = physicalPlan(query.replace("airports_city_boundaries", testData.index.name()), testData); var limit = as(plan, LimitExec.class); var agg = as(limit.child(), AggregateExec.class); @@ -3707,11 +3718,14 @@ public void testSpatialTypesAndStatsExtentOfShapesNegativeCases() { */ public void testSpatialTypesAndStatsExtentOfCartesianShapesWithAndWithoutDocValues() { for (boolean hasDocValues : new boolean[] { true, false }) { - var query = """ - FROM cartesian_multipolygons \ - | STATS extent = ST_EXTENT_AGG(shape)"""; - var testData = hasDocValues ? cartesianMultipolygons : cartesianMultipolygonsNoDocValues; - var fieldExtractPreference = hasDocValues ? FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS : FieldExtractPreference.NONE; + var query = "FROM cartesian_multipolygons | STATS extent = ST_EXTENT_AGG(shape)"; + var testData = cartesianMultipolygons; + var fieldExtractPreference = FieldExtractPreference.EXTRACT_SPATIAL_BOUNDS; + if (hasDocValues == false) { + query = "FROM cartesian_multipolygons_no_doc_values | STATS extent = ST_EXTENT_AGG(shape)"; + testData = cartesianMultipolygonsNoDocValues; + fieldExtractPreference = FieldExtractPreference.NONE; + } var plan = physicalPlan(query, testData); var limit = as(plan, LimitExec.class); @@ -3759,7 +3773,7 @@ public void testSpatialTypesAndStatsExtentOfCartesianShapesWithAndWithoutDocValu */ public void testMixedSpatialBoundsAndPointsExtracted() { var query = """ - FROM airports_city_boundaries \ + FROM INDEX \ | STATS extent = ST_EXTENT_AGG(city_boundary), centroid = ST_CENTROID_AGG(city_location)"""; for (boolean pointDocValues : new Boolean[] { true, false }) { for (boolean shapeDocValues : new Boolean[] { true, false }) { @@ -3767,7 +3781,7 @@ public void testMixedSpatialBoundsAndPointsExtracted() { ? (shapeDocValues ? airportsCityBoundaries : airportsCityBoundariesNoShapeDocValues) : (shapeDocValues ? airportsCityBoundariesNoPointDocValues : airportsCityBoundariesNoDocValues); var msg = "DocValues[point:" + pointDocValues + ", shape:" + shapeDocValues + "]"; - var plan = physicalPlan(query, testData); + var plan = physicalPlan(query.replace("INDEX", testData.index.name()), testData); var limit = as(plan, LimitExec.class); var agg = as(limit.child(), AggregateExec.class); @@ -4058,7 +4072,7 @@ public void testSpatialTypesAndStatsUseDocValuesMultiAggregationsGrouped() { var plan = this.physicalPlan(""" FROM airports | STATS centroid=ST_CENTROID_AGG(location), count=COUNT() BY scalerank - """, testData); + """.replace("airports", testData.index.name()), testData); var limit = as(plan, LimitExec.class); var agg = as(limit.child(), AggregateExec.class); @@ -4625,7 +4639,7 @@ public void testPushSpatialIntersectsStringToSourceAndUseDocValuesForCentroid() var testData = useDocValues ? (isIndexed ? airports : airportsNotIndexed) : (isIndexed ? airportsNoDocValues : airportsNotIndexedNorDocValues); - var plan = this.physicalPlan(query, testData); + var plan = this.physicalPlan(query.replace("airports", testData.index.name()), testData); var limit = as(plan, LimitExec.class); var agg = as(limit.child(), AggregateExec.class); assertThat("No groupings in aggregation", agg.groupings().size(), equalTo(0)); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java index eedad20dcc42f..2f41ace68b8c3 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java @@ -18,7 +18,6 @@ import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.optimizer.AbstractLogicalPlanOptimizerTests; import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; import org.elasticsearch.xpack.esql.parser.EsqlParser; @@ -42,6 +41,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultInferenceResolution; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -57,12 +57,11 @@ public static void init() { parser = new EsqlParser(); mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(test); analyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), defaultLookupResolution(), new EnrichResolution(), defaultInferenceResolution() diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java index ecd12af404108..3860bb5217cd3 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.FoldContext; +import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; @@ -42,12 +43,14 @@ import org.elasticsearch.xpack.esql.plan.logical.Limit; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.logical.Project; +import org.elasticsearch.xpack.esql.plan.logical.UnionAll; import org.elasticsearch.xpack.esql.plan.logical.inference.Completion; import org.elasticsearch.xpack.esql.plan.logical.inference.Rerank; import org.elasticsearch.xpack.esql.plan.logical.join.Join; import org.elasticsearch.xpack.esql.plan.logical.join.JoinConfig; import org.elasticsearch.xpack.esql.plan.logical.join.JoinTypes; import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject; +import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation; import java.util.ArrayList; import java.util.HashSet; @@ -1006,4 +1009,231 @@ public void testPushDownLookupJoinExpressionMultipleWhere() { ); assertEquals(expectedPushedFilters, actualPushedFilters); } + + public void testPushDownSimpleFilterPastUnionAll() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("Disabled until subquery support comes back", false); + var plan = planSubquery(""" + FROM test, (FROM test1 | WHERE languages > 0), (FROM languages | WHERE language_code > 0) + | WHERE emp_no > 10000 + """); + + Limit limit = as(plan, Limit.class); + UnionAll unionAll = as(limit.child(), UnionAll.class); + assertEquals(3, unionAll.children().size()); + + EsqlProject child1 = as(unionAll.children().get(0), EsqlProject.class); + Eval eval = as(child1.child(), Eval.class); + Limit childLimit = as(eval.child(), Limit.class); + Filter childFilter = as(childLimit.child(), Filter.class); + GreaterThan greaterThan = as(childFilter.condition(), GreaterThan.class); + FieldAttribute empNo = as(greaterThan.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + Literal right = as(greaterThan.right(), Literal.class); + assertEquals(10000, right.value()); + EsRelation relation = as(childFilter.child(), EsRelation.class); + assertEquals("test", relation.indexPattern()); + + EsqlProject child2 = as(unionAll.children().get(1), EsqlProject.class); + eval = as(child2.child(), Eval.class); + childLimit = as(eval.child(), Limit.class); + /* + Subquery subquery = as(childLimit.child(), Subquery.class); + childFilter = as(subquery.child(), Filter.class); + */ + And and = as(childFilter.condition(), And.class); + greaterThan = as(and.left(), GreaterThan.class); + empNo = as(greaterThan.left(), FieldAttribute.class); + assertEquals("languages", empNo.name()); + right = as(greaterThan.right(), Literal.class); + assertEquals(0, right.value()); + greaterThan = as(and.right(), GreaterThan.class); + empNo = as(greaterThan.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + right = as(greaterThan.right(), Literal.class); + assertEquals(10000, right.value()); + relation = as(childFilter.child(), EsRelation.class); + assertEquals("test1", relation.indexPattern()); + + LocalRelation localRelation = as(unionAll.children().get(2), LocalRelation.class); + } + + public void testPushDownConjunctiveFilterPastUnionAll() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("Disabled until subquery support comes back", false); + var plan = planSubquery(""" + FROM test, (FROM test1 | WHERE languages > 0), (FROM languages | WHERE language_code > 0) + | WHERE emp_no > 10000 and salary > 50000 + """); + + Limit limit = as(plan, Limit.class); + UnionAll unionAll = as(limit.child(), UnionAll.class); + assertEquals(3, unionAll.children().size()); + EsqlProject child1 = as(unionAll.children().get(0), EsqlProject.class); + Eval eval = as(child1.child(), Eval.class); + Limit childLimit = as(eval.child(), Limit.class); + Filter childFilter = as(childLimit.child(), Filter.class); + And and = as(childFilter.condition(), And.class); + GreaterThan emp_no = as(and.left(), GreaterThan.class); + FieldAttribute empNo = as(emp_no.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + Literal right = as(emp_no.right(), Literal.class); + assertEquals(10000, right.value()); + GreaterThan salary = as(and.right(), GreaterThan.class); + FieldAttribute salaryField = as(salary.left(), FieldAttribute.class); + assertEquals("salary", salaryField.name()); + right = as(salary.right(), Literal.class); + assertEquals(50000, right.value()); + EsRelation relation = as(childFilter.child(), EsRelation.class); + assertEquals("test", relation.indexPattern()); + + EsqlProject child2 = as(unionAll.children().get(1), EsqlProject.class); + eval = as(child2.child(), Eval.class); + childLimit = as(eval.child(), Limit.class); + /* + Subquery subquery = as(childLimit.child(), Subquery.class); + childFilter = as(subquery.child(), Filter.class); + */ + and = as(childFilter.condition(), And.class); + GreaterThan greaterThan = as(and.left(), GreaterThan.class); + FieldAttribute languages = as(greaterThan.left(), FieldAttribute.class); + assertEquals("languages", languages.name()); + right = as(greaterThan.right(), Literal.class); + assertEquals(0, right.value()); + and = as(and.right(), And.class); + emp_no = as(and.left(), GreaterThan.class); + empNo = as(emp_no.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + right = as(emp_no.right(), Literal.class); + assertEquals(10000, right.value()); + salary = as(and.right(), GreaterThan.class); + salaryField = as(salary.left(), FieldAttribute.class); + assertEquals("salary", salaryField.name()); + right = as(salary.right(), Literal.class); + assertEquals(50000, right.value()); + relation = as(childFilter.child(), EsRelation.class); + assertEquals("test1", relation.indexPattern()); + LocalRelation localRelation = as(unionAll.children().get(2), LocalRelation.class); + } + + public void testPushDownDisjunctiveFilterPastUnionAll() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("Disabled until subquery support comes back", false); + var plan = planSubquery(""" + FROM test, (FROM test1 | WHERE languages > 0), (FROM languages | WHERE language_code > 0) + | WHERE emp_no > 10000 or salary > 50000 + """); + + Limit limit = as(plan, Limit.class); + UnionAll unionAll = as(limit.child(), UnionAll.class); + assertEquals(3, unionAll.children().size()); + EsqlProject child1 = as(unionAll.children().get(0), EsqlProject.class); + Eval eval = as(child1.child(), Eval.class); + Limit childLimit = as(eval.child(), Limit.class); + Filter childFilter = as(childLimit.child(), Filter.class); + Or or = as(childFilter.condition(), Or.class); + GreaterThan emp_no = as(or.left(), GreaterThan.class); + FieldAttribute empNo = as(emp_no.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + Literal right = as(emp_no.right(), Literal.class); + assertEquals(10000, right.value()); + GreaterThan salary = as(or.right(), GreaterThan.class); + FieldAttribute salaryField = as(salary.left(), FieldAttribute.class); + assertEquals("salary", salaryField.name()); + right = as(salary.right(), Literal.class); + assertEquals(50000, right.value()); + EsRelation relation = as(childFilter.child(), EsRelation.class); + assertEquals("test", relation.indexPattern()); + + EsqlProject child2 = as(unionAll.children().get(1), EsqlProject.class); + eval = as(child2.child(), Eval.class); + childLimit = as(eval.child(), Limit.class); + /* + Subquery subquery = as(childLimit.child(), Subquery.class); + childFilter = as(subquery.child(), Filter.class); + */ + And and = as(childFilter.condition(), And.class); + GreaterThan greaterThan = as(and.left(), GreaterThan.class); + FieldAttribute languages = as(greaterThan.left(), FieldAttribute.class); + assertEquals("languages", languages.name()); + right = as(greaterThan.right(), Literal.class); + assertEquals(0, right.value()); + or = as(and.right(), Or.class); + emp_no = as(or.left(), GreaterThan.class); + empNo = as(emp_no.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + right = as(emp_no.right(), Literal.class); + assertEquals(10000, right.value()); + salary = as(or.right(), GreaterThan.class); + salaryField = as(salary.left(), FieldAttribute.class); + assertEquals("salary", salaryField.name()); + right = as(salary.right(), Literal.class); + assertEquals(50000, right.value()); + relation = as(childFilter.child(), EsRelation.class); + assertEquals("test1", relation.indexPattern()); + LocalRelation localRelation = as(unionAll.children().get(2), LocalRelation.class); + } + + public void testPushDownAndCombineFilterPastUnionAll() { + // TODO: Replace with a VIEWS example + assumeTrue("Requires subquery in FROM command support", EsqlCapabilities.Cap.SUBQUERY_IN_FROM_COMMAND.isEnabled()); + assumeTrue("Disabled until subquery support comes back", false); + var plan = planSubquery(""" + FROM test, (FROM test1 | where salary < 100000), (FROM languages | WHERE language_code > 0) + | WHERE emp_no > 10000 and salary > 50000 + """); + + Limit limit = as(plan, Limit.class); + UnionAll unionAll = as(limit.child(), UnionAll.class); + assertEquals(3, unionAll.children().size()); + EsqlProject child1 = as(unionAll.children().get(0), EsqlProject.class); + Eval eval = as(child1.child(), Eval.class); + Limit childLimit = as(eval.child(), Limit.class); + Filter childFilter = as(childLimit.child(), Filter.class); + And and = as(childFilter.condition(), And.class); + GreaterThan emp_no = as(and.left(), GreaterThan.class); + FieldAttribute empNo = as(emp_no.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + Literal right = as(emp_no.right(), Literal.class); + assertEquals(10000, right.value()); + GreaterThan salary = as(and.right(), GreaterThan.class); + FieldAttribute salaryField = as(salary.left(), FieldAttribute.class); + assertEquals("salary", salaryField.name()); + right = as(salary.right(), Literal.class); + assertEquals(50000, right.value()); + EsRelation relation = as(childFilter.child(), EsRelation.class); + assertEquals("test", relation.indexPattern()); + + EsqlProject child2 = as(unionAll.children().get(1), EsqlProject.class); + eval = as(child2.child(), Eval.class); + childLimit = as(eval.child(), Limit.class); + /* + Subquery subquery = as(childLimit.child(), Subquery.class); + childFilter = as(subquery.child(), Filter.class); + */ + and = as(childFilter.condition(), And.class); + And empNoAndSalary = as(and.right(), And.class); + emp_no = as(empNoAndSalary.left(), GreaterThan.class); + empNo = as(emp_no.left(), FieldAttribute.class); + assertEquals("emp_no", empNo.name()); + right = as(emp_no.right(), Literal.class); + assertEquals(10000, right.value()); + salary = as(empNoAndSalary.right(), GreaterThan.class); + salaryField = as(salary.left(), FieldAttribute.class); + assertEquals("salary", salaryField.name()); + right = as(salary.right(), Literal.class); + assertEquals(50000, right.value()); + LessThan lessThan = as(and.left(), LessThan.class); + salaryField = as(lessThan.left(), FieldAttribute.class); + assertEquals("salary", salaryField.name()); + right = as(lessThan.right(), Literal.class); + assertEquals(100000, right.value()); + relation = as(childFilter.child(), EsRelation.class); + assertEquals("test1", relation.indexPattern()); + + LocalRelation localRelation = as(unionAll.children().get(2), LocalRelation.class); + } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownJoinPastProjectTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownJoinPastProjectTests.java index 260feb5e9b8b2..549ac3987c8b6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownJoinPastProjectTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownJoinPastProjectTests.java @@ -174,7 +174,7 @@ public void testShadowingAfterPushdown() { // \_EsRelation[test_lookup][LOOKUP][emp_no{f}#37, languages{f}#40, salary{f}#42] public void testShadowingAfterPushdown2() { String query = """ - FROM test_lookup + FROM test | RENAME emp_no AS x, salary AS salary2 | EVAL y = x, gender = last_name | RENAME y AS languages, gender AS ln @@ -247,7 +247,7 @@ public void testShadowingAfterPushdownExpressionJoin() { ); String query = """ - FROM test_lookup + FROM test | RENAME languages as lang2 | EVAL y = emp_no | RENAME y AS lang @@ -295,7 +295,7 @@ public void testShadowingAfterPushdownExpressionJoinKeepOrig() { ); String query = """ - FROM test_lookup + FROM test | RENAME languages as lang2 | EVAL y = emp_no | RENAME y AS lang @@ -347,7 +347,7 @@ public void testShadowingAfterPushdownRenameExpressionJoin() { ); String query = """ - FROM test_lookup + FROM test | RENAME languages AS lang | LOOKUP JOIN test_lookup ON lang == languages | KEEP languages, emp_no, salary @@ -393,7 +393,7 @@ public void testShadowingAfterPushdownEvalExpressionJoin() { ); String query = """ - FROM test_lookup + FROM test | EVAL lang = languages + 0 | DROP languages | LOOKUP JOIN test_lookup ON lang == languages diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java index 6f927dae662d8..bbf18569ede0b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java @@ -24,7 +24,6 @@ import org.elasticsearch.xpack.esql.expression.predicate.logical.Or; import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNotNull; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.optimizer.LocalLogicalOptimizerContext; import org.elasticsearch.xpack.esql.optimizer.LocalLogicalPlanOptimizer; import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; @@ -49,6 +48,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; @@ -84,12 +84,11 @@ private static void init() { new EsField("_tsid", DataType.TSID_DATA_TYPE, Map.of(), true, EsField.TimeSeriesFieldType.NONE) ); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.TIME_SERIES)); - IndexResolution getIndexResult = IndexResolution.valid(test); analyzer = new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), defaultLookupResolution(), enrichResolution, emptyInferenceResolution() diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/AbstractStatementParserTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/AbstractStatementParserTests.java index 462b9f7373ecf..f35a159bbd2df 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/AbstractStatementParserTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/AbstractStatementParserTests.java @@ -34,7 +34,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -abstract class AbstractStatementParserTests extends ESTestCase { +public abstract class AbstractStatementParserTests extends ESTestCase { EsqlParser parser = new EsqlParser(); @@ -52,7 +52,7 @@ LogicalPlan statement(String query, String arg) { return statement(LoggerMessageFormat.format(null, query, arg), new QueryParams()); } - LogicalPlan statement(String e) { + protected LogicalPlan statement(String e) { return statement(e, new QueryParams()); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java index 96507bf9615a1..9e1084ebe2d07 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java @@ -28,7 +28,6 @@ import org.elasticsearch.xpack.esql.core.util.Queries; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.io.stream.ExpressionQueryBuilder; import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput; @@ -61,6 +60,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.SerializationTestUtils.assertSerialization; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.elasticsearch.xpack.esql.core.querydsl.query.Query.unscore; import static org.elasticsearch.xpack.esql.core.util.Queries.Clause.FILTER; import static org.elasticsearch.xpack.esql.core.util.Queries.Clause.MUST; @@ -86,7 +86,6 @@ public static void init() { Map mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(test); logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); TransportVersion minimumVersion = logicalOptimizer.context().minimumVersion(); physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(EsqlTestUtils.TEST_CFG, minimumVersion)); @@ -96,7 +95,7 @@ public static void init() { new AnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), Map.of(), EsqlTestUtils.emptyPolicyResolution(), emptyInferenceResolution(), diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java index 5212abf4270f7..027e7d8b7fc89 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java @@ -36,32 +36,32 @@ public class PlanConcurrencyCalculatorTests extends ESTestCase { public void testSimpleLimit() { assertConcurrency(""" - FROM x + FROM test | LIMIT 512 """, 9); } public void testLimitZero() { - assertConcurrency("FROM x | LIMIT 0", null); + assertConcurrency("FROM test | LIMIT 0", null); } public void testBiggestPragmaOverride() { assertConcurrency(""" - FROM x + FROM test | LIMIT 512 """, Integer.MAX_VALUE, Integer.MAX_VALUE); } public void testSmallestPragmaOverride() { assertConcurrency(""" - FROM x + FROM test | LIMIT 512 """, 1, 1); } public void testPragmaOverrideWithUnsupportedCommands() { assertConcurrency(""" - FROM x + FROM test | WHERE salary * 2 > 5 | LIMIT 512 """, 1, 1); @@ -69,20 +69,20 @@ public void testPragmaOverrideWithUnsupportedCommands() { public void testImplicitLimit() { assertConcurrency(""" - FROM x + FROM test """, 9); } public void testStats() { assertConcurrency(""" - FROM x + FROM test | STATS COUNT(salary) """, null); } public void testStatsWithLimit() { assertConcurrency(""" - FROM x + FROM test | LIMIT 512 | STATS COUNT(salary) """, 9); @@ -90,14 +90,14 @@ public void testStatsWithLimit() { public void testSortBeforeLimit() { assertConcurrency(""" - FROM x + FROM test | SORT salary """, null); } public void testSortAfterLimit() { assertConcurrency(""" - FROM x + FROM test | LIMIT 512 | SORT salary """, 9); @@ -105,7 +105,7 @@ public void testSortAfterLimit() { public void testStatsWithSortBeforeLimit() { assertConcurrency(""" - FROM x + FROM test | SORT salary | LIMIT 512 | STATS COUNT(salary) @@ -114,7 +114,7 @@ public void testStatsWithSortBeforeLimit() { public void testStatsWithSortAfterLimit() { assertConcurrency(""" - FROM x + FROM test | SORT salary | LIMIT 512 | STATS COUNT(salary) @@ -123,7 +123,7 @@ public void testStatsWithSortAfterLimit() { public void testWhereBeforeLimit() { assertConcurrency(""" - FROM x + FROM test | WHERE salary * 2 > 5 | LIMIT 512 """, null); @@ -131,7 +131,7 @@ public void testWhereBeforeLimit() { public void testWhereAfterLimit() { assertConcurrency(""" - FROM x + FROM test | LIMIT 512 | WHERE salary * 2 > 5 """, 9); @@ -139,7 +139,7 @@ public void testWhereAfterLimit() { public void testWherePushedToLuceneQueryBeforeLimit() { assertConcurrency(""" - FROM x + FROM test | WHERE first_name LIKE "A%" | LIMIT 512 """, null); @@ -147,7 +147,7 @@ public void testWherePushedToLuceneQueryBeforeLimit() { public void testWherePushedToLuceneQueryAfterLimit() { assertConcurrency(""" - FROM x + FROM test | LIMIT 512 | WHERE first_name LIKE "A%" """, 9); @@ -155,7 +155,7 @@ public void testWherePushedToLuceneQueryAfterLimit() { public void testExpand() { assertConcurrency(""" - FROM x + FROM test | LIMIT 2048 | MV_EXPAND salary | LIMIT 512 @@ -164,7 +164,7 @@ public void testExpand() { public void testEval() { assertConcurrency(""" - FROM x + FROM test | EVAL x=salary*2 | LIMIT 512 """, 9); @@ -172,7 +172,7 @@ public void testEval() { public void testRename() { assertConcurrency(""" - FROM x + FROM test | RENAME salary as x | LIMIT 512 """, 9); @@ -180,7 +180,7 @@ public void testRename() { public void testKeep() { assertConcurrency(""" - FROM x + FROM test | KEEP salary | LIMIT 512 """, 9); @@ -188,7 +188,7 @@ public void testKeep() { public void testDrop() { assertConcurrency(""" - FROM x + FROM test | DROP salary | LIMIT 512 """, 9); @@ -196,7 +196,7 @@ public void testDrop() { public void testDissect() { assertConcurrency(""" - FROM x + FROM test | DISSECT first_name "%{a} %{b}" | LIMIT 512 """, 9); @@ -204,7 +204,7 @@ public void testDissect() { public void testGrok() { assertConcurrency(""" - FROM x + FROM test | GROK first_name "%{EMAILADDRESS:email}" | LIMIT 512 """, 9); @@ -212,7 +212,7 @@ public void testGrok() { public void testEnrich() { assertConcurrency(""" - FROM x + FROM test | ENRICH languages ON first_name | LIMIT 512 """, 9); @@ -220,7 +220,7 @@ public void testEnrich() { public void testLookup() { assertConcurrency(""" - FROM x + FROM test | RENAME salary as language_code | LOOKUP JOIN languages_lookup on language_code | LIMIT 512 diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java index 4fd28e8897ee7..cca78dcc8b466 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java @@ -32,6 +32,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexWithDateDateNanosUnionType; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.matchesRegex; @@ -45,16 +46,15 @@ public class QueryTranslatorTests extends ESTestCase { private static TestPlannerOptimizer plannerOptimizerDateDateNanosUnionTypes; - private static Analyzer makeAnalyzer(String mappingFileName) { + private static Analyzer makeAnalyzer(String indexName, String mappingFileName) { var mapping = loadMapping(mappingFileName); - EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(test); + EsIndex test = new EsIndex(indexName, mapping, Map.of(indexName, IndexMode.STANDARD)); return new Analyzer( testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), emptyPolicyResolution(), emptyInferenceResolution() ), @@ -67,7 +67,7 @@ public static Analyzer makeAnalyzer(IndexResolution indexResolution) { testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - indexResolution, + indexResolutions(indexResolution), emptyPolicyResolution(), emptyInferenceResolution() ), @@ -77,8 +77,8 @@ public static Analyzer makeAnalyzer(IndexResolution indexResolution) { @BeforeClass public static void init() { - plannerOptimizer = new TestPlannerOptimizer(EsqlTestUtils.TEST_CFG, makeAnalyzer("mapping-all-types.json")); - plannerOptimizerIPs = new TestPlannerOptimizer(EsqlTestUtils.TEST_CFG, makeAnalyzer("mapping-hosts.json")); + plannerOptimizer = new TestPlannerOptimizer(EsqlTestUtils.TEST_CFG, makeAnalyzer("test", "mapping-all-types.json")); + plannerOptimizerIPs = new TestPlannerOptimizer(EsqlTestUtils.TEST_CFG, makeAnalyzer("hosts", "mapping-hosts.json")); } @Override @@ -331,27 +331,27 @@ public void testToDateNanos() { // == term assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos == "2025-01-01\"""", stats, containsString(""" + FROM index* | WHERE date_and_date_nanos == "2025-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ "next":{"term":{"date_and_date_nanos":{"value":"2025-01-01T00:00:00.000Z","boost":0.0}}}""")); // != term assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos != "2025-01-01\"""", stats, containsString(""" + FROM index* | WHERE date_and_date_nanos != "2025-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ "next":{"bool":{"must_not":[{"term":{"date_and_date_nanos":{"value":"2025-01-01T00:00:00.000Z","boost":0.0}}}],\ "boost":0.0}}""")); // > range assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos > "2025-01-01\"""", stats, containsString(""" + FROM index* | WHERE date_and_date_nanos > "2025-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ "next":{"range":{"date_and_date_nanos":{"gt":"2025-01-01T00:00:00.000Z","time_zone":"Z",\ "format":"strict_date_optional_time_nanos","boost":0.0}}}""")); // >= range assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos >= "2025-01-01\"""", stats, containsString(""" + FROM index* | WHERE date_and_date_nanos >= "2025-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ "next":{"range":{"date_and_date_nanos":{"gte":"2025-01-01T00:00:00.000Z","time_zone":"Z",\ "format":"strict_date_optional_time_nanos","boost":0.0}}}""")); @@ -359,7 +359,7 @@ public void testToDateNanos() { // < range assertQueryTranslationDateDateNanosUnionTypes( """ - FROM test* | WHERE date_and_date_nanos < "2025-01-01" and date_and_date_nanos_and_long::date_nanos > "2025-01-01\"""", + FROM index* | WHERE date_and_date_nanos < "2025-01-01" and date_and_date_nanos_and_long::date_nanos > "2025-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ @@ -369,21 +369,21 @@ public void testToDateNanos() { // <= range assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos <= "2025-01-01\"""", stats, containsString(""" + FROM index* | WHERE date_and_date_nanos <= "2025-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ "next":{"range":{"date_and_date_nanos":{"lte":"2025-01-01T00:00:00.000Z","time_zone":"Z",\ "format":"strict_date_optional_time_nanos","boost":0.0}}}""")); // <= and >= assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos <= "2025-01-01" and date_and_date_nanos > "2020-01-01\"""", stats, containsString(""" + FROM index* | WHERE date_and_date_nanos <= "2025-01-01" and date_and_date_nanos > "2020-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ "next":{"range":{"date_and_date_nanos":{"gt":"2020-01-01T00:00:00.000Z","lte":"2025-01-01T00:00:00.000Z","time_zone":"Z",\ "format":"strict_date_optional_time_nanos","boost":0.0}}}""")); // >= or < assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos >= "2025-01-01" or date_and_date_nanos < "2020-01-01\"""", stats, matchesRegex(""" + FROM index* | WHERE date_and_date_nanos >= "2025-01-01" or date_and_date_nanos < "2020-01-01\"""", stats, matchesRegex(""" .*bool.*should.*""" + """ esql_single_value":\\{"field":"date_and_date_nanos".*"range":\\{"date_and_date_nanos":\\{"gte":"2025-01-01T00:00:00.000Z",\ "time_zone":"Z","format":"strict_date_optional_time_nanos","boost":0.0.*""" + """ @@ -392,7 +392,7 @@ public void testToDateNanos() { // > or = assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos > "2025-01-01" or date_and_date_nanos == "2020-01-01\"""", stats, matchesRegex(""" + FROM index* | WHERE date_and_date_nanos > "2025-01-01" or date_and_date_nanos == "2020-01-01\"""", stats, matchesRegex(""" .*bool.*should.*""" + """ esql_single_value":\\{"field":"date_and_date_nanos".*"range":\\{"date_and_date_nanos":\\{"gt":"2025-01-01T00:00:00.000Z",\ "time_zone":"Z","format":"strict_date_optional_time_nanos","boost":0.0.*""" + """ @@ -401,7 +401,7 @@ public void testToDateNanos() { // < or != assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos < "2020-01-01" or date_and_date_nanos != "2025-01-01\"""", stats, matchesRegex(""" + FROM index* | WHERE date_and_date_nanos < "2020-01-01" or date_and_date_nanos != "2025-01-01\"""", stats, matchesRegex(""" .*bool.*should.*""" + """ esql_single_value":\\{"field":"date_and_date_nanos".*"range":\\{"date_and_date_nanos":\\{"lt":"2020-01-01T00:00:00.000Z",\ "time_zone":"Z","format":"strict_date_optional_time_nanos","boost":0.0.*""" + """ @@ -410,7 +410,7 @@ public void testToDateNanos() { // == or == assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos == "2020-01-01" or date_and_date_nanos == "2025-01-01\"""", stats, matchesRegex(""" + FROM index* | WHERE date_and_date_nanos == "2020-01-01" or date_and_date_nanos == "2025-01-01\"""", stats, matchesRegex(""" .*bool.*should.*""" + """ esql_single_value":\\{"field":"date_and_date_nanos".*"term":\\{"date_and_date_nanos":\\{"value":"2020-01-01T00:00:00.000Z",\ "boost":0.0.*""" + """ @@ -419,7 +419,7 @@ public void testToDateNanos() { // != or != assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos != "2020-01-01" or date_and_date_nanos != "2025-01-01\"""", stats, matchesRegex(""" + FROM index* | WHERE date_and_date_nanos != "2020-01-01" or date_and_date_nanos != "2025-01-01\"""", stats, matchesRegex(""" .*bool.*should.*""" + """ esql_single_value":\\{"field":"date_and_date_nanos".*"must_not".*"term":\\{"date_and_date_nanos":\\{"value":\ "2020-01-01T00:00:00.000Z","boost":0.0.*""" + """ @@ -428,7 +428,7 @@ public void testToDateNanos() { // = or != assertQueryTranslationDateDateNanosUnionTypes(""" - FROM test* | WHERE date_and_date_nanos == "2020-01-01" or date_and_date_nanos != "2025-01-01\"""", stats, matchesRegex(""" + FROM index* | WHERE date_and_date_nanos == "2020-01-01" or date_and_date_nanos != "2025-01-01\"""", stats, matchesRegex(""" .*bool.*should.*""" + """ esql_single_value":\\{"field":"date_and_date_nanos".*"term":\\{"date_and_date_nanos":\\{"value":\ "2020-01-01T00:00:00.000Z","boost":0.0.*""" + """ @@ -438,7 +438,7 @@ public void testToDateNanos() { // explicit casting assertQueryTranslationDateDateNanosUnionTypes( """ - FROM test* | WHERE date_and_date_nanos::datetime < "2025-12-31" and date_and_date_nanos > "2025-01-01\"""", + FROM index* | WHERE date_and_date_nanos::datetime < "2025-12-31" and date_and_date_nanos > "2025-01-01\"""", stats, containsString(""" "esql_single_value":{"field":"date_and_date_nanos",\ diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java index b25fba6f1dc48..3f1591a6928b9 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java @@ -23,7 +23,6 @@ import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; import org.elasticsearch.xpack.esql.parser.EsqlParser; @@ -44,6 +43,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; public class ClusterRequestTests extends AbstractWireSerializingTestCase { @@ -170,7 +170,6 @@ private static String randomQuery() { static Versioned parse(String query) { Map mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(test); LogicalOptimizerContext context = unboundLogicalOptimizerContext(); TransportVersion minimumVersion = context.minimumVersion(); var logicalOptimizer = new LogicalPlanOptimizer(context); @@ -178,7 +177,7 @@ static Versioned parse(String query) { new AnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), - getIndexResult, + indexResolutions(test), Map.of(), emptyPolicyResolution(), emptyInferenceResolution(), diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java index a9faac859f0da..e31759901a209 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java @@ -24,7 +24,6 @@ import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; -import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; import org.elasticsearch.xpack.esql.optimizer.PhysicalOptimizerContext; @@ -49,6 +48,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; public class DataNodeRequestSerializationTests extends AbstractWireSerializingTestCase { @@ -302,9 +302,14 @@ protected DataNodeRequest mutateInstance(DataNodeRequest in) throws IOException static Versioned parse(String query) { Map mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); - IndexResolution getIndexResult = IndexResolution.valid(test); var analyzer = new Analyzer( - testAnalyzerContext(TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, emptyPolicyResolution(), emptyInferenceResolution()), + testAnalyzerContext( + TEST_CFG, + new EsqlFunctionRegistry(), + indexResolutions(test), + emptyPolicyResolution(), + emptyInferenceResolution() + ), TEST_VERIFIER ); TransportVersion minimumVersion = analyzer.context().minimumVersion(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/EsqlCCSUtilsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/EsqlCCSUtilsTests.java index 3a2eaa544c6ab..4504e2fdec96b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/EsqlCCSUtilsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/EsqlCCSUtilsTests.java @@ -224,7 +224,7 @@ public void testUpdateExecutionInfoWithClustersWithNoMatchingIndices() { IndexResolution indexResolution = IndexResolution.valid(esIndex, esIndex.concreteIndices(), Map.of()); - EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, indexResolution); + EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, Set.of(indexResolution)); EsqlExecutionInfo.Cluster localCluster = executionInfo.getCluster(LOCAL_CLUSTER_ALIAS); assertThat(localCluster.getIndexExpression(), equalTo("logs*")); @@ -267,7 +267,7 @@ public void testUpdateExecutionInfoWithClustersWithNoMatchingIndices() { ); IndexResolution indexResolution = IndexResolution.valid(esIndex, esIndex.concreteIndices(), Map.of()); - EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, indexResolution); + EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, Set.of(indexResolution)); EsqlExecutionInfo.Cluster localCluster = executionInfo.getCluster(LOCAL_CLUSTER_ALIAS); assertThat(localCluster.getIndexExpression(), equalTo("logs*")); @@ -309,7 +309,7 @@ public void testUpdateExecutionInfoWithClustersWithNoMatchingIndices() { var failures = Map.of(REMOTE1_ALIAS, List.of(failure)); IndexResolution indexResolution = IndexResolution.valid(esIndex, esIndex.concreteIndices(), failures); - EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, indexResolution); + EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, Set.of(indexResolution)); EsqlExecutionInfo.Cluster localCluster = executionInfo.getCluster(LOCAL_CLUSTER_ALIAS); assertThat(localCluster.getIndexExpression(), equalTo("logs*")); @@ -350,7 +350,7 @@ public void testUpdateExecutionInfoWithClustersWithNoMatchingIndices() { var failure = new FieldCapabilitiesFailure(new String[] { "logs-a" }, new NoSeedNodeLeftException("unable to connect")); var failures = Map.of(REMOTE1_ALIAS, List.of(failure)); IndexResolution indexResolution = IndexResolution.valid(esIndex, esIndex.concreteIndices(), failures); - EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, indexResolution); + EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, Set.of(indexResolution)); EsqlExecutionInfo.Cluster localCluster = executionInfo.getCluster(LOCAL_CLUSTER_ALIAS); assertThat(localCluster.getIndexExpression(), equalTo("logs*")); @@ -399,7 +399,7 @@ public void testUpdateExecutionInfoWithClustersWithNoMatchingIndices() { var failures = Map.of(REMOTE1_ALIAS, List.of(failure)); IndexResolution indexResolution = IndexResolution.valid(esIndex, esIndex.concreteIndices(), failures); - EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, indexResolution); + EsqlCCSUtils.updateExecutionInfoWithClustersWithNoMatchingIndices(executionInfo, Set.of(indexResolution)); EsqlExecutionInfo.Cluster localCluster = executionInfo.getCluster(LOCAL_CLUSTER_ALIAS); assertThat(localCluster.getIndexExpression(), equalTo("logs*")); @@ -713,7 +713,7 @@ private void assertLicenseCheckPasses( String... expectedRemotes ) { var executionInfo = new EsqlExecutionInfo(true); - initCrossClusterState(indicesGrouper, createLicenseState(status), pattern, executionInfo); + initCrossClusterState(indicesGrouper, createLicenseState(status), Set.of(pattern), executionInfo); assertThat(executionInfo.clusterAliases(), containsInAnyOrder(expectedRemotes)); } @@ -728,7 +728,7 @@ private void assertLicenseCheckFails( equalTo( "A valid Enterprise license is required to run ES|QL cross-cluster searches. License found: " + expectedErrorMessageSuffix ), - () -> initCrossClusterState(indicesGrouper, createLicenseState(licenseStatus), pattern, new EsqlExecutionInfo(true)) + () -> initCrossClusterState(indicesGrouper, createLicenseState(licenseStatus), Set.of(pattern), new EsqlExecutionInfo(true)) ); assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST)); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java index bf8434e3c11c5..5dd264646f584 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java @@ -38,11 +38,13 @@ import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.enrich.EnrichPolicyResolver; import org.elasticsearch.xpack.esql.execution.PlanExecutor; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.querylog.EsqlQueryLog; import org.elasticsearch.xpack.esql.session.EsqlSession; import org.elasticsearch.xpack.esql.session.IndexResolver; import org.elasticsearch.xpack.esql.session.Result; +import org.elasticsearch.xpack.esql.view.InMemoryViewService; import org.junit.After; import org.junit.Before; import org.mockito.stubbing.Answer; @@ -156,8 +158,17 @@ public void testFailedMetric() { ); return null; }).when(esqlClient).execute(eq(EsqlResolveFieldsAction.TYPE), any(), any()); + EsqlFunctionRegistry registry = new EsqlFunctionRegistry(); + InMemoryViewService viewService = new InMemoryViewService(registry); - var planExecutor = new PlanExecutor(indexResolver, MeterRegistry.NOOP, new XPackLicenseState(() -> 0L), mockQueryLog(), List.of()); + var planExecutor = new PlanExecutor( + indexResolver, + registry, + MeterRegistry.NOOP, + new XPackLicenseState(() -> 0L), + mockQueryLog(), + List.of() + ); var enrichResolver = mockEnrichResolver(); var request = new EsqlQueryRequest(); @@ -175,6 +186,7 @@ public void testFailedMetric() { randomAlphaOfLength(10), queryClusterSettings(), enrichResolver, + viewService, new EsqlExecutionInfo(randomBoolean()), groupIndicesByCluster, runPhase, @@ -205,6 +217,7 @@ public void onFailure(Exception e) { randomAlphaOfLength(10), queryClusterSettings(), enrichResolver, + viewService, new EsqlExecutionInfo(randomBoolean()), groupIndicesByCluster, runPhase, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/VerifierMetricsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/VerifierMetricsTests.java index 7013cc6bc9ea0..94a1db9bcb676 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/VerifierMetricsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/VerifierMetricsTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.telemetry; +import org.elasticsearch.index.IndexMode; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.watcher.common.stats.Counters; @@ -14,6 +15,7 @@ import org.elasticsearch.xpack.esql.analysis.Verifier; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.expression.function.FunctionDefinition; +import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.parser.EsqlParser; import java.util.HashMap; @@ -23,6 +25,8 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyzer; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexResolutions; +import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.loadMapping; import static org.elasticsearch.xpack.esql.telemetry.FeatureMetric.DISSECT; import static org.elasticsearch.xpack.esql.telemetry.FeatureMetric.DROP; import static org.elasticsearch.xpack.esql.telemetry.FeatureMetric.ENRICH; @@ -833,7 +837,9 @@ private Counters esql(String esql, Verifier v) { metrics = new Metrics(new EsqlFunctionRegistry()); verifier = new Verifier(metrics, new XPackLicenseState(() -> 0L)); } - analyzer(verifier).analyze(parser.createStatement(esql)); + IndexResolution metricsIndex = loadMapping("mapping-basic.json", "metrics", IndexMode.TIME_SERIES); + IndexResolution employees = loadMapping("mapping-basic.json", "employees"); + analyzer(indexResolutions(metricsIndex, employees), verifier).analyze(parser.createStatement(esql)); return metrics == null ? null : metrics.stats(); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java index 2f848d07f657f..b91fdc6090e95 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/tree/EsqlNodeSubclassTests.java @@ -44,12 +44,15 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pow; import org.elasticsearch.xpack.esql.expression.function.scalar.string.Concat; import org.elasticsearch.xpack.esql.expression.predicate.fulltext.FullTextPredicate; +import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals; import org.elasticsearch.xpack.esql.index.EsIndex; import org.elasticsearch.xpack.esql.plan.logical.Dissect; import org.elasticsearch.xpack.esql.plan.logical.EsRelation; +import org.elasticsearch.xpack.esql.plan.logical.Filter; import org.elasticsearch.xpack.esql.plan.logical.Fork; import org.elasticsearch.xpack.esql.plan.logical.Grok; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; +import org.elasticsearch.xpack.esql.plan.logical.UnionAll; import org.elasticsearch.xpack.esql.plan.logical.join.JoinConfig; import org.elasticsearch.xpack.esql.plan.logical.join.JoinType; import org.elasticsearch.xpack.esql.plan.logical.join.JoinTypes; @@ -96,6 +99,7 @@ import static java.util.Collections.emptyList; import static org.elasticsearch.xpack.esql.ConfigurationTestUtils.randomConfiguration; import static org.elasticsearch.xpack.esql.core.type.DataType.GEO_POINT; +import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; import static org.elasticsearch.xpack.esql.index.EsIndexSerializationTests.randomEsIndex; import static org.elasticsearch.xpack.esql.index.EsIndexSerializationTests.randomIndexNameWithModes; import static org.elasticsearch.xpack.esql.plan.AbstractNodeSerializationTests.randomFieldAttributes; @@ -150,7 +154,7 @@ public static List nodeSubclasses() throws IOException { .toList(); } - private static final List> CLASSES_WITH_MIN_TWO_CHILDREN = List.of(Concat.class, CIDRMatch.class, Fork.class); + private static final List> CLASSES_WITH_MIN_TWO_CHILDREN = List.of(Concat.class, CIDRMatch.class, Fork.class, UnionAll.class); // List of classes that are "unresolved" NamedExpression subclasses, therefore not suitable for use with logical/physical plan nodes. private static final List> UNRESOLVED_CLASSES = List.of( @@ -449,7 +453,7 @@ public void accept(Page page) { return randomInt(); } else if (argClass == JoinType.class) { return JoinTypes.LEFT; - } else if (List.of(Fork.class, MergeExec.class).contains(toBuildClass) && argType == LogicalPlan.class) { + } else if (List.of(Fork.class, MergeExec.class, UnionAll.class).contains(toBuildClass) && argType == LogicalPlan.class) { // limit recursion of plans, in order to prevent stackoverflow errors return randomEsRelation(); } @@ -745,6 +749,24 @@ static EsQueryExec.QueryBuilderAndTags randomQueryBuildAndTags() { return new EsQueryExec.QueryBuilderAndTags(randomQuery(), List.of(randomBoolean() ? randomLong() : randomDouble())); } + static EsRelation randomEsRelationInUnionAll() { + return new EsRelation( + SourceTests.randomSource(), + randomIdentifier(), + randomFrom(IndexMode.STANDARD, IndexMode.LOOKUP), + randomIndexNameWithModes(), + randomFieldAttributes(0, 10, false) + ); + } + + static Filter randomFilterInUnionAll() { + return new Filter( + Source.EMPTY, + randomEsRelationInUnionAll(), + new Equals(Source.EMPTY, field(randomAlphaOfLength(16), INTEGER), new Literal(Source.EMPTY, randomInt(), INTEGER)) + ); + } + static FieldAttribute field(String name, DataType type) { return new FieldAttribute( Source.EMPTY, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/view/InMemoryViewServiceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/view/InMemoryViewServiceTests.java new file mode 100644 index 0000000000000..d8f5c0d96e241 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/view/InMemoryViewServiceTests.java @@ -0,0 +1,146 @@ +/* + * 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.view; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.xpack.esql.VerificationException; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; +import org.elasticsearch.xpack.esql.parser.AbstractStatementParserTests; +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; +import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; + +public class InMemoryViewServiceTests extends AbstractStatementParserTests { + EsqlFunctionRegistry functionRegistry = new EsqlFunctionRegistry(); + InMemoryViewService viewService = new InMemoryViewService(functionRegistry); + PlanTelemetry telemetry = new PlanTelemetry(functionRegistry); + + public void testPutGet() throws Exception { + addView("view1", "from emp"); + addView("view2", "from view1"); + addView("view3", "from view2"); + assertThat(viewService.get("view1").query(), equalTo("from emp")); + assertThat(viewService.get("view2").query(), equalTo("from view1")); + assertThat(viewService.get("view3").query(), equalTo("from view2")); + } + + public void testReplaceView() throws Exception { + addView("view1", "from emp"); + addView("view2", "from view1"); + addView("view3", "from view2"); + LogicalPlan plan = statement("from view3"); + LogicalPlan rewritten = viewService.replaceViews(plan, telemetry); + assertThat(rewritten, equalTo(statement("from emp"))); + } + + public void testViewDepthExceeded() throws Exception { + addView("view1", "from emp"); + addView("view2", "from view1"); + addView("view3", "from view2"); + addView("view4", "from view3"); + addView("view5", "from view4"); + addView("view6", "from view5"); + addView("view7", "from view6"); + addView("view8", "from view7"); + addView("view9", "from view8"); + addView("view10", "from view9"); + addView("view11", "from view10"); + + // FROM view11 should fail + Exception e = expectThrows(VerificationException.class, () -> viewService.replaceViews(statement("from view11"), telemetry)); + assertThat(e.getMessage(), startsWith("The maximum allowed view depth of 10 has been exceeded")); + + // But FROM view10 should work + LogicalPlan rewritten = viewService.replaceViews(statement("from view10"), telemetry); + assertThat(rewritten, equalTo(statement("from emp"))); + } + + public void testModifiedViewDepth() { + var config = new ViewService.ViewServiceConfig(100, 10_000, 1); + InMemoryViewService customViewService = new InMemoryViewService(functionRegistry, config); + try { + addView("view1", "from emp", customViewService); + addView("view2", "from view1", customViewService); + addView("view3", "from view2", customViewService); + + // FROM view2 should fail + Exception e = expectThrows( + VerificationException.class, + () -> customViewService.replaceViews(statement("from view2"), telemetry) + ); + assertThat(e.getMessage(), startsWith("The maximum allowed view depth of 1 has been exceeded")); + + // But FROM view1 should work + LogicalPlan rewritten = customViewService.replaceViews(statement("from view1"), telemetry); + assertThat(rewritten, equalTo(statement("from emp"))); + } catch (Exception e) { + throw new AssertionError("unexpected exception", e); + } + } + + public void testViewCountExceeded() throws Exception { + for (int i = 0; i < ViewService.ViewServiceConfig.DEFAULT.maxViews(); i++) { + addView("view" + i, "from emp"); + } + + // FROM view11 should fail + Exception e = expectThrows(IllegalArgumentException.class, () -> addView("viewX", "from emp")); + assertThat(e.getMessage(), startsWith("cannot add view, the maximum number of views is reached: 100")); + } + + public void testModifiedViewCount() { + var config = new ViewService.ViewServiceConfig(1, 10_000, 10); + InMemoryViewService customViewService = new InMemoryViewService(functionRegistry, config); + try { + addView("view1", "from emp", customViewService); + + // View2 should fail + Exception e = expectThrows(IllegalArgumentException.class, () -> addView("view2", "from emp", customViewService)); + assertThat(e.getMessage(), startsWith("cannot add view, the maximum number of views is reached: 1")); + } catch (Exception e) { + throw new AssertionError("unexpected exception", e); + } + } + + public void testViewLengthExceeded() throws Exception { + addView("view1", "from short"); + + // Long view definition should fail + StringBuilder longView = new StringBuilder("from "); + for (int i = 0; i < ViewService.ViewServiceConfig.DEFAULT.maxViewSize(); i++) { + longView.append("a"); + } + Exception e = expectThrows(IllegalArgumentException.class, () -> addView("viewX", longView.toString())); + assertThat(e.getMessage(), startsWith("view query is too large: 10005 characters, the maximum allowed is 10000")); + } + + public void testModifiedViewLength() { + var config = new ViewService.ViewServiceConfig(100, 6, 10); + InMemoryViewService customViewService = new InMemoryViewService(functionRegistry, config); + try { + addView("view1", "from a", customViewService); + + // Just one character longer should fail + Exception e = expectThrows(IllegalArgumentException.class, () -> addView("view2", "from aa", customViewService)); + assertThat(e.getMessage(), startsWith("view query is too large: 7 characters, the maximum allowed is 6")); + } catch (Exception e) { + throw new AssertionError("unexpected exception", e); + } + } + + private void addView(String name, String query) { + addView(name, query, viewService); + } + + private void addView(String name, String query, ViewService viewService) { + viewService.put(name, new View(query), ActionListener.noop()); + } + +} diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index b4506a62b9947..e5860eca48fa8 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -174,6 +174,10 @@ public class Constants { "cluster:admin/xpack/enrich/get", "cluster:admin/xpack/enrich/put", "cluster:admin/xpack/enrich/reindex", + "cluster:admin/xpack/esql/view/put", + "cluster:admin/xpack/esql/view/delete", + "cluster:admin/xpack/esql/view/get", + "cluster:admin/xpack/esql/views", "cluster:internal/xpack/inference/clear_inference_endpoint_cache", "cluster:admin/xpack/inference/delete", "cluster:admin/xpack/inference/put", diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/200_view.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/200_view.yml new file mode 100644 index 0000000000000..ea9ccfb770b4f --- /dev/null +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/200_view.yml @@ -0,0 +1,60 @@ +--- +setup: + - requires: + capabilities: + - method: PUT + path: /_query + parameters: [ method, path, parameters, capabilities ] + capabilities: [ views_v1 ] + reason: "Views not yet supported" + test_runner_features: [capabilities, allowed_warnings_regex, warnings_regex] + - do: + indices.create: + index: test + body: + mappings: + properties: + content: + type: keyword + color: + type: keyword + animal: + type: keyword + id: + type: integer + - do: + bulk: + index: "test" + refresh: true + body: + - { "index": { } } + - { "content": "This is a brown fox", "color": "brown", "animal": "fox", "id": 1 } + - { "index": { } } + - { "content": "This is a brown dog", "color": "brown", "animal": "dog", "id": 2 } + - { "index": { } } + - { "content": "This dog is really white", "color": "white", "animal": "dog", "id": 3 } + - { "index": { } } + - { "content": "The dog is brown but this document is very very long", "color": "brown", "animal": "dog", "id": 4 } + - { "index": { } } + - { "content": "There is also a white cat", "color": "white", "animal": "cat", "id": 5 } + - { "index": { } } + - { "content": "The quick brown fox jumps over the lazy dog", "color": "brown", "animal": "dog", "id": 6 } + +--- +basic: + - do: + esql.put_view: + name: dogs + body: + query: 'FROM test | WHERE animal == "dog"' + + - do: + allowed_warnings_regex: + - "No limit defined, adding default limit of \\[.*\\]" + esql.query: + body: + query: 'FROM dogs | WHERE color == "white" | KEEP id | SORT id' + - match: { columns.0.name: "id" } + - match: { columns.0.type: "integer" } + - length: { values: 1 } + - match: { values.0.0: 3 }