diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java index 683a782a12230..59c8daa99430a 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java @@ -57,8 +57,13 @@ import org.elasticsearch.xpack.core.esql.action.ColumnInfo; import org.elasticsearch.xpack.esql.VerificationException; import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; +import org.elasticsearch.xpack.esql.core.expression.Alias; +import org.elasticsearch.xpack.esql.core.expression.Literal; +import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.parser.ParsingException; +import org.elasticsearch.xpack.esql.plan.EsqlStatement; +import org.elasticsearch.xpack.esql.plan.logical.Row; import org.elasticsearch.xpack.esql.planner.PlannerSettings; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.junit.Before; @@ -150,6 +155,22 @@ public void testRow() { } } + /** + * Verifies that a pre-built {@link EsqlStatement} supplied via + * {@link EsqlQueryRequest#syncEsqlQueryRequestWithPlan} is executed + * without going through ES|QL string parsing. + */ + public void testRowWithParsedStatement() { + var plan = new Row(Source.EMPTY, List.of(new Alias(Source.EMPTY, "x", new Literal(Source.EMPTY, 1, DataType.INTEGER)))); + var statement = new EsqlStatement(plan, List.of()); + EsqlQueryRequest request = EsqlQueryRequest.syncEsqlQueryRequestWithPlan(statement); + request.pragmas(getPragmas()); + try (EsqlQueryResponse response = run(request)) { + assertThat(response.columns(), equalTo(List.of(new ColumnInfoImpl("x", "integer", null)))); + assertEquals(List.of(List.of(1)), getValuesList(response)); + } + } + public void testRowWithFilter() { long value = randomLongBetween(0, Long.MAX_VALUE); try (EsqlQueryResponse response = run(syncEsqlQueryRequest("ROW " + value).filter(randomQueryFilter()))) { diff --git a/x-pack/plugin/esql/src/main/antlr/parser/Promql.g4 b/x-pack/plugin/esql/src/main/antlr/parser/Promql.g4 index bc6f8e35de3fb..dd9c3fbf08265 100644 --- a/x-pack/plugin/esql/src/main/antlr/parser/Promql.g4 +++ b/x-pack/plugin/esql/src/main/antlr/parser/Promql.g4 @@ -7,7 +7,9 @@ parser grammar Promql; promqlCommand - : PROMQL promqlParam* (valueName ASSIGN)? LP promqlQueryPart+ RP + : PROMQL promqlParam* (valueName ASSIGN)? LP NAMED_OR_POSITIONAL_PARAM RP // ?param as the entire PromQL expression (parenthesized) + | PROMQL promqlParam* (valueName ASSIGN)? NAMED_OR_POSITIONAL_PARAM // ?param as the entire PromQL expression (unparenthesized) + | PROMQL promqlParam* (valueName ASSIGN)? LP promqlQueryPart+ RP | PROMQL promqlParam* promqlQueryPart+ ; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java index eaee8bee2fa9a..f579b7a6fd3a5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java @@ -24,6 +24,7 @@ import org.elasticsearch.xpack.core.async.AsyncExecutionId; import org.elasticsearch.xpack.esql.Column; import org.elasticsearch.xpack.esql.parser.QueryParams; +import org.elasticsearch.xpack.esql.plan.EsqlStatement; import org.elasticsearch.xpack.esql.plugin.EsqlQueryStatus; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; @@ -66,6 +67,14 @@ public class EsqlQueryRequest extends org.elasticsearch.xpack.core.esql.action.E */ private final Map> tables = new TreeMap<>(); + /** + * An optional pre-built statement that bypasses ES|QL string parsing. + * This is transient and never serialized over the wire. It's used by internal callers + * (such as the Prometheus REST endpoints) that construct a {@link EsqlStatement} directly + * instead of going through ES|QL string construction and parsing. + */ + private EsqlStatement parsedStatement; + public static EsqlQueryRequest syncEsqlQueryRequest(String query) { return new EsqlQueryRequest(false, query); } @@ -74,6 +83,17 @@ public static EsqlQueryRequest asyncEsqlQueryRequest(String query) { return new EsqlQueryRequest(true, query); } + /** + * Creates a synchronous request with a pre-built statement, bypassing ES|QL string parsing. + * The query string is only used for logging/display since the plan is already built. + */ + public static EsqlQueryRequest syncEsqlQueryRequestWithPlan(EsqlStatement statement) { + String queryText = statement.plan().sourceText(); + EsqlQueryRequest request = new EsqlQueryRequest(false, queryText.isEmpty() ? "[pre-built plan]" : queryText); + request.parsedStatement = statement; + return request; + } + private EsqlQueryRequest(boolean async, String query) { this.async = async; this.query = query; @@ -119,6 +139,13 @@ public String query() { return query; } + /** + * Returns the pre-built statement, or {@code null} if the query string should be parsed. + */ + public EsqlStatement parsedStatement() { + return parsedStatement; + } + public boolean async() { return async; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.interp b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.interp index 521716b5cd644..927c2acb4adba 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.interp +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.interp @@ -457,4 +457,4 @@ promqlIndexString atn: -[4, 1, 168, 1120, 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, 1, 0, 5, 0, 228, 8, 0, 10, 0, 12, 0, 231, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 245, 8, 2, 10, 2, 12, 2, 248, 9, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 259, 8, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 290, 8, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 5, 8, 303, 8, 8, 10, 8, 12, 8, 306, 9, 8, 1, 9, 1, 9, 1, 9, 3, 9, 311, 8, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 328, 8, 13, 10, 13, 12, 13, 331, 9, 13, 1, 13, 3, 13, 334, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 339, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 345, 8, 15, 10, 15, 12, 15, 348, 9, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 3, 16, 355, 8, 16, 1, 16, 1, 16, 1, 16, 3, 16, 360, 8, 16, 1, 16, 3, 16, 363, 8, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 377, 8, 21, 10, 21, 12, 21, 380, 9, 21, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 3, 23, 387, 8, 23, 1, 23, 1, 23, 3, 23, 391, 8, 23, 1, 24, 1, 24, 1, 24, 5, 24, 396, 8, 24, 10, 24, 12, 24, 399, 9, 24, 1, 25, 1, 25, 1, 25, 3, 25, 404, 8, 25, 1, 26, 1, 26, 1, 26, 3, 26, 409, 8, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 418, 8, 26, 1, 27, 1, 27, 1, 27, 5, 27, 423, 8, 27, 10, 27, 12, 27, 426, 9, 27, 1, 28, 1, 28, 1, 28, 3, 28, 431, 8, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 3, 28, 440, 8, 28, 1, 29, 1, 29, 1, 29, 5, 29, 445, 8, 29, 10, 29, 12, 29, 448, 9, 29, 1, 30, 1, 30, 1, 30, 5, 30, 453, 8, 30, 10, 30, 12, 30, 456, 9, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 3, 32, 463, 8, 32, 1, 33, 1, 33, 3, 33, 467, 8, 33, 1, 34, 1, 34, 3, 34, 471, 8, 34, 1, 35, 1, 35, 1, 35, 3, 35, 476, 8, 35, 1, 36, 1, 36, 3, 36, 480, 8, 36, 1, 37, 1, 37, 1, 37, 3, 37, 485, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 5, 39, 495, 8, 39, 10, 39, 12, 39, 498, 9, 39, 1, 40, 1, 40, 3, 40, 502, 8, 40, 1, 40, 1, 40, 3, 40, 506, 8, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 5, 43, 518, 8, 43, 10, 43, 12, 43, 521, 9, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 531, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 3, 45, 537, 8, 45, 1, 46, 1, 46, 1, 46, 5, 46, 542, 8, 46, 10, 46, 12, 46, 545, 9, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 3, 48, 553, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 5, 49, 560, 8, 49, 10, 49, 12, 49, 563, 9, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 3, 54, 582, 8, 54, 1, 54, 1, 54, 1, 54, 1, 54, 5, 54, 588, 8, 54, 10, 54, 12, 54, 591, 9, 54, 3, 54, 593, 8, 54, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 3, 56, 600, 8, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 611, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 618, 8, 58, 1, 59, 1, 59, 1, 59, 1, 60, 4, 60, 624, 8, 60, 11, 60, 12, 60, 625, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 5, 62, 638, 8, 62, 10, 62, 12, 62, 641, 9, 62, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 649, 8, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 660, 8, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 670, 8, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 676, 8, 66, 3, 66, 678, 8, 66, 1, 67, 1, 67, 3, 67, 682, 8, 67, 1, 67, 5, 67, 685, 8, 67, 10, 67, 12, 67, 688, 9, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 701, 8, 68, 1, 69, 1, 69, 1, 69, 5, 69, 706, 8, 69, 10, 69, 12, 69, 709, 9, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 3, 77, 741, 8, 77, 1, 78, 1, 78, 3, 78, 745, 8, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 3, 79, 755, 8, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 764, 8, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 771, 8, 80, 10, 80, 12, 80, 774, 9, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 781, 8, 80, 1, 80, 1, 80, 1, 80, 3, 80, 786, 8, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 794, 8, 80, 10, 80, 12, 80, 797, 9, 80, 1, 81, 1, 81, 3, 81, 801, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 3, 81, 808, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 3, 81, 815, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 5, 81, 822, 8, 81, 10, 81, 12, 81, 825, 9, 81, 1, 81, 1, 81, 1, 81, 1, 81, 3, 81, 831, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 5, 81, 838, 8, 81, 10, 81, 12, 81, 841, 9, 81, 1, 81, 1, 81, 3, 81, 845, 8, 81, 1, 82, 1, 82, 1, 82, 3, 82, 850, 8, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 3, 83, 860, 8, 83, 1, 84, 1, 84, 1, 84, 1, 84, 3, 84, 866, 8, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 5, 84, 874, 8, 84, 10, 84, 12, 84, 877, 9, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 3, 85, 887, 8, 85, 1, 85, 1, 85, 1, 85, 5, 85, 892, 8, 85, 10, 85, 12, 85, 895, 9, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 5, 86, 903, 8, 86, 10, 86, 12, 86, 906, 9, 86, 1, 86, 1, 86, 3, 86, 910, 8, 86, 3, 86, 912, 8, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 3, 87, 919, 8, 87, 1, 88, 1, 88, 1, 88, 1, 88, 5, 88, 925, 8, 88, 10, 88, 12, 88, 928, 9, 88, 3, 88, 930, 8, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 3, 90, 940, 8, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 5, 91, 955, 8, 91, 10, 91, 12, 91, 958, 9, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 5, 91, 966, 8, 91, 10, 91, 12, 91, 969, 9, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 5, 91, 977, 8, 91, 10, 91, 12, 91, 980, 9, 91, 1, 91, 1, 91, 3, 91, 984, 8, 91, 1, 92, 1, 92, 1, 93, 1, 93, 3, 93, 990, 8, 93, 1, 94, 3, 94, 993, 8, 94, 1, 94, 1, 94, 1, 95, 3, 95, 998, 8, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 3, 99, 1014, 8, 99, 1, 99, 1, 99, 1, 99, 3, 99, 1019, 8, 99, 1, 100, 1, 100, 1, 100, 1, 100, 5, 100, 1025, 8, 100, 10, 100, 12, 100, 1028, 9, 100, 1, 101, 1, 101, 5, 101, 1032, 8, 101, 10, 101, 12, 101, 1035, 9, 101, 1, 101, 1, 101, 1, 101, 3, 101, 1040, 8, 101, 1, 101, 1, 101, 4, 101, 1044, 8, 101, 11, 101, 12, 101, 1045, 1, 101, 1, 101, 1, 101, 1, 101, 5, 101, 1052, 8, 101, 10, 101, 12, 101, 1055, 9, 101, 1, 101, 4, 101, 1058, 8, 101, 11, 101, 12, 101, 1059, 3, 101, 1062, 8, 101, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 5, 105, 1075, 8, 105, 10, 105, 12, 105, 1078, 9, 105, 1, 105, 1, 105, 3, 105, 1082, 8, 105, 1, 106, 1, 106, 1, 107, 4, 107, 1087, 8, 107, 11, 107, 12, 107, 1088, 1, 107, 1, 107, 5, 107, 1093, 8, 107, 10, 107, 12, 107, 1096, 9, 107, 1, 107, 3, 107, 1099, 8, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 3, 108, 1110, 8, 108, 1, 109, 1, 109, 1, 110, 1, 110, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 0, 5, 4, 124, 160, 168, 170, 113, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 0, 14, 2, 0, 58, 58, 113, 113, 1, 0, 107, 108, 2, 0, 62, 62, 69, 69, 2, 0, 72, 72, 75, 75, 2, 0, 47, 47, 58, 58, 1, 0, 93, 94, 1, 0, 95, 97, 2, 0, 71, 71, 84, 84, 2, 0, 86, 86, 88, 92, 2, 0, 29, 29, 31, 32, 3, 0, 58, 58, 101, 101, 107, 108, 8, 0, 58, 58, 63, 63, 65, 66, 68, 68, 101, 101, 107, 108, 113, 113, 155, 157, 2, 0, 107, 107, 113, 113, 3, 0, 58, 58, 107, 107, 113, 113, 1169, 0, 229, 1, 0, 0, 0, 2, 235, 1, 0, 0, 0, 4, 238, 1, 0, 0, 0, 6, 258, 1, 0, 0, 0, 8, 289, 1, 0, 0, 0, 10, 291, 1, 0, 0, 0, 12, 294, 1, 0, 0, 0, 14, 296, 1, 0, 0, 0, 16, 299, 1, 0, 0, 0, 18, 310, 1, 0, 0, 0, 20, 314, 1, 0, 0, 0, 22, 317, 1, 0, 0, 0, 24, 320, 1, 0, 0, 0, 26, 324, 1, 0, 0, 0, 28, 338, 1, 0, 0, 0, 30, 340, 1, 0, 0, 0, 32, 362, 1, 0, 0, 0, 34, 364, 1, 0, 0, 0, 36, 366, 1, 0, 0, 0, 38, 368, 1, 0, 0, 0, 40, 370, 1, 0, 0, 0, 42, 372, 1, 0, 0, 0, 44, 381, 1, 0, 0, 0, 46, 384, 1, 0, 0, 0, 48, 392, 1, 0, 0, 0, 50, 400, 1, 0, 0, 0, 52, 417, 1, 0, 0, 0, 54, 419, 1, 0, 0, 0, 56, 439, 1, 0, 0, 0, 58, 441, 1, 0, 0, 0, 60, 449, 1, 0, 0, 0, 62, 457, 1, 0, 0, 0, 64, 462, 1, 0, 0, 0, 66, 466, 1, 0, 0, 0, 68, 470, 1, 0, 0, 0, 70, 475, 1, 0, 0, 0, 72, 479, 1, 0, 0, 0, 74, 481, 1, 0, 0, 0, 76, 486, 1, 0, 0, 0, 78, 490, 1, 0, 0, 0, 80, 499, 1, 0, 0, 0, 82, 507, 1, 0, 0, 0, 84, 510, 1, 0, 0, 0, 86, 513, 1, 0, 0, 0, 88, 530, 1, 0, 0, 0, 90, 532, 1, 0, 0, 0, 92, 538, 1, 0, 0, 0, 94, 546, 1, 0, 0, 0, 96, 552, 1, 0, 0, 0, 98, 554, 1, 0, 0, 0, 100, 564, 1, 0, 0, 0, 102, 567, 1, 0, 0, 0, 104, 570, 1, 0, 0, 0, 106, 574, 1, 0, 0, 0, 108, 577, 1, 0, 0, 0, 110, 594, 1, 0, 0, 0, 112, 599, 1, 0, 0, 0, 114, 603, 1, 0, 0, 0, 116, 606, 1, 0, 0, 0, 118, 619, 1, 0, 0, 0, 120, 623, 1, 0, 0, 0, 122, 627, 1, 0, 0, 0, 124, 631, 1, 0, 0, 0, 126, 642, 1, 0, 0, 0, 128, 644, 1, 0, 0, 0, 130, 655, 1, 0, 0, 0, 132, 677, 1, 0, 0, 0, 134, 679, 1, 0, 0, 0, 136, 700, 1, 0, 0, 0, 138, 702, 1, 0, 0, 0, 140, 710, 1, 0, 0, 0, 142, 712, 1, 0, 0, 0, 144, 714, 1, 0, 0, 0, 146, 719, 1, 0, 0, 0, 148, 722, 1, 0, 0, 0, 150, 727, 1, 0, 0, 0, 152, 732, 1, 0, 0, 0, 154, 736, 1, 0, 0, 0, 156, 742, 1, 0, 0, 0, 158, 754, 1, 0, 0, 0, 160, 785, 1, 0, 0, 0, 162, 844, 1, 0, 0, 0, 164, 846, 1, 0, 0, 0, 166, 859, 1, 0, 0, 0, 168, 865, 1, 0, 0, 0, 170, 886, 1, 0, 0, 0, 172, 896, 1, 0, 0, 0, 174, 918, 1, 0, 0, 0, 176, 920, 1, 0, 0, 0, 178, 933, 1, 0, 0, 0, 180, 939, 1, 0, 0, 0, 182, 983, 1, 0, 0, 0, 184, 985, 1, 0, 0, 0, 186, 989, 1, 0, 0, 0, 188, 992, 1, 0, 0, 0, 190, 997, 1, 0, 0, 0, 192, 1001, 1, 0, 0, 0, 194, 1003, 1, 0, 0, 0, 196, 1005, 1, 0, 0, 0, 198, 1018, 1, 0, 0, 0, 200, 1020, 1, 0, 0, 0, 202, 1061, 1, 0, 0, 0, 204, 1063, 1, 0, 0, 0, 206, 1065, 1, 0, 0, 0, 208, 1069, 1, 0, 0, 0, 210, 1081, 1, 0, 0, 0, 212, 1083, 1, 0, 0, 0, 214, 1098, 1, 0, 0, 0, 216, 1109, 1, 0, 0, 0, 218, 1111, 1, 0, 0, 0, 220, 1113, 1, 0, 0, 0, 222, 1115, 1, 0, 0, 0, 224, 1117, 1, 0, 0, 0, 226, 228, 3, 152, 76, 0, 227, 226, 1, 0, 0, 0, 228, 231, 1, 0, 0, 0, 229, 227, 1, 0, 0, 0, 229, 230, 1, 0, 0, 0, 230, 232, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 232, 233, 3, 2, 1, 0, 233, 234, 5, 0, 0, 1, 234, 1, 1, 0, 0, 0, 235, 236, 3, 4, 2, 0, 236, 237, 5, 0, 0, 1, 237, 3, 1, 0, 0, 0, 238, 239, 6, 2, -1, 0, 239, 240, 3, 6, 3, 0, 240, 246, 1, 0, 0, 0, 241, 242, 10, 1, 0, 0, 242, 243, 5, 57, 0, 0, 243, 245, 3, 8, 4, 0, 244, 241, 1, 0, 0, 0, 245, 248, 1, 0, 0, 0, 246, 244, 1, 0, 0, 0, 246, 247, 1, 0, 0, 0, 247, 5, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 249, 259, 3, 20, 10, 0, 250, 259, 3, 14, 7, 0, 251, 259, 3, 106, 53, 0, 252, 259, 3, 22, 11, 0, 253, 259, 3, 202, 101, 0, 254, 255, 4, 3, 1, 0, 255, 259, 3, 102, 51, 0, 256, 257, 4, 3, 2, 0, 257, 259, 3, 24, 12, 0, 258, 249, 1, 0, 0, 0, 258, 250, 1, 0, 0, 0, 258, 251, 1, 0, 0, 0, 258, 252, 1, 0, 0, 0, 258, 253, 1, 0, 0, 0, 258, 254, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 259, 7, 1, 0, 0, 0, 260, 290, 3, 44, 22, 0, 261, 290, 3, 10, 5, 0, 262, 290, 3, 82, 41, 0, 263, 290, 3, 74, 37, 0, 264, 290, 3, 46, 23, 0, 265, 290, 3, 78, 39, 0, 266, 290, 3, 84, 42, 0, 267, 290, 3, 86, 43, 0, 268, 290, 3, 90, 45, 0, 269, 290, 3, 98, 49, 0, 270, 290, 3, 108, 54, 0, 271, 290, 3, 100, 50, 0, 272, 290, 3, 196, 98, 0, 273, 290, 3, 116, 58, 0, 274, 290, 3, 130, 65, 0, 275, 290, 3, 114, 57, 0, 276, 290, 3, 118, 59, 0, 277, 290, 3, 128, 64, 0, 278, 290, 3, 132, 66, 0, 279, 290, 3, 134, 67, 0, 280, 290, 3, 148, 74, 0, 281, 290, 3, 140, 70, 0, 282, 290, 3, 150, 75, 0, 283, 290, 3, 142, 71, 0, 284, 290, 3, 156, 78, 0, 285, 286, 4, 4, 3, 0, 286, 290, 3, 144, 72, 0, 287, 288, 4, 4, 4, 0, 288, 290, 3, 146, 73, 0, 289, 260, 1, 0, 0, 0, 289, 261, 1, 0, 0, 0, 289, 262, 1, 0, 0, 0, 289, 263, 1, 0, 0, 0, 289, 264, 1, 0, 0, 0, 289, 265, 1, 0, 0, 0, 289, 266, 1, 0, 0, 0, 289, 267, 1, 0, 0, 0, 289, 268, 1, 0, 0, 0, 289, 269, 1, 0, 0, 0, 289, 270, 1, 0, 0, 0, 289, 271, 1, 0, 0, 0, 289, 272, 1, 0, 0, 0, 289, 273, 1, 0, 0, 0, 289, 274, 1, 0, 0, 0, 289, 275, 1, 0, 0, 0, 289, 276, 1, 0, 0, 0, 289, 277, 1, 0, 0, 0, 289, 278, 1, 0, 0, 0, 289, 279, 1, 0, 0, 0, 289, 280, 1, 0, 0, 0, 289, 281, 1, 0, 0, 0, 289, 282, 1, 0, 0, 0, 289, 283, 1, 0, 0, 0, 289, 284, 1, 0, 0, 0, 289, 285, 1, 0, 0, 0, 289, 287, 1, 0, 0, 0, 290, 9, 1, 0, 0, 0, 291, 292, 5, 17, 0, 0, 292, 293, 3, 160, 80, 0, 293, 11, 1, 0, 0, 0, 294, 295, 3, 62, 31, 0, 295, 13, 1, 0, 0, 0, 296, 297, 5, 13, 0, 0, 297, 298, 3, 16, 8, 0, 298, 15, 1, 0, 0, 0, 299, 304, 3, 18, 9, 0, 300, 301, 5, 68, 0, 0, 301, 303, 3, 18, 9, 0, 302, 300, 1, 0, 0, 0, 303, 306, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 17, 1, 0, 0, 0, 306, 304, 1, 0, 0, 0, 307, 308, 3, 52, 26, 0, 308, 309, 5, 63, 0, 0, 309, 311, 1, 0, 0, 0, 310, 307, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 313, 3, 160, 80, 0, 313, 19, 1, 0, 0, 0, 314, 315, 5, 22, 0, 0, 315, 316, 3, 26, 13, 0, 316, 21, 1, 0, 0, 0, 317, 318, 5, 23, 0, 0, 318, 319, 3, 26, 13, 0, 319, 23, 1, 0, 0, 0, 320, 321, 5, 24, 0, 0, 321, 322, 3, 72, 36, 0, 322, 323, 3, 96, 48, 0, 323, 25, 1, 0, 0, 0, 324, 329, 3, 28, 14, 0, 325, 326, 5, 68, 0, 0, 326, 328, 3, 28, 14, 0, 327, 325, 1, 0, 0, 0, 328, 331, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 329, 330, 1, 0, 0, 0, 330, 333, 1, 0, 0, 0, 331, 329, 1, 0, 0, 0, 332, 334, 3, 42, 21, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 27, 1, 0, 0, 0, 335, 339, 3, 32, 16, 0, 336, 337, 4, 14, 5, 0, 337, 339, 3, 30, 15, 0, 338, 335, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 339, 29, 1, 0, 0, 0, 340, 341, 5, 105, 0, 0, 341, 346, 3, 20, 10, 0, 342, 343, 5, 57, 0, 0, 343, 345, 3, 8, 4, 0, 344, 342, 1, 0, 0, 0, 345, 348, 1, 0, 0, 0, 346, 344, 1, 0, 0, 0, 346, 347, 1, 0, 0, 0, 347, 349, 1, 0, 0, 0, 348, 346, 1, 0, 0, 0, 349, 350, 5, 106, 0, 0, 350, 31, 1, 0, 0, 0, 351, 352, 3, 34, 17, 0, 352, 353, 5, 66, 0, 0, 353, 355, 1, 0, 0, 0, 354, 351, 1, 0, 0, 0, 354, 355, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 359, 3, 38, 19, 0, 357, 358, 5, 65, 0, 0, 358, 360, 3, 36, 18, 0, 359, 357, 1, 0, 0, 0, 359, 360, 1, 0, 0, 0, 360, 363, 1, 0, 0, 0, 361, 363, 3, 40, 20, 0, 362, 354, 1, 0, 0, 0, 362, 361, 1, 0, 0, 0, 363, 33, 1, 0, 0, 0, 364, 365, 5, 113, 0, 0, 365, 35, 1, 0, 0, 0, 366, 367, 5, 113, 0, 0, 367, 37, 1, 0, 0, 0, 368, 369, 5, 113, 0, 0, 369, 39, 1, 0, 0, 0, 370, 371, 7, 0, 0, 0, 371, 41, 1, 0, 0, 0, 372, 373, 5, 112, 0, 0, 373, 378, 5, 113, 0, 0, 374, 375, 5, 68, 0, 0, 375, 377, 5, 113, 0, 0, 376, 374, 1, 0, 0, 0, 377, 380, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 378, 379, 1, 0, 0, 0, 379, 43, 1, 0, 0, 0, 380, 378, 1, 0, 0, 0, 381, 382, 5, 9, 0, 0, 382, 383, 3, 16, 8, 0, 383, 45, 1, 0, 0, 0, 384, 386, 5, 16, 0, 0, 385, 387, 3, 48, 24, 0, 386, 385, 1, 0, 0, 0, 386, 387, 1, 0, 0, 0, 387, 390, 1, 0, 0, 0, 388, 389, 5, 64, 0, 0, 389, 391, 3, 16, 8, 0, 390, 388, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 47, 1, 0, 0, 0, 392, 397, 3, 50, 25, 0, 393, 394, 5, 68, 0, 0, 394, 396, 3, 50, 25, 0, 395, 393, 1, 0, 0, 0, 396, 399, 1, 0, 0, 0, 397, 395, 1, 0, 0, 0, 397, 398, 1, 0, 0, 0, 398, 49, 1, 0, 0, 0, 399, 397, 1, 0, 0, 0, 400, 403, 3, 18, 9, 0, 401, 402, 5, 17, 0, 0, 402, 404, 3, 160, 80, 0, 403, 401, 1, 0, 0, 0, 403, 404, 1, 0, 0, 0, 404, 51, 1, 0, 0, 0, 405, 406, 4, 26, 6, 0, 406, 408, 5, 103, 0, 0, 407, 409, 5, 107, 0, 0, 408, 407, 1, 0, 0, 0, 408, 409, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 411, 5, 104, 0, 0, 411, 412, 5, 70, 0, 0, 412, 413, 5, 103, 0, 0, 413, 414, 3, 54, 27, 0, 414, 415, 5, 104, 0, 0, 415, 418, 1, 0, 0, 0, 416, 418, 3, 54, 27, 0, 417, 405, 1, 0, 0, 0, 417, 416, 1, 0, 0, 0, 418, 53, 1, 0, 0, 0, 419, 424, 3, 70, 35, 0, 420, 421, 5, 70, 0, 0, 421, 423, 3, 70, 35, 0, 422, 420, 1, 0, 0, 0, 423, 426, 1, 0, 0, 0, 424, 422, 1, 0, 0, 0, 424, 425, 1, 0, 0, 0, 425, 55, 1, 0, 0, 0, 426, 424, 1, 0, 0, 0, 427, 428, 4, 28, 7, 0, 428, 430, 5, 103, 0, 0, 429, 431, 5, 148, 0, 0, 430, 429, 1, 0, 0, 0, 430, 431, 1, 0, 0, 0, 431, 432, 1, 0, 0, 0, 432, 433, 5, 104, 0, 0, 433, 434, 5, 70, 0, 0, 434, 435, 5, 103, 0, 0, 435, 436, 3, 58, 29, 0, 436, 437, 5, 104, 0, 0, 437, 440, 1, 0, 0, 0, 438, 440, 3, 58, 29, 0, 439, 427, 1, 0, 0, 0, 439, 438, 1, 0, 0, 0, 440, 57, 1, 0, 0, 0, 441, 446, 3, 64, 32, 0, 442, 443, 5, 70, 0, 0, 443, 445, 3, 64, 32, 0, 444, 442, 1, 0, 0, 0, 445, 448, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 59, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 449, 454, 3, 56, 28, 0, 450, 451, 5, 68, 0, 0, 451, 453, 3, 56, 28, 0, 452, 450, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 454, 455, 1, 0, 0, 0, 455, 61, 1, 0, 0, 0, 456, 454, 1, 0, 0, 0, 457, 458, 7, 1, 0, 0, 458, 63, 1, 0, 0, 0, 459, 463, 5, 148, 0, 0, 460, 463, 3, 66, 33, 0, 461, 463, 3, 68, 34, 0, 462, 459, 1, 0, 0, 0, 462, 460, 1, 0, 0, 0, 462, 461, 1, 0, 0, 0, 463, 65, 1, 0, 0, 0, 464, 467, 5, 82, 0, 0, 465, 467, 5, 101, 0, 0, 466, 464, 1, 0, 0, 0, 466, 465, 1, 0, 0, 0, 467, 67, 1, 0, 0, 0, 468, 471, 5, 100, 0, 0, 469, 471, 5, 102, 0, 0, 470, 468, 1, 0, 0, 0, 470, 469, 1, 0, 0, 0, 471, 69, 1, 0, 0, 0, 472, 476, 3, 62, 31, 0, 473, 476, 3, 66, 33, 0, 474, 476, 3, 68, 34, 0, 475, 472, 1, 0, 0, 0, 475, 473, 1, 0, 0, 0, 475, 474, 1, 0, 0, 0, 476, 71, 1, 0, 0, 0, 477, 480, 3, 192, 96, 0, 478, 480, 3, 66, 33, 0, 479, 477, 1, 0, 0, 0, 479, 478, 1, 0, 0, 0, 480, 73, 1, 0, 0, 0, 481, 482, 5, 11, 0, 0, 482, 484, 3, 182, 91, 0, 483, 485, 3, 76, 38, 0, 484, 483, 1, 0, 0, 0, 484, 485, 1, 0, 0, 0, 485, 75, 1, 0, 0, 0, 486, 487, 4, 38, 8, 0, 487, 488, 5, 64, 0, 0, 488, 489, 3, 16, 8, 0, 489, 77, 1, 0, 0, 0, 490, 491, 5, 15, 0, 0, 491, 496, 3, 80, 40, 0, 492, 493, 5, 68, 0, 0, 493, 495, 3, 80, 40, 0, 494, 492, 1, 0, 0, 0, 495, 498, 1, 0, 0, 0, 496, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 79, 1, 0, 0, 0, 498, 496, 1, 0, 0, 0, 499, 501, 3, 160, 80, 0, 500, 502, 7, 2, 0, 0, 501, 500, 1, 0, 0, 0, 501, 502, 1, 0, 0, 0, 502, 505, 1, 0, 0, 0, 503, 504, 5, 79, 0, 0, 504, 506, 7, 3, 0, 0, 505, 503, 1, 0, 0, 0, 505, 506, 1, 0, 0, 0, 506, 81, 1, 0, 0, 0, 507, 508, 5, 37, 0, 0, 508, 509, 3, 60, 30, 0, 509, 83, 1, 0, 0, 0, 510, 511, 5, 36, 0, 0, 511, 512, 3, 60, 30, 0, 512, 85, 1, 0, 0, 0, 513, 514, 5, 40, 0, 0, 514, 519, 3, 88, 44, 0, 515, 516, 5, 68, 0, 0, 516, 518, 3, 88, 44, 0, 517, 515, 1, 0, 0, 0, 518, 521, 1, 0, 0, 0, 519, 517, 1, 0, 0, 0, 519, 520, 1, 0, 0, 0, 520, 87, 1, 0, 0, 0, 521, 519, 1, 0, 0, 0, 522, 523, 3, 56, 28, 0, 523, 524, 5, 158, 0, 0, 524, 525, 3, 56, 28, 0, 525, 531, 1, 0, 0, 0, 526, 527, 3, 56, 28, 0, 527, 528, 5, 63, 0, 0, 528, 529, 3, 56, 28, 0, 529, 531, 1, 0, 0, 0, 530, 522, 1, 0, 0, 0, 530, 526, 1, 0, 0, 0, 531, 89, 1, 0, 0, 0, 532, 533, 5, 8, 0, 0, 533, 534, 3, 170, 85, 0, 534, 536, 3, 192, 96, 0, 535, 537, 3, 92, 46, 0, 536, 535, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 91, 1, 0, 0, 0, 538, 543, 3, 94, 47, 0, 539, 540, 5, 68, 0, 0, 540, 542, 3, 94, 47, 0, 541, 539, 1, 0, 0, 0, 542, 545, 1, 0, 0, 0, 543, 541, 1, 0, 0, 0, 543, 544, 1, 0, 0, 0, 544, 93, 1, 0, 0, 0, 545, 543, 1, 0, 0, 0, 546, 547, 3, 62, 31, 0, 547, 548, 5, 63, 0, 0, 548, 549, 3, 182, 91, 0, 549, 95, 1, 0, 0, 0, 550, 551, 5, 85, 0, 0, 551, 553, 3, 176, 88, 0, 552, 550, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 97, 1, 0, 0, 0, 554, 555, 5, 10, 0, 0, 555, 556, 3, 170, 85, 0, 556, 561, 3, 192, 96, 0, 557, 558, 5, 68, 0, 0, 558, 560, 3, 192, 96, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 99, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 35, 0, 0, 565, 566, 3, 52, 26, 0, 566, 101, 1, 0, 0, 0, 567, 568, 5, 6, 0, 0, 568, 569, 3, 104, 52, 0, 569, 103, 1, 0, 0, 0, 570, 571, 5, 105, 0, 0, 571, 572, 3, 4, 2, 0, 572, 573, 5, 106, 0, 0, 573, 105, 1, 0, 0, 0, 574, 575, 5, 42, 0, 0, 575, 576, 5, 165, 0, 0, 576, 107, 1, 0, 0, 0, 577, 578, 5, 5, 0, 0, 578, 581, 3, 110, 55, 0, 579, 580, 5, 80, 0, 0, 580, 582, 3, 56, 28, 0, 581, 579, 1, 0, 0, 0, 581, 582, 1, 0, 0, 0, 582, 592, 1, 0, 0, 0, 583, 584, 5, 85, 0, 0, 584, 589, 3, 112, 56, 0, 585, 586, 5, 68, 0, 0, 586, 588, 3, 112, 56, 0, 587, 585, 1, 0, 0, 0, 588, 591, 1, 0, 0, 0, 589, 587, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 593, 1, 0, 0, 0, 591, 589, 1, 0, 0, 0, 592, 583, 1, 0, 0, 0, 592, 593, 1, 0, 0, 0, 593, 109, 1, 0, 0, 0, 594, 595, 7, 4, 0, 0, 595, 111, 1, 0, 0, 0, 596, 597, 3, 56, 28, 0, 597, 598, 5, 63, 0, 0, 598, 600, 1, 0, 0, 0, 599, 596, 1, 0, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 1, 0, 0, 0, 601, 602, 3, 56, 28, 0, 602, 113, 1, 0, 0, 0, 603, 604, 5, 14, 0, 0, 604, 605, 3, 182, 91, 0, 605, 115, 1, 0, 0, 0, 606, 607, 5, 4, 0, 0, 607, 610, 3, 52, 26, 0, 608, 609, 5, 80, 0, 0, 609, 611, 3, 52, 26, 0, 610, 608, 1, 0, 0, 0, 610, 611, 1, 0, 0, 0, 611, 617, 1, 0, 0, 0, 612, 613, 5, 158, 0, 0, 613, 614, 3, 52, 26, 0, 614, 615, 5, 68, 0, 0, 615, 616, 3, 52, 26, 0, 616, 618, 1, 0, 0, 0, 617, 612, 1, 0, 0, 0, 617, 618, 1, 0, 0, 0, 618, 117, 1, 0, 0, 0, 619, 620, 5, 25, 0, 0, 620, 621, 3, 120, 60, 0, 621, 119, 1, 0, 0, 0, 622, 624, 3, 122, 61, 0, 623, 622, 1, 0, 0, 0, 624, 625, 1, 0, 0, 0, 625, 623, 1, 0, 0, 0, 625, 626, 1, 0, 0, 0, 626, 121, 1, 0, 0, 0, 627, 628, 5, 105, 0, 0, 628, 629, 3, 124, 62, 0, 629, 630, 5, 106, 0, 0, 630, 123, 1, 0, 0, 0, 631, 632, 6, 62, -1, 0, 632, 633, 3, 126, 63, 0, 633, 639, 1, 0, 0, 0, 634, 635, 10, 1, 0, 0, 635, 636, 5, 57, 0, 0, 636, 638, 3, 126, 63, 0, 637, 634, 1, 0, 0, 0, 638, 641, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 639, 640, 1, 0, 0, 0, 640, 125, 1, 0, 0, 0, 641, 639, 1, 0, 0, 0, 642, 643, 3, 8, 4, 0, 643, 127, 1, 0, 0, 0, 644, 648, 5, 12, 0, 0, 645, 646, 3, 52, 26, 0, 646, 647, 5, 63, 0, 0, 647, 649, 1, 0, 0, 0, 648, 645, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 651, 3, 182, 91, 0, 651, 652, 5, 80, 0, 0, 652, 653, 3, 16, 8, 0, 653, 654, 3, 96, 48, 0, 654, 129, 1, 0, 0, 0, 655, 659, 5, 7, 0, 0, 656, 657, 3, 52, 26, 0, 657, 658, 5, 63, 0, 0, 658, 660, 1, 0, 0, 0, 659, 656, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 661, 1, 0, 0, 0, 661, 662, 3, 170, 85, 0, 662, 663, 3, 96, 48, 0, 663, 131, 1, 0, 0, 0, 664, 665, 5, 27, 0, 0, 665, 666, 5, 126, 0, 0, 666, 669, 3, 48, 24, 0, 667, 668, 5, 64, 0, 0, 668, 670, 3, 16, 8, 0, 669, 667, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 678, 1, 0, 0, 0, 671, 672, 5, 28, 0, 0, 672, 675, 3, 48, 24, 0, 673, 674, 5, 64, 0, 0, 674, 676, 3, 16, 8, 0, 675, 673, 1, 0, 0, 0, 675, 676, 1, 0, 0, 0, 676, 678, 1, 0, 0, 0, 677, 664, 1, 0, 0, 0, 677, 671, 1, 0, 0, 0, 678, 133, 1, 0, 0, 0, 679, 681, 5, 26, 0, 0, 680, 682, 3, 62, 31, 0, 681, 680, 1, 0, 0, 0, 681, 682, 1, 0, 0, 0, 682, 686, 1, 0, 0, 0, 683, 685, 3, 136, 68, 0, 684, 683, 1, 0, 0, 0, 685, 688, 1, 0, 0, 0, 686, 684, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 135, 1, 0, 0, 0, 688, 686, 1, 0, 0, 0, 689, 690, 5, 121, 0, 0, 690, 691, 5, 64, 0, 0, 691, 701, 3, 52, 26, 0, 692, 693, 5, 122, 0, 0, 693, 694, 5, 64, 0, 0, 694, 701, 3, 138, 69, 0, 695, 696, 5, 120, 0, 0, 696, 697, 5, 64, 0, 0, 697, 701, 3, 52, 26, 0, 698, 699, 5, 85, 0, 0, 699, 701, 3, 176, 88, 0, 700, 689, 1, 0, 0, 0, 700, 692, 1, 0, 0, 0, 700, 695, 1, 0, 0, 0, 700, 698, 1, 0, 0, 0, 701, 137, 1, 0, 0, 0, 702, 707, 3, 52, 26, 0, 703, 704, 5, 68, 0, 0, 704, 706, 3, 52, 26, 0, 705, 703, 1, 0, 0, 0, 706, 709, 1, 0, 0, 0, 707, 705, 1, 0, 0, 0, 707, 708, 1, 0, 0, 0, 708, 139, 1, 0, 0, 0, 709, 707, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 141, 1, 0, 0, 0, 712, 713, 5, 21, 0, 0, 713, 143, 1, 0, 0, 0, 714, 715, 5, 33, 0, 0, 715, 716, 3, 32, 16, 0, 716, 717, 5, 80, 0, 0, 717, 718, 3, 60, 30, 0, 718, 145, 1, 0, 0, 0, 719, 720, 5, 38, 0, 0, 720, 721, 3, 60, 30, 0, 721, 147, 1, 0, 0, 0, 722, 723, 5, 18, 0, 0, 723, 724, 3, 52, 26, 0, 724, 725, 5, 63, 0, 0, 725, 726, 3, 170, 85, 0, 726, 149, 1, 0, 0, 0, 727, 728, 5, 20, 0, 0, 728, 729, 3, 52, 26, 0, 729, 730, 5, 63, 0, 0, 730, 731, 3, 170, 85, 0, 731, 151, 1, 0, 0, 0, 732, 733, 5, 41, 0, 0, 733, 734, 3, 154, 77, 0, 734, 735, 5, 67, 0, 0, 735, 153, 1, 0, 0, 0, 736, 737, 3, 62, 31, 0, 737, 740, 5, 63, 0, 0, 738, 741, 3, 182, 91, 0, 739, 741, 3, 176, 88, 0, 740, 738, 1, 0, 0, 0, 740, 739, 1, 0, 0, 0, 741, 155, 1, 0, 0, 0, 742, 744, 5, 34, 0, 0, 743, 745, 3, 158, 79, 0, 744, 743, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 5, 80, 0, 0, 747, 748, 3, 52, 26, 0, 748, 749, 5, 141, 0, 0, 749, 750, 3, 190, 95, 0, 750, 751, 3, 96, 48, 0, 751, 157, 1, 0, 0, 0, 752, 755, 3, 66, 33, 0, 753, 755, 3, 170, 85, 0, 754, 752, 1, 0, 0, 0, 754, 753, 1, 0, 0, 0, 755, 159, 1, 0, 0, 0, 756, 757, 6, 80, -1, 0, 757, 758, 5, 77, 0, 0, 758, 786, 3, 160, 80, 8, 759, 786, 3, 166, 83, 0, 760, 786, 3, 162, 81, 0, 761, 763, 3, 166, 83, 0, 762, 764, 5, 77, 0, 0, 763, 762, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 766, 5, 73, 0, 0, 766, 767, 5, 105, 0, 0, 767, 772, 3, 166, 83, 0, 768, 769, 5, 68, 0, 0, 769, 771, 3, 166, 83, 0, 770, 768, 1, 0, 0, 0, 771, 774, 1, 0, 0, 0, 772, 770, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 775, 1, 0, 0, 0, 774, 772, 1, 0, 0, 0, 775, 776, 5, 106, 0, 0, 776, 786, 1, 0, 0, 0, 777, 778, 3, 166, 83, 0, 778, 780, 5, 74, 0, 0, 779, 781, 5, 77, 0, 0, 780, 779, 1, 0, 0, 0, 780, 781, 1, 0, 0, 0, 781, 782, 1, 0, 0, 0, 782, 783, 5, 78, 0, 0, 783, 786, 1, 0, 0, 0, 784, 786, 3, 164, 82, 0, 785, 756, 1, 0, 0, 0, 785, 759, 1, 0, 0, 0, 785, 760, 1, 0, 0, 0, 785, 761, 1, 0, 0, 0, 785, 777, 1, 0, 0, 0, 785, 784, 1, 0, 0, 0, 786, 795, 1, 0, 0, 0, 787, 788, 10, 5, 0, 0, 788, 789, 5, 61, 0, 0, 789, 794, 3, 160, 80, 6, 790, 791, 10, 4, 0, 0, 791, 792, 5, 81, 0, 0, 792, 794, 3, 160, 80, 5, 793, 787, 1, 0, 0, 0, 793, 790, 1, 0, 0, 0, 794, 797, 1, 0, 0, 0, 795, 793, 1, 0, 0, 0, 795, 796, 1, 0, 0, 0, 796, 161, 1, 0, 0, 0, 797, 795, 1, 0, 0, 0, 798, 800, 3, 166, 83, 0, 799, 801, 5, 77, 0, 0, 800, 799, 1, 0, 0, 0, 800, 801, 1, 0, 0, 0, 801, 802, 1, 0, 0, 0, 802, 803, 5, 76, 0, 0, 803, 804, 3, 72, 36, 0, 804, 845, 1, 0, 0, 0, 805, 807, 3, 166, 83, 0, 806, 808, 5, 77, 0, 0, 807, 806, 1, 0, 0, 0, 807, 808, 1, 0, 0, 0, 808, 809, 1, 0, 0, 0, 809, 810, 5, 83, 0, 0, 810, 811, 3, 72, 36, 0, 811, 845, 1, 0, 0, 0, 812, 814, 3, 166, 83, 0, 813, 815, 5, 77, 0, 0, 814, 813, 1, 0, 0, 0, 814, 815, 1, 0, 0, 0, 815, 816, 1, 0, 0, 0, 816, 817, 5, 76, 0, 0, 817, 818, 5, 105, 0, 0, 818, 823, 3, 72, 36, 0, 819, 820, 5, 68, 0, 0, 820, 822, 3, 72, 36, 0, 821, 819, 1, 0, 0, 0, 822, 825, 1, 0, 0, 0, 823, 821, 1, 0, 0, 0, 823, 824, 1, 0, 0, 0, 824, 826, 1, 0, 0, 0, 825, 823, 1, 0, 0, 0, 826, 827, 5, 106, 0, 0, 827, 845, 1, 0, 0, 0, 828, 830, 3, 166, 83, 0, 829, 831, 5, 77, 0, 0, 830, 829, 1, 0, 0, 0, 830, 831, 1, 0, 0, 0, 831, 832, 1, 0, 0, 0, 832, 833, 5, 83, 0, 0, 833, 834, 5, 105, 0, 0, 834, 839, 3, 72, 36, 0, 835, 836, 5, 68, 0, 0, 836, 838, 3, 72, 36, 0, 837, 835, 1, 0, 0, 0, 838, 841, 1, 0, 0, 0, 839, 837, 1, 0, 0, 0, 839, 840, 1, 0, 0, 0, 840, 842, 1, 0, 0, 0, 841, 839, 1, 0, 0, 0, 842, 843, 5, 106, 0, 0, 843, 845, 1, 0, 0, 0, 844, 798, 1, 0, 0, 0, 844, 805, 1, 0, 0, 0, 844, 812, 1, 0, 0, 0, 844, 828, 1, 0, 0, 0, 845, 163, 1, 0, 0, 0, 846, 849, 3, 52, 26, 0, 847, 848, 5, 65, 0, 0, 848, 850, 3, 12, 6, 0, 849, 847, 1, 0, 0, 0, 849, 850, 1, 0, 0, 0, 850, 851, 1, 0, 0, 0, 851, 852, 5, 66, 0, 0, 852, 853, 3, 182, 91, 0, 853, 165, 1, 0, 0, 0, 854, 860, 3, 168, 84, 0, 855, 856, 3, 168, 84, 0, 856, 857, 3, 194, 97, 0, 857, 858, 3, 168, 84, 0, 858, 860, 1, 0, 0, 0, 859, 854, 1, 0, 0, 0, 859, 855, 1, 0, 0, 0, 860, 167, 1, 0, 0, 0, 861, 862, 6, 84, -1, 0, 862, 866, 3, 170, 85, 0, 863, 864, 7, 5, 0, 0, 864, 866, 3, 168, 84, 3, 865, 861, 1, 0, 0, 0, 865, 863, 1, 0, 0, 0, 866, 875, 1, 0, 0, 0, 867, 868, 10, 2, 0, 0, 868, 869, 7, 6, 0, 0, 869, 874, 3, 168, 84, 3, 870, 871, 10, 1, 0, 0, 871, 872, 7, 5, 0, 0, 872, 874, 3, 168, 84, 2, 873, 867, 1, 0, 0, 0, 873, 870, 1, 0, 0, 0, 874, 877, 1, 0, 0, 0, 875, 873, 1, 0, 0, 0, 875, 876, 1, 0, 0, 0, 876, 169, 1, 0, 0, 0, 877, 875, 1, 0, 0, 0, 878, 879, 6, 85, -1, 0, 879, 887, 3, 182, 91, 0, 880, 887, 3, 52, 26, 0, 881, 887, 3, 172, 86, 0, 882, 883, 5, 105, 0, 0, 883, 884, 3, 160, 80, 0, 884, 885, 5, 106, 0, 0, 885, 887, 1, 0, 0, 0, 886, 878, 1, 0, 0, 0, 886, 880, 1, 0, 0, 0, 886, 881, 1, 0, 0, 0, 886, 882, 1, 0, 0, 0, 887, 893, 1, 0, 0, 0, 888, 889, 10, 1, 0, 0, 889, 890, 5, 65, 0, 0, 890, 892, 3, 12, 6, 0, 891, 888, 1, 0, 0, 0, 892, 895, 1, 0, 0, 0, 893, 891, 1, 0, 0, 0, 893, 894, 1, 0, 0, 0, 894, 171, 1, 0, 0, 0, 895, 893, 1, 0, 0, 0, 896, 897, 3, 174, 87, 0, 897, 911, 5, 105, 0, 0, 898, 912, 5, 95, 0, 0, 899, 904, 3, 160, 80, 0, 900, 901, 5, 68, 0, 0, 901, 903, 3, 160, 80, 0, 902, 900, 1, 0, 0, 0, 903, 906, 1, 0, 0, 0, 904, 902, 1, 0, 0, 0, 904, 905, 1, 0, 0, 0, 905, 909, 1, 0, 0, 0, 906, 904, 1, 0, 0, 0, 907, 908, 5, 68, 0, 0, 908, 910, 3, 176, 88, 0, 909, 907, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 910, 912, 1, 0, 0, 0, 911, 898, 1, 0, 0, 0, 911, 899, 1, 0, 0, 0, 911, 912, 1, 0, 0, 0, 912, 913, 1, 0, 0, 0, 913, 914, 5, 106, 0, 0, 914, 173, 1, 0, 0, 0, 915, 919, 3, 70, 35, 0, 916, 919, 5, 72, 0, 0, 917, 919, 5, 75, 0, 0, 918, 915, 1, 0, 0, 0, 918, 916, 1, 0, 0, 0, 918, 917, 1, 0, 0, 0, 919, 175, 1, 0, 0, 0, 920, 929, 5, 98, 0, 0, 921, 926, 3, 178, 89, 0, 922, 923, 5, 68, 0, 0, 923, 925, 3, 178, 89, 0, 924, 922, 1, 0, 0, 0, 925, 928, 1, 0, 0, 0, 926, 924, 1, 0, 0, 0, 926, 927, 1, 0, 0, 0, 927, 930, 1, 0, 0, 0, 928, 926, 1, 0, 0, 0, 929, 921, 1, 0, 0, 0, 929, 930, 1, 0, 0, 0, 930, 931, 1, 0, 0, 0, 931, 932, 5, 99, 0, 0, 932, 177, 1, 0, 0, 0, 933, 934, 3, 192, 96, 0, 934, 935, 5, 66, 0, 0, 935, 936, 3, 180, 90, 0, 936, 179, 1, 0, 0, 0, 937, 940, 3, 182, 91, 0, 938, 940, 3, 176, 88, 0, 939, 937, 1, 0, 0, 0, 939, 938, 1, 0, 0, 0, 940, 181, 1, 0, 0, 0, 941, 984, 5, 78, 0, 0, 942, 943, 3, 190, 95, 0, 943, 944, 5, 107, 0, 0, 944, 984, 1, 0, 0, 0, 945, 984, 3, 188, 94, 0, 946, 984, 3, 190, 95, 0, 947, 984, 3, 184, 92, 0, 948, 984, 3, 66, 33, 0, 949, 984, 3, 192, 96, 0, 950, 951, 5, 103, 0, 0, 951, 956, 3, 186, 93, 0, 952, 953, 5, 68, 0, 0, 953, 955, 3, 186, 93, 0, 954, 952, 1, 0, 0, 0, 955, 958, 1, 0, 0, 0, 956, 954, 1, 0, 0, 0, 956, 957, 1, 0, 0, 0, 957, 959, 1, 0, 0, 0, 958, 956, 1, 0, 0, 0, 959, 960, 5, 104, 0, 0, 960, 984, 1, 0, 0, 0, 961, 962, 5, 103, 0, 0, 962, 967, 3, 184, 92, 0, 963, 964, 5, 68, 0, 0, 964, 966, 3, 184, 92, 0, 965, 963, 1, 0, 0, 0, 966, 969, 1, 0, 0, 0, 967, 965, 1, 0, 0, 0, 967, 968, 1, 0, 0, 0, 968, 970, 1, 0, 0, 0, 969, 967, 1, 0, 0, 0, 970, 971, 5, 104, 0, 0, 971, 984, 1, 0, 0, 0, 972, 973, 5, 103, 0, 0, 973, 978, 3, 192, 96, 0, 974, 975, 5, 68, 0, 0, 975, 977, 3, 192, 96, 0, 976, 974, 1, 0, 0, 0, 977, 980, 1, 0, 0, 0, 978, 976, 1, 0, 0, 0, 978, 979, 1, 0, 0, 0, 979, 981, 1, 0, 0, 0, 980, 978, 1, 0, 0, 0, 981, 982, 5, 104, 0, 0, 982, 984, 1, 0, 0, 0, 983, 941, 1, 0, 0, 0, 983, 942, 1, 0, 0, 0, 983, 945, 1, 0, 0, 0, 983, 946, 1, 0, 0, 0, 983, 947, 1, 0, 0, 0, 983, 948, 1, 0, 0, 0, 983, 949, 1, 0, 0, 0, 983, 950, 1, 0, 0, 0, 983, 961, 1, 0, 0, 0, 983, 972, 1, 0, 0, 0, 984, 183, 1, 0, 0, 0, 985, 986, 7, 7, 0, 0, 986, 185, 1, 0, 0, 0, 987, 990, 3, 188, 94, 0, 988, 990, 3, 190, 95, 0, 989, 987, 1, 0, 0, 0, 989, 988, 1, 0, 0, 0, 990, 187, 1, 0, 0, 0, 991, 993, 7, 5, 0, 0, 992, 991, 1, 0, 0, 0, 992, 993, 1, 0, 0, 0, 993, 994, 1, 0, 0, 0, 994, 995, 5, 60, 0, 0, 995, 189, 1, 0, 0, 0, 996, 998, 7, 5, 0, 0, 997, 996, 1, 0, 0, 0, 997, 998, 1, 0, 0, 0, 998, 999, 1, 0, 0, 0, 999, 1000, 5, 59, 0, 0, 1000, 191, 1, 0, 0, 0, 1001, 1002, 5, 58, 0, 0, 1002, 193, 1, 0, 0, 0, 1003, 1004, 7, 8, 0, 0, 1004, 195, 1, 0, 0, 0, 1005, 1006, 7, 9, 0, 0, 1006, 1007, 5, 130, 0, 0, 1007, 1008, 3, 198, 99, 0, 1008, 1009, 3, 200, 100, 0, 1009, 197, 1, 0, 0, 0, 1010, 1011, 4, 99, 15, 0, 1011, 1013, 3, 32, 16, 0, 1012, 1014, 5, 158, 0, 0, 1013, 1012, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1016, 5, 113, 0, 0, 1016, 1019, 1, 0, 0, 0, 1017, 1019, 3, 32, 16, 0, 1018, 1010, 1, 0, 0, 0, 1018, 1017, 1, 0, 0, 0, 1019, 199, 1, 0, 0, 0, 1020, 1021, 5, 80, 0, 0, 1021, 1026, 3, 160, 80, 0, 1022, 1023, 5, 68, 0, 0, 1023, 1025, 3, 160, 80, 0, 1024, 1022, 1, 0, 0, 0, 1025, 1028, 1, 0, 0, 0, 1026, 1024, 1, 0, 0, 0, 1026, 1027, 1, 0, 0, 0, 1027, 201, 1, 0, 0, 0, 1028, 1026, 1, 0, 0, 0, 1029, 1033, 5, 39, 0, 0, 1030, 1032, 3, 206, 103, 0, 1031, 1030, 1, 0, 0, 0, 1032, 1035, 1, 0, 0, 0, 1033, 1031, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1039, 1, 0, 0, 0, 1035, 1033, 1, 0, 0, 0, 1036, 1037, 3, 204, 102, 0, 1037, 1038, 5, 63, 0, 0, 1038, 1040, 1, 0, 0, 0, 1039, 1036, 1, 0, 0, 0, 1039, 1040, 1, 0, 0, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1043, 5, 105, 0, 0, 1042, 1044, 3, 214, 107, 0, 1043, 1042, 1, 0, 0, 0, 1044, 1045, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1045, 1046, 1, 0, 0, 0, 1046, 1047, 1, 0, 0, 0, 1047, 1048, 5, 106, 0, 0, 1048, 1062, 1, 0, 0, 0, 1049, 1053, 5, 39, 0, 0, 1050, 1052, 3, 206, 103, 0, 1051, 1050, 1, 0, 0, 0, 1052, 1055, 1, 0, 0, 0, 1053, 1051, 1, 0, 0, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1057, 1, 0, 0, 0, 1055, 1053, 1, 0, 0, 0, 1056, 1058, 3, 214, 107, 0, 1057, 1056, 1, 0, 0, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1057, 1, 0, 0, 0, 1059, 1060, 1, 0, 0, 0, 1060, 1062, 1, 0, 0, 0, 1061, 1029, 1, 0, 0, 0, 1061, 1049, 1, 0, 0, 0, 1062, 203, 1, 0, 0, 0, 1063, 1064, 7, 1, 0, 0, 1064, 205, 1, 0, 0, 0, 1065, 1066, 3, 208, 104, 0, 1066, 1067, 5, 63, 0, 0, 1067, 1068, 3, 210, 105, 0, 1068, 207, 1, 0, 0, 0, 1069, 1070, 7, 10, 0, 0, 1070, 209, 1, 0, 0, 0, 1071, 1076, 3, 216, 108, 0, 1072, 1073, 5, 68, 0, 0, 1073, 1075, 3, 216, 108, 0, 1074, 1072, 1, 0, 0, 0, 1075, 1078, 1, 0, 0, 0, 1076, 1074, 1, 0, 0, 0, 1076, 1077, 1, 0, 0, 0, 1077, 1082, 1, 0, 0, 0, 1078, 1076, 1, 0, 0, 0, 1079, 1082, 5, 108, 0, 0, 1080, 1082, 5, 101, 0, 0, 1081, 1071, 1, 0, 0, 0, 1081, 1079, 1, 0, 0, 0, 1081, 1080, 1, 0, 0, 0, 1082, 211, 1, 0, 0, 0, 1083, 1084, 7, 11, 0, 0, 1084, 213, 1, 0, 0, 0, 1085, 1087, 3, 212, 106, 0, 1086, 1085, 1, 0, 0, 0, 1087, 1088, 1, 0, 0, 0, 1088, 1086, 1, 0, 0, 0, 1088, 1089, 1, 0, 0, 0, 1089, 1099, 1, 0, 0, 0, 1090, 1094, 5, 105, 0, 0, 1091, 1093, 3, 214, 107, 0, 1092, 1091, 1, 0, 0, 0, 1093, 1096, 1, 0, 0, 0, 1094, 1092, 1, 0, 0, 0, 1094, 1095, 1, 0, 0, 0, 1095, 1097, 1, 0, 0, 0, 1096, 1094, 1, 0, 0, 0, 1097, 1099, 5, 106, 0, 0, 1098, 1086, 1, 0, 0, 0, 1098, 1090, 1, 0, 0, 0, 1099, 215, 1, 0, 0, 0, 1100, 1101, 3, 218, 109, 0, 1101, 1102, 5, 66, 0, 0, 1102, 1103, 3, 222, 111, 0, 1103, 1110, 1, 0, 0, 0, 1104, 1105, 3, 222, 111, 0, 1105, 1106, 5, 65, 0, 0, 1106, 1107, 3, 220, 110, 0, 1107, 1110, 1, 0, 0, 0, 1108, 1110, 3, 224, 112, 0, 1109, 1100, 1, 0, 0, 0, 1109, 1104, 1, 0, 0, 0, 1109, 1108, 1, 0, 0, 0, 1110, 217, 1, 0, 0, 0, 1111, 1112, 7, 12, 0, 0, 1112, 219, 1, 0, 0, 0, 1113, 1114, 7, 12, 0, 0, 1114, 221, 1, 0, 0, 0, 1115, 1116, 7, 12, 0, 0, 1116, 223, 1, 0, 0, 0, 1117, 1118, 7, 13, 0, 0, 1118, 225, 1, 0, 0, 0, 109, 229, 246, 258, 289, 304, 310, 329, 333, 338, 346, 354, 359, 362, 378, 386, 390, 397, 403, 408, 417, 424, 430, 439, 446, 454, 462, 466, 470, 475, 479, 484, 496, 501, 505, 519, 530, 536, 543, 552, 561, 581, 589, 592, 599, 610, 617, 625, 639, 648, 659, 669, 675, 677, 681, 686, 700, 707, 740, 744, 754, 763, 772, 780, 785, 793, 795, 800, 807, 814, 823, 830, 839, 844, 849, 859, 865, 873, 875, 886, 893, 904, 909, 911, 918, 926, 929, 939, 956, 967, 978, 983, 989, 992, 997, 1013, 1018, 1026, 1033, 1039, 1045, 1053, 1059, 1061, 1076, 1081, 1088, 1094, 1098, 1109] \ No newline at end of file +[4, 1, 168, 1148, 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, 1, 0, 5, 0, 228, 8, 0, 10, 0, 12, 0, 231, 9, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 245, 8, 2, 10, 2, 12, 2, 248, 9, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 259, 8, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 290, 8, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 5, 8, 303, 8, 8, 10, 8, 12, 8, 306, 9, 8, 1, 9, 1, 9, 1, 9, 3, 9, 311, 8, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 328, 8, 13, 10, 13, 12, 13, 331, 9, 13, 1, 13, 3, 13, 334, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 339, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 345, 8, 15, 10, 15, 12, 15, 348, 9, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 3, 16, 355, 8, 16, 1, 16, 1, 16, 1, 16, 3, 16, 360, 8, 16, 1, 16, 3, 16, 363, 8, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 377, 8, 21, 10, 21, 12, 21, 380, 9, 21, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 3, 23, 387, 8, 23, 1, 23, 1, 23, 3, 23, 391, 8, 23, 1, 24, 1, 24, 1, 24, 5, 24, 396, 8, 24, 10, 24, 12, 24, 399, 9, 24, 1, 25, 1, 25, 1, 25, 3, 25, 404, 8, 25, 1, 26, 1, 26, 1, 26, 3, 26, 409, 8, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 3, 26, 418, 8, 26, 1, 27, 1, 27, 1, 27, 5, 27, 423, 8, 27, 10, 27, 12, 27, 426, 9, 27, 1, 28, 1, 28, 1, 28, 3, 28, 431, 8, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 3, 28, 440, 8, 28, 1, 29, 1, 29, 1, 29, 5, 29, 445, 8, 29, 10, 29, 12, 29, 448, 9, 29, 1, 30, 1, 30, 1, 30, 5, 30, 453, 8, 30, 10, 30, 12, 30, 456, 9, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 3, 32, 463, 8, 32, 1, 33, 1, 33, 3, 33, 467, 8, 33, 1, 34, 1, 34, 3, 34, 471, 8, 34, 1, 35, 1, 35, 1, 35, 3, 35, 476, 8, 35, 1, 36, 1, 36, 3, 36, 480, 8, 36, 1, 37, 1, 37, 1, 37, 3, 37, 485, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 5, 39, 495, 8, 39, 10, 39, 12, 39, 498, 9, 39, 1, 40, 1, 40, 3, 40, 502, 8, 40, 1, 40, 1, 40, 3, 40, 506, 8, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 5, 43, 518, 8, 43, 10, 43, 12, 43, 521, 9, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 531, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 3, 45, 537, 8, 45, 1, 46, 1, 46, 1, 46, 5, 46, 542, 8, 46, 10, 46, 12, 46, 545, 9, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 3, 48, 553, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 5, 49, 560, 8, 49, 10, 49, 12, 49, 563, 9, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 3, 54, 582, 8, 54, 1, 54, 1, 54, 1, 54, 1, 54, 5, 54, 588, 8, 54, 10, 54, 12, 54, 591, 9, 54, 3, 54, 593, 8, 54, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 3, 56, 600, 8, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 611, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 618, 8, 58, 1, 59, 1, 59, 1, 59, 1, 60, 4, 60, 624, 8, 60, 11, 60, 12, 60, 625, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 5, 62, 638, 8, 62, 10, 62, 12, 62, 641, 9, 62, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 649, 8, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 660, 8, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 670, 8, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 676, 8, 66, 3, 66, 678, 8, 66, 1, 67, 1, 67, 3, 67, 682, 8, 67, 1, 67, 5, 67, 685, 8, 67, 10, 67, 12, 67, 688, 9, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 701, 8, 68, 1, 69, 1, 69, 1, 69, 5, 69, 706, 8, 69, 10, 69, 12, 69, 709, 9, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 3, 77, 741, 8, 77, 1, 78, 1, 78, 3, 78, 745, 8, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 3, 79, 755, 8, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 764, 8, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 771, 8, 80, 10, 80, 12, 80, 774, 9, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 781, 8, 80, 1, 80, 1, 80, 1, 80, 3, 80, 786, 8, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 794, 8, 80, 10, 80, 12, 80, 797, 9, 80, 1, 81, 1, 81, 3, 81, 801, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 3, 81, 808, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 3, 81, 815, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 5, 81, 822, 8, 81, 10, 81, 12, 81, 825, 9, 81, 1, 81, 1, 81, 1, 81, 1, 81, 3, 81, 831, 8, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 5, 81, 838, 8, 81, 10, 81, 12, 81, 841, 9, 81, 1, 81, 1, 81, 3, 81, 845, 8, 81, 1, 82, 1, 82, 1, 82, 3, 82, 850, 8, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 3, 83, 860, 8, 83, 1, 84, 1, 84, 1, 84, 1, 84, 3, 84, 866, 8, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 5, 84, 874, 8, 84, 10, 84, 12, 84, 877, 9, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 3, 85, 887, 8, 85, 1, 85, 1, 85, 1, 85, 5, 85, 892, 8, 85, 10, 85, 12, 85, 895, 9, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 1, 86, 5, 86, 903, 8, 86, 10, 86, 12, 86, 906, 9, 86, 1, 86, 1, 86, 3, 86, 910, 8, 86, 3, 86, 912, 8, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 3, 87, 919, 8, 87, 1, 88, 1, 88, 1, 88, 1, 88, 5, 88, 925, 8, 88, 10, 88, 12, 88, 928, 9, 88, 3, 88, 930, 8, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 3, 90, 940, 8, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 5, 91, 955, 8, 91, 10, 91, 12, 91, 958, 9, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 5, 91, 966, 8, 91, 10, 91, 12, 91, 969, 9, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 1, 91, 5, 91, 977, 8, 91, 10, 91, 12, 91, 980, 9, 91, 1, 91, 1, 91, 3, 91, 984, 8, 91, 1, 92, 1, 92, 1, 93, 1, 93, 3, 93, 990, 8, 93, 1, 94, 3, 94, 993, 8, 94, 1, 94, 1, 94, 1, 95, 3, 95, 998, 8, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 3, 99, 1014, 8, 99, 1, 99, 1, 99, 1, 99, 3, 99, 1019, 8, 99, 1, 100, 1, 100, 1, 100, 1, 100, 5, 100, 1025, 8, 100, 10, 100, 12, 100, 1028, 9, 100, 1, 101, 1, 101, 5, 101, 1032, 8, 101, 10, 101, 12, 101, 1035, 9, 101, 1, 101, 1, 101, 1, 101, 3, 101, 1040, 8, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 5, 101, 1047, 8, 101, 10, 101, 12, 101, 1050, 9, 101, 1, 101, 1, 101, 1, 101, 3, 101, 1055, 8, 101, 1, 101, 1, 101, 1, 101, 5, 101, 1060, 8, 101, 10, 101, 12, 101, 1063, 9, 101, 1, 101, 1, 101, 1, 101, 3, 101, 1068, 8, 101, 1, 101, 1, 101, 4, 101, 1072, 8, 101, 11, 101, 12, 101, 1073, 1, 101, 1, 101, 1, 101, 1, 101, 5, 101, 1080, 8, 101, 10, 101, 12, 101, 1083, 9, 101, 1, 101, 4, 101, 1086, 8, 101, 11, 101, 12, 101, 1087, 3, 101, 1090, 8, 101, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 5, 105, 1103, 8, 105, 10, 105, 12, 105, 1106, 9, 105, 1, 105, 1, 105, 3, 105, 1110, 8, 105, 1, 106, 1, 106, 1, 107, 4, 107, 1115, 8, 107, 11, 107, 12, 107, 1116, 1, 107, 1, 107, 5, 107, 1121, 8, 107, 10, 107, 12, 107, 1124, 9, 107, 1, 107, 3, 107, 1127, 8, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 3, 108, 1138, 8, 108, 1, 109, 1, 109, 1, 110, 1, 110, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 0, 5, 4, 124, 160, 168, 170, 113, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 0, 14, 2, 0, 58, 58, 113, 113, 1, 0, 107, 108, 2, 0, 62, 62, 69, 69, 2, 0, 72, 72, 75, 75, 2, 0, 47, 47, 58, 58, 1, 0, 93, 94, 1, 0, 95, 97, 2, 0, 71, 71, 84, 84, 2, 0, 86, 86, 88, 92, 2, 0, 29, 29, 31, 32, 3, 0, 58, 58, 101, 101, 107, 108, 8, 0, 58, 58, 63, 63, 65, 66, 68, 68, 101, 101, 107, 108, 113, 113, 155, 157, 2, 0, 107, 107, 113, 113, 3, 0, 58, 58, 107, 107, 113, 113, 1203, 0, 229, 1, 0, 0, 0, 2, 235, 1, 0, 0, 0, 4, 238, 1, 0, 0, 0, 6, 258, 1, 0, 0, 0, 8, 289, 1, 0, 0, 0, 10, 291, 1, 0, 0, 0, 12, 294, 1, 0, 0, 0, 14, 296, 1, 0, 0, 0, 16, 299, 1, 0, 0, 0, 18, 310, 1, 0, 0, 0, 20, 314, 1, 0, 0, 0, 22, 317, 1, 0, 0, 0, 24, 320, 1, 0, 0, 0, 26, 324, 1, 0, 0, 0, 28, 338, 1, 0, 0, 0, 30, 340, 1, 0, 0, 0, 32, 362, 1, 0, 0, 0, 34, 364, 1, 0, 0, 0, 36, 366, 1, 0, 0, 0, 38, 368, 1, 0, 0, 0, 40, 370, 1, 0, 0, 0, 42, 372, 1, 0, 0, 0, 44, 381, 1, 0, 0, 0, 46, 384, 1, 0, 0, 0, 48, 392, 1, 0, 0, 0, 50, 400, 1, 0, 0, 0, 52, 417, 1, 0, 0, 0, 54, 419, 1, 0, 0, 0, 56, 439, 1, 0, 0, 0, 58, 441, 1, 0, 0, 0, 60, 449, 1, 0, 0, 0, 62, 457, 1, 0, 0, 0, 64, 462, 1, 0, 0, 0, 66, 466, 1, 0, 0, 0, 68, 470, 1, 0, 0, 0, 70, 475, 1, 0, 0, 0, 72, 479, 1, 0, 0, 0, 74, 481, 1, 0, 0, 0, 76, 486, 1, 0, 0, 0, 78, 490, 1, 0, 0, 0, 80, 499, 1, 0, 0, 0, 82, 507, 1, 0, 0, 0, 84, 510, 1, 0, 0, 0, 86, 513, 1, 0, 0, 0, 88, 530, 1, 0, 0, 0, 90, 532, 1, 0, 0, 0, 92, 538, 1, 0, 0, 0, 94, 546, 1, 0, 0, 0, 96, 552, 1, 0, 0, 0, 98, 554, 1, 0, 0, 0, 100, 564, 1, 0, 0, 0, 102, 567, 1, 0, 0, 0, 104, 570, 1, 0, 0, 0, 106, 574, 1, 0, 0, 0, 108, 577, 1, 0, 0, 0, 110, 594, 1, 0, 0, 0, 112, 599, 1, 0, 0, 0, 114, 603, 1, 0, 0, 0, 116, 606, 1, 0, 0, 0, 118, 619, 1, 0, 0, 0, 120, 623, 1, 0, 0, 0, 122, 627, 1, 0, 0, 0, 124, 631, 1, 0, 0, 0, 126, 642, 1, 0, 0, 0, 128, 644, 1, 0, 0, 0, 130, 655, 1, 0, 0, 0, 132, 677, 1, 0, 0, 0, 134, 679, 1, 0, 0, 0, 136, 700, 1, 0, 0, 0, 138, 702, 1, 0, 0, 0, 140, 710, 1, 0, 0, 0, 142, 712, 1, 0, 0, 0, 144, 714, 1, 0, 0, 0, 146, 719, 1, 0, 0, 0, 148, 722, 1, 0, 0, 0, 150, 727, 1, 0, 0, 0, 152, 732, 1, 0, 0, 0, 154, 736, 1, 0, 0, 0, 156, 742, 1, 0, 0, 0, 158, 754, 1, 0, 0, 0, 160, 785, 1, 0, 0, 0, 162, 844, 1, 0, 0, 0, 164, 846, 1, 0, 0, 0, 166, 859, 1, 0, 0, 0, 168, 865, 1, 0, 0, 0, 170, 886, 1, 0, 0, 0, 172, 896, 1, 0, 0, 0, 174, 918, 1, 0, 0, 0, 176, 920, 1, 0, 0, 0, 178, 933, 1, 0, 0, 0, 180, 939, 1, 0, 0, 0, 182, 983, 1, 0, 0, 0, 184, 985, 1, 0, 0, 0, 186, 989, 1, 0, 0, 0, 188, 992, 1, 0, 0, 0, 190, 997, 1, 0, 0, 0, 192, 1001, 1, 0, 0, 0, 194, 1003, 1, 0, 0, 0, 196, 1005, 1, 0, 0, 0, 198, 1018, 1, 0, 0, 0, 200, 1020, 1, 0, 0, 0, 202, 1089, 1, 0, 0, 0, 204, 1091, 1, 0, 0, 0, 206, 1093, 1, 0, 0, 0, 208, 1097, 1, 0, 0, 0, 210, 1109, 1, 0, 0, 0, 212, 1111, 1, 0, 0, 0, 214, 1126, 1, 0, 0, 0, 216, 1137, 1, 0, 0, 0, 218, 1139, 1, 0, 0, 0, 220, 1141, 1, 0, 0, 0, 222, 1143, 1, 0, 0, 0, 224, 1145, 1, 0, 0, 0, 226, 228, 3, 152, 76, 0, 227, 226, 1, 0, 0, 0, 228, 231, 1, 0, 0, 0, 229, 227, 1, 0, 0, 0, 229, 230, 1, 0, 0, 0, 230, 232, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 232, 233, 3, 2, 1, 0, 233, 234, 5, 0, 0, 1, 234, 1, 1, 0, 0, 0, 235, 236, 3, 4, 2, 0, 236, 237, 5, 0, 0, 1, 237, 3, 1, 0, 0, 0, 238, 239, 6, 2, -1, 0, 239, 240, 3, 6, 3, 0, 240, 246, 1, 0, 0, 0, 241, 242, 10, 1, 0, 0, 242, 243, 5, 57, 0, 0, 243, 245, 3, 8, 4, 0, 244, 241, 1, 0, 0, 0, 245, 248, 1, 0, 0, 0, 246, 244, 1, 0, 0, 0, 246, 247, 1, 0, 0, 0, 247, 5, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 249, 259, 3, 20, 10, 0, 250, 259, 3, 14, 7, 0, 251, 259, 3, 106, 53, 0, 252, 259, 3, 22, 11, 0, 253, 259, 3, 202, 101, 0, 254, 255, 4, 3, 1, 0, 255, 259, 3, 102, 51, 0, 256, 257, 4, 3, 2, 0, 257, 259, 3, 24, 12, 0, 258, 249, 1, 0, 0, 0, 258, 250, 1, 0, 0, 0, 258, 251, 1, 0, 0, 0, 258, 252, 1, 0, 0, 0, 258, 253, 1, 0, 0, 0, 258, 254, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 259, 7, 1, 0, 0, 0, 260, 290, 3, 44, 22, 0, 261, 290, 3, 10, 5, 0, 262, 290, 3, 82, 41, 0, 263, 290, 3, 74, 37, 0, 264, 290, 3, 46, 23, 0, 265, 290, 3, 78, 39, 0, 266, 290, 3, 84, 42, 0, 267, 290, 3, 86, 43, 0, 268, 290, 3, 90, 45, 0, 269, 290, 3, 98, 49, 0, 270, 290, 3, 108, 54, 0, 271, 290, 3, 100, 50, 0, 272, 290, 3, 196, 98, 0, 273, 290, 3, 116, 58, 0, 274, 290, 3, 130, 65, 0, 275, 290, 3, 114, 57, 0, 276, 290, 3, 118, 59, 0, 277, 290, 3, 128, 64, 0, 278, 290, 3, 132, 66, 0, 279, 290, 3, 134, 67, 0, 280, 290, 3, 148, 74, 0, 281, 290, 3, 140, 70, 0, 282, 290, 3, 150, 75, 0, 283, 290, 3, 142, 71, 0, 284, 290, 3, 156, 78, 0, 285, 286, 4, 4, 3, 0, 286, 290, 3, 144, 72, 0, 287, 288, 4, 4, 4, 0, 288, 290, 3, 146, 73, 0, 289, 260, 1, 0, 0, 0, 289, 261, 1, 0, 0, 0, 289, 262, 1, 0, 0, 0, 289, 263, 1, 0, 0, 0, 289, 264, 1, 0, 0, 0, 289, 265, 1, 0, 0, 0, 289, 266, 1, 0, 0, 0, 289, 267, 1, 0, 0, 0, 289, 268, 1, 0, 0, 0, 289, 269, 1, 0, 0, 0, 289, 270, 1, 0, 0, 0, 289, 271, 1, 0, 0, 0, 289, 272, 1, 0, 0, 0, 289, 273, 1, 0, 0, 0, 289, 274, 1, 0, 0, 0, 289, 275, 1, 0, 0, 0, 289, 276, 1, 0, 0, 0, 289, 277, 1, 0, 0, 0, 289, 278, 1, 0, 0, 0, 289, 279, 1, 0, 0, 0, 289, 280, 1, 0, 0, 0, 289, 281, 1, 0, 0, 0, 289, 282, 1, 0, 0, 0, 289, 283, 1, 0, 0, 0, 289, 284, 1, 0, 0, 0, 289, 285, 1, 0, 0, 0, 289, 287, 1, 0, 0, 0, 290, 9, 1, 0, 0, 0, 291, 292, 5, 17, 0, 0, 292, 293, 3, 160, 80, 0, 293, 11, 1, 0, 0, 0, 294, 295, 3, 62, 31, 0, 295, 13, 1, 0, 0, 0, 296, 297, 5, 13, 0, 0, 297, 298, 3, 16, 8, 0, 298, 15, 1, 0, 0, 0, 299, 304, 3, 18, 9, 0, 300, 301, 5, 68, 0, 0, 301, 303, 3, 18, 9, 0, 302, 300, 1, 0, 0, 0, 303, 306, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 17, 1, 0, 0, 0, 306, 304, 1, 0, 0, 0, 307, 308, 3, 52, 26, 0, 308, 309, 5, 63, 0, 0, 309, 311, 1, 0, 0, 0, 310, 307, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 313, 3, 160, 80, 0, 313, 19, 1, 0, 0, 0, 314, 315, 5, 22, 0, 0, 315, 316, 3, 26, 13, 0, 316, 21, 1, 0, 0, 0, 317, 318, 5, 23, 0, 0, 318, 319, 3, 26, 13, 0, 319, 23, 1, 0, 0, 0, 320, 321, 5, 24, 0, 0, 321, 322, 3, 72, 36, 0, 322, 323, 3, 96, 48, 0, 323, 25, 1, 0, 0, 0, 324, 329, 3, 28, 14, 0, 325, 326, 5, 68, 0, 0, 326, 328, 3, 28, 14, 0, 327, 325, 1, 0, 0, 0, 328, 331, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 329, 330, 1, 0, 0, 0, 330, 333, 1, 0, 0, 0, 331, 329, 1, 0, 0, 0, 332, 334, 3, 42, 21, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 27, 1, 0, 0, 0, 335, 339, 3, 32, 16, 0, 336, 337, 4, 14, 5, 0, 337, 339, 3, 30, 15, 0, 338, 335, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 339, 29, 1, 0, 0, 0, 340, 341, 5, 105, 0, 0, 341, 346, 3, 20, 10, 0, 342, 343, 5, 57, 0, 0, 343, 345, 3, 8, 4, 0, 344, 342, 1, 0, 0, 0, 345, 348, 1, 0, 0, 0, 346, 344, 1, 0, 0, 0, 346, 347, 1, 0, 0, 0, 347, 349, 1, 0, 0, 0, 348, 346, 1, 0, 0, 0, 349, 350, 5, 106, 0, 0, 350, 31, 1, 0, 0, 0, 351, 352, 3, 34, 17, 0, 352, 353, 5, 66, 0, 0, 353, 355, 1, 0, 0, 0, 354, 351, 1, 0, 0, 0, 354, 355, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 359, 3, 38, 19, 0, 357, 358, 5, 65, 0, 0, 358, 360, 3, 36, 18, 0, 359, 357, 1, 0, 0, 0, 359, 360, 1, 0, 0, 0, 360, 363, 1, 0, 0, 0, 361, 363, 3, 40, 20, 0, 362, 354, 1, 0, 0, 0, 362, 361, 1, 0, 0, 0, 363, 33, 1, 0, 0, 0, 364, 365, 5, 113, 0, 0, 365, 35, 1, 0, 0, 0, 366, 367, 5, 113, 0, 0, 367, 37, 1, 0, 0, 0, 368, 369, 5, 113, 0, 0, 369, 39, 1, 0, 0, 0, 370, 371, 7, 0, 0, 0, 371, 41, 1, 0, 0, 0, 372, 373, 5, 112, 0, 0, 373, 378, 5, 113, 0, 0, 374, 375, 5, 68, 0, 0, 375, 377, 5, 113, 0, 0, 376, 374, 1, 0, 0, 0, 377, 380, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 378, 379, 1, 0, 0, 0, 379, 43, 1, 0, 0, 0, 380, 378, 1, 0, 0, 0, 381, 382, 5, 9, 0, 0, 382, 383, 3, 16, 8, 0, 383, 45, 1, 0, 0, 0, 384, 386, 5, 16, 0, 0, 385, 387, 3, 48, 24, 0, 386, 385, 1, 0, 0, 0, 386, 387, 1, 0, 0, 0, 387, 390, 1, 0, 0, 0, 388, 389, 5, 64, 0, 0, 389, 391, 3, 16, 8, 0, 390, 388, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 47, 1, 0, 0, 0, 392, 397, 3, 50, 25, 0, 393, 394, 5, 68, 0, 0, 394, 396, 3, 50, 25, 0, 395, 393, 1, 0, 0, 0, 396, 399, 1, 0, 0, 0, 397, 395, 1, 0, 0, 0, 397, 398, 1, 0, 0, 0, 398, 49, 1, 0, 0, 0, 399, 397, 1, 0, 0, 0, 400, 403, 3, 18, 9, 0, 401, 402, 5, 17, 0, 0, 402, 404, 3, 160, 80, 0, 403, 401, 1, 0, 0, 0, 403, 404, 1, 0, 0, 0, 404, 51, 1, 0, 0, 0, 405, 406, 4, 26, 6, 0, 406, 408, 5, 103, 0, 0, 407, 409, 5, 107, 0, 0, 408, 407, 1, 0, 0, 0, 408, 409, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 411, 5, 104, 0, 0, 411, 412, 5, 70, 0, 0, 412, 413, 5, 103, 0, 0, 413, 414, 3, 54, 27, 0, 414, 415, 5, 104, 0, 0, 415, 418, 1, 0, 0, 0, 416, 418, 3, 54, 27, 0, 417, 405, 1, 0, 0, 0, 417, 416, 1, 0, 0, 0, 418, 53, 1, 0, 0, 0, 419, 424, 3, 70, 35, 0, 420, 421, 5, 70, 0, 0, 421, 423, 3, 70, 35, 0, 422, 420, 1, 0, 0, 0, 423, 426, 1, 0, 0, 0, 424, 422, 1, 0, 0, 0, 424, 425, 1, 0, 0, 0, 425, 55, 1, 0, 0, 0, 426, 424, 1, 0, 0, 0, 427, 428, 4, 28, 7, 0, 428, 430, 5, 103, 0, 0, 429, 431, 5, 148, 0, 0, 430, 429, 1, 0, 0, 0, 430, 431, 1, 0, 0, 0, 431, 432, 1, 0, 0, 0, 432, 433, 5, 104, 0, 0, 433, 434, 5, 70, 0, 0, 434, 435, 5, 103, 0, 0, 435, 436, 3, 58, 29, 0, 436, 437, 5, 104, 0, 0, 437, 440, 1, 0, 0, 0, 438, 440, 3, 58, 29, 0, 439, 427, 1, 0, 0, 0, 439, 438, 1, 0, 0, 0, 440, 57, 1, 0, 0, 0, 441, 446, 3, 64, 32, 0, 442, 443, 5, 70, 0, 0, 443, 445, 3, 64, 32, 0, 444, 442, 1, 0, 0, 0, 445, 448, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 59, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 449, 454, 3, 56, 28, 0, 450, 451, 5, 68, 0, 0, 451, 453, 3, 56, 28, 0, 452, 450, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 454, 455, 1, 0, 0, 0, 455, 61, 1, 0, 0, 0, 456, 454, 1, 0, 0, 0, 457, 458, 7, 1, 0, 0, 458, 63, 1, 0, 0, 0, 459, 463, 5, 148, 0, 0, 460, 463, 3, 66, 33, 0, 461, 463, 3, 68, 34, 0, 462, 459, 1, 0, 0, 0, 462, 460, 1, 0, 0, 0, 462, 461, 1, 0, 0, 0, 463, 65, 1, 0, 0, 0, 464, 467, 5, 82, 0, 0, 465, 467, 5, 101, 0, 0, 466, 464, 1, 0, 0, 0, 466, 465, 1, 0, 0, 0, 467, 67, 1, 0, 0, 0, 468, 471, 5, 100, 0, 0, 469, 471, 5, 102, 0, 0, 470, 468, 1, 0, 0, 0, 470, 469, 1, 0, 0, 0, 471, 69, 1, 0, 0, 0, 472, 476, 3, 62, 31, 0, 473, 476, 3, 66, 33, 0, 474, 476, 3, 68, 34, 0, 475, 472, 1, 0, 0, 0, 475, 473, 1, 0, 0, 0, 475, 474, 1, 0, 0, 0, 476, 71, 1, 0, 0, 0, 477, 480, 3, 192, 96, 0, 478, 480, 3, 66, 33, 0, 479, 477, 1, 0, 0, 0, 479, 478, 1, 0, 0, 0, 480, 73, 1, 0, 0, 0, 481, 482, 5, 11, 0, 0, 482, 484, 3, 182, 91, 0, 483, 485, 3, 76, 38, 0, 484, 483, 1, 0, 0, 0, 484, 485, 1, 0, 0, 0, 485, 75, 1, 0, 0, 0, 486, 487, 4, 38, 8, 0, 487, 488, 5, 64, 0, 0, 488, 489, 3, 16, 8, 0, 489, 77, 1, 0, 0, 0, 490, 491, 5, 15, 0, 0, 491, 496, 3, 80, 40, 0, 492, 493, 5, 68, 0, 0, 493, 495, 3, 80, 40, 0, 494, 492, 1, 0, 0, 0, 495, 498, 1, 0, 0, 0, 496, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 79, 1, 0, 0, 0, 498, 496, 1, 0, 0, 0, 499, 501, 3, 160, 80, 0, 500, 502, 7, 2, 0, 0, 501, 500, 1, 0, 0, 0, 501, 502, 1, 0, 0, 0, 502, 505, 1, 0, 0, 0, 503, 504, 5, 79, 0, 0, 504, 506, 7, 3, 0, 0, 505, 503, 1, 0, 0, 0, 505, 506, 1, 0, 0, 0, 506, 81, 1, 0, 0, 0, 507, 508, 5, 37, 0, 0, 508, 509, 3, 60, 30, 0, 509, 83, 1, 0, 0, 0, 510, 511, 5, 36, 0, 0, 511, 512, 3, 60, 30, 0, 512, 85, 1, 0, 0, 0, 513, 514, 5, 40, 0, 0, 514, 519, 3, 88, 44, 0, 515, 516, 5, 68, 0, 0, 516, 518, 3, 88, 44, 0, 517, 515, 1, 0, 0, 0, 518, 521, 1, 0, 0, 0, 519, 517, 1, 0, 0, 0, 519, 520, 1, 0, 0, 0, 520, 87, 1, 0, 0, 0, 521, 519, 1, 0, 0, 0, 522, 523, 3, 56, 28, 0, 523, 524, 5, 158, 0, 0, 524, 525, 3, 56, 28, 0, 525, 531, 1, 0, 0, 0, 526, 527, 3, 56, 28, 0, 527, 528, 5, 63, 0, 0, 528, 529, 3, 56, 28, 0, 529, 531, 1, 0, 0, 0, 530, 522, 1, 0, 0, 0, 530, 526, 1, 0, 0, 0, 531, 89, 1, 0, 0, 0, 532, 533, 5, 8, 0, 0, 533, 534, 3, 170, 85, 0, 534, 536, 3, 192, 96, 0, 535, 537, 3, 92, 46, 0, 536, 535, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 91, 1, 0, 0, 0, 538, 543, 3, 94, 47, 0, 539, 540, 5, 68, 0, 0, 540, 542, 3, 94, 47, 0, 541, 539, 1, 0, 0, 0, 542, 545, 1, 0, 0, 0, 543, 541, 1, 0, 0, 0, 543, 544, 1, 0, 0, 0, 544, 93, 1, 0, 0, 0, 545, 543, 1, 0, 0, 0, 546, 547, 3, 62, 31, 0, 547, 548, 5, 63, 0, 0, 548, 549, 3, 182, 91, 0, 549, 95, 1, 0, 0, 0, 550, 551, 5, 85, 0, 0, 551, 553, 3, 176, 88, 0, 552, 550, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 97, 1, 0, 0, 0, 554, 555, 5, 10, 0, 0, 555, 556, 3, 170, 85, 0, 556, 561, 3, 192, 96, 0, 557, 558, 5, 68, 0, 0, 558, 560, 3, 192, 96, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 99, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 35, 0, 0, 565, 566, 3, 52, 26, 0, 566, 101, 1, 0, 0, 0, 567, 568, 5, 6, 0, 0, 568, 569, 3, 104, 52, 0, 569, 103, 1, 0, 0, 0, 570, 571, 5, 105, 0, 0, 571, 572, 3, 4, 2, 0, 572, 573, 5, 106, 0, 0, 573, 105, 1, 0, 0, 0, 574, 575, 5, 42, 0, 0, 575, 576, 5, 165, 0, 0, 576, 107, 1, 0, 0, 0, 577, 578, 5, 5, 0, 0, 578, 581, 3, 110, 55, 0, 579, 580, 5, 80, 0, 0, 580, 582, 3, 56, 28, 0, 581, 579, 1, 0, 0, 0, 581, 582, 1, 0, 0, 0, 582, 592, 1, 0, 0, 0, 583, 584, 5, 85, 0, 0, 584, 589, 3, 112, 56, 0, 585, 586, 5, 68, 0, 0, 586, 588, 3, 112, 56, 0, 587, 585, 1, 0, 0, 0, 588, 591, 1, 0, 0, 0, 589, 587, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 593, 1, 0, 0, 0, 591, 589, 1, 0, 0, 0, 592, 583, 1, 0, 0, 0, 592, 593, 1, 0, 0, 0, 593, 109, 1, 0, 0, 0, 594, 595, 7, 4, 0, 0, 595, 111, 1, 0, 0, 0, 596, 597, 3, 56, 28, 0, 597, 598, 5, 63, 0, 0, 598, 600, 1, 0, 0, 0, 599, 596, 1, 0, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 1, 0, 0, 0, 601, 602, 3, 56, 28, 0, 602, 113, 1, 0, 0, 0, 603, 604, 5, 14, 0, 0, 604, 605, 3, 182, 91, 0, 605, 115, 1, 0, 0, 0, 606, 607, 5, 4, 0, 0, 607, 610, 3, 52, 26, 0, 608, 609, 5, 80, 0, 0, 609, 611, 3, 52, 26, 0, 610, 608, 1, 0, 0, 0, 610, 611, 1, 0, 0, 0, 611, 617, 1, 0, 0, 0, 612, 613, 5, 158, 0, 0, 613, 614, 3, 52, 26, 0, 614, 615, 5, 68, 0, 0, 615, 616, 3, 52, 26, 0, 616, 618, 1, 0, 0, 0, 617, 612, 1, 0, 0, 0, 617, 618, 1, 0, 0, 0, 618, 117, 1, 0, 0, 0, 619, 620, 5, 25, 0, 0, 620, 621, 3, 120, 60, 0, 621, 119, 1, 0, 0, 0, 622, 624, 3, 122, 61, 0, 623, 622, 1, 0, 0, 0, 624, 625, 1, 0, 0, 0, 625, 623, 1, 0, 0, 0, 625, 626, 1, 0, 0, 0, 626, 121, 1, 0, 0, 0, 627, 628, 5, 105, 0, 0, 628, 629, 3, 124, 62, 0, 629, 630, 5, 106, 0, 0, 630, 123, 1, 0, 0, 0, 631, 632, 6, 62, -1, 0, 632, 633, 3, 126, 63, 0, 633, 639, 1, 0, 0, 0, 634, 635, 10, 1, 0, 0, 635, 636, 5, 57, 0, 0, 636, 638, 3, 126, 63, 0, 637, 634, 1, 0, 0, 0, 638, 641, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 639, 640, 1, 0, 0, 0, 640, 125, 1, 0, 0, 0, 641, 639, 1, 0, 0, 0, 642, 643, 3, 8, 4, 0, 643, 127, 1, 0, 0, 0, 644, 648, 5, 12, 0, 0, 645, 646, 3, 52, 26, 0, 646, 647, 5, 63, 0, 0, 647, 649, 1, 0, 0, 0, 648, 645, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 651, 3, 182, 91, 0, 651, 652, 5, 80, 0, 0, 652, 653, 3, 16, 8, 0, 653, 654, 3, 96, 48, 0, 654, 129, 1, 0, 0, 0, 655, 659, 5, 7, 0, 0, 656, 657, 3, 52, 26, 0, 657, 658, 5, 63, 0, 0, 658, 660, 1, 0, 0, 0, 659, 656, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 661, 1, 0, 0, 0, 661, 662, 3, 170, 85, 0, 662, 663, 3, 96, 48, 0, 663, 131, 1, 0, 0, 0, 664, 665, 5, 27, 0, 0, 665, 666, 5, 126, 0, 0, 666, 669, 3, 48, 24, 0, 667, 668, 5, 64, 0, 0, 668, 670, 3, 16, 8, 0, 669, 667, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 678, 1, 0, 0, 0, 671, 672, 5, 28, 0, 0, 672, 675, 3, 48, 24, 0, 673, 674, 5, 64, 0, 0, 674, 676, 3, 16, 8, 0, 675, 673, 1, 0, 0, 0, 675, 676, 1, 0, 0, 0, 676, 678, 1, 0, 0, 0, 677, 664, 1, 0, 0, 0, 677, 671, 1, 0, 0, 0, 678, 133, 1, 0, 0, 0, 679, 681, 5, 26, 0, 0, 680, 682, 3, 62, 31, 0, 681, 680, 1, 0, 0, 0, 681, 682, 1, 0, 0, 0, 682, 686, 1, 0, 0, 0, 683, 685, 3, 136, 68, 0, 684, 683, 1, 0, 0, 0, 685, 688, 1, 0, 0, 0, 686, 684, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 135, 1, 0, 0, 0, 688, 686, 1, 0, 0, 0, 689, 690, 5, 121, 0, 0, 690, 691, 5, 64, 0, 0, 691, 701, 3, 52, 26, 0, 692, 693, 5, 122, 0, 0, 693, 694, 5, 64, 0, 0, 694, 701, 3, 138, 69, 0, 695, 696, 5, 120, 0, 0, 696, 697, 5, 64, 0, 0, 697, 701, 3, 52, 26, 0, 698, 699, 5, 85, 0, 0, 699, 701, 3, 176, 88, 0, 700, 689, 1, 0, 0, 0, 700, 692, 1, 0, 0, 0, 700, 695, 1, 0, 0, 0, 700, 698, 1, 0, 0, 0, 701, 137, 1, 0, 0, 0, 702, 707, 3, 52, 26, 0, 703, 704, 5, 68, 0, 0, 704, 706, 3, 52, 26, 0, 705, 703, 1, 0, 0, 0, 706, 709, 1, 0, 0, 0, 707, 705, 1, 0, 0, 0, 707, 708, 1, 0, 0, 0, 708, 139, 1, 0, 0, 0, 709, 707, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 141, 1, 0, 0, 0, 712, 713, 5, 21, 0, 0, 713, 143, 1, 0, 0, 0, 714, 715, 5, 33, 0, 0, 715, 716, 3, 32, 16, 0, 716, 717, 5, 80, 0, 0, 717, 718, 3, 60, 30, 0, 718, 145, 1, 0, 0, 0, 719, 720, 5, 38, 0, 0, 720, 721, 3, 60, 30, 0, 721, 147, 1, 0, 0, 0, 722, 723, 5, 18, 0, 0, 723, 724, 3, 52, 26, 0, 724, 725, 5, 63, 0, 0, 725, 726, 3, 170, 85, 0, 726, 149, 1, 0, 0, 0, 727, 728, 5, 20, 0, 0, 728, 729, 3, 52, 26, 0, 729, 730, 5, 63, 0, 0, 730, 731, 3, 170, 85, 0, 731, 151, 1, 0, 0, 0, 732, 733, 5, 41, 0, 0, 733, 734, 3, 154, 77, 0, 734, 735, 5, 67, 0, 0, 735, 153, 1, 0, 0, 0, 736, 737, 3, 62, 31, 0, 737, 740, 5, 63, 0, 0, 738, 741, 3, 182, 91, 0, 739, 741, 3, 176, 88, 0, 740, 738, 1, 0, 0, 0, 740, 739, 1, 0, 0, 0, 741, 155, 1, 0, 0, 0, 742, 744, 5, 34, 0, 0, 743, 745, 3, 158, 79, 0, 744, 743, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 5, 80, 0, 0, 747, 748, 3, 52, 26, 0, 748, 749, 5, 141, 0, 0, 749, 750, 3, 190, 95, 0, 750, 751, 3, 96, 48, 0, 751, 157, 1, 0, 0, 0, 752, 755, 3, 66, 33, 0, 753, 755, 3, 170, 85, 0, 754, 752, 1, 0, 0, 0, 754, 753, 1, 0, 0, 0, 755, 159, 1, 0, 0, 0, 756, 757, 6, 80, -1, 0, 757, 758, 5, 77, 0, 0, 758, 786, 3, 160, 80, 8, 759, 786, 3, 166, 83, 0, 760, 786, 3, 162, 81, 0, 761, 763, 3, 166, 83, 0, 762, 764, 5, 77, 0, 0, 763, 762, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 766, 5, 73, 0, 0, 766, 767, 5, 105, 0, 0, 767, 772, 3, 166, 83, 0, 768, 769, 5, 68, 0, 0, 769, 771, 3, 166, 83, 0, 770, 768, 1, 0, 0, 0, 771, 774, 1, 0, 0, 0, 772, 770, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 775, 1, 0, 0, 0, 774, 772, 1, 0, 0, 0, 775, 776, 5, 106, 0, 0, 776, 786, 1, 0, 0, 0, 777, 778, 3, 166, 83, 0, 778, 780, 5, 74, 0, 0, 779, 781, 5, 77, 0, 0, 780, 779, 1, 0, 0, 0, 780, 781, 1, 0, 0, 0, 781, 782, 1, 0, 0, 0, 782, 783, 5, 78, 0, 0, 783, 786, 1, 0, 0, 0, 784, 786, 3, 164, 82, 0, 785, 756, 1, 0, 0, 0, 785, 759, 1, 0, 0, 0, 785, 760, 1, 0, 0, 0, 785, 761, 1, 0, 0, 0, 785, 777, 1, 0, 0, 0, 785, 784, 1, 0, 0, 0, 786, 795, 1, 0, 0, 0, 787, 788, 10, 5, 0, 0, 788, 789, 5, 61, 0, 0, 789, 794, 3, 160, 80, 6, 790, 791, 10, 4, 0, 0, 791, 792, 5, 81, 0, 0, 792, 794, 3, 160, 80, 5, 793, 787, 1, 0, 0, 0, 793, 790, 1, 0, 0, 0, 794, 797, 1, 0, 0, 0, 795, 793, 1, 0, 0, 0, 795, 796, 1, 0, 0, 0, 796, 161, 1, 0, 0, 0, 797, 795, 1, 0, 0, 0, 798, 800, 3, 166, 83, 0, 799, 801, 5, 77, 0, 0, 800, 799, 1, 0, 0, 0, 800, 801, 1, 0, 0, 0, 801, 802, 1, 0, 0, 0, 802, 803, 5, 76, 0, 0, 803, 804, 3, 72, 36, 0, 804, 845, 1, 0, 0, 0, 805, 807, 3, 166, 83, 0, 806, 808, 5, 77, 0, 0, 807, 806, 1, 0, 0, 0, 807, 808, 1, 0, 0, 0, 808, 809, 1, 0, 0, 0, 809, 810, 5, 83, 0, 0, 810, 811, 3, 72, 36, 0, 811, 845, 1, 0, 0, 0, 812, 814, 3, 166, 83, 0, 813, 815, 5, 77, 0, 0, 814, 813, 1, 0, 0, 0, 814, 815, 1, 0, 0, 0, 815, 816, 1, 0, 0, 0, 816, 817, 5, 76, 0, 0, 817, 818, 5, 105, 0, 0, 818, 823, 3, 72, 36, 0, 819, 820, 5, 68, 0, 0, 820, 822, 3, 72, 36, 0, 821, 819, 1, 0, 0, 0, 822, 825, 1, 0, 0, 0, 823, 821, 1, 0, 0, 0, 823, 824, 1, 0, 0, 0, 824, 826, 1, 0, 0, 0, 825, 823, 1, 0, 0, 0, 826, 827, 5, 106, 0, 0, 827, 845, 1, 0, 0, 0, 828, 830, 3, 166, 83, 0, 829, 831, 5, 77, 0, 0, 830, 829, 1, 0, 0, 0, 830, 831, 1, 0, 0, 0, 831, 832, 1, 0, 0, 0, 832, 833, 5, 83, 0, 0, 833, 834, 5, 105, 0, 0, 834, 839, 3, 72, 36, 0, 835, 836, 5, 68, 0, 0, 836, 838, 3, 72, 36, 0, 837, 835, 1, 0, 0, 0, 838, 841, 1, 0, 0, 0, 839, 837, 1, 0, 0, 0, 839, 840, 1, 0, 0, 0, 840, 842, 1, 0, 0, 0, 841, 839, 1, 0, 0, 0, 842, 843, 5, 106, 0, 0, 843, 845, 1, 0, 0, 0, 844, 798, 1, 0, 0, 0, 844, 805, 1, 0, 0, 0, 844, 812, 1, 0, 0, 0, 844, 828, 1, 0, 0, 0, 845, 163, 1, 0, 0, 0, 846, 849, 3, 52, 26, 0, 847, 848, 5, 65, 0, 0, 848, 850, 3, 12, 6, 0, 849, 847, 1, 0, 0, 0, 849, 850, 1, 0, 0, 0, 850, 851, 1, 0, 0, 0, 851, 852, 5, 66, 0, 0, 852, 853, 3, 182, 91, 0, 853, 165, 1, 0, 0, 0, 854, 860, 3, 168, 84, 0, 855, 856, 3, 168, 84, 0, 856, 857, 3, 194, 97, 0, 857, 858, 3, 168, 84, 0, 858, 860, 1, 0, 0, 0, 859, 854, 1, 0, 0, 0, 859, 855, 1, 0, 0, 0, 860, 167, 1, 0, 0, 0, 861, 862, 6, 84, -1, 0, 862, 866, 3, 170, 85, 0, 863, 864, 7, 5, 0, 0, 864, 866, 3, 168, 84, 3, 865, 861, 1, 0, 0, 0, 865, 863, 1, 0, 0, 0, 866, 875, 1, 0, 0, 0, 867, 868, 10, 2, 0, 0, 868, 869, 7, 6, 0, 0, 869, 874, 3, 168, 84, 3, 870, 871, 10, 1, 0, 0, 871, 872, 7, 5, 0, 0, 872, 874, 3, 168, 84, 2, 873, 867, 1, 0, 0, 0, 873, 870, 1, 0, 0, 0, 874, 877, 1, 0, 0, 0, 875, 873, 1, 0, 0, 0, 875, 876, 1, 0, 0, 0, 876, 169, 1, 0, 0, 0, 877, 875, 1, 0, 0, 0, 878, 879, 6, 85, -1, 0, 879, 887, 3, 182, 91, 0, 880, 887, 3, 52, 26, 0, 881, 887, 3, 172, 86, 0, 882, 883, 5, 105, 0, 0, 883, 884, 3, 160, 80, 0, 884, 885, 5, 106, 0, 0, 885, 887, 1, 0, 0, 0, 886, 878, 1, 0, 0, 0, 886, 880, 1, 0, 0, 0, 886, 881, 1, 0, 0, 0, 886, 882, 1, 0, 0, 0, 887, 893, 1, 0, 0, 0, 888, 889, 10, 1, 0, 0, 889, 890, 5, 65, 0, 0, 890, 892, 3, 12, 6, 0, 891, 888, 1, 0, 0, 0, 892, 895, 1, 0, 0, 0, 893, 891, 1, 0, 0, 0, 893, 894, 1, 0, 0, 0, 894, 171, 1, 0, 0, 0, 895, 893, 1, 0, 0, 0, 896, 897, 3, 174, 87, 0, 897, 911, 5, 105, 0, 0, 898, 912, 5, 95, 0, 0, 899, 904, 3, 160, 80, 0, 900, 901, 5, 68, 0, 0, 901, 903, 3, 160, 80, 0, 902, 900, 1, 0, 0, 0, 903, 906, 1, 0, 0, 0, 904, 902, 1, 0, 0, 0, 904, 905, 1, 0, 0, 0, 905, 909, 1, 0, 0, 0, 906, 904, 1, 0, 0, 0, 907, 908, 5, 68, 0, 0, 908, 910, 3, 176, 88, 0, 909, 907, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 910, 912, 1, 0, 0, 0, 911, 898, 1, 0, 0, 0, 911, 899, 1, 0, 0, 0, 911, 912, 1, 0, 0, 0, 912, 913, 1, 0, 0, 0, 913, 914, 5, 106, 0, 0, 914, 173, 1, 0, 0, 0, 915, 919, 3, 70, 35, 0, 916, 919, 5, 72, 0, 0, 917, 919, 5, 75, 0, 0, 918, 915, 1, 0, 0, 0, 918, 916, 1, 0, 0, 0, 918, 917, 1, 0, 0, 0, 919, 175, 1, 0, 0, 0, 920, 929, 5, 98, 0, 0, 921, 926, 3, 178, 89, 0, 922, 923, 5, 68, 0, 0, 923, 925, 3, 178, 89, 0, 924, 922, 1, 0, 0, 0, 925, 928, 1, 0, 0, 0, 926, 924, 1, 0, 0, 0, 926, 927, 1, 0, 0, 0, 927, 930, 1, 0, 0, 0, 928, 926, 1, 0, 0, 0, 929, 921, 1, 0, 0, 0, 929, 930, 1, 0, 0, 0, 930, 931, 1, 0, 0, 0, 931, 932, 5, 99, 0, 0, 932, 177, 1, 0, 0, 0, 933, 934, 3, 192, 96, 0, 934, 935, 5, 66, 0, 0, 935, 936, 3, 180, 90, 0, 936, 179, 1, 0, 0, 0, 937, 940, 3, 182, 91, 0, 938, 940, 3, 176, 88, 0, 939, 937, 1, 0, 0, 0, 939, 938, 1, 0, 0, 0, 940, 181, 1, 0, 0, 0, 941, 984, 5, 78, 0, 0, 942, 943, 3, 190, 95, 0, 943, 944, 5, 107, 0, 0, 944, 984, 1, 0, 0, 0, 945, 984, 3, 188, 94, 0, 946, 984, 3, 190, 95, 0, 947, 984, 3, 184, 92, 0, 948, 984, 3, 66, 33, 0, 949, 984, 3, 192, 96, 0, 950, 951, 5, 103, 0, 0, 951, 956, 3, 186, 93, 0, 952, 953, 5, 68, 0, 0, 953, 955, 3, 186, 93, 0, 954, 952, 1, 0, 0, 0, 955, 958, 1, 0, 0, 0, 956, 954, 1, 0, 0, 0, 956, 957, 1, 0, 0, 0, 957, 959, 1, 0, 0, 0, 958, 956, 1, 0, 0, 0, 959, 960, 5, 104, 0, 0, 960, 984, 1, 0, 0, 0, 961, 962, 5, 103, 0, 0, 962, 967, 3, 184, 92, 0, 963, 964, 5, 68, 0, 0, 964, 966, 3, 184, 92, 0, 965, 963, 1, 0, 0, 0, 966, 969, 1, 0, 0, 0, 967, 965, 1, 0, 0, 0, 967, 968, 1, 0, 0, 0, 968, 970, 1, 0, 0, 0, 969, 967, 1, 0, 0, 0, 970, 971, 5, 104, 0, 0, 971, 984, 1, 0, 0, 0, 972, 973, 5, 103, 0, 0, 973, 978, 3, 192, 96, 0, 974, 975, 5, 68, 0, 0, 975, 977, 3, 192, 96, 0, 976, 974, 1, 0, 0, 0, 977, 980, 1, 0, 0, 0, 978, 976, 1, 0, 0, 0, 978, 979, 1, 0, 0, 0, 979, 981, 1, 0, 0, 0, 980, 978, 1, 0, 0, 0, 981, 982, 5, 104, 0, 0, 982, 984, 1, 0, 0, 0, 983, 941, 1, 0, 0, 0, 983, 942, 1, 0, 0, 0, 983, 945, 1, 0, 0, 0, 983, 946, 1, 0, 0, 0, 983, 947, 1, 0, 0, 0, 983, 948, 1, 0, 0, 0, 983, 949, 1, 0, 0, 0, 983, 950, 1, 0, 0, 0, 983, 961, 1, 0, 0, 0, 983, 972, 1, 0, 0, 0, 984, 183, 1, 0, 0, 0, 985, 986, 7, 7, 0, 0, 986, 185, 1, 0, 0, 0, 987, 990, 3, 188, 94, 0, 988, 990, 3, 190, 95, 0, 989, 987, 1, 0, 0, 0, 989, 988, 1, 0, 0, 0, 990, 187, 1, 0, 0, 0, 991, 993, 7, 5, 0, 0, 992, 991, 1, 0, 0, 0, 992, 993, 1, 0, 0, 0, 993, 994, 1, 0, 0, 0, 994, 995, 5, 60, 0, 0, 995, 189, 1, 0, 0, 0, 996, 998, 7, 5, 0, 0, 997, 996, 1, 0, 0, 0, 997, 998, 1, 0, 0, 0, 998, 999, 1, 0, 0, 0, 999, 1000, 5, 59, 0, 0, 1000, 191, 1, 0, 0, 0, 1001, 1002, 5, 58, 0, 0, 1002, 193, 1, 0, 0, 0, 1003, 1004, 7, 8, 0, 0, 1004, 195, 1, 0, 0, 0, 1005, 1006, 7, 9, 0, 0, 1006, 1007, 5, 130, 0, 0, 1007, 1008, 3, 198, 99, 0, 1008, 1009, 3, 200, 100, 0, 1009, 197, 1, 0, 0, 0, 1010, 1011, 4, 99, 15, 0, 1011, 1013, 3, 32, 16, 0, 1012, 1014, 5, 158, 0, 0, 1013, 1012, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1016, 5, 113, 0, 0, 1016, 1019, 1, 0, 0, 0, 1017, 1019, 3, 32, 16, 0, 1018, 1010, 1, 0, 0, 0, 1018, 1017, 1, 0, 0, 0, 1019, 199, 1, 0, 0, 0, 1020, 1021, 5, 80, 0, 0, 1021, 1026, 3, 160, 80, 0, 1022, 1023, 5, 68, 0, 0, 1023, 1025, 3, 160, 80, 0, 1024, 1022, 1, 0, 0, 0, 1025, 1028, 1, 0, 0, 0, 1026, 1024, 1, 0, 0, 0, 1026, 1027, 1, 0, 0, 0, 1027, 201, 1, 0, 0, 0, 1028, 1026, 1, 0, 0, 0, 1029, 1033, 5, 39, 0, 0, 1030, 1032, 3, 206, 103, 0, 1031, 1030, 1, 0, 0, 0, 1032, 1035, 1, 0, 0, 0, 1033, 1031, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1039, 1, 0, 0, 0, 1035, 1033, 1, 0, 0, 0, 1036, 1037, 3, 204, 102, 0, 1037, 1038, 5, 63, 0, 0, 1038, 1040, 1, 0, 0, 0, 1039, 1036, 1, 0, 0, 0, 1039, 1040, 1, 0, 0, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 5, 105, 0, 0, 1042, 1043, 5, 101, 0, 0, 1043, 1090, 5, 106, 0, 0, 1044, 1048, 5, 39, 0, 0, 1045, 1047, 3, 206, 103, 0, 1046, 1045, 1, 0, 0, 0, 1047, 1050, 1, 0, 0, 0, 1048, 1046, 1, 0, 0, 0, 1048, 1049, 1, 0, 0, 0, 1049, 1054, 1, 0, 0, 0, 1050, 1048, 1, 0, 0, 0, 1051, 1052, 3, 204, 102, 0, 1052, 1053, 5, 63, 0, 0, 1053, 1055, 1, 0, 0, 0, 1054, 1051, 1, 0, 0, 0, 1054, 1055, 1, 0, 0, 0, 1055, 1056, 1, 0, 0, 0, 1056, 1090, 5, 101, 0, 0, 1057, 1061, 5, 39, 0, 0, 1058, 1060, 3, 206, 103, 0, 1059, 1058, 1, 0, 0, 0, 1060, 1063, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1061, 1062, 1, 0, 0, 0, 1062, 1067, 1, 0, 0, 0, 1063, 1061, 1, 0, 0, 0, 1064, 1065, 3, 204, 102, 0, 1065, 1066, 5, 63, 0, 0, 1066, 1068, 1, 0, 0, 0, 1067, 1064, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, 1068, 1069, 1, 0, 0, 0, 1069, 1071, 5, 105, 0, 0, 1070, 1072, 3, 214, 107, 0, 1071, 1070, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1071, 1, 0, 0, 0, 1073, 1074, 1, 0, 0, 0, 1074, 1075, 1, 0, 0, 0, 1075, 1076, 5, 106, 0, 0, 1076, 1090, 1, 0, 0, 0, 1077, 1081, 5, 39, 0, 0, 1078, 1080, 3, 206, 103, 0, 1079, 1078, 1, 0, 0, 0, 1080, 1083, 1, 0, 0, 0, 1081, 1079, 1, 0, 0, 0, 1081, 1082, 1, 0, 0, 0, 1082, 1085, 1, 0, 0, 0, 1083, 1081, 1, 0, 0, 0, 1084, 1086, 3, 214, 107, 0, 1085, 1084, 1, 0, 0, 0, 1086, 1087, 1, 0, 0, 0, 1087, 1085, 1, 0, 0, 0, 1087, 1088, 1, 0, 0, 0, 1088, 1090, 1, 0, 0, 0, 1089, 1029, 1, 0, 0, 0, 1089, 1044, 1, 0, 0, 0, 1089, 1057, 1, 0, 0, 0, 1089, 1077, 1, 0, 0, 0, 1090, 203, 1, 0, 0, 0, 1091, 1092, 7, 1, 0, 0, 1092, 205, 1, 0, 0, 0, 1093, 1094, 3, 208, 104, 0, 1094, 1095, 5, 63, 0, 0, 1095, 1096, 3, 210, 105, 0, 1096, 207, 1, 0, 0, 0, 1097, 1098, 7, 10, 0, 0, 1098, 209, 1, 0, 0, 0, 1099, 1104, 3, 216, 108, 0, 1100, 1101, 5, 68, 0, 0, 1101, 1103, 3, 216, 108, 0, 1102, 1100, 1, 0, 0, 0, 1103, 1106, 1, 0, 0, 0, 1104, 1102, 1, 0, 0, 0, 1104, 1105, 1, 0, 0, 0, 1105, 1110, 1, 0, 0, 0, 1106, 1104, 1, 0, 0, 0, 1107, 1110, 5, 108, 0, 0, 1108, 1110, 5, 101, 0, 0, 1109, 1099, 1, 0, 0, 0, 1109, 1107, 1, 0, 0, 0, 1109, 1108, 1, 0, 0, 0, 1110, 211, 1, 0, 0, 0, 1111, 1112, 7, 11, 0, 0, 1112, 213, 1, 0, 0, 0, 1113, 1115, 3, 212, 106, 0, 1114, 1113, 1, 0, 0, 0, 1115, 1116, 1, 0, 0, 0, 1116, 1114, 1, 0, 0, 0, 1116, 1117, 1, 0, 0, 0, 1117, 1127, 1, 0, 0, 0, 1118, 1122, 5, 105, 0, 0, 1119, 1121, 3, 214, 107, 0, 1120, 1119, 1, 0, 0, 0, 1121, 1124, 1, 0, 0, 0, 1122, 1120, 1, 0, 0, 0, 1122, 1123, 1, 0, 0, 0, 1123, 1125, 1, 0, 0, 0, 1124, 1122, 1, 0, 0, 0, 1125, 1127, 5, 106, 0, 0, 1126, 1114, 1, 0, 0, 0, 1126, 1118, 1, 0, 0, 0, 1127, 215, 1, 0, 0, 0, 1128, 1129, 3, 218, 109, 0, 1129, 1130, 5, 66, 0, 0, 1130, 1131, 3, 222, 111, 0, 1131, 1138, 1, 0, 0, 0, 1132, 1133, 3, 222, 111, 0, 1133, 1134, 5, 65, 0, 0, 1134, 1135, 3, 220, 110, 0, 1135, 1138, 1, 0, 0, 0, 1136, 1138, 3, 224, 112, 0, 1137, 1128, 1, 0, 0, 0, 1137, 1132, 1, 0, 0, 0, 1137, 1136, 1, 0, 0, 0, 1138, 217, 1, 0, 0, 0, 1139, 1140, 7, 12, 0, 0, 1140, 219, 1, 0, 0, 0, 1141, 1142, 7, 12, 0, 0, 1142, 221, 1, 0, 0, 0, 1143, 1144, 7, 12, 0, 0, 1144, 223, 1, 0, 0, 0, 1145, 1146, 7, 13, 0, 0, 1146, 225, 1, 0, 0, 0, 113, 229, 246, 258, 289, 304, 310, 329, 333, 338, 346, 354, 359, 362, 378, 386, 390, 397, 403, 408, 417, 424, 430, 439, 446, 454, 462, 466, 470, 475, 479, 484, 496, 501, 505, 519, 530, 536, 543, 552, 561, 581, 589, 592, 599, 610, 617, 625, 639, 648, 659, 669, 675, 677, 681, 686, 700, 707, 740, 744, 754, 763, 772, 780, 785, 793, 795, 800, 807, 814, 823, 830, 839, 844, 849, 859, 865, 873, 875, 886, 893, 904, 909, 911, 918, 926, 929, 939, 956, 967, 978, 983, 989, 992, 997, 1013, 1018, 1026, 1033, 1039, 1048, 1054, 1061, 1067, 1073, 1081, 1087, 1089, 1104, 1109, 1116, 1122, 1126, 1137] \ No newline at end of file diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.java index ad93d856c336c..be3020b5561ba 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.java @@ -8624,6 +8624,7 @@ public final JoinConditionContext joinCondition() throws RecognitionException { public static class PromqlCommandContext extends ParserRuleContext { public TerminalNode PROMQL() { return getToken(EsqlBaseParser.PROMQL, 0); } public TerminalNode LP() { return getToken(EsqlBaseParser.LP, 0); } + public TerminalNode NAMED_OR_POSITIONAL_PARAM() { return getToken(EsqlBaseParser.NAMED_OR_POSITIONAL_PARAM, 0); } public TerminalNode RP() { return getToken(EsqlBaseParser.RP, 0); } public List promqlParam() { return getRuleContexts(PromqlParamContext.class); @@ -8667,9 +8668,9 @@ public final PromqlCommandContext promqlCommand() throws RecognitionException { int _la; try { int _alt; - setState(1061); + setState(1089); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,102,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,106,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { @@ -8705,46 +8706,124 @@ public final PromqlCommandContext promqlCommand() throws RecognitionException { setState(1041); match(LP); - setState(1043); + setState(1042); + match(NAMED_OR_POSITIONAL_PARAM); + setState(1043); + match(RP); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(1044); + match(PROMQL); + setState(1048); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,99,_ctx); + while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { + if ( _alt==1 ) { + { + { + setState(1045); + promqlParam(); + } + } + } + setState(1050); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,99,_ctx); + } + setState(1054); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==UNQUOTED_IDENTIFIER || _la==QUOTED_IDENTIFIER) { + { + setState(1051); + valueName(); + setState(1052); + match(ASSIGN); + } + } + + setState(1056); + match(NAMED_OR_POSITIONAL_PARAM); + } + break; + case 3: + enterOuterAlt(_localctx, 3); + { + setState(1057); + match(PROMQL); + setState(1061); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,101,_ctx); + while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { + if ( _alt==1 ) { + { + { + setState(1058); + promqlParam(); + } + } + } + setState(1063); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,101,_ctx); + } + setState(1067); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==UNQUOTED_IDENTIFIER || _la==QUOTED_IDENTIFIER) { + { + setState(1064); + valueName(); + setState(1065); + match(ASSIGN); + } + } + + setState(1069); + match(LP); + setState(1071); _errHandler.sync(this); _la = _input.LA(1); do { { { - setState(1042); + setState(1070); promqlQueryPart(); } } - setState(1045); + setState(1073); _errHandler.sync(this); _la = _input.LA(1); } while ( ((((_la - 58)) & ~0x3f) == 0 && ((1L << (_la - 58)) & 37867180460606881L) != 0) || ((((_la - 155)) & ~0x3f) == 0 && ((1L << (_la - 155)) & 7L) != 0) ); - setState(1047); + setState(1075); match(RP); } break; - case 2: - enterOuterAlt(_localctx, 2); + case 4: + enterOuterAlt(_localctx, 4); { - setState(1049); + setState(1077); match(PROMQL); - setState(1053); + setState(1081); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,100,_ctx); + _alt = getInterpreter().adaptivePredict(_input,104,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(1050); + setState(1078); promqlParam(); } } } - setState(1055); + setState(1083); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,100,_ctx); + _alt = getInterpreter().adaptivePredict(_input,104,_ctx); } - setState(1057); + setState(1085); _errHandler.sync(this); _alt = 1; do { @@ -8752,7 +8831,7 @@ public final PromqlCommandContext promqlCommand() throws RecognitionException { case 1: { { - setState(1056); + setState(1084); promqlQueryPart(); } } @@ -8760,9 +8839,9 @@ public final PromqlCommandContext promqlCommand() throws RecognitionException { default: throw new NoViableAltException(this); } - setState(1059); + setState(1087); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,101,_ctx); + _alt = getInterpreter().adaptivePredict(_input,105,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); } break; @@ -8810,7 +8889,7 @@ public final ValueNameContext valueName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(1063); + setState(1091); _la = _input.LA(1); if ( !(_la==UNQUOTED_IDENTIFIER || _la==QUOTED_IDENTIFIER) ) { _errHandler.recoverInline(this); @@ -8870,11 +8949,11 @@ public final PromqlParamContext promqlParam() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(1065); + setState(1093); ((PromqlParamContext)_localctx).name = promqlParamName(); - setState(1066); + setState(1094); match(ASSIGN); - setState(1067); + setState(1095); ((PromqlParamContext)_localctx).value = promqlParamValue(); } } @@ -8922,7 +9001,7 @@ public final PromqlParamNameContext promqlParamName() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(1069); + setState(1097); _la = _input.LA(1); if ( !(((((_la - 58)) & ~0x3f) == 0 && ((1L << (_la - 58)) & 1697645953286145L) != 0)) ) { _errHandler.recoverInline(this); @@ -8984,7 +9063,7 @@ public final PromqlParamValueContext promqlParamValue() throws RecognitionExcept enterRule(_localctx, 210, RULE_promqlParamValue); try { int _alt; - setState(1081); + setState(1109); _errHandler.sync(this); switch (_input.LA(1)) { case QUOTED_STRING: @@ -8992,39 +9071,39 @@ public final PromqlParamValueContext promqlParamValue() throws RecognitionExcept case UNQUOTED_SOURCE: enterOuterAlt(_localctx, 1); { - setState(1071); + setState(1099); promqlIndexPattern(); - setState(1076); + setState(1104); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,103,_ctx); + _alt = getInterpreter().adaptivePredict(_input,107,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(1072); + setState(1100); match(COMMA); - setState(1073); + setState(1101); promqlIndexPattern(); } } } - setState(1078); + setState(1106); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,103,_ctx); + _alt = getInterpreter().adaptivePredict(_input,107,_ctx); } } break; case QUOTED_IDENTIFIER: enterOuterAlt(_localctx, 2); { - setState(1079); + setState(1107); match(QUOTED_IDENTIFIER); } break; case NAMED_OR_POSITIONAL_PARAM: enterOuterAlt(_localctx, 3); { - setState(1080); + setState(1108); match(NAMED_OR_POSITIONAL_PARAM); } break; @@ -9084,7 +9163,7 @@ public final PromqlQueryContentContext promqlQueryContent() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(1083); + setState(1111); _la = _input.LA(1); if ( !(((((_la - 58)) & ~0x3f) == 0 && ((1L << (_la - 58)) & 37726442972251553L) != 0) || ((((_la - 155)) & ~0x3f) == 0 && ((1L << (_la - 155)) & 7L) != 0)) ) { _errHandler.recoverInline(this); @@ -9149,7 +9228,7 @@ public final PromqlQueryPartContext promqlQueryPart() throws RecognitionExceptio int _la; try { int _alt; - setState(1098); + setState(1126); _errHandler.sync(this); switch (_input.LA(1)) { case QUOTED_STRING: @@ -9166,7 +9245,7 @@ public final PromqlQueryPartContext promqlQueryPart() throws RecognitionExceptio case PROMQL_OTHER_QUERY_CONTENT: enterOuterAlt(_localctx, 1); { - setState(1086); + setState(1114); _errHandler.sync(this); _alt = 1; do { @@ -9174,7 +9253,7 @@ public final PromqlQueryPartContext promqlQueryPart() throws RecognitionExceptio case 1: { { - setState(1085); + setState(1113); promqlQueryContent(); } } @@ -9182,32 +9261,32 @@ public final PromqlQueryPartContext promqlQueryPart() throws RecognitionExceptio default: throw new NoViableAltException(this); } - setState(1088); + setState(1116); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,105,_ctx); + _alt = getInterpreter().adaptivePredict(_input,109,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); } break; case LP: enterOuterAlt(_localctx, 2); { - setState(1090); + setState(1118); match(LP); - setState(1094); + setState(1122); _errHandler.sync(this); _la = _input.LA(1); while (((((_la - 58)) & ~0x3f) == 0 && ((1L << (_la - 58)) & 37867180460606881L) != 0) || ((((_la - 155)) & ~0x3f) == 0 && ((1L << (_la - 155)) & 7L) != 0)) { { { - setState(1091); + setState(1119); promqlQueryPart(); } } - setState(1096); + setState(1124); _errHandler.sync(this); _la = _input.LA(1); } - setState(1097); + setState(1125); match(RP); } break; @@ -9266,35 +9345,35 @@ public final PromqlIndexPatternContext promqlIndexPattern() throws RecognitionEx PromqlIndexPatternContext _localctx = new PromqlIndexPatternContext(_ctx, getState()); enterRule(_localctx, 216, RULE_promqlIndexPattern); try { - setState(1109); + setState(1137); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,108,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,112,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(1100); + setState(1128); promqlClusterString(); - setState(1101); + setState(1129); match(COLON); - setState(1102); + setState(1130); promqlUnquotedIndexString(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(1104); + setState(1132); promqlUnquotedIndexString(); - setState(1105); + setState(1133); match(CAST_OP); - setState(1106); + setState(1134); promqlSelectorString(); } break; case 3: enterOuterAlt(_localctx, 3); { - setState(1108); + setState(1136); promqlIndexString(); } break; @@ -9342,7 +9421,7 @@ public final PromqlClusterStringContext promqlClusterString() throws Recognition try { enterOuterAlt(_localctx, 1); { - setState(1111); + setState(1139); _la = _input.LA(1); if ( !(_la==UNQUOTED_IDENTIFIER || _la==UNQUOTED_SOURCE) ) { _errHandler.recoverInline(this); @@ -9396,7 +9475,7 @@ public final PromqlSelectorStringContext promqlSelectorString() throws Recogniti try { enterOuterAlt(_localctx, 1); { - setState(1113); + setState(1141); _la = _input.LA(1); if ( !(_la==UNQUOTED_IDENTIFIER || _la==UNQUOTED_SOURCE) ) { _errHandler.recoverInline(this); @@ -9450,7 +9529,7 @@ public final PromqlUnquotedIndexStringContext promqlUnquotedIndexString() throws try { enterOuterAlt(_localctx, 1); { - setState(1115); + setState(1143); _la = _input.LA(1); if ( !(_la==UNQUOTED_IDENTIFIER || _la==UNQUOTED_SOURCE) ) { _errHandler.recoverInline(this); @@ -9505,7 +9584,7 @@ public final PromqlIndexStringContext promqlIndexString() throws RecognitionExce try { enterOuterAlt(_localctx, 1); { - setState(1117); + setState(1145); _la = _input.LA(1); if ( !(((((_la - 58)) & ~0x3f) == 0 && ((1L << (_la - 58)) & 36591746972385281L) != 0)) ) { _errHandler.recoverInline(this); @@ -9651,7 +9730,7 @@ private boolean joinTarget_sempred(JoinTargetContext _localctx, int predIndex) { } public static final String _serializedATN = - "\u0004\u0001\u00a8\u0460\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u00a8\u047c\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ "\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004"+ "\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007"+ "\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b"+ @@ -9771,46 +9850,49 @@ private boolean joinTarget_sempred(JoinTargetContext _localctx, int predIndex) { "a\u0001a\u0001b\u0001b\u0001b\u0001b\u0001b\u0001c\u0001c\u0001c\u0003"+ "c\u03f6\bc\u0001c\u0001c\u0001c\u0003c\u03fb\bc\u0001d\u0001d\u0001d\u0001"+ "d\u0005d\u0401\bd\nd\fd\u0404\td\u0001e\u0001e\u0005e\u0408\be\ne\fe\u040b"+ - "\te\u0001e\u0001e\u0001e\u0003e\u0410\be\u0001e\u0001e\u0004e\u0414\b"+ - "e\u000be\fe\u0415\u0001e\u0001e\u0001e\u0001e\u0005e\u041c\be\ne\fe\u041f"+ - "\te\u0001e\u0004e\u0422\be\u000be\fe\u0423\u0003e\u0426\be\u0001f\u0001"+ - "f\u0001g\u0001g\u0001g\u0001g\u0001h\u0001h\u0001i\u0001i\u0001i\u0005"+ - "i\u0433\bi\ni\fi\u0436\ti\u0001i\u0001i\u0003i\u043a\bi\u0001j\u0001j"+ - "\u0001k\u0004k\u043f\bk\u000bk\fk\u0440\u0001k\u0001k\u0005k\u0445\bk"+ - "\nk\fk\u0448\tk\u0001k\u0003k\u044b\bk\u0001l\u0001l\u0001l\u0001l\u0001"+ - "l\u0001l\u0001l\u0001l\u0001l\u0003l\u0456\bl\u0001m\u0001m\u0001n\u0001"+ - "n\u0001o\u0001o\u0001p\u0001p\u0001p\u0000\u0005\u0004|\u00a0\u00a8\u00aa"+ - "q\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ - "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ - "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ - "\u009c\u009e\u00a0\u00a2\u00a4\u00a6\u00a8\u00aa\u00ac\u00ae\u00b0\u00b2"+ - "\u00b4\u00b6\u00b8\u00ba\u00bc\u00be\u00c0\u00c2\u00c4\u00c6\u00c8\u00ca"+ - "\u00cc\u00ce\u00d0\u00d2\u00d4\u00d6\u00d8\u00da\u00dc\u00de\u00e0\u0000"+ - "\u000e\u0002\u0000::qq\u0001\u0000kl\u0002\u0000>>EE\u0002\u0000HHKK\u0002"+ - "\u0000//::\u0001\u0000]^\u0001\u0000_a\u0002\u0000GGTT\u0002\u0000VVX"+ - "\\\u0002\u0000\u001d\u001d\u001f \u0003\u0000::eekl\b\u0000::??ABDDee"+ - "klqq\u009b\u009d\u0002\u0000kkqq\u0003\u0000::kkqq\u0491\u0000\u00e5\u0001"+ - "\u0000\u0000\u0000\u0002\u00eb\u0001\u0000\u0000\u0000\u0004\u00ee\u0001"+ - "\u0000\u0000\u0000\u0006\u0102\u0001\u0000\u0000\u0000\b\u0121\u0001\u0000"+ - "\u0000\u0000\n\u0123\u0001\u0000\u0000\u0000\f\u0126\u0001\u0000\u0000"+ - "\u0000\u000e\u0128\u0001\u0000\u0000\u0000\u0010\u012b\u0001\u0000\u0000"+ - "\u0000\u0012\u0136\u0001\u0000\u0000\u0000\u0014\u013a\u0001\u0000\u0000"+ - "\u0000\u0016\u013d\u0001\u0000\u0000\u0000\u0018\u0140\u0001\u0000\u0000"+ - "\u0000\u001a\u0144\u0001\u0000\u0000\u0000\u001c\u0152\u0001\u0000\u0000"+ - "\u0000\u001e\u0154\u0001\u0000\u0000\u0000 \u016a\u0001\u0000\u0000\u0000"+ - "\"\u016c\u0001\u0000\u0000\u0000$\u016e\u0001\u0000\u0000\u0000&\u0170"+ - "\u0001\u0000\u0000\u0000(\u0172\u0001\u0000\u0000\u0000*\u0174\u0001\u0000"+ - "\u0000\u0000,\u017d\u0001\u0000\u0000\u0000.\u0180\u0001\u0000\u0000\u0000"+ - "0\u0188\u0001\u0000\u0000\u00002\u0190\u0001\u0000\u0000\u00004\u01a1"+ - "\u0001\u0000\u0000\u00006\u01a3\u0001\u0000\u0000\u00008\u01b7\u0001\u0000"+ - "\u0000\u0000:\u01b9\u0001\u0000\u0000\u0000<\u01c1\u0001\u0000\u0000\u0000"+ - ">\u01c9\u0001\u0000\u0000\u0000@\u01ce\u0001\u0000\u0000\u0000B\u01d2"+ - "\u0001\u0000\u0000\u0000D\u01d6\u0001\u0000\u0000\u0000F\u01db\u0001\u0000"+ - "\u0000\u0000H\u01df\u0001\u0000\u0000\u0000J\u01e1\u0001\u0000\u0000\u0000"+ - "L\u01e6\u0001\u0000\u0000\u0000N\u01ea\u0001\u0000\u0000\u0000P\u01f3"+ - "\u0001\u0000\u0000\u0000R\u01fb\u0001\u0000\u0000\u0000T\u01fe\u0001\u0000"+ - "\u0000\u0000V\u0201\u0001\u0000\u0000\u0000X\u0212\u0001\u0000\u0000\u0000"+ - "Z\u0214\u0001\u0000\u0000\u0000\\\u021a\u0001\u0000\u0000\u0000^\u0222"+ + "\te\u0001e\u0001e\u0001e\u0003e\u0410\be\u0001e\u0001e\u0001e\u0001e\u0001"+ + "e\u0005e\u0417\be\ne\fe\u041a\te\u0001e\u0001e\u0001e\u0003e\u041f\be"+ + "\u0001e\u0001e\u0001e\u0005e\u0424\be\ne\fe\u0427\te\u0001e\u0001e\u0001"+ + "e\u0003e\u042c\be\u0001e\u0001e\u0004e\u0430\be\u000be\fe\u0431\u0001"+ + "e\u0001e\u0001e\u0001e\u0005e\u0438\be\ne\fe\u043b\te\u0001e\u0004e\u043e"+ + "\be\u000be\fe\u043f\u0003e\u0442\be\u0001f\u0001f\u0001g\u0001g\u0001"+ + "g\u0001g\u0001h\u0001h\u0001i\u0001i\u0001i\u0005i\u044f\bi\ni\fi\u0452"+ + "\ti\u0001i\u0001i\u0003i\u0456\bi\u0001j\u0001j\u0001k\u0004k\u045b\b"+ + "k\u000bk\fk\u045c\u0001k\u0001k\u0005k\u0461\bk\nk\fk\u0464\tk\u0001k"+ + "\u0003k\u0467\bk\u0001l\u0001l\u0001l\u0001l\u0001l\u0001l\u0001l\u0001"+ + "l\u0001l\u0003l\u0472\bl\u0001m\u0001m\u0001n\u0001n\u0001o\u0001o\u0001"+ + "p\u0001p\u0001p\u0000\u0005\u0004|\u00a0\u00a8\u00aaq\u0000\u0002\u0004"+ + "\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a\u001c\u001e \""+ + "$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082\u0084\u0086"+ + "\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a\u009c\u009e"+ + "\u00a0\u00a2\u00a4\u00a6\u00a8\u00aa\u00ac\u00ae\u00b0\u00b2\u00b4\u00b6"+ + "\u00b8\u00ba\u00bc\u00be\u00c0\u00c2\u00c4\u00c6\u00c8\u00ca\u00cc\u00ce"+ + "\u00d0\u00d2\u00d4\u00d6\u00d8\u00da\u00dc\u00de\u00e0\u0000\u000e\u0002"+ + "\u0000::qq\u0001\u0000kl\u0002\u0000>>EE\u0002\u0000HHKK\u0002\u0000/"+ + "/::\u0001\u0000]^\u0001\u0000_a\u0002\u0000GGTT\u0002\u0000VVX\\\u0002"+ + "\u0000\u001d\u001d\u001f \u0003\u0000::eekl\b\u0000::??ABDDeeklqq\u009b"+ + "\u009d\u0002\u0000kkqq\u0003\u0000::kkqq\u04b3\u0000\u00e5\u0001\u0000"+ + "\u0000\u0000\u0002\u00eb\u0001\u0000\u0000\u0000\u0004\u00ee\u0001\u0000"+ + "\u0000\u0000\u0006\u0102\u0001\u0000\u0000\u0000\b\u0121\u0001\u0000\u0000"+ + "\u0000\n\u0123\u0001\u0000\u0000\u0000\f\u0126\u0001\u0000\u0000\u0000"+ + "\u000e\u0128\u0001\u0000\u0000\u0000\u0010\u012b\u0001\u0000\u0000\u0000"+ + "\u0012\u0136\u0001\u0000\u0000\u0000\u0014\u013a\u0001\u0000\u0000\u0000"+ + "\u0016\u013d\u0001\u0000\u0000\u0000\u0018\u0140\u0001\u0000\u0000\u0000"+ + "\u001a\u0144\u0001\u0000\u0000\u0000\u001c\u0152\u0001\u0000\u0000\u0000"+ + "\u001e\u0154\u0001\u0000\u0000\u0000 \u016a\u0001\u0000\u0000\u0000\""+ + "\u016c\u0001\u0000\u0000\u0000$\u016e\u0001\u0000\u0000\u0000&\u0170\u0001"+ + "\u0000\u0000\u0000(\u0172\u0001\u0000\u0000\u0000*\u0174\u0001\u0000\u0000"+ + "\u0000,\u017d\u0001\u0000\u0000\u0000.\u0180\u0001\u0000\u0000\u00000"+ + "\u0188\u0001\u0000\u0000\u00002\u0190\u0001\u0000\u0000\u00004\u01a1\u0001"+ + "\u0000\u0000\u00006\u01a3\u0001\u0000\u0000\u00008\u01b7\u0001\u0000\u0000"+ + "\u0000:\u01b9\u0001\u0000\u0000\u0000<\u01c1\u0001\u0000\u0000\u0000>"+ + "\u01c9\u0001\u0000\u0000\u0000@\u01ce\u0001\u0000\u0000\u0000B\u01d2\u0001"+ + "\u0000\u0000\u0000D\u01d6\u0001\u0000\u0000\u0000F\u01db\u0001\u0000\u0000"+ + "\u0000H\u01df\u0001\u0000\u0000\u0000J\u01e1\u0001\u0000\u0000\u0000L"+ + "\u01e6\u0001\u0000\u0000\u0000N\u01ea\u0001\u0000\u0000\u0000P\u01f3\u0001"+ + "\u0000\u0000\u0000R\u01fb\u0001\u0000\u0000\u0000T\u01fe\u0001\u0000\u0000"+ + "\u0000V\u0201\u0001\u0000\u0000\u0000X\u0212\u0001\u0000\u0000\u0000Z"+ + "\u0214\u0001\u0000\u0000\u0000\\\u021a\u0001\u0000\u0000\u0000^\u0222"+ "\u0001\u0000\u0000\u0000`\u0228\u0001\u0000\u0000\u0000b\u022a\u0001\u0000"+ "\u0000\u0000d\u0234\u0001\u0000\u0000\u0000f\u0237\u0001\u0000\u0000\u0000"+ "h\u023a\u0001\u0000\u0000\u0000j\u023e\u0001\u0000\u0000\u0000l\u0241"+ @@ -9836,13 +9918,13 @@ private boolean joinTarget_sempred(JoinTargetContext _localctx, int predIndex) { "\u0000\u0000\u00bc\u03e0\u0001\u0000\u0000\u0000\u00be\u03e5\u0001\u0000"+ "\u0000\u0000\u00c0\u03e9\u0001\u0000\u0000\u0000\u00c2\u03eb\u0001\u0000"+ "\u0000\u0000\u00c4\u03ed\u0001\u0000\u0000\u0000\u00c6\u03fa\u0001\u0000"+ - "\u0000\u0000\u00c8\u03fc\u0001\u0000\u0000\u0000\u00ca\u0425\u0001\u0000"+ - "\u0000\u0000\u00cc\u0427\u0001\u0000\u0000\u0000\u00ce\u0429\u0001\u0000"+ - "\u0000\u0000\u00d0\u042d\u0001\u0000\u0000\u0000\u00d2\u0439\u0001\u0000"+ - "\u0000\u0000\u00d4\u043b\u0001\u0000\u0000\u0000\u00d6\u044a\u0001\u0000"+ - "\u0000\u0000\u00d8\u0455\u0001\u0000\u0000\u0000\u00da\u0457\u0001\u0000"+ - "\u0000\u0000\u00dc\u0459\u0001\u0000\u0000\u0000\u00de\u045b\u0001\u0000"+ - "\u0000\u0000\u00e0\u045d\u0001\u0000\u0000\u0000\u00e2\u00e4\u0003\u0098"+ + "\u0000\u0000\u00c8\u03fc\u0001\u0000\u0000\u0000\u00ca\u0441\u0001\u0000"+ + "\u0000\u0000\u00cc\u0443\u0001\u0000\u0000\u0000\u00ce\u0445\u0001\u0000"+ + "\u0000\u0000\u00d0\u0449\u0001\u0000\u0000\u0000\u00d2\u0455\u0001\u0000"+ + "\u0000\u0000\u00d4\u0457\u0001\u0000\u0000\u0000\u00d6\u0466\u0001\u0000"+ + "\u0000\u0000\u00d8\u0471\u0001\u0000\u0000\u0000\u00da\u0473\u0001\u0000"+ + "\u0000\u0000\u00dc\u0475\u0001\u0000\u0000\u0000\u00de\u0477\u0001\u0000"+ + "\u0000\u0000\u00e0\u0479\u0001\u0000\u0000\u0000\u00e2\u00e4\u0003\u0098"+ "L\u0000\u00e3\u00e2\u0001\u0000\u0000\u0000\u00e4\u00e7\u0001\u0000\u0000"+ "\u0000\u00e5\u00e3\u0001\u0000\u0000\u0000\u00e5\u00e6\u0001\u0000\u0000"+ "\u0000\u00e6\u00e8\u0001\u0000\u0000\u0000\u00e7\u00e5\u0001\u0000\u0000"+ @@ -10290,59 +10372,76 @@ private boolean joinTarget_sempred(JoinTargetContext _localctx, int predIndex) { "\u040a\u040f\u0001\u0000\u0000\u0000\u040b\u0409\u0001\u0000\u0000\u0000"+ "\u040c\u040d\u0003\u00ccf\u0000\u040d\u040e\u0005?\u0000\u0000\u040e\u0410"+ "\u0001\u0000\u0000\u0000\u040f\u040c\u0001\u0000\u0000\u0000\u040f\u0410"+ - "\u0001\u0000\u0000\u0000\u0410\u0411\u0001\u0000\u0000\u0000\u0411\u0413"+ - "\u0005i\u0000\u0000\u0412\u0414\u0003\u00d6k\u0000\u0413\u0412\u0001\u0000"+ - "\u0000\u0000\u0414\u0415\u0001\u0000\u0000\u0000\u0415\u0413\u0001\u0000"+ - "\u0000\u0000\u0415\u0416\u0001\u0000\u0000\u0000\u0416\u0417\u0001\u0000"+ - "\u0000\u0000\u0417\u0418\u0005j\u0000\u0000\u0418\u0426\u0001\u0000\u0000"+ - "\u0000\u0419\u041d\u0005\'\u0000\u0000\u041a\u041c\u0003\u00ceg\u0000"+ - "\u041b\u041a\u0001\u0000\u0000\u0000\u041c\u041f\u0001\u0000\u0000\u0000"+ - "\u041d\u041b\u0001\u0000\u0000\u0000\u041d\u041e\u0001\u0000\u0000\u0000"+ - "\u041e\u0421\u0001\u0000\u0000\u0000\u041f\u041d\u0001\u0000\u0000\u0000"+ - "\u0420\u0422\u0003\u00d6k\u0000\u0421\u0420\u0001\u0000\u0000\u0000\u0422"+ - "\u0423\u0001\u0000\u0000\u0000\u0423\u0421\u0001\u0000\u0000\u0000\u0423"+ - "\u0424\u0001\u0000\u0000\u0000\u0424\u0426\u0001\u0000\u0000\u0000\u0425"+ - "\u0405\u0001\u0000\u0000\u0000\u0425\u0419\u0001\u0000\u0000\u0000\u0426"+ - "\u00cb\u0001\u0000\u0000\u0000\u0427\u0428\u0007\u0001\u0000\u0000\u0428"+ - "\u00cd\u0001\u0000\u0000\u0000\u0429\u042a\u0003\u00d0h\u0000\u042a\u042b"+ - "\u0005?\u0000\u0000\u042b\u042c\u0003\u00d2i\u0000\u042c\u00cf\u0001\u0000"+ - "\u0000\u0000\u042d\u042e\u0007\n\u0000\u0000\u042e\u00d1\u0001\u0000\u0000"+ - "\u0000\u042f\u0434\u0003\u00d8l\u0000\u0430\u0431\u0005D\u0000\u0000\u0431"+ - "\u0433\u0003\u00d8l\u0000\u0432\u0430\u0001\u0000\u0000\u0000\u0433\u0436"+ - "\u0001\u0000\u0000\u0000\u0434\u0432\u0001\u0000\u0000\u0000\u0434\u0435"+ - "\u0001\u0000\u0000\u0000\u0435\u043a\u0001\u0000\u0000\u0000\u0436\u0434"+ - "\u0001\u0000\u0000\u0000\u0437\u043a\u0005l\u0000\u0000\u0438\u043a\u0005"+ - "e\u0000\u0000\u0439\u042f\u0001\u0000\u0000\u0000\u0439\u0437\u0001\u0000"+ - "\u0000\u0000\u0439\u0438\u0001\u0000\u0000\u0000\u043a\u00d3\u0001\u0000"+ - "\u0000\u0000\u043b\u043c\u0007\u000b\u0000\u0000\u043c\u00d5\u0001\u0000"+ - "\u0000\u0000\u043d\u043f\u0003\u00d4j\u0000\u043e\u043d\u0001\u0000\u0000"+ - "\u0000\u043f\u0440\u0001\u0000\u0000\u0000\u0440\u043e\u0001\u0000\u0000"+ - "\u0000\u0440\u0441\u0001\u0000\u0000\u0000\u0441\u044b\u0001\u0000\u0000"+ - "\u0000\u0442\u0446\u0005i\u0000\u0000\u0443\u0445\u0003\u00d6k\u0000\u0444"+ - "\u0443\u0001\u0000\u0000\u0000\u0445\u0448\u0001\u0000\u0000\u0000\u0446"+ - "\u0444\u0001\u0000\u0000\u0000\u0446\u0447\u0001\u0000\u0000\u0000\u0447"+ - "\u0449\u0001\u0000\u0000\u0000\u0448\u0446\u0001\u0000\u0000\u0000\u0449"+ - "\u044b\u0005j\u0000\u0000\u044a\u043e\u0001\u0000\u0000\u0000\u044a\u0442"+ - "\u0001\u0000\u0000\u0000\u044b\u00d7\u0001\u0000\u0000\u0000\u044c\u044d"+ - "\u0003\u00dam\u0000\u044d\u044e\u0005B\u0000\u0000\u044e\u044f\u0003\u00de"+ - "o\u0000\u044f\u0456\u0001\u0000\u0000\u0000\u0450\u0451\u0003\u00deo\u0000"+ - "\u0451\u0452\u0005A\u0000\u0000\u0452\u0453\u0003\u00dcn\u0000\u0453\u0456"+ - "\u0001\u0000\u0000\u0000\u0454\u0456\u0003\u00e0p\u0000\u0455\u044c\u0001"+ - "\u0000\u0000\u0000\u0455\u0450\u0001\u0000\u0000\u0000\u0455\u0454\u0001"+ - "\u0000\u0000\u0000\u0456\u00d9\u0001\u0000\u0000\u0000\u0457\u0458\u0007"+ - "\f\u0000\u0000\u0458\u00db\u0001\u0000\u0000\u0000\u0459\u045a\u0007\f"+ - "\u0000\u0000\u045a\u00dd\u0001\u0000\u0000\u0000\u045b\u045c\u0007\f\u0000"+ - "\u0000\u045c\u00df\u0001\u0000\u0000\u0000\u045d\u045e\u0007\r\u0000\u0000"+ - "\u045e\u00e1\u0001\u0000\u0000\u0000m\u00e5\u00f6\u0102\u0121\u0130\u0136"+ - "\u0149\u014d\u0152\u015a\u0162\u0167\u016a\u017a\u0182\u0186\u018d\u0193"+ - "\u0198\u01a1\u01a8\u01ae\u01b7\u01be\u01c6\u01ce\u01d2\u01d6\u01db\u01df"+ - "\u01e4\u01f0\u01f5\u01f9\u0207\u0212\u0218\u021f\u0228\u0231\u0245\u024d"+ - "\u0250\u0257\u0262\u0269\u0271\u027f\u0288\u0293\u029d\u02a3\u02a5\u02a9"+ - "\u02ae\u02bc\u02c3\u02e4\u02e8\u02f2\u02fb\u0304\u030c\u0311\u0319\u031b"+ - "\u0320\u0327\u032e\u0337\u033e\u0347\u034c\u0351\u035b\u0361\u0369\u036b"+ - "\u0376\u037d\u0388\u038d\u038f\u0396\u039e\u03a1\u03ab\u03bc\u03c7\u03d2"+ - "\u03d7\u03dd\u03e0\u03e5\u03f5\u03fa\u0402\u0409\u040f\u0415\u041d\u0423"+ - "\u0425\u0434\u0439\u0440\u0446\u044a\u0455"; + "\u0001\u0000\u0000\u0000\u0410\u0411\u0001\u0000\u0000\u0000\u0411\u0412"+ + "\u0005i\u0000\u0000\u0412\u0413\u0005e\u0000\u0000\u0413\u0442\u0005j"+ + "\u0000\u0000\u0414\u0418\u0005\'\u0000\u0000\u0415\u0417\u0003\u00ceg"+ + "\u0000\u0416\u0415\u0001\u0000\u0000\u0000\u0417\u041a\u0001\u0000\u0000"+ + "\u0000\u0418\u0416\u0001\u0000\u0000\u0000\u0418\u0419\u0001\u0000\u0000"+ + "\u0000\u0419\u041e\u0001\u0000\u0000\u0000\u041a\u0418\u0001\u0000\u0000"+ + "\u0000\u041b\u041c\u0003\u00ccf\u0000\u041c\u041d\u0005?\u0000\u0000\u041d"+ + "\u041f\u0001\u0000\u0000\u0000\u041e\u041b\u0001\u0000\u0000\u0000\u041e"+ + "\u041f\u0001\u0000\u0000\u0000\u041f\u0420\u0001\u0000\u0000\u0000\u0420"+ + "\u0442\u0005e\u0000\u0000\u0421\u0425\u0005\'\u0000\u0000\u0422\u0424"+ + "\u0003\u00ceg\u0000\u0423\u0422\u0001\u0000\u0000\u0000\u0424\u0427\u0001"+ + "\u0000\u0000\u0000\u0425\u0423\u0001\u0000\u0000\u0000\u0425\u0426\u0001"+ + "\u0000\u0000\u0000\u0426\u042b\u0001\u0000\u0000\u0000\u0427\u0425\u0001"+ + "\u0000\u0000\u0000\u0428\u0429\u0003\u00ccf\u0000\u0429\u042a\u0005?\u0000"+ + "\u0000\u042a\u042c\u0001\u0000\u0000\u0000\u042b\u0428\u0001\u0000\u0000"+ + "\u0000\u042b\u042c\u0001\u0000\u0000\u0000\u042c\u042d\u0001\u0000\u0000"+ + "\u0000\u042d\u042f\u0005i\u0000\u0000\u042e\u0430\u0003\u00d6k\u0000\u042f"+ + "\u042e\u0001\u0000\u0000\u0000\u0430\u0431\u0001\u0000\u0000\u0000\u0431"+ + "\u042f\u0001\u0000\u0000\u0000\u0431\u0432\u0001\u0000\u0000\u0000\u0432"+ + "\u0433\u0001\u0000\u0000\u0000\u0433\u0434\u0005j\u0000\u0000\u0434\u0442"+ + "\u0001\u0000\u0000\u0000\u0435\u0439\u0005\'\u0000\u0000\u0436\u0438\u0003"+ + "\u00ceg\u0000\u0437\u0436\u0001\u0000\u0000\u0000\u0438\u043b\u0001\u0000"+ + "\u0000\u0000\u0439\u0437\u0001\u0000\u0000\u0000\u0439\u043a\u0001\u0000"+ + "\u0000\u0000\u043a\u043d\u0001\u0000\u0000\u0000\u043b\u0439\u0001\u0000"+ + "\u0000\u0000\u043c\u043e\u0003\u00d6k\u0000\u043d\u043c\u0001\u0000\u0000"+ + "\u0000\u043e\u043f\u0001\u0000\u0000\u0000\u043f\u043d\u0001\u0000\u0000"+ + "\u0000\u043f\u0440\u0001\u0000\u0000\u0000\u0440\u0442\u0001\u0000\u0000"+ + "\u0000\u0441\u0405\u0001\u0000\u0000\u0000\u0441\u0414\u0001\u0000\u0000"+ + "\u0000\u0441\u0421\u0001\u0000\u0000\u0000\u0441\u0435\u0001\u0000\u0000"+ + "\u0000\u0442\u00cb\u0001\u0000\u0000\u0000\u0443\u0444\u0007\u0001\u0000"+ + "\u0000\u0444\u00cd\u0001\u0000\u0000\u0000\u0445\u0446\u0003\u00d0h\u0000"+ + "\u0446\u0447\u0005?\u0000\u0000\u0447\u0448\u0003\u00d2i\u0000\u0448\u00cf"+ + "\u0001\u0000\u0000\u0000\u0449\u044a\u0007\n\u0000\u0000\u044a\u00d1\u0001"+ + "\u0000\u0000\u0000\u044b\u0450\u0003\u00d8l\u0000\u044c\u044d\u0005D\u0000"+ + "\u0000\u044d\u044f\u0003\u00d8l\u0000\u044e\u044c\u0001\u0000\u0000\u0000"+ + "\u044f\u0452\u0001\u0000\u0000\u0000\u0450\u044e\u0001\u0000\u0000\u0000"+ + "\u0450\u0451\u0001\u0000\u0000\u0000\u0451\u0456\u0001\u0000\u0000\u0000"+ + "\u0452\u0450\u0001\u0000\u0000\u0000\u0453\u0456\u0005l\u0000\u0000\u0454"+ + "\u0456\u0005e\u0000\u0000\u0455\u044b\u0001\u0000\u0000\u0000\u0455\u0453"+ + "\u0001\u0000\u0000\u0000\u0455\u0454\u0001\u0000\u0000\u0000\u0456\u00d3"+ + "\u0001\u0000\u0000\u0000\u0457\u0458\u0007\u000b\u0000\u0000\u0458\u00d5"+ + "\u0001\u0000\u0000\u0000\u0459\u045b\u0003\u00d4j\u0000\u045a\u0459\u0001"+ + "\u0000\u0000\u0000\u045b\u045c\u0001\u0000\u0000\u0000\u045c\u045a\u0001"+ + "\u0000\u0000\u0000\u045c\u045d\u0001\u0000\u0000\u0000\u045d\u0467\u0001"+ + "\u0000\u0000\u0000\u045e\u0462\u0005i\u0000\u0000\u045f\u0461\u0003\u00d6"+ + "k\u0000\u0460\u045f\u0001\u0000\u0000\u0000\u0461\u0464\u0001\u0000\u0000"+ + "\u0000\u0462\u0460\u0001\u0000\u0000\u0000\u0462\u0463\u0001\u0000\u0000"+ + "\u0000\u0463\u0465\u0001\u0000\u0000\u0000\u0464\u0462\u0001\u0000\u0000"+ + "\u0000\u0465\u0467\u0005j\u0000\u0000\u0466\u045a\u0001\u0000\u0000\u0000"+ + "\u0466\u045e\u0001\u0000\u0000\u0000\u0467\u00d7\u0001\u0000\u0000\u0000"+ + "\u0468\u0469\u0003\u00dam\u0000\u0469\u046a\u0005B\u0000\u0000\u046a\u046b"+ + "\u0003\u00deo\u0000\u046b\u0472\u0001\u0000\u0000\u0000\u046c\u046d\u0003"+ + "\u00deo\u0000\u046d\u046e\u0005A\u0000\u0000\u046e\u046f\u0003\u00dcn"+ + "\u0000\u046f\u0472\u0001\u0000\u0000\u0000\u0470\u0472\u0003\u00e0p\u0000"+ + "\u0471\u0468\u0001\u0000\u0000\u0000\u0471\u046c\u0001\u0000\u0000\u0000"+ + "\u0471\u0470\u0001\u0000\u0000\u0000\u0472\u00d9\u0001\u0000\u0000\u0000"+ + "\u0473\u0474\u0007\f\u0000\u0000\u0474\u00db\u0001\u0000\u0000\u0000\u0475"+ + "\u0476\u0007\f\u0000\u0000\u0476\u00dd\u0001\u0000\u0000\u0000\u0477\u0478"+ + "\u0007\f\u0000\u0000\u0478\u00df\u0001\u0000\u0000\u0000\u0479\u047a\u0007"+ + "\r\u0000\u0000\u047a\u00e1\u0001\u0000\u0000\u0000q\u00e5\u00f6\u0102"+ + "\u0121\u0130\u0136\u0149\u014d\u0152\u015a\u0162\u0167\u016a\u017a\u0182"+ + "\u0186\u018d\u0193\u0198\u01a1\u01a8\u01ae\u01b7\u01be\u01c6\u01ce\u01d2"+ + "\u01d6\u01db\u01df\u01e4\u01f0\u01f5\u01f9\u0207\u0212\u0218\u021f\u0228"+ + "\u0231\u0245\u024d\u0250\u0257\u0262\u0269\u0271\u027f\u0288\u0293\u029d"+ + "\u02a3\u02a5\u02a9\u02ae\u02bc\u02c3\u02e4\u02e8\u02f2\u02fb\u0304\u030c"+ + "\u0311\u0319\u031b\u0320\u0327\u032e\u0337\u033e\u0347\u034c\u0351\u035b"+ + "\u0361\u0369\u036b\u0376\u037d\u0388\u038d\u038f\u0396\u039e\u03a1\u03ab"+ + "\u03bc\u03c7\u03d2\u03d7\u03dd\u03e0\u03e5\u03f5\u03fa\u0402\u0409\u040f"+ + "\u0418\u041e\u0425\u042b\u0431\u0439\u043f\u0441\u0450\u0455\u045c\u0462"+ + "\u0466\u0471"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java index 9e3bb8f0ee009..bf60829b0b57f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java @@ -1294,21 +1294,47 @@ public LogicalPlan visitPromqlCommand(EsqlBaseParser.PromqlCommandContext ctx) { ); // TODO: Perform type and value validation - var queryCtx = ctx.promqlQueryPart(); - if (queryCtx == null || queryCtx.isEmpty()) { - throw new ParsingException(source, "PromQL expression cannot be empty"); - } + final String promqlQuery; + final int promqlStartLine; + final int promqlStartColumn; + if (ctx.NAMED_OR_POSITIONAL_PARAM() != null) { + // PromQL expression supplied as a query parameter, e.g. value=(?query) + QueryParam param = paramByNameOrPosition(ctx.NAMED_OR_POSITIONAL_PARAM()); + if (param == null) { + throw new ParsingException(source, "No value found for parameter [{}]", ctx.NAMED_OR_POSITIONAL_PARAM().getText()); + } + if (param.value() instanceof String s) { + promqlQuery = s; + } else { + throw new ParsingException( + source, + "Parameter [{}] in PromQL expression must be a string", + ctx.NAMED_OR_POSITIONAL_PARAM().getText() + ); + } + if (promqlQuery.isBlank()) { + throw new ParsingException(source, "PromQL expression cannot be empty"); + } + Token paramToken = ctx.NAMED_OR_POSITIONAL_PARAM().getSymbol(); + promqlStartLine = paramToken.getLine(); + promqlStartColumn = paramToken.getCharPositionInLine(); + } else { + var queryCtx = ctx.promqlQueryPart(); + if (queryCtx == null || queryCtx.isEmpty()) { + throw new ParsingException(source, "PromQL expression cannot be empty"); + } - Token startToken = queryCtx.getFirst().start; - Token stopToken = queryCtx.getLast().stop; - // copy the query verbatim to avoid missing tokens interpreted by the enclosing lexer - String promqlQuery = source(startToken, stopToken).text(); - if (promqlQuery.isBlank()) { - throw new ParsingException(source, "PromQL expression cannot be empty"); - } + Token startToken = queryCtx.getFirst().start; + Token stopToken = queryCtx.getLast().stop; + // copy the query verbatim to avoid missing tokens interpreted by the enclosing lexer + promqlQuery = source(startToken, stopToken).text(); + if (promqlQuery.isBlank()) { + throw new ParsingException(source, "PromQL expression cannot be empty"); + } - int promqlStartLine = startToken.getLine(); - int promqlStartColumn = startToken.getCharPositionInLine(); + promqlStartLine = startToken.getLine(); + promqlStartColumn = startToken.getCharPositionInLine(); + } PromqlParser promqlParser = new PromqlParser(); LogicalPlan promqlPlan; @@ -1497,7 +1523,16 @@ private String parseParamValueString(EsqlBaseParser.PromqlParamValueContext ctx) } private IndexPattern parseIndexPattern(EsqlBaseParser.PromqlParamValueContext ctx) { - if (ctx.promqlIndexPattern().isEmpty()) { + if (ctx.NAMED_OR_POSITIONAL_PARAM() != null) { + QueryParam param = paramByNameOrPosition(ctx.NAMED_OR_POSITIONAL_PARAM()); + if (param == null) { + throw new ParsingException(source(ctx), "No value found for parameter [{}]", ctx.NAMED_OR_POSITIONAL_PARAM().getText()); + } + if (param.value() instanceof String s) { + return new IndexPattern(source(ctx), s); + } + throw new ParsingException(source(ctx), "Parameter [{}] for index must be a string", ctx.NAMED_OR_POSITIONAL_PARAM().getText()); + } else if (ctx.promqlIndexPattern().isEmpty()) { // Default to all indices if no index pattern is provided return new IndexPattern(source(ctx), "*"); } else { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java index 32a3d9e5098c9..d451541426f63 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java @@ -55,7 +55,7 @@ public class PromqlCommand extends UnaryPlan /** * The name of the column containing the step value (aka time bucket) in range queries. */ - private static final String STEP_COLUMN_NAME = "step"; + public static final String STEP_COLUMN_NAME = "step"; private final LogicalPlan promqlPlan; private final Literal start; 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 39fe018fe7e2a..40679d8424937 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 @@ -245,7 +245,7 @@ public void execute( LOGGER.debug("ESQL query:\n{}", request.query()); TimeSpanMarker parsingProfile = executionInfo.queryProfile().parsing(); parsingProfile.start(); - EsqlStatement statement = parse(request); + EsqlStatement statement = request.parsedStatement() != null ? request.parsedStatement() : parse(request); gatherSettingsMetrics(statement); parsingProfile.stop(); viewResolver.replaceViews( diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java index fe8d91fa08997..d05c27ea71c4a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java @@ -39,12 +39,16 @@ import org.elasticsearch.xpack.core.async.AsyncExecutionId; import org.elasticsearch.xpack.core.async.AsyncTask; import org.elasticsearch.xpack.esql.Column; +import org.elasticsearch.xpack.esql.core.expression.Alias; +import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.parser.ParserUtils; import org.elasticsearch.xpack.esql.parser.ParsingException; import org.elasticsearch.xpack.esql.parser.QueryParam; import org.elasticsearch.xpack.esql.parser.QueryParams; +import org.elasticsearch.xpack.esql.plan.EsqlStatement; +import org.elasticsearch.xpack.esql.plan.logical.Row; import org.elasticsearch.xpack.esql.plugin.EsqlQueryStatus; import java.io.IOException; @@ -74,6 +78,19 @@ public class EsqlQueryRequestTests extends ESTestCase { private final NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(List.of(EsqlQueryStatus.ENTRY)); + public void testSyncRequestWithPlanCarriesParsedStatement() { + var plan = new Row(Source.EMPTY, List.of(new Alias(Source.EMPTY, "x", new Literal(Source.EMPTY, 1, DataType.INTEGER)))); + var statement = new EsqlStatement(plan, List.of()); + EsqlQueryRequest request = EsqlQueryRequest.syncEsqlQueryRequestWithPlan(statement); + assertFalse(request.async()); + assertSame(statement, request.parsedStatement()); + } + + public void testSyncRequestFromStringHasNullParsedStatement() { + EsqlQueryRequest request = EsqlQueryRequest.syncEsqlQueryRequest("ROW x = 1"); + assertNull(request.parsedStatement()); + } + public void testParseFields() throws IOException { String query = randomAlphaOfLengthBetween(1, 100); boolean columnar = randomBoolean(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/promql/PromqlParserTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/promql/PromqlParserTests.java index 84c11ed2e4e27..64064e0cbb91c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/promql/PromqlParserTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/promql/PromqlParserTests.java @@ -514,6 +514,155 @@ public void testMatchMetricNameMultipleTimesError() { assertThat(e.getMessage(), containsString("Metric name must not be defined twice: [foo] or [bar]")); } + // ---- query-as-param tests ---- + + public void testQueryAsNamedParam() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=test step=5m (?query)", paramsAsConstant("query", "foo")), + PromqlCommand.class + ); + assertThat(promql.step().value(), equalTo(Duration.ofMinutes(5))); + assertThat(promql.promqlPlan(), instanceOf(InstantSelector.class)); + } + + public void testQueryAsPositionalParam() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=test step=5m (?1)", paramsAsConstant(null, "foo")), + PromqlCommand.class + ); + assertThat(promql.step().value(), equalTo(Duration.ofMinutes(5))); + assertThat(promql.promqlPlan(), instanceOf(InstantSelector.class)); + } + + public void testQueryAsParamWithExplicitValueName() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=test step=5m value=(?query)", paramsAsConstant("query", "foo")), + PromqlCommand.class + ); + assertThat(promql.step().value(), equalTo(Duration.ofMinutes(5))); + assertThat(promql.promqlPlan(), instanceOf(InstantSelector.class)); + } + + public void testQueryAsParamWithAllParamsAsNamedParams() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery( + "PROMQL step=?step start=?start end=?end index=?index value=(?query)", + new QueryParams( + List.of( + paramAsConstant("query", "avg(foo)"), + paramAsConstant("step", "1m"), + paramAsConstant("start", "2025-10-31T00:00:00Z"), + paramAsConstant("end", "2025-10-31T01:00:00Z"), + paramAsConstant("index", "my-metrics") + ) + ) + ), + PromqlCommand.class + ); + assertThat(promql.step().value(), equalTo(Duration.ofMinutes(1))); + assertThat(promql.start().value(), equalTo(Instant.parse("2025-10-31T00:00:00Z").toEpochMilli())); + assertThat(promql.end().value(), equalTo(Instant.parse("2025-10-31T01:00:00Z").toEpochMilli())); + List unresolvedRelations = promql.collect(UnresolvedRelation.class); + assertThat(unresolvedRelations.getFirst().indexPattern().indexPattern(), equalTo("my-metrics")); + assertThat(promql.promqlPlan(), instanceOf(AcrossSeriesAggregate.class)); + } + + public void testQueryAsNamedParamWithoutParens() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=test step=5m ?query", paramsAsConstant("query", "foo")), + PromqlCommand.class + ); + assertThat(promql.step().value(), equalTo(Duration.ofMinutes(5))); + assertThat(promql.promqlPlan(), instanceOf(InstantSelector.class)); + } + + public void testQueryAsPositionalParamWithoutParens() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=test step=5m ?1", paramsAsConstant(null, "foo")), + PromqlCommand.class + ); + assertThat(promql.step().value(), equalTo(Duration.ofMinutes(5))); + assertThat(promql.promqlPlan(), instanceOf(InstantSelector.class)); + } + + public void testQueryAsParamNonStringError() { + ParsingException e = assertThrows( + ParsingException.class, + () -> TEST_PARSER.parseQuery("PROMQL index=test step=5m (?query)", paramsAsConstant("query", 42)) + ); + assertThat(e.getMessage(), containsString("Parameter [?query] in PromQL expression must be a string")); + } + + public void testQueryAsParamUnknownParamError() { + ParsingException e = assertThrows( + ParsingException.class, + () -> TEST_PARSER.parseQuery("PROMQL index=test step=5m (?unknown)", new QueryParams(List.of())) + ); + assertThat(e.getMessage(), containsString("No value found for parameter [?unknown]")); + } + + public void testQueryAsParamUnknownNamedParamError() { + ParsingException e = assertThrows( + ParsingException.class, + () -> TEST_PARSER.parseQuery("PROMQL index=test step=5m (?_typo)", paramsAsConstant("_correct", "foo")) + ); + assertThat(e.getMessage(), containsString("No value found for parameter [?_typo]")); + } + + public void testQueryAsParamBlankStringError() { + ParsingException e = assertThrows( + ParsingException.class, + () -> TEST_PARSER.parseQuery("PROMQL index=test step=5m (?query)", paramsAsConstant("query", "")) + ); + assertThat(e.getMessage(), containsString("PromQL expression cannot be empty")); + } + + public void testQueryAsNamedParamWithValueName() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=test step=5m value=?query", paramsAsConstant("query", "foo")), + PromqlCommand.class + ); + assertThat(promql.step().value(), equalTo(Duration.ofMinutes(5))); + assertThat(promql.promqlPlan(), instanceOf(InstantSelector.class)); + assertThat(promql.valueColumnName(), equalTo("value")); + } + + // ---- index-as-param tests ---- + + public void testIndexAsNamedParam() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=?idx step=5m avg(foo)", paramsAsConstant("idx", "my-metrics")), + PromqlCommand.class + ); + List unresolvedRelations = promql.collect(UnresolvedRelation.class); + assertThat(unresolvedRelations.getFirst().indexPattern().indexPattern(), equalTo("my-metrics")); + } + + public void testIndexAsPositionalParam() { + PromqlCommand promql = as( + TEST_PARSER.parseQuery("PROMQL index=?1 step=5m avg(foo)", paramsAsConstant(null, "my-metrics")), + PromqlCommand.class + ); + List unresolvedRelations = promql.collect(UnresolvedRelation.class); + assertThat(unresolvedRelations.getFirst().indexPattern().indexPattern(), equalTo("my-metrics")); + } + + public void testIndexAsParamUnknownParamError() { + ParsingException e = assertThrows( + ParsingException.class, + () -> TEST_PARSER.parseQuery("PROMQL index=?unknown step=5m avg(foo)", new QueryParams(List.of())) + ); + assertThat(e.getMessage(), containsString("No value found for parameter [?unknown]")); + } + + public void testIndexAsParamNonStringError() { + ParsingException e = assertThrows( + ParsingException.class, + () -> TEST_PARSER.parseQuery("PROMQL index=?idx step=5m avg(foo)", paramsAsConstant("idx", 42)) + ); + assertThat(e.getMessage(), containsString("Parameter [?idx] for index must be a string")); + } + private static PromqlCommand parse(String query) { return as(TEST_PARSER.parseQuery(query), PromqlCommand.class); } diff --git a/x-pack/plugin/prometheus/build.gradle b/x-pack/plugin/prometheus/build.gradle index 7a6c64fd661ce..4e557cf535b46 100644 --- a/x-pack/plugin/prometheus/build.gradle +++ b/x-pack/plugin/prometheus/build.gradle @@ -10,14 +10,17 @@ esplugin { name = 'x-pack-prometheus' description = 'The Prometheus plugin defines Prometheus mappings and contains Prometheus-compatible APIs.' classname ='org.elasticsearch.xpack.prometheus.PrometheusPlugin' - extendedPlugins = ['x-pack-core'] + extendedPlugins = ['x-pack-core', 'x-pack-esql'] } def protobufVersion = "4.32.0" dependencies { compileOnly project(path: xpackModule('core')) + compileOnly project(path: xpackModule('esql')) testImplementation(testArtifact(project(xpackModule('core')))) + testImplementation project(path: xpackModule('esql')) + testImplementation project(path: xpackModule('esql-core')) // stack is needed as we import the metrics@custom template clusterModules project(xpackModule('stack')) // transitive dependencies of stack diff --git a/x-pack/plugin/prometheus/src/javaRestTest/java/org/elasticsearch/xpack/prometheus/PrometheusQueryRangeRestIT.java b/x-pack/plugin/prometheus/src/javaRestTest/java/org/elasticsearch/xpack/prometheus/PrometheusQueryRangeRestIT.java new file mode 100644 index 0000000000000..26b5a12183c45 --- /dev/null +++ b/x-pack/plugin/prometheus/src/javaRestTest/java/org/elasticsearch/xpack/prometheus/PrometheusQueryRangeRestIT.java @@ -0,0 +1,160 @@ +/* + * 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.prometheus; + +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.FeatureFlag; +import org.elasticsearch.test.cluster.local.distribution.DistributionType; +import org.elasticsearch.test.rest.ESRestTestCase; +import org.elasticsearch.test.rest.ObjectPath; +import org.elasticsearch.xpack.prometheus.proto.RemoteWrite; +import org.junit.ClassRule; + +import java.io.IOException; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +/** + * Integration tests for the Prometheus {@code /api/v1/query_range} endpoint. + */ +public class PrometheusQueryRangeRestIT extends ESRestTestCase { + + private static final String USER = "test_admin"; + private static final String PASS = "x-pack-test-password"; + + @ClassRule + public static ElasticsearchCluster cluster = ElasticsearchCluster.local() + .distribution(DistributionType.DEFAULT) + .user(USER, PASS, "superuser", false) + .setting("xpack.security.enabled", "true") + .setting("xpack.security.autoconfiguration.enabled", "false") + .setting("xpack.license.self_generated.type", "trial") + .setting("xpack.ml.enabled", "false") + .setting("xpack.watcher.enabled", "false") + .feature(FeatureFlag.PROMETHEUS_FEATURE_FLAG) + .build(); + + @Override + protected String getTestRestCluster() { + return cluster.getHttpAddresses(); + } + + @Override + protected Settings restClientSettings() { + String token = basicAuthHeaderValue(USER, new SecureString(PASS.toCharArray())); + return Settings.builder().put(super.restClientSettings()).put(ThreadContext.PREFIX + ".Authorization", token).build(); + } + + public void testQueryRangeWithIngestedData() throws Exception { + ingestTestData(); + + ObjectPath responsePath = executeQueryRange(null); + assertMetricResults(responsePath); + } + + public void testQueryRangeWithExplicitIndex() throws Exception { + ingestTestData(); + + ObjectPath responsePath = executeQueryRange("metrics-generic.prometheus-default"); + assertMetricResults(responsePath); + } + + private static void assertMetricResults(ObjectPath responsePath) throws IOException { + + assertThat(responsePath.evaluate("data.result"), hasSize(1)); + assertThat(responsePath.evaluate("data.result.0.metric.job"), equalTo("test_job")); + assertThat(responsePath.evaluate("data.result.0.metric.instance"), equalTo("localhost:9090")); + assertThat(responsePath.evaluate("data.result.0.values"), hasSize(5)); + } + + private ObjectPath executeQueryRange(String index) throws Exception { + Request request = new Request(randomBoolean() ? "GET" : "POST", "/_prometheus/api/v1/query_range"); + request.addParameter("query", "test_gauge_qr"); + request.addParameter("start", "2026-01-01T00:00:00Z"); + request.addParameter("end", "2026-01-01T00:05:00Z"); + request.addParameter("step", "60s"); + if (index != null) { + request.addParameter("index", index); + } + + Response response = client().performRequest(request); + assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); + + ObjectPath responsePath = ObjectPath.createFromResponse(response); + assertThat(responsePath.evaluate("status"), equalTo("success")); + assertThat(responsePath.evaluate("data.resultType"), equalTo("matrix")); + return responsePath; + } + + private void ingestTestData() throws IOException { + long baseTimestamp = 1767225600000L; // 2026-01-01T00:00:00Z + + Request putCustomTemplate = new Request("PUT", "/_component_template/metrics-prometheus@custom"); + putCustomTemplate.setJsonEntity(""" + { + "template": { + "settings": { + "index": { + "time_series": { + "start_time": "2026-01-01T00:00:00Z" + } + } + } + } + } + """); + client().performRequest(putCustomTemplate); + + RemoteWrite.WriteRequest.Builder writeRequestBuilder = RemoteWrite.WriteRequest.newBuilder(); + for (int i = 0; i < 5; i++) { + writeRequestBuilder.addTimeseries( + RemoteWrite.TimeSeries.newBuilder() + .addLabels(RemoteWrite.Label.newBuilder().setName("__name__").setValue("test_gauge_qr").build()) + .addLabels(RemoteWrite.Label.newBuilder().setName("job").setValue("test_job").build()) + .addLabels(RemoteWrite.Label.newBuilder().setName("instance").setValue("localhost:9090").build()) + .addSamples(RemoteWrite.Sample.newBuilder().setValue(i * 10.0).setTimestamp(baseTimestamp + i * 60_000L).build()) + .build() + ); + } + + Request writeRequest = new Request("POST", "/_prometheus/api/v1/write"); + writeRequest.setEntity( + new ByteArrayEntity(writeRequestBuilder.build().toByteArray(), ContentType.create("application/x-protobuf")) + ); + Response writeResponse = client().performRequest(writeRequest); + assertThat(writeResponse.getStatusLine().getStatusCode(), equalTo(204)); + if (writeResponse.getEntity() != null) { + // A non-empty body would contain partial failure details from the underlying bulk request. + assertThat(EntityUtils.toString(writeResponse.getEntity()), equalTo("")); + } + + Request refresh = new Request("POST", "/metrics-generic.prometheus-default/_refresh"); + client().performRequest(refresh); + + Request searchFailures = new Request("GET", "/metrics-generic.prometheus-default::failures/_search"); + searchFailures.setJsonEntity(""" + { + "track_total_hits": true, + "size": 0 + } + """); + ObjectPath failuresSearchPath = ObjectPath.createFromResponse(client().performRequest(searchFailures)); + Number totalFailures = failuresSearchPath.evaluate("hits.total.value"); + assertThat(totalFailures.intValue(), equalTo(0)); + } + +} diff --git a/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/PrometheusPlugin.java b/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/PrometheusPlugin.java index e8b0cd00032f9..9a99022a78aa3 100644 --- a/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/PrometheusPlugin.java +++ b/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/PrometheusPlugin.java @@ -22,6 +22,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestHandler; import org.elasticsearch.xpack.core.XPackSettings; +import org.elasticsearch.xpack.prometheus.rest.PrometheusQueryRangeRestAction; import org.elasticsearch.xpack.prometheus.rest.PrometheusRemoteWriteRestAction; import org.elasticsearch.xpack.prometheus.rest.PrometheusRemoteWriteTransportAction; @@ -97,7 +98,10 @@ public Collection getRestHandlers( ) { if (enabled) { assert indexingPressure.get() != null : "indexing pressure must be set if plugin is enabled"; - return List.of(new PrometheusRemoteWriteRestAction(indexingPressure.get(), maxProtobufContentLengthBytes, recycler.get())); + return List.of( + new PrometheusRemoteWriteRestAction(indexingPressure.get(), maxProtobufContentLengthBytes, recycler.get()), + new PrometheusQueryRangeRestAction() + ); } return List.of(); } diff --git a/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeResponseListener.java b/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeResponseListener.java new file mode 100644 index 0000000000000..5970e5288857a --- /dev/null +++ b/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeResponseListener.java @@ -0,0 +1,295 @@ +/* + * 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.prometheus.rest; + +import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.logging.LogManager; +import org.elasticsearch.logging.Logger; +import org.elasticsearch.rest.RestChannel; +import org.elasticsearch.rest.RestResponse; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.esql.action.ColumnInfo; +import org.elasticsearch.xpack.core.esql.action.EsqlResponse; +import org.elasticsearch.xpack.esql.action.EsqlQueryResponse; +import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Listens for an {@link EsqlQueryResponse}, converts its columnar result into the + * Prometheus range query JSON format, and sends it as a {@link RestResponse}. + * + * @see Prometheus Range Vectors + */ +class PrometheusQueryRangeResponseListener implements ActionListener { + + private static final Logger logger = LogManager.getLogger(PrometheusQueryRangeResponseListener.class); + private static final String JSON_CONTENT_TYPE = XContentType.JSON.mediaType(); + + // Column names expected in the ES|QL PROMQL response. + static final String VALUE_COLUMN = "value"; + static final String STEP_PARAM = "step"; + + // Fixed column indices produced by the PROMQL command, matching the original PromqlCommand output order + // restored by the Project node: value, step, dimensions... + private static final int VALUE_COL_IDX = 0; + private static final int STEP_COL_IDX = 1; + private static final int DIMENSION_COL_START_IDX = 2; + + private final RestChannel channel; + + PrometheusQueryRangeResponseListener(RestChannel channel) { + this.channel = channel; + } + + @Override + public void onResponse(EsqlQueryResponse queryResponse) { + // Do not close queryResponse here - the transport framework's respondAndRelease handles decRef. + // If we close it manually, it will cause an AssertionError ("invalid decRef call: already closed") + // and crash the node. + try { + EsqlResponse response = queryResponse.response(); + XContentBuilder builder = convertToPrometheusJson(response); + channel.sendResponse(new RestResponse(RestStatus.OK, builder)); + } catch (Exception e) { + sendErrorResponse(e); + } + } + + @Override + public void onFailure(Exception e) { + sendErrorResponse(e); + } + + private void sendErrorResponse(Exception e) { + logger.debug("PromQL query_range request failed", e); + try { + RestStatus status = ExceptionsHelper.status(e); + XContentBuilder builder = buildErrorJson(status, e.getMessage()); + channel.sendResponse(new RestResponse(status, builder)); + } catch (Exception inner) { + logger.error("failed to send error response for PromQL query_range", inner); + try { + channel.sendResponse(new RestResponse(RestStatus.INTERNAL_SERVER_ERROR, JSON_CONTENT_TYPE, new BytesArray("{}"))); + } catch (Exception ignored) {} + } + } + + /** + * Converts an ES|QL response into a Prometheus-compatible JSON response. + * + *

The ES|QL PROMQL command produces rows with the following column order + * (matching the original PromqlCommand output restored by the Project node): + *

    + *
  1. Column 0: value ({@code double})
  2. + *
  3. Column 1: step ({@code long}, epoch milliseconds)
  4. + *
  5. Columns 2..N-1: either a single {@code _timeseries} keyword column (JSON labels) + * or individual dimension/label columns
  6. + *
+ */ + static XContentBuilder convertToPrometheusJson(EsqlResponse response) throws IOException { + List columns = response.columns(); + if (columns.size() < 1 || VALUE_COLUMN.equals(columns.get(VALUE_COL_IDX).name()) == false) { + throw new IllegalStateException("PROMQL response is missing required 'value' column at index " + VALUE_COL_IDX); + } + if (columns.size() < 2 || STEP_PARAM.equals(columns.get(STEP_COL_IDX).name()) == false) { + throw new IllegalStateException("PROMQL response is missing required 'step' column at index " + STEP_COL_IDX); + } + final boolean useSeriesCol = columns.size() > DIMENSION_COL_START_IDX + && MetadataAttribute.TIMESERIES.equals(columns.get(DIMENSION_COL_START_IDX).name()); + + Map seriesMap = new LinkedHashMap<>(); + + for (Iterable row : response.rows()) { + Object[] values = toArray(row, columns.size()); + + String seriesKey; + Map metric; + + if (useSeriesCol) { + seriesKey = values[DIMENSION_COL_START_IDX] != null ? values[DIMENSION_COL_START_IDX].toString() : "{}"; + metric = null; + } else { + StringBuilder keyBuilder = new StringBuilder(); + metric = new LinkedHashMap<>(); + for (int i = DIMENSION_COL_START_IDX; i < columns.size(); i++) { + String label = columns.get(i).name(); + String value = values[i] != null ? values[i].toString() : ""; + metric.put(label, value); + keyBuilder.append(label).append('\0').append(value).append('\0'); + } + seriesKey = keyBuilder.toString(); + } + + String sampleValue = formatSampleValue(values[VALUE_COL_IDX]); + double timestamp = parseTimestamp(values[STEP_COL_IDX]); + + SeriesData series = seriesMap.get(seriesKey); + if (series == null) { + series = new SeriesData(useSeriesCol ? seriesKey : null, metric); + seriesMap.put(seriesKey, series); + } + series.values.add(new double[] { timestamp }); + series.stringValues.add(sampleValue); + } + + return buildSuccessJson(seriesMap); + } + + private static Object[] toArray(Iterable row, int size) { + Object[] arr = new Object[size]; + int i = 0; + for (Object val : row) { + if (i < size) { + arr[i++] = val; + } + } + return arr; + } + + /** + * Converts a timestamp from the ES|QL response into Unix epoch seconds. + * The step column is cast to {@code LONG} (epoch milliseconds) via {@code TO_LONG(step)} in the ES|QL query. + */ + private static double parseTimestamp(Object value) { + if (value instanceof Number n) { + return n.doubleValue() / 1000.0; + } + return 0; + } + + /** + * Formats a sample value for the Prometheus JSON response. + * Prometheus represents values as strings, with special handling for NaN and Infinity. + */ + static String formatSampleValue(Object value) { + if (value == null) { + return "NaN"; + } + if (value instanceof Double d) { + if (Double.isNaN(d)) { + return "NaN"; + } else if (Double.isInfinite(d)) { + return d > 0 ? "+Inf" : "-Inf"; + } + return d.toString(); + } + return value.toString(); + } + + private static XContentBuilder buildSuccessJson(Map seriesMap) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("status", "success"); + builder.startObject("data"); + builder.field("resultType", "matrix"); + builder.startArray("result"); + + for (SeriesData series : seriesMap.values()) { + builder.startObject(); + + builder.startObject("metric"); + if (series.rawSeriesJson != null) { + writeMetricFromSeriesJson(builder, series.rawSeriesJson); + } else if (series.labels != null) { + for (Map.Entry entry : series.labels.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + } + builder.endObject(); + + builder.startArray("values"); + for (int i = 0; i < series.values.size(); i++) { + builder.startArray(); + builder.value(series.values.get(i)[0]); + builder.value(series.stringValues.get(i)); + builder.endArray(); + } + builder.endArray(); + + builder.endObject(); + } + + builder.endArray(); + builder.endObject(); + builder.endObject(); + return builder; + } + + /** + * Writes metric labels from a {@code _timeseries} JSON value. + *
    + *
  • The {@code labels} namespace is unwrapped without a prefix: + * {@code {"labels":{"__name__":"up","job":"prometheus"}}} → fields {@code __name__}, {@code job}.
  • + *
  • All other namespaces are flattened recursively with dot-separated paths: + * {@code {"attributes":{"resource":{"service.name":"foo"}}}} → field {@code attributes.resource.service.name}.
  • + *
+ */ + private static void writeMetricFromSeriesJson(XContentBuilder builder, String seriesJson) throws IOException { + try (XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, seriesJson)) { + Map root = parser.map(); + Object labelsObj = root.remove("labels"); + if (labelsObj instanceof Map labels) { + writeMetricFields(builder, "", labels); + } + writeMetricFields(builder, "", root); + } + } + + private static void writeMetricFields(XContentBuilder builder, String prefix, Map map) throws IOException { + for (Map.Entry entry : map.entrySet()) { + String key = prefix + entry.getKey(); + if (entry.getValue() instanceof Map nested) { + writeMetricFields(builder, key + ".", nested); + } else if (entry.getValue() != null) { + builder.field(key, entry.getValue().toString()); + } + } + } + + static XContentBuilder buildErrorJson(RestStatus status, String message) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("status", "error"); + builder.field("errorType", mapErrorType(status)); + builder.field("error", message != null ? message : "unknown error"); + builder.endObject(); + return builder; + } + + private static String mapErrorType(RestStatus status) { + return switch (status) { + case BAD_REQUEST -> "bad_data"; + case SERVICE_UNAVAILABLE, REQUEST_TIMEOUT, GATEWAY_TIMEOUT -> "timeout"; + default -> "execution"; + }; + } + + static class SeriesData { + final String rawSeriesJson; + final Map labels; + final List values = new ArrayList<>(); + final List stringValues = new ArrayList<>(); + + SeriesData(String rawSeriesJson, Map labels) { + this.rawSeriesJson = rawSeriesJson; + this.labels = labels; + } + } +} diff --git a/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeRestAction.java b/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeRestAction.java new file mode 100644 index 0000000000000..98a60b951b7ad --- /dev/null +++ b/x-pack/plugin/prometheus/src/main/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeRestAction.java @@ -0,0 +1,160 @@ +/* + * 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.prometheus.rest; + +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.xpack.esql.action.EsqlQueryAction; +import org.elasticsearch.xpack.esql.action.EsqlQueryRequest; +import org.elasticsearch.xpack.esql.core.expression.Alias; +import org.elasticsearch.xpack.esql.core.expression.Literal; +import org.elasticsearch.xpack.esql.core.expression.UnresolvedTimestamp; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToLong; +import org.elasticsearch.xpack.esql.parser.PromqlParser; +import org.elasticsearch.xpack.esql.parser.promql.PromqlParserUtils; +import org.elasticsearch.xpack.esql.plan.EsqlStatement; +import org.elasticsearch.xpack.esql.plan.IndexPattern; +import org.elasticsearch.xpack.esql.plan.logical.Eval; +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; +import org.elasticsearch.xpack.esql.plan.logical.Project; +import org.elasticsearch.xpack.esql.plan.logical.SourceCommand; +import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation; +import org.elasticsearch.xpack.esql.plan.logical.promql.PromqlCommand; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +/** + * REST handler for the Prometheus {@code /api/v1/query_range} endpoint. + * Translates Prometheus query_range parameters into an ES|QL {@link PromqlCommand} logical plan, + * executes it, and converts the result into the Prometheus matrix JSON format. + * + * @see Prometheus Range Queries API + */ +@ServerlessScope(Scope.PUBLIC) +public class PrometheusQueryRangeRestAction extends BaseRestHandler { + + static final String QUERY_PARAM = "query"; + static final String START_PARAM = "start"; + static final String END_PARAM = "end"; + public static final String INDEX_PARAM = "index"; + + private static final Duration DEFAULT_SCRAPE_INTERVAL = Duration.ofMinutes(1); + + @Override + public String getName() { + return "prometheus_query_range_action"; + } + + @Override + public List routes() { + return List.of(new Route(GET, "/_prometheus/api/v1/query_range"), new Route(POST, "/_prometheus/api/v1/query_range")); + } + + @Override + public boolean mediaTypesValid(RestRequest request) { + // We do not parse or allow "application/x-www-form-urlencoded" here. + // Elasticsearch's core RestController.isContentTypeDisallowed blocks it as a CSRF protection mechanism. + // Therefore, Prometheus clients sending POST requests must use query parameters. + return request.hasContent() == false || request.getXContentType() != null; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String query = getRequiredParam(request, QUERY_PARAM); + String start = getRequiredParam(request, START_PARAM); + String end = getRequiredParam(request, END_PARAM); + String step = getRequiredParam(request, PrometheusQueryRangeResponseListener.STEP_PARAM); + String index = request.param(INDEX_PARAM, "*"); + + EsqlStatement statement = buildStatement(query, index, start, end, step); + EsqlQueryRequest esqlRequest = EsqlQueryRequest.syncEsqlQueryRequestWithPlan(statement); + + return channel -> client.execute(EsqlQueryAction.INSTANCE, esqlRequest, new PrometheusQueryRangeResponseListener(channel)); + } + + private static String getRequiredParam(RestRequest request, String name) { + String value = request.param(name); + if (value == null || value.isEmpty()) { + throw new IllegalArgumentException("required parameter \"" + name + "\" is missing"); + } + return value; + } + + /** + * Builds an {@link EsqlStatement} containing a {@link PromqlCommand} with an {@link Eval} node + * for the {@code TO_LONG(step)} conversion, bypassing ES|QL string construction and parsing. + */ + static EsqlStatement buildStatement(String query, String index, String startStr, String endStr, String stepStr) { + Instant startInstant = PromqlParserUtils.parseDate(Source.EMPTY, startStr); + Instant endInstant = PromqlParserUtils.parseDate(Source.EMPTY, endStr); + Duration stepDuration = parseStep(Source.EMPTY, stepStr); + + Literal startLiteral = Literal.dateTime(Source.EMPTY, startInstant); + Literal endLiteral = Literal.dateTime(Source.EMPTY, endInstant); + Literal stepLiteral = Literal.timeDuration(Source.EMPTY, stepDuration); + + IndexPattern indexPattern = new IndexPattern(Source.EMPTY, index); + UnresolvedRelation unresolvedRelation = new UnresolvedRelation( + Source.EMPTY, + indexPattern, + false, + List.of(), + null, + SourceCommand.PROMQL + ); + + PromqlParser promqlParser = new PromqlParser(); + LogicalPlan promqlPlan = promqlParser.createStatement(query, startLiteral, endLiteral, 0, 0); + + PromqlCommand promqlCommand = new PromqlCommand( + Source.EMPTY, + unresolvedRelation, + promqlPlan, + startLiteral, + endLiteral, + stepLiteral, + Literal.NULL, + Literal.timeDuration(Source.EMPTY, DEFAULT_SCRAPE_INTERVAL), + PrometheusQueryRangeResponseListener.VALUE_COLUMN, + new UnresolvedTimestamp(Source.EMPTY) + ); + + // TO_LONG converts the step datetime to epoch millis, avoiding the need to parse a date string in the response listener. + Alias stepAlias = new Alias( + Source.EMPTY, + PromqlCommand.STEP_COLUMN_NAME, + new ToLong( + Source.EMPTY, + promqlCommand.output().stream().filter(a -> a.name().equals(PromqlCommand.STEP_COLUMN_NAME)).findFirst().get() + ) + ); + Eval eval = new Eval(Source.EMPTY, promqlCommand, List.of(stepAlias)); + + // Eval appends the replaced step column at the end; project to restore the original PromqlCommand column order. + Project project = new Project(Source.EMPTY, eval, promqlCommand.output()); + + return new EsqlStatement(project, List.of()); + } + + private static Duration parseStep(Source source, String value) { + try { + return Duration.ofSeconds(Integer.parseInt(value)); + } catch (NumberFormatException ignore) { + return PromqlParserUtils.parseDuration(source, value); + } + } +} diff --git a/x-pack/plugin/prometheus/src/test/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeResponseListenerTests.java b/x-pack/plugin/prometheus/src/test/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeResponseListenerTests.java new file mode 100644 index 0000000000000..1bd0df52b4cea --- /dev/null +++ b/x-pack/plugin/prometheus/src/test/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeResponseListenerTests.java @@ -0,0 +1,287 @@ +/* + * 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.prometheus.rest; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.rest.ObjectPath; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.esql.action.ColumnInfo; +import org.elasticsearch.xpack.core.esql.action.EsqlResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +public class PrometheusQueryRangeResponseListenerTests extends ESTestCase { + + // Column names are bare label names (e.g. "job", "instance") — no "labels." prefix. + // The Prometheus data stream maps labels with type: passthrough, so PROMQL resolves + // labels.job → column "job", labels.instance → column "instance", etc. + public void testConvertRangeQueryWithIndividualLabels() throws IOException { + List columns = List.of( + new TestColumnInfo("value", "double"), + new TestColumnInfo("step", "long"), + new TestColumnInfo("__name__", "keyword"), + new TestColumnInfo("instance", "keyword"), + new TestColumnInfo("job", "keyword") + ); + + List> rows = List.of( + List.of(1.5, 1735689600000L, "http_requests_total", "localhost:9090", "prometheus"), + List.of(2.0, 1735689660000L, "http_requests_total", "localhost:9090", "prometheus"), + List.of(3.0, 1735689600000L, "http_requests_total", "localhost:9091", "prometheus"), + List.of(4.0, 1735689660000L, "http_requests_total", "localhost:9091", "prometheus") + ); + + EsqlResponse response = new TestEsqlResponse(columns, rows); + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)) { + ObjectPath path = toObjectPath(builder); + assertSuccessMatrix(path); + + assertThat(path.evaluate("data.result"), hasSize(2)); + assertThat(path.evaluate("data.result.0.metric.__name__"), equalTo("http_requests_total")); + assertThat(path.evaluate("data.result.0.metric.instance"), equalTo("localhost:9090")); + assertThat(path.evaluate("data.result.0.metric.job"), equalTo("prometheus")); + assertThat(path.evaluate("data.result.0.values.0"), equalTo(List.of(1735689600.0, "1.5"))); + assertThat(path.evaluate("data.result.0.values.1"), equalTo(List.of(1735689660.0, "2.0"))); + assertThat(path.evaluate("data.result.1.metric.__name__"), equalTo("http_requests_total")); + assertThat(path.evaluate("data.result.1.metric.instance"), equalTo("localhost:9091")); + assertThat(path.evaluate("data.result.1.metric.job"), equalTo("prometheus")); + assertThat(path.evaluate("data.result.1.values.0"), equalTo(List.of(1735689600.0, "3.0"))); + assertThat(path.evaluate("data.result.1.values.1"), equalTo(List.of(1735689660.0, "4.0"))); + } + } + + public void testConvertRangeQueryWithTimeseriesColumn() throws IOException { + // The PROMQL command returns a _timeseries column with JSON format {"labels":{...}} + // The listener extracts the inner labels as bare metric keys (no "labels." prefix) + List columns = List.of( + new TestColumnInfo("value", "double"), + new TestColumnInfo("step", "long"), + new TestColumnInfo("_timeseries", "keyword") + ); + + List> rows = List.of( + List.of(1.5, 1735689600000L, "{\"labels\":{\"__name__\":\"http_requests_total\",\"job\":\"prometheus\"}}"), + List.of(2.0, 1735689660000L, "{\"labels\":{\"__name__\":\"http_requests_total\",\"job\":\"prometheus\"}}") + ); + + EsqlResponse response = new TestEsqlResponse(columns, rows); + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)) { + ObjectPath path = toObjectPath(builder); + assertSuccessMatrix(path); + + assertThat(path.evaluate("data.result"), hasSize(1)); + assertThat(path.evaluate("data.result.0.metric.__name__"), equalTo("http_requests_total")); + assertThat(path.evaluate("data.result.0.metric.job"), equalTo("prometheus")); + assertThat(path.evaluate("data.result.0.values.0"), equalTo(List.of(1735689600.0, "1.5"))); + assertThat(path.evaluate("data.result.0.values.1"), equalTo(List.of(1735689660.0, "2.0"))); + } + } + + public void testConvertRangeQueryWithTimeseriesColumnAttributesNamespace() throws IOException { + // _timeseries JSON with an "attributes" namespace containing a nested object. + // All non-"labels" namespaces are flattened recursively with dot-separated paths, so + // {"attributes":{"resource":{"service.name":"my-service"}}} → metric key "attributes.resource.service.name". + List columns = List.of( + new TestColumnInfo("value", "double"), + new TestColumnInfo("step", "long"), + new TestColumnInfo("_timeseries", "keyword") + ); + + List> rows = List.of( + List.of(1.5, 1735689600000L, "{\"attributes\":{\"resource\":{\"service.name\":\"my-service\"}}}"), + List.of(2.0, 1735689660000L, "{\"attributes\":{\"resource\":{\"service.name\":\"my-service\"}}}") + ); + + EsqlResponse response = new TestEsqlResponse(columns, rows); + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)) { + ObjectPath path = toObjectPath(builder); + assertSuccessMatrix(path); + + assertThat(path.evaluate("data.result"), hasSize(1)); + assertThat(path.evaluate("data.result.0.metric.attributes\\.resource\\.service\\.name"), equalTo("my-service")); + assertThat(path.evaluate("data.result.0.values.0"), equalTo(List.of(1735689600.0, "1.5"))); + assertThat(path.evaluate("data.result.0.values.1"), equalTo(List.of(1735689660.0, "2.0"))); + } + } + + public void testConvertRangeQueryWithTimeseriesColumnTopLevelScalar() throws IOException { + // _timeseries JSON with a top-level scalar field (no namespace object). + // {"host":"my-host"} → metric key "host". + List columns = List.of( + new TestColumnInfo("value", "double"), + new TestColumnInfo("step", "long"), + new TestColumnInfo("_timeseries", "keyword") + ); + + List> rows = List.of( + List.of(1.5, 1735689600000L, "{\"host\":\"my-host\"}"), + List.of(2.0, 1735689660000L, "{\"host\":\"my-host\"}") + ); + + EsqlResponse response = new TestEsqlResponse(columns, rows); + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)) { + ObjectPath path = toObjectPath(builder); + assertSuccessMatrix(path); + + assertThat(path.evaluate("data.result"), hasSize(1)); + assertThat(path.evaluate("data.result.0.metric.host"), equalTo("my-host")); + assertThat(path.evaluate("data.result.0.values.0"), equalTo(List.of(1735689600.0, "1.5"))); + assertThat(path.evaluate("data.result.0.values.1"), equalTo(List.of(1735689660.0, "2.0"))); + } + } + + public void testConvertEmptyResult() throws IOException { + List columns = List.of(new TestColumnInfo("value", "double"), new TestColumnInfo("step", "long")); + + EsqlResponse response = new TestEsqlResponse(columns, List.of()); + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)) { + ObjectPath path = toObjectPath(builder); + assertSuccessMatrix(path); + + assertThat(path.evaluate("data.result"), empty()); + } + } + + public void testTimestampConversion() throws IOException { + List columns = List.of(new TestColumnInfo("value", "double"), new TestColumnInfo("step", "long")); + List> rows = List.of(List.of(1.0, 1735689600000L)); + + EsqlResponse response = new TestEsqlResponse(columns, rows); + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)) { + ObjectPath path = toObjectPath(builder); + // 2025-01-01T00:00:00.000Z = 1735689600 epoch seconds + assertThat(path.evaluate("data.result.0.values.0"), equalTo(List.of(1735689600.0, "1.0"))); + } + } + + public void testFormatSampleValueNaN() { + assertThat(PrometheusQueryRangeResponseListener.formatSampleValue(Double.NaN), equalTo("NaN")); + } + + public void testFormatSampleValueInfinity() { + assertThat(PrometheusQueryRangeResponseListener.formatSampleValue(Double.POSITIVE_INFINITY), equalTo("+Inf")); + assertThat(PrometheusQueryRangeResponseListener.formatSampleValue(Double.NEGATIVE_INFINITY), equalTo("-Inf")); + } + + public void testFormatSampleValueNull() { + assertThat(PrometheusQueryRangeResponseListener.formatSampleValue(null), equalTo("NaN")); + } + + public void testBuildErrorJson() throws IOException { + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.buildErrorJson(RestStatus.BAD_REQUEST, "test error")) { + ObjectPath path = toObjectPath(builder); + assertThat(path.evaluate("status"), equalTo("error")); + assertThat(path.evaluate("errorType"), equalTo("bad_data")); + assertThat(path.evaluate("error"), equalTo("test error")); + } + } + + public void testBuildErrorJsonTimeout() throws IOException { + try (XContentBuilder builder = PrometheusQueryRangeResponseListener.buildErrorJson(RestStatus.SERVICE_UNAVAILABLE, "timeout")) { + ObjectPath path = toObjectPath(builder); + assertThat(path.evaluate("errorType"), equalTo("timeout")); + } + } + + public void testMissingValueColumnThrows() { + List columns = List.of(new TestColumnInfo("step", "date")); + EsqlResponse response = new TestEsqlResponse(columns, List.of()); + expectThrows(IllegalStateException.class, () -> PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)); + } + + public void testMissingStepColumnThrows() { + List columns = List.of(new TestColumnInfo("value", "double")); + EsqlResponse response = new TestEsqlResponse(columns, List.of()); + expectThrows(IllegalStateException.class, () -> PrometheusQueryRangeResponseListener.convertToPrometheusJson(response)); + } + + public void testWrongStepColumnNameThrows() { + List columns = List.of(new TestColumnInfo("value", "double"), new TestColumnInfo("timestamp", "long")); + EsqlResponse response = new TestEsqlResponse(columns, List.of()); + IllegalStateException e = expectThrows( + IllegalStateException.class, + () -> PrometheusQueryRangeResponseListener.convertToPrometheusJson(response) + ); + assertThat(e.getMessage(), containsString("missing required 'step' column at index")); + } + + private static void assertSuccessMatrix(ObjectPath path) throws IOException { + assertThat(path.evaluate("status"), equalTo("success")); + assertThat(path.evaluate("data.resultType"), equalTo("matrix")); + } + + private static ObjectPath toObjectPath(XContentBuilder builder) throws IOException { + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(XContentParserConfiguration.EMPTY, BytesReference.bytes(builder).streamInput()) + ) { + return new ObjectPath(parser.map()); + } + } + + record TestColumnInfo(String name, String outputType) implements ColumnInfo { + @Override + public org.elasticsearch.xcontent.XContentBuilder toXContent( + org.elasticsearch.xcontent.XContentBuilder builder, + org.elasticsearch.xcontent.ToXContent.Params params + ) { + return builder; + } + + @Override + public void writeTo(org.elasticsearch.common.io.stream.StreamOutput out) {} + } + + static class TestEsqlResponse implements EsqlResponse { + private final List columns; + private final List> rows; + + TestEsqlResponse(List columns, List> rows) { + this.columns = columns; + this.rows = rows; + } + + @Override + public List columns() { + return columns; + } + + @Override + public Iterable> rows() { + List> result = new ArrayList<>(); + for (List row : rows) { + result.add(row); + } + return result; + } + + @Override + public Iterable column(int columnIndex) { + List col = new ArrayList<>(); + for (List row : rows) { + col.add(row.get(columnIndex)); + } + return col; + } + + @Override + public void close() {} + } +} diff --git a/x-pack/plugin/prometheus/src/test/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeRestActionTests.java b/x-pack/plugin/prometheus/src/test/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeRestActionTests.java new file mode 100644 index 0000000000000..b1026897bd8b6 --- /dev/null +++ b/x-pack/plugin/prometheus/src/test/java/org/elasticsearch/xpack/prometheus/rest/PrometheusQueryRangeRestActionTests.java @@ -0,0 +1,92 @@ +/* + * 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.prometheus.rest; + +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.xcontent.NamedXContentRegistry; +import org.elasticsearch.xpack.esql.plan.EsqlStatement; +import org.elasticsearch.xpack.esql.plan.logical.Eval; +import org.elasticsearch.xpack.esql.plan.logical.Project; +import org.elasticsearch.xpack.esql.plan.logical.promql.PromqlCommand; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +public class PrometheusQueryRangeRestActionTests extends ESTestCase { + + public void testPrepareRequestMissingParams() { + PrometheusQueryRangeRestAction action = new PrometheusQueryRangeRestAction(); + Map allParams = Map.of( + "query", + "up", + "start", + "2026-01-01T00:00:00Z", + "end", + "2026-01-01T01:00:00Z", + "step", + "15s" + ); + + for (String missingParam : allParams.keySet()) { + Map params = new HashMap<>(allParams); + params.remove(missingParam); + RestRequest request = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY).withParams(params).build(); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> action.prepareRequest(request, null)); + assertThat(e.getMessage(), equalTo("required parameter \"" + missingParam + "\" is missing")); + } + } + + public void testBuildStatementPlanStructure() { + EsqlStatement statement = PrometheusQueryRangeRestAction.buildStatement( + "up", + "*", + "2025-01-01T00:00:00Z", + "2025-01-01T01:00:00Z", + "15s" + ); + assertThat(statement.plan(), instanceOf(Project.class)); + Project project = (Project) statement.plan(); + assertThat(project.child(), instanceOf(Eval.class)); + Eval eval = (Eval) project.child(); + assertThat(eval.fields().size(), equalTo(1)); + assertThat(eval.fields().get(0).name(), equalTo("step")); + assertThat(eval.child(), instanceOf(PromqlCommand.class)); + PromqlCommand promqlCommand = (PromqlCommand) eval.child(); + assertThat(promqlCommand.valueColumnName(), equalTo("value")); + assertThat(promqlCommand.isRangeQuery(), equalTo(true)); + } + + public void testBuildStatementWithCustomIndex() { + EsqlStatement statement = PrometheusQueryRangeRestAction.buildStatement( + "up", + "logs-*,metrics-*", + "2025-01-01T00:00:00Z", + "2025-01-01T01:00:00Z", + "15s" + ); + assertThat(statement.plan(), instanceOf(Project.class)); + Project project = (Project) statement.plan(); + Eval eval = (Eval) project.child(); + PromqlCommand promqlCommand = (PromqlCommand) eval.child(); + assertThat(promqlCommand.valueColumnName(), equalTo("value")); + } + + public void testBuildStatementWithNumericStep() { + EsqlStatement statement = PrometheusQueryRangeRestAction.buildStatement("up", "*", "1735689600", "1735693200", "60"); + assertThat(statement.plan(), instanceOf(Project.class)); + Project project = (Project) statement.plan(); + assertThat(project.child(), instanceOf(Eval.class)); + Eval eval = (Eval) project.child(); + assertThat(eval.child(), instanceOf(PromqlCommand.class)); + } +}