diff --git a/.buildkite/pipelines/dra-workflow.yml b/.buildkite/pipelines/dra-workflow.yml index 25477c8541fa9..36828a6512db8 100644 --- a/.buildkite/pipelines/dra-workflow.yml +++ b/.buildkite/pipelines/dra-workflow.yml @@ -2,6 +2,7 @@ steps: - command: .buildkite/scripts/dra-workflow.sh env: USE_DRA_CREDENTIALS: "true" + USE_PROD_DOCKER_CREDENTIALS: "true" agents: provider: gcp image: family/elasticsearch-ubuntu-2204 @@ -18,4 +19,5 @@ steps: branch: "${BUILDKITE_BRANCH}" env: DRA_WORKFLOW: staging + USE_PROD_DOCKER_CREDENTIALS: "true" if: build.env('DRA_WORKFLOW') == 'staging' diff --git a/.buildkite/pipelines/periodic-packaging.template.yml b/.buildkite/pipelines/periodic-packaging.template.yml index e0da1f46486ea..dfedfac9d5b04 100644 --- a/.buildkite/pipelines/periodic-packaging.template.yml +++ b/.buildkite/pipelines/periodic-packaging.template.yml @@ -27,7 +27,8 @@ steps: image: family/elasticsearch-{{matrix.image}} diskSizeGb: 350 machineType: n1-standard-8 - env: {} + env: + USE_PROD_DOCKER_CREDENTIALS: "true" - group: packaging-tests-upgrade steps: $BWC_STEPS - group: packaging-tests-windows diff --git a/.buildkite/pipelines/periodic-packaging.yml b/.buildkite/pipelines/periodic-packaging.yml index 5a05d75cf95ac..b29747c60617e 100644 --- a/.buildkite/pipelines/periodic-packaging.yml +++ b/.buildkite/pipelines/periodic-packaging.yml @@ -28,7 +28,8 @@ steps: image: family/elasticsearch-{{matrix.image}} diskSizeGb: 350 machineType: n1-standard-8 - env: {} + env: + USE_PROD_DOCKER_CREDENTIALS: "true" - group: packaging-tests-upgrade steps: - label: "{{matrix.image}} / 8.0.1 / packaging-tests-upgrade" diff --git a/.buildkite/pipelines/pull-request/packaging-tests-unix-sample.yml b/.buildkite/pipelines/pull-request/packaging-tests-unix-sample.yml index 98bc61ea33738..97558381d0a01 100644 --- a/.buildkite/pipelines/pull-request/packaging-tests-unix-sample.yml +++ b/.buildkite/pipelines/pull-request/packaging-tests-unix-sample.yml @@ -24,4 +24,5 @@ steps: diskSizeGb: 350 machineType: custom-16-32768 env: + USE_PROD_DOCKER_CREDENTIALS: "true" PACKAGING_TASK: "{{matrix.PACKAGING_TASK}}" diff --git a/build-tools-internal/build.gradle b/build-tools-internal/build.gradle index 949091816a72f..38d3c0cd326f9 100644 --- a/build-tools-internal/build.gradle +++ b/build-tools-internal/build.gradle @@ -384,6 +384,15 @@ tasks.named("jar") { exclude("classpath.index") } +spotless { + java { + // IDEs can sometimes run annotation processors that leave files in + // here, causing Spotless to complain. Even though this path ought not + // to exist, exclude it anyway in order to avoid spurious failures. + toggleOffOn() + } +} + def resolveMainWrapperVersion() { new URL("https://raw.githubusercontent.com/elastic/elasticsearch/main/build-tools-internal/src/main/resources/minimumGradleVersion").text.trim() } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DockerBase.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DockerBase.java index 793ff6049e10e..ac83a01ffc294 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DockerBase.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DockerBase.java @@ -29,11 +29,13 @@ public enum DockerBase { CLOUD_ESS(null, "-cloud-ess", "apt-get"), // Chainguard based wolfi image with latest jdk - WOLFI( - "docker.elastic.co/wolfi/chainguard-base:latest@sha256:c16d3ad6cebf387e8dd2ad769f54320c4819fbbaa21e729fad087c7ae223b4d0", + // This is usually updated via renovatebot + // spotless:off + WOLFI("docker.elastic.co/wolfi/chainguard-base:latest@sha256:c16d3ad6cebf387e8dd2ad769f54320c4819fbbaa21e729fad087c7ae223b4d0", "-wolfi", "apk" ), + // spotless:on // Based on WOLFI above, with more extras. We don't set a base image because // we programmatically extend from the Wolfi image. diff --git a/build-tools-internal/src/main/resources/changelog-schema.json b/build-tools-internal/src/main/resources/changelog-schema.json index a435305a8e3e2..451701d74d690 100644 --- a/build-tools-internal/src/main/resources/changelog-schema.json +++ b/build-tools-internal/src/main/resources/changelog-schema.json @@ -284,6 +284,7 @@ "Cluster and node setting", "Command line tool", "CRUD", + "ES|QL", "Index setting", "Ingest", "JVM option", diff --git a/docs/changelog/112567.yaml b/docs/changelog/112567.yaml new file mode 100644 index 0000000000000..25e3ac8360c2b --- /dev/null +++ b/docs/changelog/112567.yaml @@ -0,0 +1,5 @@ +pr: 112567 +summary: Track shard snapshot progress during node shutdown +area: Snapshot/Restore +type: enhancement +issues: [] diff --git a/docs/changelog/113563.yaml b/docs/changelog/113563.yaml new file mode 100644 index 0000000000000..48484ead99d77 --- /dev/null +++ b/docs/changelog/113563.yaml @@ -0,0 +1,5 @@ +pr: 113563 +summary: Use ELSER By Default For Semantic Text +area: Mapping +type: enhancement +issues: [] diff --git a/docs/changelog/113897.yaml b/docs/changelog/113897.yaml new file mode 100644 index 0000000000000..db0c53518613c --- /dev/null +++ b/docs/changelog/113897.yaml @@ -0,0 +1,6 @@ +pr: 113897 +summary: "Add chunking settings configuration to `CohereService,` `AmazonBedrockService,`\ + \ and `AzureOpenAiService`" +area: Machine Learning +type: enhancement +issues: [] diff --git a/docs/changelog/113967.yaml b/docs/changelog/113967.yaml new file mode 100644 index 0000000000000..58b72eba49deb --- /dev/null +++ b/docs/changelog/113967.yaml @@ -0,0 +1,13 @@ +pr: 113967 +summary: "ESQL: Entirely remove META FUNCTIONS" +area: ES|QL +type: breaking +issues: [] +breaking: + title: "ESQL: Entirely remove META FUNCTIONS" + area: ES|QL + details: | + Removes an undocumented syntax from ESQL: META FUNCTION. This was never + reliable or really useful. Consult the documentation instead. + impact: "Removes an undocumented syntax from ESQL: META FUNCTION" + notable: false diff --git a/docs/changelog/114128.yaml b/docs/changelog/114128.yaml new file mode 100644 index 0000000000000..721649d0d6fe0 --- /dev/null +++ b/docs/changelog/114128.yaml @@ -0,0 +1,5 @@ +pr: 114128 +summary: Adding `index_template_substitutions` to the simulate ingest API +area: Ingest Node +type: enhancement +issues: [] diff --git a/docs/changelog/114157.yaml b/docs/changelog/114157.yaml new file mode 100644 index 0000000000000..22e0fda173e98 --- /dev/null +++ b/docs/changelog/114157.yaml @@ -0,0 +1,6 @@ +pr: 114157 +summary: Add a `terminate` ingest processor +area: Ingest Node +type: feature +issues: + - 110218 diff --git a/docs/changelog/114264.yaml b/docs/changelog/114264.yaml new file mode 100644 index 0000000000000..fe421f6422830 --- /dev/null +++ b/docs/changelog/114264.yaml @@ -0,0 +1,5 @@ +pr: 114264 +summary: "Fix analyzed wildcard query in simple_query_string when disjunctions is empty" +area: Search +type: bug +issues: [114185] diff --git a/docs/reference/esql/functions/description/mv_first.asciidoc b/docs/reference/esql/functions/description/mv_first.asciidoc index 99223e2c02d9f..13c433ce209d0 100644 --- a/docs/reference/esql/functions/description/mv_first.asciidoc +++ b/docs/reference/esql/functions/description/mv_first.asciidoc @@ -2,4 +2,10 @@ *Description* -Converts a multivalued expression into a single valued column containing the first value. This is most useful when reading from a function that emits multivalued columns in a known order like <>. The order that <> are read from underlying storage is not guaranteed. It is *frequently* ascending, but don't rely on that. If you need the minimum value use <> instead of `MV_FIRST`. `MV_MIN` has optimizations for sorted values so there isn't a performance benefit to `MV_FIRST`. +Converts a multivalued expression into a single valued column containing the first value. This is most useful when reading from a function that emits multivalued columns in a known order like <>. + +The order that <> are read from +underlying storage is not guaranteed. It is *frequently* ascending, but don't +rely on that. If you need the minimum value use <> instead of +`MV_FIRST`. `MV_MIN` has optimizations for sorted values so there isn't a +performance benefit to `MV_FIRST`. diff --git a/docs/reference/esql/functions/description/mv_last.asciidoc b/docs/reference/esql/functions/description/mv_last.asciidoc index 4b4b4336588d1..beba7b5a402c9 100644 --- a/docs/reference/esql/functions/description/mv_last.asciidoc +++ b/docs/reference/esql/functions/description/mv_last.asciidoc @@ -2,4 +2,10 @@ *Description* -Converts a multivalue expression into a single valued column containing the last value. This is most useful when reading from a function that emits multivalued columns in a known order like <>. The order that <> are read from underlying storage is not guaranteed. It is *frequently* ascending, but don't rely on that. If you need the maximum value use <> instead of `MV_LAST`. `MV_MAX` has optimizations for sorted values so there isn't a performance benefit to `MV_LAST`. +Converts a multivalue expression into a single valued column containing the last value. This is most useful when reading from a function that emits multivalued columns in a known order like <>. + +The order that <> are read from +underlying storage is not guaranteed. It is *frequently* ascending, but don't +rely on that. If you need the maximum value use <> instead of +`MV_LAST`. `MV_MAX` has optimizations for sorted values so there isn't a +performance benefit to `MV_LAST`. diff --git a/docs/reference/esql/functions/description/mv_slice.asciidoc b/docs/reference/esql/functions/description/mv_slice.asciidoc index 24d3183b6f40e..98438ae097fe7 100644 --- a/docs/reference/esql/functions/description/mv_slice.asciidoc +++ b/docs/reference/esql/functions/description/mv_slice.asciidoc @@ -2,4 +2,8 @@ *Description* -Returns a subset of the multivalued field using the start and end index values. +Returns a subset of the multivalued field using the start and end index values. This is most useful when reading from a function that emits multivalued columns in a known order like <> or <>. + +The order that <> are read from +underlying storage is not guaranteed. It is *frequently* ascending, but don't +rely on that. diff --git a/docs/reference/esql/functions/kibana/definition/mv_first.json b/docs/reference/esql/functions/kibana/definition/mv_first.json index 480c0af2f0918..80e761faafab9 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_first.json +++ b/docs/reference/esql/functions/kibana/definition/mv_first.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", "type" : "eval", "name" : "mv_first", - "description" : "Converts a multivalued expression into a single valued column containing the\nfirst value. This is most useful when reading from a function that emits\nmultivalued columns in a known order like <>.\n\nThe order that <> are read from\nunderlying storage is not guaranteed. It is *frequently* ascending, but don't\nrely on that. If you need the minimum value use <> instead of\n`MV_FIRST`. `MV_MIN` has optimizations for sorted values so there isn't a\nperformance benefit to `MV_FIRST`.", + "description" : "Converts a multivalued expression into a single valued column containing the\nfirst value. This is most useful when reading from a function that emits\nmultivalued columns in a known order like <>.", "signatures" : [ { "params" : [ diff --git a/docs/reference/esql/functions/kibana/definition/mv_last.json b/docs/reference/esql/functions/kibana/definition/mv_last.json index 0918e46454265..fb16400f86e62 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_last.json +++ b/docs/reference/esql/functions/kibana/definition/mv_last.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", "type" : "eval", "name" : "mv_last", - "description" : "Converts a multivalue expression into a single valued column containing the last\nvalue. This is most useful when reading from a function that emits multivalued\ncolumns in a known order like <>.\n\nThe order that <> are read from\nunderlying storage is not guaranteed. It is *frequently* ascending, but don't\nrely on that. If you need the maximum value use <> instead of\n`MV_LAST`. `MV_MAX` has optimizations for sorted values so there isn't a\nperformance benefit to `MV_LAST`.", + "description" : "Converts a multivalue expression into a single valued column containing the last\nvalue. This is most useful when reading from a function that emits multivalued\ncolumns in a known order like <>.", "signatures" : [ { "params" : [ diff --git a/docs/reference/esql/functions/kibana/definition/mv_slice.json b/docs/reference/esql/functions/kibana/definition/mv_slice.json index dcae77f1545a0..399a6145b040e 100644 --- a/docs/reference/esql/functions/kibana/definition/mv_slice.json +++ b/docs/reference/esql/functions/kibana/definition/mv_slice.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", "type" : "eval", "name" : "mv_slice", - "description" : "Returns a subset of the multivalued field using the start and end index values.", + "description" : "Returns a subset of the multivalued field using the start and end index values.\nThis is most useful when reading from a function that emits multivalued columns\nin a known order like <> or <>.", "signatures" : [ { "params" : [ diff --git a/docs/reference/esql/functions/kibana/docs/mv_first.md b/docs/reference/esql/functions/kibana/docs/mv_first.md index 4faea6edd9162..c50ed7d764020 100644 --- a/docs/reference/esql/functions/kibana/docs/mv_first.md +++ b/docs/reference/esql/functions/kibana/docs/mv_first.md @@ -7,12 +7,6 @@ Converts a multivalued expression into a single valued column containing the first value. This is most useful when reading from a function that emits multivalued columns in a known order like <>. -The order that <> are read from -underlying storage is not guaranteed. It is *frequently* ascending, but don't -rely on that. If you need the minimum value use <> instead of -`MV_FIRST`. `MV_MIN` has optimizations for sorted values so there isn't a -performance benefit to `MV_FIRST`. - ``` ROW a="foo;bar;baz" | EVAL first_a = MV_FIRST(SPLIT(a, ";")) diff --git a/docs/reference/esql/functions/kibana/docs/mv_last.md b/docs/reference/esql/functions/kibana/docs/mv_last.md index a8c3bf25eb51b..eeefd929c1359 100644 --- a/docs/reference/esql/functions/kibana/docs/mv_last.md +++ b/docs/reference/esql/functions/kibana/docs/mv_last.md @@ -7,12 +7,6 @@ Converts a multivalue expression into a single valued column containing the last value. This is most useful when reading from a function that emits multivalued columns in a known order like <>. -The order that <> are read from -underlying storage is not guaranteed. It is *frequently* ascending, but don't -rely on that. If you need the maximum value use <> instead of -`MV_LAST`. `MV_MAX` has optimizations for sorted values so there isn't a -performance benefit to `MV_LAST`. - ``` ROW a="foo;bar;baz" | EVAL last_a = MV_LAST(SPLIT(a, ";")) diff --git a/docs/reference/esql/functions/kibana/docs/mv_slice.md b/docs/reference/esql/functions/kibana/docs/mv_slice.md index 3daf0de930a7f..bba7a219960ef 100644 --- a/docs/reference/esql/functions/kibana/docs/mv_slice.md +++ b/docs/reference/esql/functions/kibana/docs/mv_slice.md @@ -4,6 +4,8 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ ### MV_SLICE Returns a subset of the multivalued field using the start and end index values. +This is most useful when reading from a function that emits multivalued columns +in a known order like <> or <>. ``` row a = [1, 2, 2, 3] diff --git a/docs/reference/esql/multivalued-fields.asciidoc b/docs/reference/esql/multivalued-fields.asciidoc index 2dfda3060d3ea..562ea2a2e6b4a 100644 --- a/docs/reference/esql/multivalued-fields.asciidoc +++ b/docs/reference/esql/multivalued-fields.asciidoc @@ -177,6 +177,37 @@ POST /_query ---- // TESTRESPONSE[s/"took": 28/"took": "$body.took"/] +[discrete] +[[esql-multivalued-nulls]] +==== `null` in a list + +`null` values in a list are not preserved at the storage layer: + +[source,console,id=esql-multivalued-fields-multivalued-nulls] +---- +POST /mv/_doc?refresh +{ "a": [2, null, 1] } + +POST /_query +{ + "query": "FROM mv | LIMIT 1" +} +---- + +[source,console-result] +---- +{ + "took": 28, + "columns": [ + { "name": "a", "type": "long"}, + ], + "values": [ + [[1, 2]], + ] +} +---- +// TESTRESPONSE[s/"took": 28/"took": "$body.took"/] + [discrete] [[esql-multivalued-fields-functions]] ==== Functions diff --git a/docs/reference/indices/put-index-template.asciidoc b/docs/reference/indices/put-index-template.asciidoc index 772bd51afdce8..36fc66ecb90b8 100644 --- a/docs/reference/indices/put-index-template.asciidoc +++ b/docs/reference/indices/put-index-template.asciidoc @@ -85,6 +85,8 @@ include::{docdir}/rest-api/common-parms.asciidoc[tag=master-timeout] [[put-index-template-api-request-body]] ==== {api-request-body-title} +// tag::request-body[] + `composed_of`:: (Optional, array of strings) An ordered list of component template names. Component templates are merged in the order @@ -102,7 +104,7 @@ See <>. + .Properties of `data_stream` [%collapsible%open] -==== +===== `allow_custom_routing`:: (Optional, Boolean) If `true`, the data stream supports <>. Defaults to `false`. @@ -117,7 +119,7 @@ See <>. + If `time_series`, each backing index has an `index.mode` index setting of `time_series`. -==== +===== `index_patterns`:: (Required, array of strings) @@ -146,7 +148,7 @@ Template to be applied. It may optionally include an `aliases`, `mappings`, or + .Properties of `template` [%collapsible%open] -==== +===== `aliases`:: (Optional, object of objects) Aliases to add. + @@ -161,7 +163,7 @@ include::{es-ref-dir}/indices/create-index.asciidoc[tag=aliases-props] include::{docdir}/rest-api/common-parms.asciidoc[tag=mappings] include::{docdir}/rest-api/common-parms.asciidoc[tag=settings] -==== +===== `version`:: (Optional, integer) @@ -174,6 +176,7 @@ Marks this index template as deprecated. When creating or updating a non-deprecated index template that uses deprecated components, {es} will emit a deprecation warning. // end::index-template-api-body[] +// end::request-body[] [[put-index-template-api-example]] ==== {api-examples-title} diff --git a/docs/reference/ingest/apis/simulate-ingest.asciidoc b/docs/reference/ingest/apis/simulate-ingest.asciidoc index ac6da515402bb..1bee03ea3e58a 100644 --- a/docs/reference/ingest/apis/simulate-ingest.asciidoc +++ b/docs/reference/ingest/apis/simulate-ingest.asciidoc @@ -102,6 +102,12 @@ POST /_ingest/_simulate } } } + }, + "index_template_substitutions": { <3> + "my-index-template": { + "index_patterns": ["my-index-*"], + "composed_of": ["component_template_1", "component_template_2"] + } } } ---- @@ -109,6 +115,8 @@ POST /_ingest/_simulate <1> This replaces the existing `my-pipeline` pipeline with the contents given here for the duration of this request. <2> This replaces the existing `my-component-template` component template with the contents given here for the duration of this request. These templates can be used to change the pipeline(s) used, or to modify the mapping that will be used to validate the result. +<3> This replaces the existing `my-index-template` index template with the contents given here for the duration of this request. +These templates can be used to change the pipeline(s) used, or to modify the mapping that will be used to validate the result. [[simulate-ingest-api-request]] ==== {api-request-title} @@ -225,6 +233,19 @@ include::{es-ref-dir}/indices/put-component-template.asciidoc[tag=template] ==== +`index_template_substitutions`:: +(Optional, map of strings to objects) +Map of index template names to substitute index template definition objects. ++ +.Properties of index template definition objects +[%collapsible%open] + +==== + +include::{es-ref-dir}/indices/put-index-template.asciidoc[tag=request-body] + +==== + [[simulate-ingest-api-example]] ==== {api-examples-title} diff --git a/docs/reference/ingest/processors/terminate.asciidoc b/docs/reference/ingest/processors/terminate.asciidoc new file mode 100644 index 0000000000000..a2643fbd955f1 --- /dev/null +++ b/docs/reference/ingest/processors/terminate.asciidoc @@ -0,0 +1,30 @@ +[[terminate-processor]] +=== Terminate processor + +++++ +Terminate +++++ + +Terminates the current ingest pipeline, causing no further processors to be run. +This will normally be executed conditionally, using the `if` option. + +If this pipeline is being called from another pipeline, the calling pipeline is *not* terminated. + +[[terminate-options]] +.Terminate Options +[options="header"] +|====== +| Name | Required | Default | Description +include::common-options.asciidoc[] +|====== + +[source,js] +-------------------------------------------------- +{ + "description" : "terminates the current pipeline if the error field is present", + "terminate": { + "if": "ctx.error != null" + } +} +-------------------------------------------------- +// NOTCONSOLE diff --git a/docs/reference/rest-api/usage.asciidoc b/docs/reference/rest-api/usage.asciidoc index 4dcf0d328e4f1..957f57ffc9105 100644 --- a/docs/reference/rest-api/usage.asciidoc +++ b/docs/reference/rest-api/usage.asciidoc @@ -239,8 +239,7 @@ GET /_xpack/usage "keep" : 0, "enrich" : 0, "from" : 0, - "row" : 0, - "meta" : 0 + "row" : 0 }, "queries" : { "rest" : { diff --git a/modules/aggregations/src/yamlRestTest/resources/rest-api-spec/test/aggregations/stats_metric_fail_formatting.yml b/modules/aggregations/src/yamlRestTest/resources/rest-api-spec/test/aggregations/stats_metric_fail_formatting.yml index 82371c973407c..1ff376eac61d1 100644 --- a/modules/aggregations/src/yamlRestTest/resources/rest-api-spec/test/aggregations/stats_metric_fail_formatting.yml +++ b/modules/aggregations/src/yamlRestTest/resources/rest-api-spec/test/aggregations/stats_metric_fail_formatting.yml @@ -3,6 +3,8 @@ setup: indices.create: index: test_date body: + settings: + number_of_shards: 1 mappings: properties: date_field: diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java index 6ef636847e2df..d585c6217202f 100644 --- a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java +++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java @@ -72,6 +72,7 @@ public Map getProcessors(Processor.Parameters paramet entry(SetProcessor.TYPE, new SetProcessor.Factory(parameters.scriptService)), entry(SortProcessor.TYPE, new SortProcessor.Factory()), entry(SplitProcessor.TYPE, new SplitProcessor.Factory()), + entry(TerminateProcessor.TYPE, new TerminateProcessor.Factory()), entry(TrimProcessor.TYPE, new TrimProcessor.Factory()), entry(URLDecodeProcessor.TYPE, new URLDecodeProcessor.Factory()), entry(UppercaseProcessor.TYPE, new UppercaseProcessor.Factory()), diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/TerminateProcessor.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/TerminateProcessor.java new file mode 100644 index 0000000000000..5b6144ba8eab9 --- /dev/null +++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/TerminateProcessor.java @@ -0,0 +1,53 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.ingest.common; + +import org.elasticsearch.ingest.AbstractProcessor; +import org.elasticsearch.ingest.IngestDocument; +import org.elasticsearch.ingest.Processor; + +import java.util.Map; + +/** + * A {@link Processor} which simply prevents subsequent processors in the pipeline from running (without failing, like {@link FailProcessor} + * does). This will normally be run conditionally, using the {@code if} option. + */ +public class TerminateProcessor extends AbstractProcessor { + + static final String TYPE = "terminate"; + + TerminateProcessor(String tag, String description) { + super(tag, description); + } + + @Override + public IngestDocument execute(IngestDocument ingestDocument) { + ingestDocument.terminate(); + return ingestDocument; + } + + @Override + public String getType() { + return TYPE; + } + + public static final class Factory implements Processor.Factory { + + @Override + public Processor create( + Map processorFactories, + String tag, + String description, + Map config + ) { + return new TerminateProcessor(tag, description); + } + } +} diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/TerminateProcessorTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/TerminateProcessorTests.java new file mode 100644 index 0000000000000..1888f8366edd3 --- /dev/null +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/TerminateProcessorTests.java @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.ingest.common; + +import org.elasticsearch.ingest.CompoundProcessor; +import org.elasticsearch.ingest.IngestDocument; +import org.elasticsearch.ingest.Pipeline; +import org.elasticsearch.ingest.TestTemplateService; +import org.elasticsearch.ingest.ValueSource; +import org.elasticsearch.test.ESTestCase; + +import java.util.Map; + +import static org.elasticsearch.ingest.RandomDocumentPicks.randomIngestDocument; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +public class TerminateProcessorTests extends ESTestCase { + + public void testTerminateInPipeline() throws Exception { + Pipeline pipeline = new Pipeline( + "my-pipeline", + null, + null, + null, + new CompoundProcessor( + new SetProcessor( + "before-set", + "Sets before field to true", + new TestTemplateService.MockTemplateScript.Factory("before"), + ValueSource.wrap(true, TestTemplateService.instance()), + null + ), + new TerminateProcessor("terminate", "terminates the pipeline"), + new SetProcessor( + "after-set", + "Sets after field to true", + new TestTemplateService.MockTemplateScript.Factory("after"), + ValueSource.wrap(true, TestTemplateService.instance()), + null + ) + ) + ); + IngestDocument input = randomIngestDocument(random(), Map.of("foo", "bar")); + PipelineOutput output = new PipelineOutput(); + + pipeline.execute(input, output::set); + + assertThat(output.exception, nullValue()); + // We expect the before-set processor to have run, but not the after-set one: + assertThat(output.document.getSource(), is(Map.of("foo", "bar", "before", true))); + } + + private static class PipelineOutput { + IngestDocument document; + Exception exception; + + void set(IngestDocument document, Exception exception) { + this.document = document; + this.exception = exception; + } + } +} diff --git a/modules/ingest-common/src/yamlRestTest/resources/rest-api-spec/test/ingest/330_terminate_processor.yml b/modules/ingest-common/src/yamlRestTest/resources/rest-api-spec/test/ingest/330_terminate_processor.yml new file mode 100644 index 0000000000000..7a46d7bb272d8 --- /dev/null +++ b/modules/ingest-common/src/yamlRestTest/resources/rest-api-spec/test/ingest/330_terminate_processor.yml @@ -0,0 +1,138 @@ +--- +setup: + - do: + ingest.put_pipeline: + id: "test-pipeline" + body: > + { + "description": "Appends just 'before' to the steps field if the number field is less than 50, or both 'before' and 'after' if not", + "processors": [ + { + "append": { + "field": "steps", + "value": "before" + } + }, + { + "terminate": { + "if": "ctx.number < 50" + } + }, + { + "append": { + "field": "steps", + "value": "after" + } + } + ] + } + - do: + ingest.put_pipeline: + id: "test-final-pipeline" + body: > + { + "description": "Appends 'final' to the steps field", + "processors": [ + { + "append": { + "field": "steps", + "value": "final" + } + } + ] + } + - do: + ingest.put_pipeline: + id: "test-outer-pipeline" + body: > + { + "description": "Runs test-pipeline and then append 'outer' to the steps field", + "processors": [ + { + "pipeline": { + "name": "test-pipeline" + } + }, + { + "append": { + "field": "steps", + "value": "outer" + } + } + ] + } + - do: + indices.create: + index: "test-index-with-default-and-final-pipelines" + body: + settings: + index: + default_pipeline: "test-pipeline" + final_pipeline: "test-final-pipeline" + - do: + indices.create: + index: "test-vanilla-index" + +--- +teardown: + - do: + indices.delete: + index: "test-index-with-default-and-final-pipelines" + ignore_unavailable: true + - do: + indices.delete: + index: "test-vanilla-index" + ignore_unavailable: true + - do: + ingest.delete_pipeline: + id: "test-pipeline" + ignore: 404 + - do: + ingest.delete_pipeline: + id: "test-outer-pipeline" + ignore: 404 + +--- +"Test pipeline including conditional terminate pipeline": + + - do: + bulk: + refresh: true + body: + - '{ "index": {"_index": "test-index-with-default-and-final-pipelines" } }' + - '{ "comment": "should terminate", "number": 40, "steps": [] }' + - '{ "index": {"_index": "test-index-with-default-and-final-pipelines" } }' + - '{ "comment": "should continue to end", "number": 60, "steps": [] }' + + - do: + search: + rest_total_hits_as_int: true + index: "test-index-with-default-and-final-pipelines" + body: + sort: "number" + - match: { hits.total: 2 } + - match: { hits.hits.0._source.number: 40 } + - match: { hits.hits.1._source.number: 60 } + - match: { hits.hits.0._source.steps: ["before", "final"] } + - match: { hits.hits.1._source.steps: ["before", "after", "final"] } + +--- +"Test pipeline with terminate invoked from an outer pipeline": + + - do: + bulk: + refresh: true + pipeline: "test-outer-pipeline" + body: + - '{ "index": {"_index": "test-vanilla-index" } }' + - '{ "comment": "should terminate inner pipeline but not outer", "number": 40, "steps": [] }' + + - do: + search: + rest_total_hits_as_int: true + index: "test-vanilla-index" + body: + sort: "number" + - match: { hits.total: 1 } + - match: { hits.hits.0._source.number: 40 } + - match: { hits.hits.0._source.steps: ["before", "outer"] } diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java index 128c16e163764..4c2f047c35709 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java @@ -169,7 +169,19 @@ enum Database { Property.TYPE ), Set.of(Property.IP, Property.ASN, Property.ORGANIZATION_NAME, Property.NETWORK) - ); + ), + CityV2( + Set.of( + Property.IP, + Property.COUNTRY_ISO_CODE, + Property.REGION_NAME, + Property.CITY_NAME, + Property.TIMEZONE, + Property.LOCATION, + Property.POSTAL_CODE + ), + Set.of(Property.COUNTRY_ISO_CODE, Property.REGION_NAME, Property.CITY_NAME, Property.LOCATION) + ),; private final Set properties; private final Set defaultProperties; diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java index ac7f56468f37e..d2c734cb9bae7 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java @@ -58,6 +58,25 @@ static Long parseAsn(final String asn) { } } + /** + * Lax-ly parses a string that contains a double into a Double (or null, if such parsing isn't possible). + * @param latlon a potentially empty (or null) string that is expected to contain a parsable double + * @return the parsed double + */ + static Double parseLocationDouble(final String latlon) { + if (latlon == null || Strings.hasText(latlon) == false) { + return null; + } else { + String stripped = latlon.trim(); + try { + return Double.parseDouble(stripped); + } catch (NumberFormatException e) { + logger.trace("Unable to parse non-compliant location string [{}]", latlon); + return null; + } + } + } + public record AsnResult( Long asn, @Nullable String country, // not present in the free asn database @@ -88,6 +107,31 @@ public record CountryResult( public CountryResult {} } + public record GeolocationResult( + String city, + String country, + Double latitude, + Double longitude, + String postalCode, + String region, + String timezone + ) { + @SuppressWarnings("checkstyle:RedundantModifier") + @MaxMindDbConstructor + public GeolocationResult( + @MaxMindDbParameter(name = "city") String city, + @MaxMindDbParameter(name = "country") String country, + @MaxMindDbParameter(name = "latitude") String latitude, + @MaxMindDbParameter(name = "longitude") String longitude, + // @MaxMindDbParameter(name = "network") String network, // for now we're not exposing this + @MaxMindDbParameter(name = "postal_code") String postalCode, + @MaxMindDbParameter(name = "region") String region, + @MaxMindDbParameter(name = "timezone") String timezone + ) { + this(city, country, parseLocationDouble(latitude), parseLocationDouble(longitude), postalCode, region, timezone); + } + } + static class Asn extends AbstractBase { Asn(Set properties) { super(properties, AsnResult.class); @@ -183,6 +227,65 @@ protected Map transform(final Result result) { } } + static class Geolocation extends AbstractBase { + Geolocation(final Set properties) { + super(properties, GeolocationResult.class); + } + + @Override + protected Map transform(final Result result) { + GeolocationResult response = result.result; + + Map data = new HashMap<>(); + for (Database.Property property : this.properties) { + switch (property) { + case IP -> data.put("ip", result.ip); + case COUNTRY_ISO_CODE -> { + String countryIsoCode = response.country; + if (countryIsoCode != null) { + data.put("country_iso_code", countryIsoCode); + } + } + case REGION_NAME -> { + String subdivisionName = response.region; + if (subdivisionName != null) { + data.put("region_name", subdivisionName); + } + } + case CITY_NAME -> { + String cityName = response.city; + if (cityName != null) { + data.put("city_name", cityName); + } + } + case TIMEZONE -> { + String locationTimeZone = response.timezone; + if (locationTimeZone != null) { + data.put("timezone", locationTimeZone); + } + } + case POSTAL_CODE -> { + String postalCode = response.postalCode; + if (postalCode != null) { + data.put("postal_code", postalCode); + } + } + case LOCATION -> { + Double latitude = response.latitude; + Double longitude = response.longitude; + if (latitude != null && longitude != null) { + Map locationObject = new HashMap<>(); + locationObject.put("lat", latitude); + locationObject.put("lon", longitude); + data.put("location", locationObject); + } + } + } + } + return data; + } + } + /** * Just a little record holder -- there's the data that we receive via the binding to our record objects from the Reader via the * getRecord call, but then we also need to capture the passed-in ip address that came from the caller as well as the network for diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java index 905eb027626a1..f58f8819e7ed9 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java @@ -13,9 +13,11 @@ import com.maxmind.db.Networks; import com.maxmind.db.Reader; +import org.apache.lucene.util.Constants; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.core.IOUtils; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.TimeValue; import org.elasticsearch.test.ESTestCase; @@ -36,6 +38,7 @@ import static java.util.Map.entry; import static org.elasticsearch.ingest.geoip.GeoIpTestUtils.copyDatabase; import static org.elasticsearch.ingest.geoip.IpinfoIpDataLookups.parseAsn; +import static org.elasticsearch.ingest.geoip.IpinfoIpDataLookups.parseLocationDouble; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -48,23 +51,32 @@ public class IpinfoIpDataLookupsTests extends ESTestCase { private ThreadPool threadPool; private ResourceWatcherService resourceWatcherService; + // a temporary directory that mmdb files can be copied to and read from + private Path tmpDir; + @Before public void setup() { threadPool = new TestThreadPool(ConfigDatabases.class.getSimpleName()); Settings settings = Settings.builder().put("resource.reload.interval.high", TimeValue.timeValueMillis(100)).build(); resourceWatcherService = new ResourceWatcherService(settings, threadPool); + tmpDir = createTempDir(); } @After - public void cleanup() { + public void cleanup() throws IOException { resourceWatcherService.close(); threadPool.shutdownNow(); + IOUtils.rm(tmpDir); } public void testDatabasePropertyInvariants() { // the second ASN variant database is like a specialization of the ASN database assertThat(Sets.difference(Database.Asn.properties(), Database.AsnV2.properties()), is(empty())); assertThat(Database.Asn.defaultProperties(), equalTo(Database.AsnV2.defaultProperties())); + + // the second City variant database is like a version of the ordinary City database but lacking many fields + assertThat(Sets.difference(Database.CityV2.properties(), Database.City.properties()), is(empty())); + assertThat(Sets.difference(Database.CityV2.defaultProperties(), Database.City.defaultProperties()), is(empty())); } public void testParseAsn() { @@ -81,8 +93,21 @@ public void testParseAsn() { assertThat(parseAsn("anythingelse"), nullValue()); } + public void testParseLocationDouble() { + // expected case: "123.45" is 123.45 + assertThat(parseLocationDouble("123.45"), equalTo(123.45)); + // defensive cases: null and empty becomes null, this is not expected fwiw + assertThat(parseLocationDouble(null), nullValue()); + assertThat(parseLocationDouble(""), nullValue()); + // defensive cases: we strip whitespace + assertThat(parseLocationDouble(" -123.45 "), equalTo(-123.45)); + // bottom case: a non-parsable string is null + assertThat(parseLocationDouble("anythingelse"), nullValue()); + } + public void testAsn() throws IOException { - Path configDir = createTempDir(); + assumeFalse("https://github.com/elastic/elasticsearch/issues/114266", Constants.WINDOWS); + Path configDir = tmpDir; copyDatabase("ipinfo/ip_asn_sample.mmdb", configDir.resolve("ip_asn_sample.mmdb")); copyDatabase("ipinfo/asn_sample.mmdb", configDir.resolve("asn_sample.mmdb")); @@ -91,9 +116,8 @@ public void testAsn() throws IOException { configDatabases.initialize(resourceWatcherService); // this is the 'free' ASN database (sample) - { - DatabaseReaderLazyLoader loader = configDatabases.getDatabase("ip_asn_sample.mmdb"); - IpDataLookup lookup = new IpinfoIpDataLookups.Asn(Set.of(Database.Property.values())); + try (DatabaseReaderLazyLoader loader = configDatabases.getDatabase("ip_asn_sample.mmdb")) { + IpDataLookup lookup = new IpinfoIpDataLookups.Asn(Database.AsnV2.properties()); Map data = lookup.getData(loader, "5.182.109.0"); assertThat( data, @@ -110,9 +134,8 @@ public void testAsn() throws IOException { } // this is the non-free or 'standard' ASN database (sample) - { - DatabaseReaderLazyLoader loader = configDatabases.getDatabase("asn_sample.mmdb"); - IpDataLookup lookup = new IpinfoIpDataLookups.Asn(Set.of(Database.Property.values())); + try (DatabaseReaderLazyLoader loader = configDatabases.getDatabase("asn_sample.mmdb")) { + IpDataLookup lookup = new IpinfoIpDataLookups.Asn(Database.AsnV2.properties()); Map data = lookup.getData(loader, "23.53.116.0"); assertThat( data, @@ -132,7 +155,8 @@ public void testAsn() throws IOException { } public void testAsnInvariants() { - Path configDir = createTempDir(); + assumeFalse("https://github.com/elastic/elasticsearch/issues/114266", Constants.WINDOWS); + Path configDir = tmpDir; copyDatabase("ipinfo/ip_asn_sample.mmdb", configDir.resolve("ip_asn_sample.mmdb")); copyDatabase("ipinfo/asn_sample.mmdb", configDir.resolve("asn_sample.mmdb")); @@ -168,7 +192,8 @@ public void testAsnInvariants() { } public void testCountry() throws IOException { - Path configDir = createTempDir(); + assumeFalse("https://github.com/elastic/elasticsearch/issues/114266", Constants.WINDOWS); + Path configDir = tmpDir; copyDatabase("ipinfo/ip_country_sample.mmdb", configDir.resolve("ip_country_sample.mmdb")); GeoIpCache cache = new GeoIpCache(1000); // real cache to test purging of entries upon a reload @@ -176,9 +201,8 @@ public void testCountry() throws IOException { configDatabases.initialize(resourceWatcherService); // this is the 'free' Country database (sample) - { - DatabaseReaderLazyLoader loader = configDatabases.getDatabase("ip_country_sample.mmdb"); - IpDataLookup lookup = new IpinfoIpDataLookups.Country(Set.of(Database.Property.values())); + try (DatabaseReaderLazyLoader loader = configDatabases.getDatabase("ip_country_sample.mmdb")) { + IpDataLookup lookup = new IpinfoIpDataLookups.Country(Database.Country.properties()); Map data = lookup.getData(loader, "4.221.143.168"); assertThat( data, @@ -195,6 +219,74 @@ public void testCountry() throws IOException { } } + public void testGeolocation() throws IOException { + assumeFalse("https://github.com/elastic/elasticsearch/issues/114266", Constants.WINDOWS); + Path configDir = tmpDir; + copyDatabase("ipinfo/ip_geolocation_sample.mmdb", configDir.resolve("ip_geolocation_sample.mmdb")); + + GeoIpCache cache = new GeoIpCache(1000); // real cache to test purging of entries upon a reload + ConfigDatabases configDatabases = new ConfigDatabases(configDir, cache); + configDatabases.initialize(resourceWatcherService); + + // this is the non-free or 'standard' Geolocation database (sample) + try (DatabaseReaderLazyLoader loader = configDatabases.getDatabase("ip_geolocation_sample.mmdb")) { + IpDataLookup lookup = new IpinfoIpDataLookups.Geolocation(Database.CityV2.properties()); + Map data = lookup.getData(loader, "2.124.90.182"); + assertThat( + data, + equalTo( + Map.ofEntries( + entry("ip", "2.124.90.182"), + entry("country_iso_code", "GB"), + entry("region_name", "England"), + entry("city_name", "London"), + entry("timezone", "Europe/London"), + entry("postal_code", "E1W"), + entry("location", Map.of("lat", 51.50853, "lon", -0.12574)) + ) + ) + ); + } + } + + public void testGeolocationInvariants() { + assumeFalse("https://github.com/elastic/elasticsearch/issues/114266", Constants.WINDOWS); + Path configDir = tmpDir; + copyDatabase("ipinfo/ip_geolocation_sample.mmdb", configDir.resolve("ip_geolocation_sample.mmdb")); + + { + final Set expectedColumns = Set.of( + "network", + "city", + "region", + "country", + "postal_code", + "timezone", + "latitude", + "longitude" + ); + + Path databasePath = configDir.resolve("ip_geolocation_sample.mmdb"); + assertDatabaseInvariants(databasePath, (ip, row) -> { + assertThat(row.keySet(), equalTo(expectedColumns)); + { + String latitude = (String) row.get("latitude"); + assertThat(latitude, equalTo(latitude.trim())); + Double parsed = parseLocationDouble(latitude); + assertThat(parsed, notNullValue()); + assertThat(latitude, equalTo(Double.toString(parsed))); // reverse it + } + { + String longitude = (String) row.get("longitude"); + assertThat(longitude, equalTo(longitude.trim())); + Double parsed = parseLocationDouble(longitude); + assertThat(parsed, notNullValue()); + assertThat(longitude, equalTo(Double.toString(parsed))); // reverse it + } + }); + } + } + private static void assertDatabaseInvariants(final Path databasePath, final BiConsumer> rowConsumer) { try (Reader reader = new Reader(pathToFile(databasePath))) { Networks networks = reader.networks(Map.class); diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java index 1e05cf2b3ba33..d377a9b97fcc4 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java @@ -361,7 +361,7 @@ public class MaxMindSupportTests extends ESTestCase { private static final Set> KNOWN_UNSUPPORTED_RESPONSE_CLASSES = Set.of(IpRiskResponse.class); - private static final Set KNOWN_UNSUPPORTED_DATABASE_VARIANTS = Set.of(Database.AsnV2); + private static final Set KNOWN_UNSUPPORTED_DATABASE_VARIANTS = Set.of(Database.AsnV2, Database.CityV2); public void testMaxMindSupport() { for (Database databaseType : Database.values()) { diff --git a/modules/ingest-geoip/src/test/resources/ipinfo/ip_geolocation_sample.mmdb b/modules/ingest-geoip/src/test/resources/ipinfo/ip_geolocation_sample.mmdb new file mode 100644 index 0000000000000..ed738bdde1450 Binary files /dev/null and b/modules/ingest-geoip/src/test/resources/ipinfo/ip_geolocation_sample.mmdb differ diff --git a/muted-tests.yml b/muted-tests.yml index d6d5d47af3a76..ef27eeeffc14a 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -354,9 +354,6 @@ tests: - class: org.elasticsearch.action.bulk.IncrementalBulkIT method: testIncrementalBulkLowWatermarkBackOff issue: https://github.com/elastic/elasticsearch/issues/114182 -- class: org.elasticsearch.aggregations.AggregationsClientYamlTestSuiteIT - method: test {yaml=aggregations/stats_metric_fail_formatting/fail formatting} - issue: https://github.com/elastic/elasticsearch/issues/114187 - class: org.elasticsearch.xpack.esql.action.EsqlActionBreakerIT issue: https://github.com/elastic/elasticsearch/issues/114194 - class: org.elasticsearch.xpack.ilm.ExplainLifecycleIT @@ -365,17 +362,21 @@ tests: - class: org.elasticsearch.xpack.inference.services.openai.OpenAiServiceTests method: testInfer_StreamRequest issue: https://github.com/elastic/elasticsearch/issues/114232 -- class: org.elasticsearch.logsdb.datageneration.DataGeneratorTests - method: testDataGeneratorProducesValidMappingAndDocument - issue: https://github.com/elastic/elasticsearch/issues/114188 -- class: org.elasticsearch.ingest.geoip.IpinfoIpDataLookupsTests - issue: https://github.com/elastic/elasticsearch/issues/114266 -- class: org.elasticsearch.index.SearchSlowLogTests - method: testLevelPrecedence - issue: https://github.com/elastic/elasticsearch/issues/114300 -- class: org.elasticsearch.index.SearchSlowLogTests - method: testTwoLoggersDifferentLevel - issue: https://github.com/elastic/elasticsearch/issues/114301 +- class: org.elasticsearch.xpack.inference.services.cohere.CohereServiceTests + method: testInfer_StreamRequest_ErrorResponse + issue: https://github.com/elastic/elasticsearch/issues/114327 +- class: org.elasticsearch.xpack.rank.rrf.RRFRankClientYamlTestSuiteIT + method: test {yaml=rrf/700_rrf_retriever_search_api_compatibility/rrf retriever with top-level collapse} + issue: https://github.com/elastic/elasticsearch/issues/114331 +- class: org.elasticsearch.xpack.security.CoreWithSecurityClientYamlTestSuiteIT + method: test {yaml=cluster.stats/30_ccs_stats/cross-cluster search stats search} + issue: https://github.com/elastic/elasticsearch/issues/114371 +- class: org.elasticsearch.xpack.esql.qa.single_node.RestEsqlIT + method: testProfileOrdinalsGroupingOperator {SYNC} + issue: https://github.com/elastic/elasticsearch/issues/114380 +- class: org.elasticsearch.xpack.inference.services.cohere.CohereServiceTests + method: testInfer_StreamRequest + issue: https://github.com/elastic/elasticsearch/issues/114385 # Examples: # diff --git a/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml b/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml index b4672b1d8924d..18eb401aaa0fe 100644 --- a/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml +++ b/qa/smoke-test-ingest-with-all-dependencies/src/yamlRestTest/resources/rest-api-spec/test/ingest/80_ingest_simulate.yml @@ -371,7 +371,7 @@ setup: template: settings: index: - default_pipeline: "foo_pipeline" + default_pipeline: "foo-pipeline" - do: allowed_warnings: @@ -523,7 +523,7 @@ setup: template: settings: index: - default_pipeline: "foo_pipeline" + default_pipeline: "foo-pipeline" - do: allowed_warnings: @@ -807,3 +807,412 @@ setup: - match: { docs.0.doc._source.foo: "FOO" } - match: { docs.0.doc.executed_pipelines: ["foo-pipeline-2"] } - not_exists: docs.0.doc.error + +--- +"Test ingest simulate with index template substitutions": + + - skip: + features: + - headers + - allowed_warnings + + - requires: + cluster_features: ["simulate.index.template.substitutions"] + reason: "ingest simulate index template substitutions added in 8.16" + + - do: + headers: + Content-Type: application/json + ingest.put_pipeline: + id: "foo-pipeline" + body: > + { + "processors": [ + { + "set": { + "field": "foo", + "value": true + } + } + ] + } + - match: { acknowledged: true } + + - do: + cluster.put_component_template: + name: settings_template + body: + template: + settings: + index: + default_pipeline: "foo-pipeline" + + - do: + cluster.put_component_template: + name: mappings_template + body: + template: + mappings: + dynamic: strict + properties: + foo: + type: keyword + + - do: + allowed_warnings: + - "index template [test-composable-1] has index patterns [foo*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [test-composable-1] will take precedence during new index creation" + indices.put_index_template: + name: test-composable-1 + body: + index_patterns: + - foo* + composed_of: + - mappings_template + + - do: + headers: + Content-Type: application/json + simulate.ingest: + index: foo-1 + body: > + { + "docs": [ + { + "_id": "asdf", + "_source": { + "foo": "FOO" + } + } + ], + "component_template_substitutions": { + "settings_template": { + "template": { + "settings": { + "index": { + "default_pipeline": null + } + } + } + } + }, + "index_template_substitutions": { + "foo_index_template": { + "index_patterns":[ + "foo*" + ], + "composed_of": ["settings_template"] + } + } + } + - length: { docs: 1 } + - match: { docs.0.doc._index: "foo-1" } + - match: { docs.0.doc._source.foo: "FOO" } + - match: { docs.0.doc.executed_pipelines: [] } + - not_exists: docs.0.doc.error + + - do: + indices.create: + index: foo-1 + - match: { acknowledged: true } + + - do: + headers: + Content-Type: application/json + simulate.ingest: + index: foo-1 + body: > + { + "docs": [ + { + "_id": "asdf", + "_source": { + "foo": "FOO" + } + } + ], + "component_template_substitutions": { + "settings_template": { + "template": { + "settings": { + "index": { + "default_pipeline": null + } + } + } + } + }, + "index_template_substitutions": { + "foo_index_template": { + "index_patterns":[ + "foo*" + ], + "composed_of": ["settings_template", "mappings_template"] + } + } + } + - length: { docs: 1 } + - match: { docs.0.doc._index: "foo-1" } + - match: { docs.0.doc._source.foo: "FOO" } + - match: { docs.0.doc.executed_pipelines: [] } + - not_exists: docs.0.doc.error + + - do: + headers: + Content-Type: application/json + simulate.ingest: + index: foo-1 + body: > + { + "docs": [ + { + "_id": "asdf", + "_source": { + "foo": "FOO" + } + } + ], + "component_template_substitutions": { + "mappings_template": { + "template": { + "mappings": { + "dynamic": "strict", + "properties": { + "foo": { + "type": "boolean" + } + } + } + } + } + }, + "index_template_substitutions": { + "foo_index_template": { + "index_patterns":[ + "foo*" + ], + "composed_of": ["settings_template", "mappings_template"] + } + } + } + - length: { docs: 1 } + - match: { docs.0.doc._index: "foo-1" } + - match: { docs.0.doc._source.foo: true } + - match: { docs.0.doc.executed_pipelines: ["foo-pipeline"] } + - not_exists: docs.0.doc.error + +--- +"Test ingest simulate with index template substitutions for data streams": + # In this test, we make sure that when the index template is a data stream template, simulate ingest works the same whether the data + # stream has been created or not -- either way, we expect it to use the template rather than the data stream / index mappings and settings. + + - skip: + features: + - headers + - allowed_warnings + + - requires: + cluster_features: ["simulate.index.template.substitutions"] + reason: "ingest simulate component template substitutions added in 8.16" + + - do: + headers: + Content-Type: application/json + ingest.put_pipeline: + id: "foo-pipeline" + body: > + { + "processors": [ + { + "set": { + "field": "foo", + "value": true + } + } + ] + } + - match: { acknowledged: true } + + - do: + cluster.put_component_template: + name: mappings_template + body: + template: + mappings: + dynamic: strict + properties: + foo: + type: boolean + + - do: + cluster.put_component_template: + name: settings_template + body: + template: + settings: + index: + default_pipeline: "foo-pipeline" + + - do: + allowed_warnings: + - "index template [test-composable-1] has index patterns [foo*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [test-composable-1] will take precedence during new index creation" + indices.put_index_template: + name: test-composable-1 + body: + index_patterns: + - foo* + composed_of: + - mappings_template + - settings_template + + - do: + allowed_warnings: + - "index template [my-template-1] has index patterns [simple-data-stream1] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-template-1] will take precedence during new index creation" + indices.put_index_template: + name: my-template-1 + body: + index_patterns: [simple-data-stream1] + composed_of: + - mappings_template + - settings_template + data_stream: {} + + # Here we replace my-template-1 with a substitute version that uses the settings_template_2 and mappings_template_2 templates defined in + # this request, and foo-pipeline-2 defined in this request. + - do: + headers: + Content-Type: application/json + simulate.ingest: + index: simple-data-stream1 + body: > + { + "docs": [ + { + "_id": "asdf", + "_source": { + "@timestamp": 1234, + "foo": false + } + } + ], + "pipeline_substitutions": { + "foo-pipeline-2": { + "processors": [ + { + "set": { + "field": "foo", + "value": "FOO" + } + } + ] + } + }, + "component_template_substitutions": { + "settings_template_2": { + "template": { + "settings": { + "index": { + "default_pipeline": "foo-pipeline-2" + } + } + } + }, + "mappings_template_2": { + "template": { + "mappings": { + "dynamic": "strict", + "properties": { + "foo": { + "type": "keyword" + } + } + } + } + } + }, + "index_template_substitutions": { + "my-template-1": { + "index_patterns": ["simple-data-stream1"], + "composed_of": ["settings_template_2", "mappings_template_2"], + "data_stream": {} + } + } + } + - length: { docs: 1 } + - match: { docs.0.doc._index: "simple-data-stream1" } + - match: { docs.0.doc._source.foo: "FOO" } + - match: { docs.0.doc.executed_pipelines: ["foo-pipeline-2"] } + - not_exists: docs.0.doc.error + + - do: + indices.create_data_stream: + name: simple-data-stream1 + - is_true: acknowledged + + - do: + cluster.health: + wait_for_status: yellow + + - do: + headers: + Content-Type: application/json + simulate.ingest: + index: simple-data-stream1 + body: > + { + "docs": [ + { + "_id": "asdf", + "_source": { + "@timestamp": 1234, + "foo": false + } + } + ], + "pipeline_substitutions": { + "foo-pipeline-2": { + "processors": [ + { + "set": { + "field": "foo", + "value": "FOO" + } + } + ] + } + }, + "component_template_substitutions": { + "settings_template_2": { + "template": { + "settings": { + "index": { + "default_pipeline": "foo-pipeline-2" + } + } + } + }, + "mappings_template_2": { + "template": { + "mappings": { + "dynamic": "strict", + "properties": { + "foo": { + "type": "keyword" + } + } + } + } + } + }, + "index_template_substitutions": { + "my-template-1": { + "index_patterns": ["simple-data-stream1"], + "composed_of": ["settings_template_2", "mappings_template_2"], + "data_stream": {} + } + } + } + - length: { docs: 1 } + - match: { docs.0.doc._index: "simple-data-stream1" } + - match: { docs.0.doc._source.foo: "FOO" } + - match: { docs.0.doc.executed_pipelines: ["foo-pipeline-2"] } + - not_exists: docs.0.doc.error diff --git a/renovate.json b/renovate.json index 7dde3a9440ed5..0a1d588e6332c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,23 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "github>elastic/renovate-config:only-chainguard" + "github>elastic/renovate-config:only-chainguard", + ":disableDependencyDashboard" + ], + "labels": [">non-issue", ":Delivery/Packaging", "Team:Delivery"], + "baseBranches": ["main", "8.x"], + "packageRules": [ + { + "groupName": "wolfi (versioned)", + "groupSlug": "wolfi-versioned", + "description": "Override the `groupSlug` to create a non-special-character branch name", + "matchDatasources": [ + "docker" + ], + "matchPackagePatterns": [ + "^docker.elastic.co/wolfi/chainguard-base$" + ] + } ], "customManagers": [ { diff --git a/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionIT.java b/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionIT.java index 91674b7ce9050..af99a0344e030 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionIT.java @@ -59,7 +59,7 @@ public void testMappingValidationIndexExists() { } """; indicesAdmin().create(new CreateIndexRequest(indexName).mapping(mapping)).actionGet(); - BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); bulkRequest.add(new IndexRequest(indexName).source(""" { "foo1": "baz" @@ -90,7 +90,7 @@ public void testMappingValidationIndexExists() { } @SuppressWarnings("unchecked") - public void testMappingValidationIndexExistsWithComponentTemplate() throws IOException { + public void testMappingValidationIndexExistsTemplateSubstitutions() throws IOException { /* * This test simulates a BulkRequest of two documents into an existing index. Then we make sure the index contains no documents, and * that the index's mapping in the cluster state has not been updated with the two new field. With the mapping from the template @@ -122,16 +122,19 @@ public void testMappingValidationIndexExistsWithComponentTemplate() throws IOExc .indexPatterns(List.of("my-index-*")) .componentTemplates(List.of("test-component-template")) .build(); - TransportPutComposableIndexTemplateAction.Request request = new TransportPutComposableIndexTemplateAction.Request("test"); + final String indexTemplateName = "test-index-template"; + TransportPutComposableIndexTemplateAction.Request request = new TransportPutComposableIndexTemplateAction.Request( + indexTemplateName + ); request.indexTemplate(composableIndexTemplate); client().execute(TransportPutComposableIndexTemplateAction.TYPE, request).actionGet(); String indexName = "my-index-1"; // First, run before the index is created: - assertMappingsUpdatedFromComponentTemplateSubstitutions(indexName); + assertMappingsUpdatedFromComponentTemplateSubstitutions(indexName, indexTemplateName); // Now, create the index and make sure the component template substitutions work the same: indicesAdmin().create(new CreateIndexRequest(indexName)).actionGet(); - assertMappingsUpdatedFromComponentTemplateSubstitutions(indexName); + assertMappingsUpdatedFromComponentTemplateSubstitutions(indexName, indexTemplateName); // Now make sure nothing was actually changed: indicesAdmin().refresh(new RefreshRequest(indexName)).actionGet(); SearchResponse searchResponse = client().search(new SearchRequest(indexName)).actionGet(); @@ -143,7 +146,7 @@ public void testMappingValidationIndexExistsWithComponentTemplate() throws IOExc assertThat(fields.size(), equalTo(1)); } - private void assertMappingsUpdatedFromComponentTemplateSubstitutions(String indexName) { + private void assertMappingsUpdatedFromComponentTemplateSubstitutions(String indexName, String indexTemplateName) { IndexRequest indexRequest1 = new IndexRequest(indexName).source(""" { "foo1": "baz" @@ -156,7 +159,7 @@ private void assertMappingsUpdatedFromComponentTemplateSubstitutions(String inde """, XContentType.JSON).id(randomUUID()); { // First we use the original component template, and expect a failure in the second document: - BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); bulkRequest.add(indexRequest1); bulkRequest.add(indexRequest2); BulkResponse response = client().execute(new ActionType(SimulateBulkAction.NAME), bulkRequest).actionGet(); @@ -188,7 +191,42 @@ private void assertMappingsUpdatedFromComponentTemplateSubstitutions(String inde ) ) ) - ) + ), + Map.of() + ); + bulkRequest.add(indexRequest1); + bulkRequest.add(indexRequest2); + BulkResponse response = client().execute(new ActionType(SimulateBulkAction.NAME), bulkRequest).actionGet(); + assertThat(response.getItems().length, equalTo(2)); + assertThat(response.getItems()[0].getResponse().getResult(), equalTo(DocWriteResponse.Result.CREATED)); + assertNull(((SimulateIndexResponse) response.getItems()[0].getResponse()).getException()); + assertThat(response.getItems()[1].getResponse().getResult(), equalTo(DocWriteResponse.Result.CREATED)); + assertNull(((SimulateIndexResponse) response.getItems()[1].getResponse()).getException()); + } + + { + /* + * Now we substitute a "test-component-template-2" that defines both fields, and an index template that pulls it in, so we + * expect no exception: + */ + BulkRequest bulkRequest = new SimulateBulkRequest( + Map.of(), + Map.of( + "test-component-template-2", + Map.of( + "template", + Map.of( + "mappings", + Map.of( + "dynamic", + "strict", + "properties", + Map.of("foo1", Map.of("type", "text"), "foo3", Map.of("type", "text")) + ) + ) + ) + ), + Map.of(indexTemplateName, Map.of("index_patterns", List.of(indexName), "composed_of", List.of("test-component-template-2"))) ); bulkRequest.add(indexRequest1); bulkRequest.add(indexRequest2); @@ -207,7 +245,7 @@ public void testMappingValidationIndexDoesNotExistsNoTemplate() { * mapping-less "random-index-template" created by the parent class), so we expect no mapping validation failure. */ String indexName = randomAlphaOfLength(20).toLowerCase(Locale.ROOT); - BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); bulkRequest.add(new IndexRequest(indexName).source(""" { "foo1": "baz" @@ -254,7 +292,7 @@ public void testMappingValidationIndexDoesNotExistsV2Template() throws IOExcepti request.indexTemplate(composableIndexTemplate); client().execute(TransportPutComposableIndexTemplateAction.TYPE, request).actionGet(); - BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); bulkRequest.add(new IndexRequest(indexName).source(""" { "foo1": "baz" @@ -286,7 +324,7 @@ public void testMappingValidationIndexDoesNotExistsV1Template() { indicesAdmin().putTemplate( new PutIndexTemplateRequest("test-template").patterns(List.of("my-index-*")).mapping("foo1", "type=integer") ).actionGet(); - BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); bulkRequest.add(new IndexRequest(indexName).source(""" { "foo1": "baz" @@ -340,7 +378,7 @@ public void testMappingValidationIndexDoesNotExistsDataStream() throws IOExcepti client().execute(TransportPutComposableIndexTemplateAction.TYPE, request).actionGet(); { // First, try with no @timestamp to make sure we're picking up data-stream-specific templates - BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); bulkRequest.add(new IndexRequest(indexName).source(""" { "foo1": "baz" @@ -366,7 +404,7 @@ public void testMappingValidationIndexDoesNotExistsDataStream() throws IOExcepti } { // Now with @timestamp - BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + BulkRequest bulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); bulkRequest.add(new IndexRequest(indexName).source(""" { "@timestamp": "2024-08-27", diff --git a/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderIT.java b/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderIT.java index 2a275cf563d86..19b0f0bd73233 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderIT.java @@ -253,35 +253,17 @@ private Set getShardIds(final String nodeId, final String indexName) { } /** - * Index documents until all the shards are at least WATERMARK_BYTES in size, and return the one with the smallest size + * Index documents until all the shards are at least WATERMARK_BYTES in size. + * @return the shard sizes. */ private ShardSizes createReasonableSizedShards(final String indexName) { - while (true) { - indexRandom(false, indexName, scaledRandomIntBetween(100, 10000)); - forceMerge(); - refresh(); - - final ShardStats[] shardStates = indicesAdmin().prepareStats(indexName) - .clear() - .setStore(true) - .setTranslog(true) - .get() - .getShards(); - - var smallestShardSize = Arrays.stream(shardStates) - .mapToLong(it -> it.getStats().getStore().sizeInBytes()) - .min() - .orElseThrow(() -> new AssertionError("no shards")); - - if (smallestShardSize > WATERMARK_BYTES) { - var shardSizes = Arrays.stream(shardStates) - .map(it -> new ShardSize(removeIndexUUID(it.getShardRouting().shardId()), it.getStats().getStore().sizeInBytes())) - .sorted(Comparator.comparing(ShardSize::size)) - .toList(); - logger.info("Created shards with sizes {}", shardSizes); - return new ShardSizes(shardSizes); - } - } + ShardStats[] shardStats = indexAllShardsToAnEqualOrGreaterMinimumSize(indexName, WATERMARK_BYTES); + var shardSizes = Arrays.stream(shardStats) + .map(it -> new ShardSize(removeIndexUUID(it.getShardRouting().shardId()), it.getStats().getStore().sizeInBytes())) + .sorted(Comparator.comparing(ShardSize::size)) + .toList(); + logger.info("Created shards with sizes {}", shardSizes); + return new ShardSizes(shardSizes); } private record ShardSizes(List sizes) { diff --git a/server/src/internalClusterTest/java/org/elasticsearch/monitor/metrics/IndicesMetricsIT.java b/server/src/internalClusterTest/java/org/elasticsearch/monitor/metrics/IndicesMetricsIT.java index 4a060eadc735b..fb563ee333d07 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/monitor/metrics/IndicesMetricsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/monitor/metrics/IndicesMetricsIT.java @@ -107,14 +107,14 @@ protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) { static final String LOGSDB_INDEXING_TIME = "es.indices.logsdb.indexing.time"; static final String LOGSDB_INDEXING_FAILURE = "es.indices.logsdb.indexing.failure.total"; - public void testIndicesMetrics() throws Exception { - String node = internalCluster().startNode(); + public void testIndicesMetrics() { + String indexNode = internalCluster().startNode(); ensureStableCluster(1); - final TestTelemetryPlugin telemetry = internalCluster().getInstance(PluginsService.class, node) + TestTelemetryPlugin telemetry = internalCluster().getInstance(PluginsService.class, indexNode) .filterPlugins(TestTelemetryPlugin.class) .findFirst() .orElseThrow(); - final IndicesService indicesService = internalCluster().getInstance(IndicesService.class, node); + IndicesService indicesService = internalCluster().getInstance(IndicesService.class, indexNode); var indexing0 = indicesService.stats(CommonStatsFlags.ALL, false).getIndexing().getTotal(); telemetry.resetMeter(); long numStandardIndices = randomIntBetween(1, 5); @@ -131,19 +131,12 @@ public void testIndicesMetrics() throws Exception { STANDARD_BYTES_SIZE, greaterThan(0L), - TIME_SERIES_INDEX_COUNT, - equalTo(0L), - TIME_SERIES_DOCS_COUNT, - equalTo(0L), - TIME_SERIES_BYTES_SIZE, - equalTo(0L), - - LOGSDB_INDEX_COUNT, - equalTo(0L), - LOGSDB_DOCS_COUNT, - equalTo(0L), - LOGSDB_BYTES_SIZE, - equalTo(0L) + STANDARD_INDEXING_COUNT, + equalTo(numStandardDocs), + STANDARD_INDEXING_TIME, + greaterThanOrEqualTo(0L), + STANDARD_INDEXING_FAILURE, + equalTo(indexing1.getIndexFailedCount() - indexing0.getIndexCount()) ) ); @@ -154,13 +147,6 @@ public void testIndicesMetrics() throws Exception { telemetry, 2, Map.of( - STANDARD_INDEX_COUNT, - equalTo(numStandardIndices), - STANDARD_DOCS_COUNT, - equalTo(numStandardDocs), - STANDARD_BYTES_SIZE, - greaterThan(0L), - TIME_SERIES_INDEX_COUNT, equalTo(numTimeSeriesIndices), TIME_SERIES_DOCS_COUNT, @@ -168,12 +154,12 @@ public void testIndicesMetrics() throws Exception { TIME_SERIES_BYTES_SIZE, greaterThan(20L), - LOGSDB_INDEX_COUNT, - equalTo(0L), - LOGSDB_DOCS_COUNT, - equalTo(0L), - LOGSDB_BYTES_SIZE, - equalTo(0L) + TIME_SERIES_INDEXING_COUNT, + equalTo(numTimeSeriesDocs), + TIME_SERIES_INDEXING_TIME, + greaterThanOrEqualTo(0L), + TIME_SERIES_INDEXING_FAILURE, + equalTo(indexing2.getIndexFailedCount() - indexing1.getIndexFailedCount()) ) ); @@ -184,60 +170,58 @@ public void testIndicesMetrics() throws Exception { telemetry, 3, Map.of( - STANDARD_INDEX_COUNT, - equalTo(numStandardIndices), - STANDARD_DOCS_COUNT, - equalTo(numStandardDocs), - STANDARD_BYTES_SIZE, - greaterThan(0L), - - TIME_SERIES_INDEX_COUNT, - equalTo(numTimeSeriesIndices), - TIME_SERIES_DOCS_COUNT, - equalTo(numTimeSeriesDocs), - TIME_SERIES_BYTES_SIZE, - greaterThan(20L), - LOGSDB_INDEX_COUNT, equalTo(numLogsdbIndices), LOGSDB_DOCS_COUNT, equalTo(numLogsdbDocs), LOGSDB_BYTES_SIZE, - greaterThan(0L) + greaterThan(0L), + LOGSDB_INDEXING_COUNT, + equalTo(numLogsdbDocs), + LOGSDB_INDEXING_TIME, + greaterThanOrEqualTo(0L), + LOGSDB_INDEXING_FAILURE, + equalTo(indexing3.getIndexFailedCount() - indexing2.getIndexFailedCount()) ) ); - // indexing stats + // already collected indexing stats collectThenAssertMetrics( telemetry, 4, Map.of( STANDARD_INDEXING_COUNT, - equalTo(numStandardDocs), + equalTo(0L), STANDARD_INDEXING_TIME, - greaterThanOrEqualTo(0L), + equalTo(0L), STANDARD_INDEXING_FAILURE, - equalTo(indexing1.getIndexFailedCount() - indexing0.getIndexCount()), + equalTo(0L), TIME_SERIES_INDEXING_COUNT, - equalTo(numTimeSeriesDocs), + equalTo(0L), TIME_SERIES_INDEXING_TIME, - greaterThanOrEqualTo(0L), + equalTo(0L), TIME_SERIES_INDEXING_FAILURE, - equalTo(indexing2.getIndexFailedCount() - indexing1.getIndexFailedCount()), + equalTo(0L), LOGSDB_INDEXING_COUNT, - equalTo(numLogsdbDocs), + equalTo(0L), LOGSDB_INDEXING_TIME, - greaterThanOrEqualTo(0L), + equalTo(0L), LOGSDB_INDEXING_FAILURE, - equalTo(indexing3.getIndexFailedCount() - indexing2.getIndexFailedCount()) + equalTo(0L) ) ); - telemetry.resetMeter(); - + String searchNode = internalCluster().startDataOnlyNode(); + indicesService = internalCluster().getInstance(IndicesService.class, searchNode); + telemetry = internalCluster().getInstance(PluginsService.class, searchNode) + .filterPlugins(TestTelemetryPlugin.class) + .findFirst() + .orElseThrow(); + ensureGreen("st*", "log*", "time*"); // search and fetch - client().prepareSearch("standard*").setSize(100).get().decRef(); - var nodeStats1 = indicesService.stats(CommonStatsFlags.ALL, false).getSearch().getTotal(); + String preference = "_only_local"; + client(searchNode).prepareSearch("standard*").setPreference(preference).setSize(100).get().decRef(); + var search1 = indicesService.stats(CommonStatsFlags.ALL, false).getSearch().getTotal(); collectThenAssertMetrics( telemetry, 1, @@ -245,11 +229,11 @@ public void testIndicesMetrics() throws Exception { STANDARD_QUERY_COUNT, equalTo(numStandardIndices), STANDARD_QUERY_TIME, - equalTo(nodeStats1.getQueryTimeInMillis()), + equalTo(search1.getQueryTimeInMillis()), STANDARD_FETCH_COUNT, - equalTo(nodeStats1.getFetchCount()), + equalTo(search1.getFetchCount()), STANDARD_FETCH_TIME, - equalTo(nodeStats1.getFetchTimeInMillis()), + equalTo(search1.getFetchTimeInMillis()), TIME_SERIES_QUERY_COUNT, equalTo(0L), @@ -263,25 +247,25 @@ public void testIndicesMetrics() throws Exception { ) ); - client().prepareSearch("time*").setSize(100).get().decRef(); - var nodeStats2 = indicesService.stats(CommonStatsFlags.ALL, false).getSearch().getTotal(); + client(searchNode).prepareSearch("time*").setPreference(preference).setSize(100).get().decRef(); + var search2 = indicesService.stats(CommonStatsFlags.ALL, false).getSearch().getTotal(); collectThenAssertMetrics( telemetry, 2, Map.of( STANDARD_QUERY_COUNT, - equalTo(numStandardIndices), + equalTo(0L), STANDARD_QUERY_TIME, - equalTo(nodeStats1.getQueryTimeInMillis()), + equalTo(0L), TIME_SERIES_QUERY_COUNT, equalTo(numTimeSeriesIndices), TIME_SERIES_QUERY_TIME, - equalTo(nodeStats2.getQueryTimeInMillis() - nodeStats1.getQueryTimeInMillis()), + equalTo(search2.getQueryTimeInMillis() - search1.getQueryTimeInMillis()), TIME_SERIES_FETCH_COUNT, - equalTo(nodeStats2.getFetchCount() - nodeStats1.getFetchCount()), + equalTo(search2.getFetchCount() - search1.getFetchCount()), TIME_SERIES_FETCH_TIME, - equalTo(nodeStats2.getFetchTimeInMillis() - nodeStats1.getFetchTimeInMillis()), + equalTo(search2.getFetchTimeInMillis() - search1.getFetchTimeInMillis()), LOGSDB_QUERY_COUNT, equalTo(0L), @@ -289,41 +273,44 @@ public void testIndicesMetrics() throws Exception { equalTo(0L) ) ); - client().prepareSearch("logs*").setSize(100).get().decRef(); - var nodeStats3 = indicesService.stats(CommonStatsFlags.ALL, false).getSearch().getTotal(); + client(searchNode).prepareSearch("logs*").setPreference(preference).setSize(100).get().decRef(); + var search3 = indicesService.stats(CommonStatsFlags.ALL, false).getSearch().getTotal(); collectThenAssertMetrics( telemetry, 3, Map.of( STANDARD_QUERY_COUNT, - equalTo(numStandardIndices), + equalTo(0L), STANDARD_QUERY_TIME, - equalTo(nodeStats1.getQueryTimeInMillis()), + equalTo(0L), TIME_SERIES_QUERY_COUNT, - equalTo(numTimeSeriesIndices), + equalTo(0L), TIME_SERIES_QUERY_TIME, - equalTo(nodeStats2.getQueryTimeInMillis() - nodeStats1.getQueryTimeInMillis()), + equalTo(0L), LOGSDB_QUERY_COUNT, equalTo(numLogsdbIndices), LOGSDB_QUERY_TIME, - equalTo(nodeStats3.getQueryTimeInMillis() - nodeStats2.getQueryTimeInMillis()), + equalTo(search3.getQueryTimeInMillis() - search2.getQueryTimeInMillis()), LOGSDB_FETCH_COUNT, - equalTo(nodeStats3.getFetchCount() - nodeStats2.getFetchCount()), + equalTo(search3.getFetchCount() - search2.getFetchCount()), LOGSDB_FETCH_TIME, - equalTo(nodeStats3.getFetchTimeInMillis() - nodeStats2.getFetchTimeInMillis()) + equalTo(search3.getFetchTimeInMillis() - search2.getFetchTimeInMillis()) ) ); // search failures - expectThrows(Exception.class, () -> { client().prepareSearch("logs*").setRuntimeMappings(parseMapping(""" - { - "fail_me": { - "type": "long", - "script": {"source": "<>", "lang": "failing_field"} + expectThrows( + Exception.class, + () -> { client(searchNode).prepareSearch("logs*").setPreference(preference).setRuntimeMappings(parseMapping(""" + { + "fail_me": { + "type": "long", + "script": {"source": "<>", "lang": "failing_field"} + } } - } - """)).setQuery(new RangeQueryBuilder("fail_me").gte(0)).setAllowPartialSearchResults(true).get(); }); + """)).setQuery(new RangeQueryBuilder("fail_me").gte(0)).setAllowPartialSearchResults(true).get(); } + ); collectThenAssertMetrics( telemetry, 4, diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/query/SimpleQueryStringIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/query/SimpleQueryStringIT.java index 2fe7931d64c81..35f11eb1429b4 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/query/SimpleQueryStringIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/query/SimpleQueryStringIT.java @@ -582,6 +582,32 @@ public void testFieldAliasOnDisallowedFieldType() throws Exception { }); } + public void testSimpleQueryStringWithAnalysisStopWords() throws Exception { + String mapping = Strings.toString( + XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject("body") + .field("type", "text") + .field("analyzer", "stop") + .endObject() + .endObject() + .endObject() + ); + + CreateIndexRequestBuilder mappingRequest = indicesAdmin().prepareCreate("test1").setMapping(mapping); + mappingRequest.get(); + indexRandom(true, prepareIndex("test1").setId("1").setSource("body", "Some Text")); + refresh(); + + assertHitCount( + prepareSearch().setQuery( + simpleQueryStringQuery("the* text*").analyzeWildcard(true).defaultOperator(Operator.AND).field("body") + ), + 1 + ); + } + private void assertHits(SearchHits hits, String... ids) { assertThat(hits.getTotalHits().value, equalTo((long) ids.length)); Set hitIds = new HashSet<>(); diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotShutdownIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotShutdownIT.java index 3c71b50321c76..980ef2a87c9c2 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotShutdownIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotShutdownIT.java @@ -9,6 +9,7 @@ package org.elasticsearch.snapshots; +import org.apache.logging.log4j.Level; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionResponse; @@ -33,19 +34,26 @@ import org.elasticsearch.common.Priority; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeUnit; +import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ClusterServiceUtils; +import org.elasticsearch.test.MockLog; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.test.transport.MockTransportService; import java.util.Collection; import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import static org.elasticsearch.snapshots.SnapshotShutdownProgressTracker.SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; @@ -55,15 +63,44 @@ public class SnapshotShutdownIT extends AbstractSnapshotIntegTestCase { private static final String REQUIRE_NODE_NAME_SETTING = IndexMetadata.INDEX_ROUTING_REQUIRE_GROUP_PREFIX + "._name"; + private MockLog mockLog; + + public void setUp() throws Exception { + super.setUp(); + mockLog = MockLog.capture(SnapshotShutdownProgressTracker.class); + } + + private void resetMockLog() { + mockLog.close(); + mockLog = MockLog.capture(SnapshotShutdownProgressTracker.class); + } + + public void tearDown() throws Exception { + mockLog.close(); + super.tearDown(); + } + @Override protected Collection> nodePlugins() { return CollectionUtils.appendToCopy(super.nodePlugins(), MockTransportService.TestPlugin.class); } + /** + * Tests that shard snapshots on a node with RESTART shutdown metadata will finish on the same node. + */ + @TestLogging( + value = "org.elasticsearch.snapshots.SnapshotShutdownProgressTracker:DEBUG", + reason = "Testing SnapshotShutdownProgressTracker's progress, which is reported at the DEBUG logging level" + ) public void testRestartNodeDuringSnapshot() throws Exception { // Marking a node for restart has no impact on snapshots (see #71333 for how to handle this case) internalCluster().ensureAtLeastNumDataNodes(1); - final var originalNode = internalCluster().startDataOnlyNode(); + final var originalNode = internalCluster().startDataOnlyNode( + // Speed up the logging frequency, so that the test doesn't have to wait too long to check for log messages. + Settings.builder().put(SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING.getKey(), TimeValue.timeValueMillis(200)).build() + ); + final String originalNodeId = internalCluster().getInstance(NodeEnvironment.class, originalNode).nodeId(); + final var indexName = randomIdentifier(); createIndexWithContent(indexName, indexSettings(1, 0).put(REQUIRE_NODE_NAME_SETTING, originalNode).build()); @@ -88,6 +125,16 @@ public void testRestartNodeDuringSnapshot() throws Exception { }); addUnassignedShardsWatcher(clusterService, indexName); + // Ensure that the SnapshotShutdownProgressTracker does not start logging in RESTART mode. + mockLog.addExpectation( + new MockLog.UnseenEventExpectation( + "SnapshotShutdownProgressTracker start log message", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.DEBUG, + "Starting shutdown snapshot progress logging on node [" + originalNodeId + "]" + ) + ); + safeAwait( (ActionListener listener) -> putShutdownMetadata( clusterService, @@ -100,9 +147,15 @@ public void testRestartNodeDuringSnapshot() throws Exception { ) ); assertFalse(snapshotCompletesWithoutPausingListener.isDone()); + + // Verify no SnapshotShutdownProgressTracker logging in RESTART mode. + mockLog.awaitAllExpectationsMatched(); + resetMockLog(); + unblockAllDataNodes(repoName); // lets the shard snapshot continue so the snapshot can succeed assertEquals(SnapshotState.SUCCESS, snapshotFuture.get(10, TimeUnit.SECONDS).getSnapshotInfo().state()); safeAwait(snapshotCompletesWithoutPausingListener); + clearShutdownMetadata(clusterService); } @@ -117,7 +170,7 @@ public void testRemoveNodeDuringSnapshot() throws Exception { final var clusterService = internalCluster().getCurrentMasterNodeInstance(ClusterService.class); final var snapshotFuture = startFullSnapshotBlockedOnDataNode(randomIdentifier(), repoName, originalNode); - final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName); + final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName, 1); addUnassignedShardsWatcher(clusterService, indexName); updateIndexSettings(Settings.builder().putNull(REQUIRE_NODE_NAME_SETTING), indexName); @@ -146,7 +199,7 @@ public void testRemoveNodeAndFailoverMasterDuringSnapshot() throws Exception { final var clusterService = internalCluster().getCurrentMasterNodeInstance(ClusterService.class); final var snapshotFuture = startFullSnapshotBlockedOnDataNode(randomIdentifier(), repoName, originalNode); - final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName); + final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName, 1); addUnassignedShardsWatcher(clusterService, indexName); final var snapshotStatusUpdateBarrier = new CyclicBarrier(2); @@ -264,7 +317,7 @@ public void testRemoveNodeDuringSnapshotWithOtherRunningShardSnapshots() throws final var clusterService = internalCluster().getCurrentMasterNodeInstance(ClusterService.class); final var snapshotFuture = startFullSnapshotBlockedOnDataNode(randomIdentifier(), repoName, nodeForRemoval); - final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName); + final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName, 1); addUnassignedShardsWatcher(clusterService, indexName); waitForBlock(otherNode, repoName); @@ -320,7 +373,7 @@ public void testStartRemoveNodeButDoNotComplete() throws Exception { final var clusterService = internalCluster().getCurrentMasterNodeInstance(ClusterService.class); final var snapshotFuture = startFullSnapshotBlockedOnDataNode(randomIdentifier(), repoName, primaryNode); - final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName); + final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName, 1); addUnassignedShardsWatcher(clusterService, indexName); putShutdownForRemovalMetadata(primaryNode, clusterService); @@ -334,6 +387,9 @@ public void testStartRemoveNodeButDoNotComplete() throws Exception { assertEquals(SnapshotState.SUCCESS, snapshotFuture.get(10, TimeUnit.SECONDS).getSnapshotInfo().state()); } + /** + * Tests that deleting a snapshot will abort paused shard snapshots on a node with shutdown metadata. + */ public void testAbortSnapshotWhileRemovingNode() throws Exception { final var primaryNode = internalCluster().startDataOnlyNode(); final var indexName = randomIdentifier(); @@ -363,7 +419,7 @@ public void testAbortSnapshotWhileRemovingNode() throws Exception { final var clusterService = internalCluster().getCurrentMasterNodeInstance(ClusterService.class); addUnassignedShardsWatcher(clusterService, indexName); putShutdownForRemovalMetadata(primaryNode, clusterService); - unblockAllDataNodes(repoName); // lets the shard snapshot abort, but allocation filtering stops it from moving + unblockAllDataNodes(repoName); // lets the shard snapshot pause, but allocation filtering stops it from moving safeAwait(updateSnapshotStatusBarrier); // wait for data node to notify master that the shard snapshot is paused // abort snapshot (and wait for the abort to land in the cluster state) @@ -414,10 +470,180 @@ public void testShutdownWhileSuccessInFlight() throws Exception { clearShutdownMetadata(clusterService); } + /** + * This test exercises the SnapshotShutdownProgressTracker's log messages reporting the progress of shard snapshots on data nodes. + */ + @TestLogging( + value = "org.elasticsearch.snapshots.SnapshotShutdownProgressTracker:TRACE", + reason = "Testing SnapshotShutdownProgressTracker's progress, which is reported at the TRACE logging level" + ) + public void testSnapshotShutdownProgressTracker() throws Exception { + final var repoName = randomIdentifier(); + final int numShards = randomIntBetween(1, 10); + createRepository(repoName, "mock"); + + // Create another index on another node which will be blocked (remain in state INIT) throughout. + // Not required for this test, just adds some more concurrency. + final var otherNode = internalCluster().startDataOnlyNode(); + final var otherIndex = randomIdentifier(); + createIndexWithContent(otherIndex, indexSettings(numShards, 0).put(REQUIRE_NODE_NAME_SETTING, otherNode).build()); + blockDataNode(repoName, otherNode); + + final var nodeForRemoval = internalCluster().startDataOnlyNode( + // Speed up the logging frequency, so that the test doesn't have to wait too long to check for log messages. + Settings.builder().put(SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING.getKey(), TimeValue.timeValueMillis(200)).build() + ); + final String nodeForRemovalId = internalCluster().getInstance(NodeEnvironment.class, nodeForRemoval).nodeId(); + final var indexName = randomIdentifier(); + createIndexWithContent(indexName, indexSettings(numShards, 0).put(REQUIRE_NODE_NAME_SETTING, nodeForRemoval).build()); + indexAllShardsToAnEqualOrGreaterMinimumSize(indexName, new ByteSizeValue(2, ByteSizeUnit.KB).getBytes()); + + // Start the snapshot with blocking in place on the data node not to allow shard snapshots to finish yet. + final var clusterService = internalCluster().getCurrentMasterNodeInstance(ClusterService.class); + final var snapshotFuture = startFullSnapshotBlockedOnDataNode(randomIdentifier(), repoName, nodeForRemoval); + final var snapshotPausedListener = createSnapshotPausedListener(clusterService, repoName, indexName, numShards); + addUnassignedShardsWatcher(clusterService, indexName); + + waitForBlock(otherNode, repoName); + + logger.info("---> nodeForRemovalId: " + nodeForRemovalId + ", numShards: " + numShards); + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "SnapshotShutdownProgressTracker start log message", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.DEBUG, + "Starting shutdown snapshot progress logging on node [" + nodeForRemovalId + "]" + ) + ); + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "SnapshotShutdownProgressTracker pause set log message", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.DEBUG, + "Pause signals have been set for all shard snapshots on data node [" + nodeForRemovalId + "]" + ) + ); + + putShutdownForRemovalMetadata(nodeForRemoval, clusterService); + + // Check that the SnapshotShutdownProgressTracker was turned on after the shutdown metadata is set above. + mockLog.awaitAllExpectationsMatched(); + resetMockLog(); + + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "SnapshotShutdownProgressTracker running number of snapshots", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.INFO, + "*Number shard snapshots running [" + numShards + "].*" + ) + ); + + // Check that the SnapshotShutdownProgressTracker is tracking the active (not yet paused) shard snapshots. + mockLog.awaitAllExpectationsMatched(); + resetMockLog(); + + // Block on the master when a shard snapshot request comes in, until we can verify that the Tracker saw the outgoing request. + final CountDownLatch snapshotStatusUpdateLatch = new CountDownLatch(1); + final var masterTransportService = MockTransportService.getInstance(internalCluster().getMasterName()); + masterTransportService.addRequestHandlingBehavior( + SnapshotsService.UPDATE_SNAPSHOT_STATUS_ACTION_NAME, + (handler, request, channel, task) -> masterTransportService.getThreadPool().generic().execute(() -> { + safeAwait(snapshotStatusUpdateLatch); + try { + handler.messageReceived(request, channel, task); + } catch (Exception e) { + fail(e); + } + }) + ); + + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "SnapshotShutdownProgressTracker shard snapshot has paused log message", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.INFO, + "*Number shard snapshots waiting for master node reply to status update request [" + numShards + "]*" + ) + ); + + // Let the shard snapshot proceed. It will still get stuck waiting for the master node to respond. + unblockNode(repoName, nodeForRemoval); + + // Check that the SnapshotShutdownProgressTracker observed the request sent to the master node. + mockLog.awaitAllExpectationsMatched(); + resetMockLog(); + + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "SnapshotShutdownProgressTracker shard snapshot has paused log message", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.INFO, + "Current active shard snapshot stats on data node [" + nodeForRemovalId + "]*Paused [" + numShards + "]" + ) + ); + + // Release the master node to respond + snapshotStatusUpdateLatch.countDown(); + + // Wait for the snapshot to fully pause. + safeAwait(snapshotPausedListener); + + // Check that the SnapshotShutdownProgressTracker observed the shard snapshot finishing as paused. + mockLog.awaitAllExpectationsMatched(); + resetMockLog(); + + // Remove the allocation filter so that the shard moves off of the node shutting down. + updateIndexSettings(Settings.builder().putNull(REQUIRE_NODE_NAME_SETTING), indexName); + + // Wait for the shard snapshot to succeed on the non-shutting down node. + safeAwait( + ClusterServiceUtils.addTemporaryStateListener( + clusterService, + state -> SnapshotsInProgress.get(state) + .asStream() + .allMatch( + e -> e.shards() + .entrySet() + .stream() + .anyMatch( + shardEntry -> shardEntry.getKey().getIndexName().equals(indexName) + && switch (shardEntry.getValue().state()) { + case INIT, PAUSED_FOR_NODE_REMOVAL -> false; + case SUCCESS -> true; + case FAILED, ABORTED, MISSING, QUEUED, WAITING -> throw new AssertionError(shardEntry.toString()); + } + ) + ) + ) + ); + + unblockAllDataNodes(repoName); + + // Snapshot completes when the node vacates even though it hasn't been removed yet + assertEquals(SnapshotState.SUCCESS, snapshotFuture.get(10, TimeUnit.SECONDS).getSnapshotInfo().state()); + + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "SnapshotShutdownProgressTracker cancelled log message", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.DEBUG, + "Cancelling shutdown snapshot progress logging on node [" + nodeForRemovalId + "]" + ) + ); + + clearShutdownMetadata(clusterService); + + // Check that the SnapshotShutdownProgressTracker logging was cancelled by the removal of the shutdown metadata. + mockLog.awaitAllExpectationsMatched(); + resetMockLog(); + } + private static SubscribableListener createSnapshotPausedListener( ClusterService clusterService, String repoName, - String indexName + String indexName, + int numShards ) { return ClusterServiceUtils.addTemporaryStateListener(clusterService, state -> { final var entriesForRepo = SnapshotsInProgress.get(state).forRepo(repoName); @@ -434,10 +660,17 @@ private static SubscribableListener createSnapshotPausedListener( .stream() .flatMap(e -> e.getKey().getIndexName().equals(indexName) ? Stream.of(e.getValue()) : Stream.of()) .toList(); - assertThat(shardSnapshotStatuses, hasSize(1)); - final var shardState = shardSnapshotStatuses.iterator().next().state(); - assertThat(shardState, oneOf(SnapshotsInProgress.ShardState.INIT, SnapshotsInProgress.ShardState.PAUSED_FOR_NODE_REMOVAL)); - return shardState == SnapshotsInProgress.ShardState.PAUSED_FOR_NODE_REMOVAL; + assertThat(shardSnapshotStatuses, hasSize(numShards)); + for (var shardStatus : shardSnapshotStatuses) { + assertThat( + shardStatus.state(), + oneOf(SnapshotsInProgress.ShardState.INIT, SnapshotsInProgress.ShardState.PAUSED_FOR_NODE_REMOVAL) + ); + if (shardStatus.state() == SnapshotsInProgress.ShardState.INIT) { + return false; + } + } + return true; }); } diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 2095ba47ee377..78fddad603cab 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -237,6 +237,7 @@ static TransportVersion def(int id) { public static final TransportVersion DATE_TIME_DOC_VALUES_LOCALES = def(8_761_00_0); public static final TransportVersion FAST_REFRESH_RCO = def(8_762_00_0); public static final TransportVersion TEXT_SIMILARITY_RERANKER_QUERY_REWRITE = def(8_763_00_0); + public static final TransportVersion SIMULATE_INDEX_TEMPLATES_SUBSTITUTIONS = def(8_764_00_0); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/action/bulk/BulkFeatures.java b/server/src/main/java/org/elasticsearch/action/bulk/BulkFeatures.java index af1782ac1ade3..78e603fba9be0 100644 --- a/server/src/main/java/org/elasticsearch/action/bulk/BulkFeatures.java +++ b/server/src/main/java/org/elasticsearch/action/bulk/BulkFeatures.java @@ -15,11 +15,17 @@ import java.util.Set; import static org.elasticsearch.action.bulk.TransportSimulateBulkAction.SIMULATE_COMPONENT_TEMPLATE_SUBSTITUTIONS; +import static org.elasticsearch.action.bulk.TransportSimulateBulkAction.SIMULATE_INDEX_TEMPLATE_SUBSTITUTIONS; import static org.elasticsearch.action.bulk.TransportSimulateBulkAction.SIMULATE_MAPPING_VALIDATION; import static org.elasticsearch.action.bulk.TransportSimulateBulkAction.SIMULATE_MAPPING_VALIDATION_TEMPLATES; public class BulkFeatures implements FeatureSpecification { public Set getFeatures() { - return Set.of(SIMULATE_MAPPING_VALIDATION, SIMULATE_MAPPING_VALIDATION_TEMPLATES, SIMULATE_COMPONENT_TEMPLATE_SUBSTITUTIONS); + return Set.of( + SIMULATE_MAPPING_VALIDATION, + SIMULATE_MAPPING_VALIDATION_TEMPLATES, + SIMULATE_COMPONENT_TEMPLATE_SUBSTITUTIONS, + SIMULATE_INDEX_TEMPLATE_SUBSTITUTIONS + ); } } diff --git a/server/src/main/java/org/elasticsearch/action/bulk/BulkRequest.java b/server/src/main/java/org/elasticsearch/action/bulk/BulkRequest.java index 558901f102299..f62b2f48fa2fd 100644 --- a/server/src/main/java/org/elasticsearch/action/bulk/BulkRequest.java +++ b/server/src/main/java/org/elasticsearch/action/bulk/BulkRequest.java @@ -23,6 +23,7 @@ import org.elasticsearch.action.support.replication.ReplicationRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.ComposableIndexTemplate; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; @@ -506,6 +507,10 @@ public Map getComponentTemplateSubstitutions() throws return Map.of(); } + public Map getIndexTemplateSubstitutions() throws IOException { + return Map.of(); + } + record IncrementalState(Map shardLevelFailures, boolean indexingPressureAccounted) implements Writeable { static final IncrementalState EMPTY = new IncrementalState(Collections.emptyMap(), false); diff --git a/server/src/main/java/org/elasticsearch/action/bulk/SimulateBulkRequest.java b/server/src/main/java/org/elasticsearch/action/bulk/SimulateBulkRequest.java index 3cc7fa12733bf..6fa22151396df 100644 --- a/server/src/main/java/org/elasticsearch/action/bulk/SimulateBulkRequest.java +++ b/server/src/main/java/org/elasticsearch/action/bulk/SimulateBulkRequest.java @@ -11,6 +11,7 @@ import org.elasticsearch.TransportVersions; import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.ComposableIndexTemplate; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentHelper; @@ -22,8 +23,8 @@ import java.util.Map; /** - * This extends BulkRequest with support for providing substitute pipeline definitions and component template definitions. In a user - * request, the substitutions will look something like this: + * This extends BulkRequest with support for providing substitute pipeline definitions, component template definitions, and index template + * substitutions. In a user request, the substitutions will look something like this: * * "pipeline_substitutions": { * "my-pipeline-1": { @@ -72,6 +73,16 @@ * } * } * } + * }, + * "index_template_substitutions": { + * "my-index-template-1": { + * "template": { + * "index_patterns": ["foo*", "bar*"] + * "composed_of": [ + * "component-template-1", + * "component-template-2" + * ] + * } * } * } * @@ -82,6 +93,7 @@ public class SimulateBulkRequest extends BulkRequest { private final Map> pipelineSubstitutions; private final Map> componentTemplateSubstitutions; + private final Map> indexTemplateSubstitutions; /** * @param pipelineSubstitutions The pipeline definitions that are to be used in place of any pre-existing pipeline definitions with @@ -89,14 +101,18 @@ public class SimulateBulkRequest extends BulkRequest { * parsed by XContentHelper.convertToMap(). * @param componentTemplateSubstitutions The component template definitions that are to be used in place of any pre-existing * component template definitions with the same name. + * @param indexTemplateSubstitutions The index template definitions that are to be used in place of any pre-existing + * index template definitions with the same name. */ public SimulateBulkRequest( @Nullable Map> pipelineSubstitutions, - @Nullable Map> componentTemplateSubstitutions + @Nullable Map> componentTemplateSubstitutions, + @Nullable Map> indexTemplateSubstitutions ) { super(); this.pipelineSubstitutions = pipelineSubstitutions; this.componentTemplateSubstitutions = componentTemplateSubstitutions; + this.indexTemplateSubstitutions = indexTemplateSubstitutions; } @SuppressWarnings("unchecked") @@ -108,6 +124,11 @@ public SimulateBulkRequest(StreamInput in) throws IOException { } else { componentTemplateSubstitutions = Map.of(); } + if (in.getTransportVersion().onOrAfter(TransportVersions.SIMULATE_INDEX_TEMPLATES_SUBSTITUTIONS)) { + this.indexTemplateSubstitutions = (Map>) in.readGenericValue(); + } else { + indexTemplateSubstitutions = Map.of(); + } } @Override @@ -117,6 +138,9 @@ public void writeTo(StreamOutput out) throws IOException { if (out.getTransportVersion().onOrAfter(TransportVersions.SIMULATE_COMPONENT_TEMPLATES_SUBSTITUTIONS)) { out.writeGenericValue(componentTemplateSubstitutions); } + if (out.getTransportVersion().onOrAfter(TransportVersions.SIMULATE_INDEX_TEMPLATES_SUBSTITUTIONS)) { + out.writeGenericValue(indexTemplateSubstitutions); + } } public Map> getPipelineSubstitutions() { @@ -140,6 +164,18 @@ public Map getComponentTemplateSubstitutions() throws return result; } + @Override + public Map getIndexTemplateSubstitutions() throws IOException { + if (indexTemplateSubstitutions == null) { + return Map.of(); + } + Map result = new HashMap<>(indexTemplateSubstitutions.size()); + for (Map.Entry> rawEntry : indexTemplateSubstitutions.entrySet()) { + result.put(rawEntry.getKey(), convertRawTemplateToIndexTemplate(rawEntry.getValue())); + } + return result; + } + private static ComponentTemplate convertRawTemplateToComponentTemplate(Map rawTemplate) throws IOException { ComponentTemplate componentTemplate; try (var parser = XContentHelper.mapToXContentParser(XContentParserConfiguration.EMPTY, rawTemplate)) { @@ -148,9 +184,21 @@ private static ComponentTemplate convertRawTemplateToComponentTemplate(Map rawTemplate) throws IOException { + ComposableIndexTemplate indexTemplate; + try (var parser = XContentHelper.mapToXContentParser(XContentParserConfiguration.EMPTY, rawTemplate)) { + indexTemplate = ComposableIndexTemplate.parse(parser); + } + return indexTemplate; + } + @Override public BulkRequest shallowClone() { - BulkRequest bulkRequest = new SimulateBulkRequest(pipelineSubstitutions, componentTemplateSubstitutions); + BulkRequest bulkRequest = new SimulateBulkRequest( + pipelineSubstitutions, + componentTemplateSubstitutions, + indexTemplateSubstitutions + ); bulkRequest.setRefreshPolicy(getRefreshPolicy()); bulkRequest.waitForActiveShards(waitForActiveShards()); bulkRequest.timeout(timeout()); diff --git a/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java b/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java index 8c6565e52daa7..111e4d72c57c6 100644 --- a/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java +++ b/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java @@ -25,6 +25,7 @@ import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.ComposableIndexTemplate; import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.io.stream.Writeable; @@ -183,7 +184,9 @@ private boolean applyPipelines(Task task, BulkRequest bulkRequest, Executor exec boolean hasIndexRequestsWithPipelines = false; final Metadata metadata; Map componentTemplateSubstitutions = bulkRequest.getComponentTemplateSubstitutions(); - if (bulkRequest.isSimulated() && componentTemplateSubstitutions.isEmpty() == false) { + Map indexTemplateSubstitutions = bulkRequest.getIndexTemplateSubstitutions(); + if (bulkRequest.isSimulated() + && (componentTemplateSubstitutions.isEmpty() == false || indexTemplateSubstitutions.isEmpty() == false)) { /* * If this is a simulated request, and there are template substitutions, then we want to create and use a new metadata that has * those templates. That is, we want to add the new templates (which will replace any that already existed with the same name), @@ -197,6 +200,12 @@ private boolean applyPipelines(Task task, BulkRequest bulkRequest, Executor exec updatedComponentTemplates.putAll(componentTemplateSubstitutions); simulatedMetadataBuilder.componentTemplates(updatedComponentTemplates); } + if (indexTemplateSubstitutions.isEmpty() == false) { + Map updatedIndexTemplates = new HashMap<>(); + updatedIndexTemplates.putAll(clusterService.state().metadata().templatesV2()); + updatedIndexTemplates.putAll(indexTemplateSubstitutions); + simulatedMetadataBuilder.indexTemplates(updatedIndexTemplates); + } /* * We now remove the index from the simulated metadata to force the templates to be used. Note that simulated requests are * always index requests -- no other type of request is supported. diff --git a/server/src/main/java/org/elasticsearch/action/bulk/TransportSimulateBulkAction.java b/server/src/main/java/org/elasticsearch/action/bulk/TransportSimulateBulkAction.java index 713116c4cf98e..d7c555879c00f 100644 --- a/server/src/main/java/org/elasticsearch/action/bulk/TransportSimulateBulkAction.java +++ b/server/src/main/java/org/elasticsearch/action/bulk/TransportSimulateBulkAction.java @@ -17,6 +17,7 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.ComposableIndexTemplate; import org.elasticsearch.cluster.metadata.IndexAbstraction; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.IndexTemplateMetadata; @@ -73,6 +74,7 @@ public class TransportSimulateBulkAction extends TransportAbstractBulkAction { public static final NodeFeature SIMULATE_COMPONENT_TEMPLATE_SUBSTITUTIONS = new NodeFeature( "simulate.component.template.substitutions" ); + public static final NodeFeature SIMULATE_INDEX_TEMPLATE_SUBSTITUTIONS = new NodeFeature("simulate.index.template.substitutions"); private final IndicesService indicesService; private final NamedXContentRegistry xContentRegistry; private final Set indexSettingProviders; @@ -119,11 +121,12 @@ protected void doInternalExecute( : "TransportSimulateBulkAction should only ever be called with a SimulateBulkRequest but got a " + bulkRequest.getClass(); final AtomicArray responses = new AtomicArray<>(bulkRequest.requests.size()); Map componentTemplateSubstitutions = bulkRequest.getComponentTemplateSubstitutions(); + Map indexTemplateSubstitutions = bulkRequest.getIndexTemplateSubstitutions(); for (int i = 0; i < bulkRequest.requests.size(); i++) { DocWriteRequest docRequest = bulkRequest.requests.get(i); assert docRequest instanceof IndexRequest : "TransportSimulateBulkAction should only ever be called with IndexRequests"; IndexRequest request = (IndexRequest) docRequest; - Exception mappingValidationException = validateMappings(componentTemplateSubstitutions, request); + Exception mappingValidationException = validateMappings(componentTemplateSubstitutions, indexTemplateSubstitutions, request); responses.set( i, BulkItemResponse.success( @@ -153,7 +156,11 @@ protected void doInternalExecute( * @param request The IndexRequest whose source will be validated against the mapping (if it exists) of its index * @return a mapping exception if the source does not match the mappings, otherwise null */ - private Exception validateMappings(Map componentTemplateSubstitutions, IndexRequest request) { + private Exception validateMappings( + Map componentTemplateSubstitutions, + Map indexTemplateSubstitutions, + IndexRequest request + ) { final SourceToParse sourceToParse = new SourceToParse( request.id(), request.source(), @@ -167,7 +174,7 @@ private Exception validateMappings(Map componentTempl Exception mappingValidationException = null; IndexAbstraction indexAbstraction = state.metadata().getIndicesLookup().get(request.index()); try { - if (indexAbstraction != null && componentTemplateSubstitutions.isEmpty()) { + if (indexAbstraction != null && componentTemplateSubstitutions.isEmpty() && indexTemplateSubstitutions.isEmpty()) { /* * In this case the index exists and we don't have any component template overrides. So we can just use withTempIndexService * to do the mapping validation, using all the existing logic for validation. @@ -222,6 +229,12 @@ private Exception validateMappings(Map componentTempl updatedComponentTemplates.putAll(componentTemplateSubstitutions); simulatedMetadata.componentTemplates(updatedComponentTemplates); } + if (indexTemplateSubstitutions.isEmpty() == false) { + Map updatedIndexTemplates = new HashMap<>(); + updatedIndexTemplates.putAll(state.metadata().templatesV2()); + updatedIndexTemplates.putAll(indexTemplateSubstitutions); + simulatedMetadata.indexTemplates(updatedIndexTemplates); + } ClusterState simulatedState = simulatedClusterStateBuilder.metadata(simulatedMetadata).build(); String matchingTemplate = findV2Template(simulatedState.metadata(), request.index(), false); diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java index abeb3279b7b50..2a2cf6743a877 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java @@ -1421,44 +1421,24 @@ public static Settings resolveSettings(final List templat * Resolve the given v2 template into a collected {@link Settings} object */ public static Settings resolveSettings(final Metadata metadata, final String templateName) { - return resolveSettings(metadata, templateName, Map.of()); - } - - public static Settings resolveSettings( - final Metadata metadata, - final String templateName, - Map templateSubstitutions - ) { final ComposableIndexTemplate template = metadata.templatesV2().get(templateName); assert template != null : "attempted to resolve settings for a template [" + templateName + "] that did not exist in the cluster state"; if (template == null) { return Settings.EMPTY; } - return resolveSettings(template, metadata.componentTemplates(), templateSubstitutions); + return resolveSettings(template, metadata.componentTemplates()); } /** * Resolve the provided v2 template and component templates into a collected {@link Settings} object */ public static Settings resolveSettings(ComposableIndexTemplate template, Map componentTemplates) { - return resolveSettings(template, componentTemplates, Map.of()); - } - - public static Settings resolveSettings( - ComposableIndexTemplate template, - Map componentTemplates, - Map templateSubstitutions - ) { Objects.requireNonNull(template, "attempted to resolve settings for a null template"); Objects.requireNonNull(componentTemplates, "attempted to resolve settings with null component templates"); - Map combinedComponentTemplates = new HashMap<>(); - combinedComponentTemplates.putAll(componentTemplates); - // We want any substitutions to take precedence: - combinedComponentTemplates.putAll(templateSubstitutions); List componentSettings = template.composedOf() .stream() - .map(combinedComponentTemplates::get) + .map(componentTemplates::get) .filter(Objects::nonNull) .map(ComponentTemplate::template) .map(Template::settings) diff --git a/server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java b/server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java index a7ae17c8dac14..9477f9c6a5cc1 100644 --- a/server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java +++ b/server/src/main/java/org/elasticsearch/cluster/node/DiscoveryNodes.java @@ -628,6 +628,11 @@ public List addedNodes() { return added; } + @Override + public String toString() { + return shortSummary(); + } + public String shortSummary() { final StringBuilder summary = new StringBuilder(); if (masterNodeChanged()) { diff --git a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java index fbce913dac139..69be30b0b5111 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -122,6 +122,7 @@ import org.elasticsearch.search.fetch.subphase.highlight.FastVectorHighlighter; import org.elasticsearch.snapshots.InternalSnapshotsInfoService; import org.elasticsearch.snapshots.RestoreService; +import org.elasticsearch.snapshots.SnapshotShutdownProgressTracker; import org.elasticsearch.snapshots.SnapshotsService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.ProxyConnectionStrategy; @@ -368,6 +369,7 @@ public void apply(Settings value, Settings current, Settings previous) { SniffConnectionStrategy.REMOTE_NODE_CONNECTIONS, TransportCloseIndexAction.CLUSTER_INDICES_CLOSE_ENABLE_SETTING, ShardsLimitAllocationDecider.CLUSTER_TOTAL_SHARDS_PER_NODE_SETTING, + SnapshotShutdownProgressTracker.SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING, NodeConnectionsService.CLUSTER_NODE_RECONNECT_INTERVAL_SETTING, HierarchyCircuitBreakerService.FIELDDATA_CIRCUIT_BREAKER_TYPE_SETTING, HierarchyCircuitBreakerService.REQUEST_CIRCUIT_BREAKER_TYPE_SETTING, diff --git a/server/src/main/java/org/elasticsearch/index/IndexingSlowLog.java b/server/src/main/java/org/elasticsearch/index/IndexingSlowLog.java index 70ecef375498e..3ae4c0eb82ad0 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexingSlowLog.java +++ b/server/src/main/java/org/elasticsearch/index/IndexingSlowLog.java @@ -9,13 +9,11 @@ package org.elasticsearch.index; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.util.StringBuilders; import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogMessage; -import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.xcontent.XContentHelper; @@ -92,9 +90,6 @@ public final class IndexingSlowLog implements IndexingOperationListener { ); private static final Logger indexLogger = LogManager.getLogger(INDEX_INDEXING_SLOWLOG_PREFIX + ".index"); - static { - Loggers.setLevel(indexLogger, Level.TRACE); - } private final Index index; diff --git a/server/src/main/java/org/elasticsearch/index/SearchSlowLog.java b/server/src/main/java/org/elasticsearch/index/SearchSlowLog.java index 2a2d650e20aa2..e4836a391bfec 100644 --- a/server/src/main/java/org/elasticsearch/index/SearchSlowLog.java +++ b/server/src/main/java/org/elasticsearch/index/SearchSlowLog.java @@ -9,11 +9,9 @@ package org.elasticsearch.index; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.common.logging.ESLogMessage; -import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.core.TimeValue; @@ -47,11 +45,6 @@ public final class SearchSlowLog implements SearchOperationListener { private static final Logger queryLogger = LogManager.getLogger(INDEX_SEARCH_SLOWLOG_PREFIX + ".query"); private static final Logger fetchLogger = LogManager.getLogger(INDEX_SEARCH_SLOWLOG_PREFIX + ".fetch"); - static { - Loggers.setLevel(queryLogger, Level.TRACE); - Loggers.setLevel(fetchLogger, Level.TRACE); - } - private final SlowLogFieldProvider slowLogFieldProvider; public static final Setting INDEX_SEARCH_SLOWLOG_INCLUDE_USER_SETTING = Setting.boolSetting( diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/BQSpaceUtils.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/BQSpaceUtils.java new file mode 100644 index 0000000000000..68363b5926a6b --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/BQSpaceUtils.java @@ -0,0 +1,78 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +/** Utility class for quantization calculations */ +public class BQSpaceUtils { + + public static final short B_QUERY = 4; + // the first four bits masked + private static final int B_QUERY_MASK = 15; + + /** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + * @param q the query vector, assumed to be half-byte quantized with values between 0 and 15 + * @param dimensions the number of dimensions in the query vector + * @param quantQueryByte the byte array to store the transposed query vector + */ + public static void transposeBin(byte[] q, int dimensions, byte[] quantQueryByte) { + // TODO: rewrite this in Panama Vector API + int qOffset = 0; + final byte[] v1 = new byte[4]; + final byte[] v = new byte[32]; + for (int i = 0; i < dimensions; i += 32) { + // for every four bytes we shift left (with remainder across those bytes) + for (int j = 0; j < v.length; j += 4) { + v[j] = (byte) (q[qOffset + j] << B_QUERY | ((q[qOffset + j] >>> B_QUERY) & B_QUERY_MASK)); + v[j + 1] = (byte) (q[qOffset + j + 1] << B_QUERY | ((q[qOffset + j + 1] >>> B_QUERY) & B_QUERY_MASK)); + v[j + 2] = (byte) (q[qOffset + j + 2] << B_QUERY | ((q[qOffset + j + 2] >>> B_QUERY) & B_QUERY_MASK)); + v[j + 3] = (byte) (q[qOffset + j + 3] << B_QUERY | ((q[qOffset + j + 3] >>> B_QUERY) & B_QUERY_MASK)); + } + for (int j = 0; j < B_QUERY; j++) { + moveMaskEpi8Byte(v, v1); + for (int k = 0; k < 4; k++) { + quantQueryByte[(B_QUERY - j - 1) * (dimensions / 8) + i / 8 + k] = v1[k]; + v1[k] = 0; + } + for (int k = 0; k < v.length; k += 4) { + v[k] = (byte) (v[k] + v[k]); + v[k + 1] = (byte) (v[k + 1] + v[k + 1]); + v[k + 2] = (byte) (v[k + 2] + v[k + 2]); + v[k + 3] = (byte) (v[k + 3] + v[k + 3]); + } + } + qOffset += 32; + } + } + + private static void moveMaskEpi8Byte(byte[] v, byte[] v1b) { + int m = 0; + for (int k = 0; k < v.length; k++) { + if ((v[k] & 0b10000000) == 0b10000000) { + v1b[m] |= 0b00000001; + } + if (k % 8 == 7) { + m++; + } else { + v1b[m] <<= 1; + } + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/BQVectorUtils.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/BQVectorUtils.java new file mode 100644 index 0000000000000..3d2acb533e26d --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/BQVectorUtils.java @@ -0,0 +1,97 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.util.ArrayUtil; +import org.apache.lucene.util.BitUtil; +import org.apache.lucene.util.VectorUtil; + +/** Utility class for vector quantization calculations */ +public class BQVectorUtils { + private static final float EPSILON = 1e-4f; + + public static boolean isUnitVector(float[] v) { + double l1norm = VectorUtil.dotProduct(v, v); + return Math.abs(l1norm - 1.0d) <= EPSILON; + } + + public static int discretize(int value, int bucket) { + return ((value + (bucket - 1)) / bucket) * bucket; + } + + public static float[] pad(float[] vector, int dimensions) { + if (vector.length >= dimensions) { + return vector; + } + return ArrayUtil.growExact(vector, dimensions); + } + + public static byte[] pad(byte[] vector, int dimensions) { + if (vector.length >= dimensions) { + return vector; + } + return ArrayUtil.growExact(vector, dimensions); + } + + /** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + * @param d the byte array to count the number of set bits in + * @return count of flipped bits in the byte array + */ + public static int popcount(byte[] d) { + int r = 0; + int cnt = 0; + for (final int upperBound = d.length & -Integer.BYTES; r < upperBound; r += Integer.BYTES) { + cnt += Integer.bitCount((int) BitUtil.VH_NATIVE_INT.get(d, r)); + } + for (; r < d.length; r++) { + cnt += Integer.bitCount(d[r] & 0xFF); + } + return cnt; + } + + // TODO: move to VectorUtil & vectorize? + public static void divideInPlace(float[] a, float b) { + for (int j = 0; j < a.length; j++) { + a[j] /= b; + } + } + + public static float[] subtract(float[] a, float[] b) { + float[] result = new float[a.length]; + subtract(a, b, result); + return result; + } + + public static void subtractInPlace(float[] target, float[] other) { + subtract(target, other, target); + } + + private static void subtract(float[] a, float[] b, float[] result) { + for (int j = 0; j < a.length; j++) { + result[j] = a[j] - b[j]; + } + } + + public static float norm(float[] vector) { + float magnitude = VectorUtil.dotProduct(vector, vector); + return (float) Math.sqrt(magnitude); + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/BinarizedByteVectorValues.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/BinarizedByteVectorValues.java new file mode 100644 index 0000000000000..73dd4273a794e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/BinarizedByteVectorValues.java @@ -0,0 +1,58 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.VectorScorer; + +import java.io.IOException; + +/** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + */ +public abstract class BinarizedByteVectorValues extends DocIdSetIterator { + + public abstract float[] getCorrectiveTerms(); + + public abstract byte[] vectorValue() throws IOException; + + /** Return the dimension of the vectors */ + public abstract int dimension(); + + /** + * Return the number of vectors for this field. + * + * @return the number of vectors returned by this iterator + */ + public abstract int size(); + + @Override + public final long cost() { + return size(); + } + + /** + * Return a {@link VectorScorer} for the given query vector. + * + * @param query the query vector + * @return a {@link VectorScorer} instance or null + */ + public abstract VectorScorer scorer(float[] query) throws IOException; +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/BinaryQuantizer.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/BinaryQuantizer.java new file mode 100644 index 0000000000000..192fb9092ac3a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/BinaryQuantizer.java @@ -0,0 +1,385 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.util.ArrayUtil; +import org.apache.lucene.util.VectorUtil; + +import static org.apache.lucene.index.VectorSimilarityFunction.COSINE; +import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; +import static org.elasticsearch.index.codec.vectors.BQVectorUtils.isUnitVector; + +/** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + * Quantized that quantizes raw vector values to binary. The algorithm is based on the paper RaBitQ. + */ +public class BinaryQuantizer { + private final int discretizedDimensions; + + private final VectorSimilarityFunction similarityFunction; + private final float sqrtDimensions; + + public BinaryQuantizer(int dimensions, int discretizedDimensions, VectorSimilarityFunction similarityFunction) { + if (dimensions <= 0) { + throw new IllegalArgumentException("dimensions must be > 0 but was: " + dimensions); + } + assert discretizedDimensions % 64 == 0 : "discretizedDimensions must be a multiple of 64 but was: " + discretizedDimensions; + this.discretizedDimensions = discretizedDimensions; + this.similarityFunction = similarityFunction; + this.sqrtDimensions = (float) Math.sqrt(dimensions); + } + + BinaryQuantizer(int dimensions, VectorSimilarityFunction similarityFunction) { + this(dimensions, dimensions, similarityFunction); + } + + private static void removeSignAndDivide(float[] a, float divisor) { + for (int i = 0; i < a.length; i++) { + a[i] = Math.abs(a[i]) / divisor; + } + } + + private static float sumAndNormalize(float[] a, float norm) { + float aDivided = 0f; + + for (int i = 0; i < a.length; i++) { + aDivided += a[i]; + } + + aDivided = aDivided / norm; + if (Float.isFinite(aDivided) == false) { + aDivided = 0.8f; // can be anything + } + + return aDivided; + } + + private static void packAsBinary(float[] vector, byte[] packedVector) { + for (int h = 0; h < vector.length; h += 8) { + byte result = 0; + int q = 0; + for (int i = 7; i >= 0; i--) { + if (vector[h + i] > 0) { + result |= (byte) (1 << q); + } + q++; + } + packedVector[h / 8] = result; + } + } + + public VectorSimilarityFunction getSimilarity() { + return this.similarityFunction; + } + + private record SubspaceOutput(float projection) {} + + private SubspaceOutput generateSubSpace(float[] vector, float[] centroid, byte[] quantizedVector) { + // typically no-op if dimensions/64 + float[] paddedCentroid = BQVectorUtils.pad(centroid, discretizedDimensions); + float[] paddedVector = BQVectorUtils.pad(vector, discretizedDimensions); + + BQVectorUtils.subtractInPlace(paddedVector, paddedCentroid); + + // The inner product between the data vector and the quantized data vector + float norm = BQVectorUtils.norm(paddedVector); + + packAsBinary(paddedVector, quantizedVector); + + removeSignAndDivide(paddedVector, sqrtDimensions); + float projection = sumAndNormalize(paddedVector, norm); + + return new SubspaceOutput(projection); + } + + record SubspaceOutputMIP(float OOQ, float normOC, float oDotC) {} + + private SubspaceOutputMIP generateSubSpaceMIP(float[] vector, float[] centroid, byte[] quantizedVector) { + + // typically no-op if dimensions/64 + float[] paddedCentroid = BQVectorUtils.pad(centroid, discretizedDimensions); + float[] paddedVector = BQVectorUtils.pad(vector, discretizedDimensions); + + float oDotC = VectorUtil.dotProduct(paddedVector, paddedCentroid); + BQVectorUtils.subtractInPlace(paddedVector, paddedCentroid); + + float normOC = BQVectorUtils.norm(paddedVector); + packAsBinary(paddedVector, quantizedVector); + BQVectorUtils.divideInPlace(paddedVector, normOC); // OmC / norm(OmC) + + float OOQ = computerOOQ(vector.length, paddedVector, quantizedVector); + + return new SubspaceOutputMIP(OOQ, normOC, oDotC); + } + + private float computerOOQ(int originalLength, float[] normOMinusC, byte[] packedBinaryVector) { + float OOQ = 0f; + for (int j = 0; j < originalLength / 8; j++) { + for (int r = 0; r < 8; r++) { + int sign = ((packedBinaryVector[j] >> (7 - r)) & 0b00000001); + OOQ += (normOMinusC[j * 8 + r] * (2 * sign - 1)); + } + } + OOQ = OOQ / sqrtDimensions; + return OOQ; + } + + private static float[] range(float[] q) { + float vl = 1e20f; + float vr = -1e20f; + for (int i = 0; i < q.length; i++) { + if (q[i] < vl) { + vl = q[i]; + } + if (q[i] > vr) { + vr = q[i]; + } + } + + return new float[] { vl, vr }; + } + + /** Results of quantizing a vector for both querying and indexing */ + public record QueryAndIndexResults(float[] indexFeatures, QueryFactors queryFeatures) {} + + public QueryAndIndexResults quantizeQueryAndIndex(float[] vector, byte[] indexDestination, byte[] queryDestination, float[] centroid) { + assert similarityFunction != COSINE || isUnitVector(vector); + assert similarityFunction != COSINE || isUnitVector(centroid); + assert this.discretizedDimensions == BQVectorUtils.discretize(vector.length, 64); + + if (this.discretizedDimensions != indexDestination.length * 8) { + throw new IllegalArgumentException( + "vector and quantized vector destination must be compatible dimensions: " + + BQVectorUtils.discretize(vector.length, 64) + + " [ " + + this.discretizedDimensions + + " ]" + + "!= " + + indexDestination.length + + " * 8" + ); + } + + if (this.discretizedDimensions != (queryDestination.length * 8) / BQSpaceUtils.B_QUERY) { + throw new IllegalArgumentException( + "vector and quantized vector destination must be compatible dimensions: " + + vector.length + + " [ " + + this.discretizedDimensions + + " ]" + + "!= (" + + queryDestination.length + + " * 8) / " + + BQSpaceUtils.B_QUERY + ); + } + + if (vector.length != centroid.length) { + throw new IllegalArgumentException( + "vector and centroid dimensions must be the same: " + vector.length + "!= " + centroid.length + ); + } + vector = ArrayUtil.copyArray(vector); + float distToC = VectorUtil.squareDistance(vector, centroid); + // only need vdotc for dot-products similarity, but not for euclidean + float vDotC = similarityFunction != EUCLIDEAN ? VectorUtil.dotProduct(vector, centroid) : 0f; + BQVectorUtils.subtractInPlace(vector, centroid); + // both euclidean and dot-product need the norm of the vector, just at different times + float normVmC = BQVectorUtils.norm(vector); + // quantize for index + packAsBinary(BQVectorUtils.pad(vector, discretizedDimensions), indexDestination); + if (similarityFunction != EUCLIDEAN) { + BQVectorUtils.divideInPlace(vector, normVmC); + } + + // Quantize for query + float[] range = range(vector); + float lower = range[0]; + float upper = range[1]; + // Δ := (𝑣𝑟 − 𝑣𝑙)/(2𝐵𝑞 − 1) + float width = (upper - lower) / ((1 << BQSpaceUtils.B_QUERY) - 1); + + QuantResult quantResult = quantize(vector, lower, width); + byte[] byteQuery = quantResult.result(); + + // q¯ = Δ · q¯𝑢 + 𝑣𝑙 · 1𝐷 + // q¯ is an approximation of q′ (scalar quantized approximation) + // FIXME: vectors need to be padded but that's expensive; update transponseBin to deal + byteQuery = BQVectorUtils.pad(byteQuery, discretizedDimensions); + BQSpaceUtils.transposeBin(byteQuery, discretizedDimensions, queryDestination); + QueryFactors factors = new QueryFactors(quantResult.quantizedSum, distToC, lower, width, normVmC, vDotC); + final float[] indexCorrections; + if (similarityFunction == EUCLIDEAN) { + indexCorrections = new float[2]; + indexCorrections[0] = (float) Math.sqrt(distToC); + removeSignAndDivide(vector, sqrtDimensions); + indexCorrections[1] = sumAndNormalize(vector, normVmC); + } else { + indexCorrections = new float[3]; + indexCorrections[0] = computerOOQ(vector.length, vector, indexDestination); + indexCorrections[1] = normVmC; + indexCorrections[2] = vDotC; + } + return new QueryAndIndexResults(indexCorrections, factors); + } + + public float[] quantizeForIndex(float[] vector, byte[] destination, float[] centroid) { + assert similarityFunction != COSINE || isUnitVector(vector); + assert similarityFunction != COSINE || isUnitVector(centroid); + assert this.discretizedDimensions == BQVectorUtils.discretize(vector.length, 64); + + if (this.discretizedDimensions != destination.length * 8) { + throw new IllegalArgumentException( + "vector and quantized vector destination must be compatible dimensions: " + + BQVectorUtils.discretize(vector.length, 64) + + " [ " + + this.discretizedDimensions + + " ]" + + "!= " + + destination.length + + " * 8" + ); + } + + if (vector.length != centroid.length) { + throw new IllegalArgumentException( + "vector and centroid dimensions must be the same: " + vector.length + "!= " + centroid.length + ); + } + + float[] corrections; + + // FIXME: make a copy of vector so we don't overwrite it here? + // ... (could trade subtractInPlace w subtract in genSubSpace) + vector = ArrayUtil.copyArray(vector); + + switch (similarityFunction) { + case EUCLIDEAN: + float distToCentroid = (float) Math.sqrt(VectorUtil.squareDistance(vector, centroid)); + + SubspaceOutput subspaceOutput = generateSubSpace(vector, centroid, destination); + corrections = new float[2]; + // FIXME: quantize these values so we are passing back 1 byte values for all three of these + corrections[0] = distToCentroid; + corrections[1] = subspaceOutput.projection(); + break; + case MAXIMUM_INNER_PRODUCT: + case COSINE: + case DOT_PRODUCT: + SubspaceOutputMIP subspaceOutputMIP = generateSubSpaceMIP(vector, centroid, destination); + corrections = new float[3]; + // FIXME: quantize these values so we are passing back 1 byte values for all three of these + corrections[0] = subspaceOutputMIP.OOQ(); + corrections[1] = subspaceOutputMIP.normOC(); + corrections[2] = subspaceOutputMIP.oDotC(); + break; + default: + throw new UnsupportedOperationException("Unsupported similarity function: " + similarityFunction); + } + + return corrections; + } + + private record QuantResult(byte[] result, int quantizedSum) {} + + private static QuantResult quantize(float[] vector, float lower, float width) { + // FIXME: speed up with panama? and/or use existing scalar quantization utils in Lucene? + byte[] result = new byte[vector.length]; + float oneOverWidth = 1.0f / width; + int sumQ = 0; + for (int i = 0; i < vector.length; i++) { + byte res = (byte) ((vector[i] - lower) * oneOverWidth); + result[i] = res; + sumQ += res; + } + + return new QuantResult(result, sumQ); + } + + /** Factors for quantizing query */ + public record QueryFactors(int quantizedSum, float distToC, float lower, float width, float normVmC, float vDotC) {} + + public QueryFactors quantizeForQuery(float[] vector, byte[] destination, float[] centroid) { + assert similarityFunction != COSINE || isUnitVector(vector); + assert similarityFunction != COSINE || isUnitVector(centroid); + assert this.discretizedDimensions == BQVectorUtils.discretize(vector.length, 64); + + if (this.discretizedDimensions != (destination.length * 8) / BQSpaceUtils.B_QUERY) { + throw new IllegalArgumentException( + "vector and quantized vector destination must be compatible dimensions: " + + vector.length + + " [ " + + this.discretizedDimensions + + " ]" + + "!= (" + + destination.length + + " * 8) / " + + BQSpaceUtils.B_QUERY + ); + } + + if (vector.length != centroid.length) { + throw new IllegalArgumentException( + "vector and centroid dimensions must be the same: " + vector.length + "!= " + centroid.length + ); + } + + float distToC = VectorUtil.squareDistance(vector, centroid); + + // FIXME: make a copy of vector so we don't overwrite it here? + // ... (could subtractInPlace but the passed vector is modified) <<--- + float[] vmC = BQVectorUtils.subtract(vector, centroid); + + // FIXME: should other similarity functions behave like MIP on query like COSINE + float normVmC = 0f; + if (similarityFunction != EUCLIDEAN) { + normVmC = BQVectorUtils.norm(vmC); + BQVectorUtils.divideInPlace(vmC, normVmC); + } + float[] range = range(vmC); + float lower = range[0]; + float upper = range[1]; + // Δ := (𝑣𝑟 − 𝑣𝑙)/(2𝐵𝑞 − 1) + float width = (upper - lower) / ((1 << BQSpaceUtils.B_QUERY) - 1); + + QuantResult quantResult = quantize(vmC, lower, width); + byte[] byteQuery = quantResult.result(); + + // q¯ = Δ · q¯𝑢 + 𝑣𝑙 · 1𝐷 + // q¯ is an approximation of q′ (scalar quantized approximation) + // FIXME: vectors need to be padded but that's expensive; update transponseBin to deal + byteQuery = BQVectorUtils.pad(byteQuery, discretizedDimensions); + BQSpaceUtils.transposeBin(byteQuery, discretizedDimensions, destination); + + QueryFactors factors; + if (similarityFunction != EUCLIDEAN) { + float vDotC = VectorUtil.dotProduct(vector, centroid); + // FIXME: quantize the corrections as well so we store less + factors = new QueryFactors(quantResult.quantizedSum, distToC, lower, width, normVmC, vDotC); + } else { + // FIXME: quantize the corrections as well so we store less + factors = new QueryFactors(quantResult.quantizedSum, distToC, lower, width, 0f, 0f); + } + + return factors; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryFlatVectorsScorer.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryFlatVectorsScorer.java new file mode 100644 index 0000000000000..78fa282709098 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryFlatVectorsScorer.java @@ -0,0 +1,273 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.hnsw.FlatVectorsScorer; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.util.ArrayUtil; +import org.apache.lucene.util.VectorUtil; +import org.apache.lucene.util.hnsw.RandomAccessVectorValues; +import org.apache.lucene.util.hnsw.RandomVectorScorer; +import org.apache.lucene.util.hnsw.RandomVectorScorerSupplier; +import org.elasticsearch.simdvec.ESVectorUtil; + +import java.io.IOException; + +import static org.apache.lucene.index.VectorSimilarityFunction.COSINE; +import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; +import static org.apache.lucene.index.VectorSimilarityFunction.MAXIMUM_INNER_PRODUCT; + +/** Vector scorer over binarized vector values */ +public class ES816BinaryFlatVectorsScorer implements FlatVectorsScorer { + private final FlatVectorsScorer nonQuantizedDelegate; + + public ES816BinaryFlatVectorsScorer(FlatVectorsScorer nonQuantizedDelegate) { + this.nonQuantizedDelegate = nonQuantizedDelegate; + } + + @Override + public RandomVectorScorerSupplier getRandomVectorScorerSupplier( + VectorSimilarityFunction similarityFunction, + RandomAccessVectorValues vectorValues + ) throws IOException { + if (vectorValues instanceof RandomAccessBinarizedByteVectorValues) { + throw new UnsupportedOperationException( + "getRandomVectorScorerSupplier(VectorSimilarityFunction,RandomAccessVectorValues) not implemented for binarized format" + ); + } + return nonQuantizedDelegate.getRandomVectorScorerSupplier(similarityFunction, vectorValues); + } + + @Override + public RandomVectorScorer getRandomVectorScorer( + VectorSimilarityFunction similarityFunction, + RandomAccessVectorValues vectorValues, + float[] target + ) throws IOException { + if (vectorValues instanceof RandomAccessBinarizedByteVectorValues binarizedVectors) { + BinaryQuantizer quantizer = binarizedVectors.getQuantizer(); + float[] centroid = binarizedVectors.getCentroid(); + // FIXME: precompute this once? + int discretizedDimensions = BQVectorUtils.discretize(target.length, 64); + if (similarityFunction == COSINE) { + float[] copy = ArrayUtil.copyOfSubArray(target, 0, target.length); + VectorUtil.l2normalize(copy); + target = copy; + } + byte[] quantized = new byte[BQSpaceUtils.B_QUERY * discretizedDimensions / 8]; + BinaryQuantizer.QueryFactors factors = quantizer.quantizeForQuery(target, quantized, centroid); + BinaryQueryVector queryVector = new BinaryQueryVector(quantized, factors); + return new BinarizedRandomVectorScorer(queryVector, binarizedVectors, similarityFunction); + } + return nonQuantizedDelegate.getRandomVectorScorer(similarityFunction, vectorValues, target); + } + + @Override + public RandomVectorScorer getRandomVectorScorer( + VectorSimilarityFunction similarityFunction, + RandomAccessVectorValues vectorValues, + byte[] target + ) throws IOException { + return nonQuantizedDelegate.getRandomVectorScorer(similarityFunction, vectorValues, target); + } + + RandomVectorScorerSupplier getRandomVectorScorerSupplier( + VectorSimilarityFunction similarityFunction, + ES816BinaryQuantizedVectorsWriter.OffHeapBinarizedQueryVectorValues scoringVectors, + RandomAccessBinarizedByteVectorValues targetVectors + ) { + return new BinarizedRandomVectorScorerSupplier(scoringVectors, targetVectors, similarityFunction); + } + + @Override + public String toString() { + return "ES816BinaryFlatVectorsScorer(nonQuantizedDelegate=" + nonQuantizedDelegate + ")"; + } + + /** Vector scorer supplier over binarized vector values */ + static class BinarizedRandomVectorScorerSupplier implements RandomVectorScorerSupplier { + private final ES816BinaryQuantizedVectorsWriter.OffHeapBinarizedQueryVectorValues queryVectors; + private final RandomAccessBinarizedByteVectorValues targetVectors; + private final VectorSimilarityFunction similarityFunction; + + BinarizedRandomVectorScorerSupplier( + ES816BinaryQuantizedVectorsWriter.OffHeapBinarizedQueryVectorValues queryVectors, + RandomAccessBinarizedByteVectorValues targetVectors, + VectorSimilarityFunction similarityFunction + ) { + this.queryVectors = queryVectors; + this.targetVectors = targetVectors; + this.similarityFunction = similarityFunction; + } + + @Override + public RandomVectorScorer scorer(int ord) throws IOException { + byte[] vector = queryVectors.vectorValue(ord); + int quantizedSum = queryVectors.sumQuantizedValues(ord); + float distanceToCentroid = queryVectors.getCentroidDistance(ord); + float lower = queryVectors.getLower(ord); + float width = queryVectors.getWidth(ord); + float normVmC = 0f; + float vDotC = 0f; + if (similarityFunction != EUCLIDEAN) { + normVmC = queryVectors.getNormVmC(ord); + vDotC = queryVectors.getVDotC(ord); + } + BinaryQueryVector binaryQueryVector = new BinaryQueryVector( + vector, + new BinaryQuantizer.QueryFactors(quantizedSum, distanceToCentroid, lower, width, normVmC, vDotC) + ); + return new BinarizedRandomVectorScorer(binaryQueryVector, targetVectors, similarityFunction); + } + + @Override + public RandomVectorScorerSupplier copy() throws IOException { + return new BinarizedRandomVectorScorerSupplier(queryVectors.copy(), targetVectors.copy(), similarityFunction); + } + } + + /** A binarized query representing its quantized form along with factors */ + public record BinaryQueryVector(byte[] vector, BinaryQuantizer.QueryFactors factors) {} + + /** Vector scorer over binarized vector values */ + public static class BinarizedRandomVectorScorer extends RandomVectorScorer.AbstractRandomVectorScorer { + private final BinaryQueryVector queryVector; + private final RandomAccessBinarizedByteVectorValues targetVectors; + private final VectorSimilarityFunction similarityFunction; + + private final float sqrtDimensions; + + public BinarizedRandomVectorScorer( + BinaryQueryVector queryVectors, + RandomAccessBinarizedByteVectorValues targetVectors, + VectorSimilarityFunction similarityFunction + ) { + super(targetVectors); + this.queryVector = queryVectors; + this.targetVectors = targetVectors; + this.similarityFunction = similarityFunction; + // FIXME: precompute this once? + this.sqrtDimensions = (float) Utils.constSqrt(targetVectors.dimension()); + } + + // FIXME: utils class; pull this out + private static class Utils { + public static double sqrtNewtonRaphson(double x, double curr, double prev) { + return (curr == prev) ? curr : sqrtNewtonRaphson(x, 0.5 * (curr + x / curr), curr); + } + + public static double constSqrt(double x) { + return x >= 0 && Double.isInfinite(x) == false ? sqrtNewtonRaphson(x, x, 0) : Double.NaN; + } + } + + @Override + public float score(int targetOrd) throws IOException { + // FIXME: implement fastscan in the future? + + byte[] quantizedQuery = queryVector.vector(); + int quantizedSum = queryVector.factors().quantizedSum(); + float lower = queryVector.factors().lower(); + float width = queryVector.factors().width(); + float distanceToCentroid = queryVector.factors().distToC(); + if (similarityFunction == EUCLIDEAN) { + return euclideanScore(targetOrd, sqrtDimensions, quantizedQuery, distanceToCentroid, lower, quantizedSum, width); + } + + float vmC = queryVector.factors().normVmC(); + float vDotC = queryVector.factors().vDotC(); + float cDotC = targetVectors.getCentroidDP(); + byte[] binaryCode = targetVectors.vectorValue(targetOrd); + float ooq = targetVectors.getOOQ(targetOrd); + float normOC = targetVectors.getNormOC(targetOrd); + float oDotC = targetVectors.getODotC(targetOrd); + + float qcDist = ESVectorUtil.ipByteBinByte(quantizedQuery, binaryCode); + + // FIXME: pre-compute these only once for each target vector + // ... pull this out or use a similar cache mechanism as do in score + float xbSum = (float) BQVectorUtils.popcount(binaryCode); + final float dist; + // If ||o-c|| == 0, so, it's ok to throw the rest of the equation away + // and simply use `oDotC + vDotC - cDotC` as centroid == doc vector + if (normOC == 0 || ooq == 0) { + dist = oDotC + vDotC - cDotC; + } else { + // If ||o-c|| != 0, we should assume that `ooq` is finite + assert Float.isFinite(ooq); + float estimatedDot = (2 * width / sqrtDimensions * qcDist + 2 * lower / sqrtDimensions * xbSum - width / sqrtDimensions + * quantizedSum - sqrtDimensions * lower) / ooq; + dist = vmC * normOC * estimatedDot + oDotC + vDotC - cDotC; + } + assert Float.isFinite(dist); + + // TODO: this is useful for mandatory rescoring by accounting for bias + // However, for just oversampling & rescoring, it isn't strictly useful. + // We should consider utilizing this bias in the future to determine which vectors need to + // be rescored + // float ooqSqr = (float) Math.pow(ooq, 2); + // float errorBound = (float) (normVmC * normOC * (maxX1 * Math.sqrt((1 - ooqSqr) / ooqSqr))); + // float score = dist - errorBound; + if (similarityFunction == MAXIMUM_INNER_PRODUCT) { + return VectorUtil.scaleMaxInnerProductScore(dist); + } + return Math.max((1f + dist) / 2f, 0); + } + + private float euclideanScore( + int targetOrd, + float sqrtDimensions, + byte[] quantizedQuery, + float distanceToCentroid, + float lower, + int quantizedSum, + float width + ) throws IOException { + byte[] binaryCode = targetVectors.vectorValue(targetOrd); + + // FIXME: pre-compute these only once for each target vector + // .. not sure how to enumerate the target ordinals but that's what we did in PoC + float targetDistToC = targetVectors.getCentroidDistance(targetOrd); + float x0 = targetVectors.getVectorMagnitude(targetOrd); + float sqrX = targetDistToC * targetDistToC; + double xX0 = targetDistToC / x0; + + // TODO maybe store? + float xbSum = (float) BQVectorUtils.popcount(binaryCode); + float factorPPC = (float) (-2.0 / sqrtDimensions * xX0 * (xbSum * 2.0 - targetVectors.dimension())); + float factorIP = (float) (-2.0 / sqrtDimensions * xX0); + + long qcDist = ESVectorUtil.ipByteBinByte(quantizedQuery, binaryCode); + float score = sqrX + distanceToCentroid + factorPPC * lower + (qcDist * 2 - quantizedSum) * factorIP * width; + // TODO: this is useful for mandatory rescoring by accounting for bias + // However, for just oversampling & rescoring, it isn't strictly useful. + // We should consider utilizing this bias in the future to determine which vectors need to + // be rescored + // float projectionDist = (float) Math.sqrt(xX0 * xX0 - targetDistToC * targetDistToC); + // float error = 2.0f * maxX1 * projectionDist; + // float y = (float) Math.sqrt(distanceToCentroid); + // float errorBound = y * error; + // if (Float.isFinite(errorBound)) { + // score = dist + errorBound; + // } + return Math.max(1 / (1f + score), 0); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsFormat.java new file mode 100644 index 0000000000000..523d5f6c4a91f --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsFormat.java @@ -0,0 +1,75 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.hnsw.FlatVectorScorerUtil; +import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.hnsw.FlatVectorsReader; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SegmentWriteState; + +import java.io.IOException; + +/** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + */ +public class ES816BinaryQuantizedVectorsFormat extends FlatVectorsFormat { + + public static final String BINARIZED_VECTOR_COMPONENT = "BVEC"; + public static final String NAME = "ES816BinaryQuantizedVectorsFormat"; + + static final int VERSION_START = 0; + static final int VERSION_CURRENT = VERSION_START; + static final String META_CODEC_NAME = "ES816BinaryQuantizedVectorsFormatMeta"; + static final String VECTOR_DATA_CODEC_NAME = "ES816BinaryQuantizedVectorsFormatData"; + static final String META_EXTENSION = "vemb"; + static final String VECTOR_DATA_EXTENSION = "veb"; + static final int DIRECT_MONOTONIC_BLOCK_SHIFT = 16; + + private static final FlatVectorsFormat rawVectorFormat = new Lucene99FlatVectorsFormat( + FlatVectorScorerUtil.getLucene99FlatVectorsScorer() + ); + + private static final ES816BinaryFlatVectorsScorer scorer = new ES816BinaryFlatVectorsScorer( + FlatVectorScorerUtil.getLucene99FlatVectorsScorer() + ); + + /** Creates a new instance with the default number of vectors per cluster. */ + public ES816BinaryQuantizedVectorsFormat() { + super(NAME); + } + + @Override + public FlatVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { + return new ES816BinaryQuantizedVectorsWriter(scorer, rawVectorFormat.fieldsWriter(state), state); + } + + @Override + public FlatVectorsReader fieldsReader(SegmentReadState state) throws IOException { + return new ES816BinaryQuantizedVectorsReader(state, rawVectorFormat.fieldsReader(state), scorer); + } + + @Override + public String toString() { + return "ES816BinaryQuantizedVectorsFormat(name=" + NAME + ", flatVectorScorer=" + scorer + ")"; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsReader.java new file mode 100644 index 0000000000000..b0378fee6793d --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsReader.java @@ -0,0 +1,412 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.codecs.hnsw.FlatVectorsReader; +import org.apache.lucene.codecs.lucene95.OrdToDocDISIReaderConfiguration; +import org.apache.lucene.index.ByteVectorValues; +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.IndexFileNames; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.search.KnnCollector; +import org.apache.lucene.search.VectorScorer; +import org.apache.lucene.store.ChecksumIndexInput; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.IOUtils; +import org.apache.lucene.util.RamUsageEstimator; +import org.apache.lucene.util.SuppressForbidden; +import org.apache.lucene.util.hnsw.OrdinalTranslatedKnnCollector; +import org.apache.lucene.util.hnsw.RandomVectorScorer; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader.readSimilarityFunction; +import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader.readVectorEncoding; + +/** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + */ +@SuppressForbidden(reason = "Lucene classes") +public class ES816BinaryQuantizedVectorsReader extends FlatVectorsReader { + + private static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(ES816BinaryQuantizedVectorsReader.class); + + private final Map fields = new HashMap<>(); + private final IndexInput quantizedVectorData; + private final FlatVectorsReader rawVectorsReader; + private final ES816BinaryFlatVectorsScorer vectorScorer; + + public ES816BinaryQuantizedVectorsReader( + SegmentReadState state, + FlatVectorsReader rawVectorsReader, + ES816BinaryFlatVectorsScorer vectorsScorer + ) throws IOException { + super(vectorsScorer); + this.vectorScorer = vectorsScorer; + this.rawVectorsReader = rawVectorsReader; + int versionMeta = -1; + String metaFileName = IndexFileNames.segmentFileName( + state.segmentInfo.name, + state.segmentSuffix, + ES816BinaryQuantizedVectorsFormat.META_EXTENSION + ); + boolean success = false; + try (ChecksumIndexInput meta = state.directory.openChecksumInput(metaFileName, state.context)) { + Throwable priorE = null; + try { + versionMeta = CodecUtil.checkIndexHeader( + meta, + ES816BinaryQuantizedVectorsFormat.META_CODEC_NAME, + ES816BinaryQuantizedVectorsFormat.VERSION_START, + ES816BinaryQuantizedVectorsFormat.VERSION_CURRENT, + state.segmentInfo.getId(), + state.segmentSuffix + ); + readFields(meta, state.fieldInfos); + } catch (Throwable exception) { + priorE = exception; + } finally { + CodecUtil.checkFooter(meta, priorE); + } + quantizedVectorData = openDataInput( + state, + versionMeta, + ES816BinaryQuantizedVectorsFormat.VECTOR_DATA_EXTENSION, + ES816BinaryQuantizedVectorsFormat.VECTOR_DATA_CODEC_NAME, + // Quantized vectors are accessed randomly from their node ID stored in the HNSW + // graph. + state.context.withRandomAccess() + ); + success = true; + } finally { + if (success == false) { + IOUtils.closeWhileHandlingException(this); + } + } + } + + private void readFields(ChecksumIndexInput meta, FieldInfos infos) throws IOException { + for (int fieldNumber = meta.readInt(); fieldNumber != -1; fieldNumber = meta.readInt()) { + FieldInfo info = infos.fieldInfo(fieldNumber); + if (info == null) { + throw new CorruptIndexException("Invalid field number: " + fieldNumber, meta); + } + FieldEntry fieldEntry = readField(meta, info); + validateFieldEntry(info, fieldEntry); + fields.put(info.name, fieldEntry); + } + } + + static void validateFieldEntry(FieldInfo info, FieldEntry fieldEntry) { + int dimension = info.getVectorDimension(); + if (dimension != fieldEntry.dimension) { + throw new IllegalStateException( + "Inconsistent vector dimension for field=\"" + info.name + "\"; " + dimension + " != " + fieldEntry.dimension + ); + } + + int binaryDims = BQVectorUtils.discretize(dimension, 64) / 8; + int correctionsCount = fieldEntry.similarityFunction != VectorSimilarityFunction.EUCLIDEAN ? 3 : 2; + long numQuantizedVectorBytes = Math.multiplyExact((binaryDims + (Float.BYTES * correctionsCount)), (long) fieldEntry.size); + if (numQuantizedVectorBytes != fieldEntry.vectorDataLength) { + throw new IllegalStateException( + "Binarized vector data length " + + fieldEntry.vectorDataLength + + " not matching size = " + + fieldEntry.size + + " * (binaryBytes=" + + binaryDims + + " + 8" + + ") = " + + numQuantizedVectorBytes + ); + } + } + + @Override + public RandomVectorScorer getRandomVectorScorer(String field, float[] target) throws IOException { + FieldEntry fi = fields.get(field); + if (fi == null) { + return null; + } + return vectorScorer.getRandomVectorScorer( + fi.similarityFunction, + OffHeapBinarizedVectorValues.load( + fi.ordToDocDISIReaderConfiguration, + fi.dimension, + fi.size, + new BinaryQuantizer(fi.dimension, fi.descritizedDimension, fi.similarityFunction), + fi.similarityFunction, + vectorScorer, + fi.centroid, + fi.centroidDP, + fi.vectorDataOffset, + fi.vectorDataLength, + quantizedVectorData + ), + target + ); + } + + @Override + public RandomVectorScorer getRandomVectorScorer(String field, byte[] target) throws IOException { + return rawVectorsReader.getRandomVectorScorer(field, target); + } + + @Override + public void checkIntegrity() throws IOException { + rawVectorsReader.checkIntegrity(); + CodecUtil.checksumEntireFile(quantizedVectorData); + } + + @Override + public FloatVectorValues getFloatVectorValues(String field) throws IOException { + FieldEntry fi = fields.get(field); + if (fi == null) { + return null; + } + if (fi.vectorEncoding != VectorEncoding.FLOAT32) { + throw new IllegalArgumentException( + "field=\"" + field + "\" is encoded as: " + fi.vectorEncoding + " expected: " + VectorEncoding.FLOAT32 + ); + } + OffHeapBinarizedVectorValues bvv = OffHeapBinarizedVectorValues.load( + fi.ordToDocDISIReaderConfiguration, + fi.dimension, + fi.size, + new BinaryQuantizer(fi.dimension, fi.descritizedDimension, fi.similarityFunction), + fi.similarityFunction, + vectorScorer, + fi.centroid, + fi.centroidDP, + fi.vectorDataOffset, + fi.vectorDataLength, + quantizedVectorData + ); + return new BinarizedVectorValues(rawVectorsReader.getFloatVectorValues(field), bvv); + } + + @Override + public ByteVectorValues getByteVectorValues(String field) throws IOException { + return rawVectorsReader.getByteVectorValues(field); + } + + @Override + public void search(String field, byte[] target, KnnCollector knnCollector, Bits acceptDocs) throws IOException { + rawVectorsReader.search(field, target, knnCollector, acceptDocs); + } + + @Override + public void search(String field, float[] target, KnnCollector knnCollector, Bits acceptDocs) throws IOException { + if (knnCollector.k() == 0) return; + final RandomVectorScorer scorer = getRandomVectorScorer(field, target); + if (scorer == null) return; + OrdinalTranslatedKnnCollector collector = new OrdinalTranslatedKnnCollector(knnCollector, scorer::ordToDoc); + Bits acceptedOrds = scorer.getAcceptOrds(acceptDocs); + for (int i = 0; i < scorer.maxOrd(); i++) { + if (acceptedOrds == null || acceptedOrds.get(i)) { + collector.collect(i, scorer.score(i)); + collector.incVisitedCount(1); + } + } + } + + @Override + public void close() throws IOException { + IOUtils.close(quantizedVectorData, rawVectorsReader); + } + + @Override + public long ramBytesUsed() { + long size = SHALLOW_SIZE; + size += RamUsageEstimator.sizeOfMap(fields, RamUsageEstimator.shallowSizeOfInstance(FieldEntry.class)); + size += rawVectorsReader.ramBytesUsed(); + return size; + } + + public float[] getCentroid(String field) { + FieldEntry fieldEntry = fields.get(field); + if (fieldEntry != null) { + return fieldEntry.centroid; + } + return null; + } + + private static IndexInput openDataInput( + SegmentReadState state, + int versionMeta, + String fileExtension, + String codecName, + IOContext context + ) throws IOException { + String fileName = IndexFileNames.segmentFileName(state.segmentInfo.name, state.segmentSuffix, fileExtension); + IndexInput in = state.directory.openInput(fileName, context); + boolean success = false; + try { + int versionVectorData = CodecUtil.checkIndexHeader( + in, + codecName, + ES816BinaryQuantizedVectorsFormat.VERSION_START, + ES816BinaryQuantizedVectorsFormat.VERSION_CURRENT, + state.segmentInfo.getId(), + state.segmentSuffix + ); + if (versionMeta != versionVectorData) { + throw new CorruptIndexException( + "Format versions mismatch: meta=" + versionMeta + ", " + codecName + "=" + versionVectorData, + in + ); + } + CodecUtil.retrieveChecksum(in); + success = true; + return in; + } finally { + if (success == false) { + IOUtils.closeWhileHandlingException(in); + } + } + } + + private FieldEntry readField(IndexInput input, FieldInfo info) throws IOException { + VectorEncoding vectorEncoding = readVectorEncoding(input); + VectorSimilarityFunction similarityFunction = readSimilarityFunction(input); + if (similarityFunction != info.getVectorSimilarityFunction()) { + throw new IllegalStateException( + "Inconsistent vector similarity function for field=\"" + + info.name + + "\"; " + + similarityFunction + + " != " + + info.getVectorSimilarityFunction() + ); + } + return FieldEntry.create(input, vectorEncoding, info.getVectorSimilarityFunction()); + } + + private record FieldEntry( + VectorSimilarityFunction similarityFunction, + VectorEncoding vectorEncoding, + int dimension, + int descritizedDimension, + long vectorDataOffset, + long vectorDataLength, + int size, + float[] centroid, + float centroidDP, + OrdToDocDISIReaderConfiguration ordToDocDISIReaderConfiguration + ) { + + static FieldEntry create(IndexInput input, VectorEncoding vectorEncoding, VectorSimilarityFunction similarityFunction) + throws IOException { + int dimension = input.readVInt(); + long vectorDataOffset = input.readVLong(); + long vectorDataLength = input.readVLong(); + int size = input.readVInt(); + final float[] centroid; + float centroidDP = 0; + if (size > 0) { + centroid = new float[dimension]; + input.readFloats(centroid, 0, dimension); + centroidDP = Float.intBitsToFloat(input.readInt()); + } else { + centroid = null; + } + OrdToDocDISIReaderConfiguration conf = OrdToDocDISIReaderConfiguration.fromStoredMeta(input, size); + return new FieldEntry( + similarityFunction, + vectorEncoding, + dimension, + BQVectorUtils.discretize(dimension, 64), + vectorDataOffset, + vectorDataLength, + size, + centroid, + centroidDP, + conf + ); + } + } + + /** Binarized vector values holding row and quantized vector values */ + protected static final class BinarizedVectorValues extends FloatVectorValues { + private final FloatVectorValues rawVectorValues; + private final OffHeapBinarizedVectorValues quantizedVectorValues; + + BinarizedVectorValues(FloatVectorValues rawVectorValues, OffHeapBinarizedVectorValues quantizedVectorValues) { + this.rawVectorValues = rawVectorValues; + this.quantizedVectorValues = quantizedVectorValues; + } + + @Override + public int dimension() { + return rawVectorValues.dimension(); + } + + @Override + public int size() { + return rawVectorValues.size(); + } + + @Override + public float[] vectorValue() throws IOException { + return rawVectorValues.vectorValue(); + } + + @Override + public int docID() { + return rawVectorValues.docID(); + } + + @Override + public int nextDoc() throws IOException { + int rawDocId = rawVectorValues.nextDoc(); + int quantizedDocId = quantizedVectorValues.nextDoc(); + assert rawDocId == quantizedDocId; + return quantizedDocId; + } + + @Override + public int advance(int target) throws IOException { + int rawDocId = rawVectorValues.advance(target); + int quantizedDocId = quantizedVectorValues.advance(target); + assert rawDocId == quantizedDocId; + return quantizedDocId; + } + + @Override + public VectorScorer scorer(float[] query) throws IOException { + return quantizedVectorValues.scorer(query); + } + + protected OffHeapBinarizedVectorValues getQuantizedVectorValues() throws IOException { + return quantizedVectorValues; + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsWriter.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsWriter.java new file mode 100644 index 0000000000000..92837a8ffce45 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsWriter.java @@ -0,0 +1,987 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.codecs.KnnVectorsReader; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.codecs.lucene95.OrdToDocDISIReaderConfiguration; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.apache.lucene.index.DocsWithFieldSet; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.IndexFileNames; +import org.apache.lucene.index.MergeState; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.index.Sorter; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.internal.hppc.FloatArrayList; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.VectorScorer; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.util.IOUtils; +import org.apache.lucene.util.RamUsageEstimator; +import org.apache.lucene.util.VectorUtil; +import org.apache.lucene.util.hnsw.CloseableRandomVectorScorerSupplier; +import org.apache.lucene.util.hnsw.RandomAccessVectorValues; +import org.apache.lucene.util.hnsw.RandomVectorScorer; +import org.apache.lucene.util.hnsw.RandomVectorScorerSupplier; +import org.elasticsearch.core.SuppressForbidden; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.apache.lucene.index.VectorSimilarityFunction.COSINE; +import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.apache.lucene.util.RamUsageEstimator.shallowSizeOfInstance; +import static org.elasticsearch.index.codec.vectors.ES816BinaryQuantizedVectorsFormat.BINARIZED_VECTOR_COMPONENT; +import static org.elasticsearch.index.codec.vectors.ES816BinaryQuantizedVectorsFormat.DIRECT_MONOTONIC_BLOCK_SHIFT; + +/** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + */ +@SuppressForbidden(reason = "Lucene classes") +public class ES816BinaryQuantizedVectorsWriter extends FlatVectorsWriter { + private static final long SHALLOW_RAM_BYTES_USED = shallowSizeOfInstance(ES816BinaryQuantizedVectorsWriter.class); + + private final SegmentWriteState segmentWriteState; + private final List fields = new ArrayList<>(); + private final IndexOutput meta, binarizedVectorData; + private final FlatVectorsWriter rawVectorDelegate; + private final ES816BinaryFlatVectorsScorer vectorsScorer; + private boolean finished; + + /** + * Sole constructor + * + * @param vectorsScorer the scorer to use for scoring vectors + */ + protected ES816BinaryQuantizedVectorsWriter( + ES816BinaryFlatVectorsScorer vectorsScorer, + FlatVectorsWriter rawVectorDelegate, + SegmentWriteState state + ) throws IOException { + super(vectorsScorer); + this.vectorsScorer = vectorsScorer; + this.segmentWriteState = state; + String metaFileName = IndexFileNames.segmentFileName( + state.segmentInfo.name, + state.segmentSuffix, + ES816BinaryQuantizedVectorsFormat.META_EXTENSION + ); + + String binarizedVectorDataFileName = IndexFileNames.segmentFileName( + state.segmentInfo.name, + state.segmentSuffix, + ES816BinaryQuantizedVectorsFormat.VECTOR_DATA_EXTENSION + ); + this.rawVectorDelegate = rawVectorDelegate; + boolean success = false; + try { + meta = state.directory.createOutput(metaFileName, state.context); + binarizedVectorData = state.directory.createOutput(binarizedVectorDataFileName, state.context); + + CodecUtil.writeIndexHeader( + meta, + ES816BinaryQuantizedVectorsFormat.META_CODEC_NAME, + ES816BinaryQuantizedVectorsFormat.VERSION_CURRENT, + state.segmentInfo.getId(), + state.segmentSuffix + ); + CodecUtil.writeIndexHeader( + binarizedVectorData, + ES816BinaryQuantizedVectorsFormat.VECTOR_DATA_CODEC_NAME, + ES816BinaryQuantizedVectorsFormat.VERSION_CURRENT, + state.segmentInfo.getId(), + state.segmentSuffix + ); + success = true; + } finally { + if (success == false) { + IOUtils.closeWhileHandlingException(this); + } + } + } + + public FlatFieldVectorsWriter addField(FieldInfo fieldInfo) throws IOException { + FlatFieldVectorsWriter rawVectorDelegate = this.rawVectorDelegate.addField(fieldInfo); + if (fieldInfo.getVectorEncoding().equals(VectorEncoding.FLOAT32)) { + @SuppressWarnings("unchecked") + FieldWriter fieldWriter = new FieldWriter(fieldInfo, (FlatFieldVectorsWriter) rawVectorDelegate); + fields.add(fieldWriter); + return fieldWriter; + } + return rawVectorDelegate; + } + + @Override + public void flush(int maxDoc, Sorter.DocMap sortMap) throws IOException { + rawVectorDelegate.flush(maxDoc, sortMap); + for (FieldWriter field : fields) { + // after raw vectors are written, normalize vectors for clustering and quantization + if (VectorSimilarityFunction.COSINE == field.fieldInfo.getVectorSimilarityFunction()) { + field.normalizeVectors(); + } + + final float[] clusterCenter; + int vectorCount = field.flatFieldVectorsWriter.getVectors().size(); + clusterCenter = new float[field.dimensionSums.length]; + if (vectorCount > 0) { + for (int i = 0; i < field.dimensionSums.length; i++) { + clusterCenter[i] = field.dimensionSums[i] / vectorCount; + } + if (VectorSimilarityFunction.COSINE == field.fieldInfo.getVectorSimilarityFunction()) { + VectorUtil.l2normalize(clusterCenter); + } + } + if (segmentWriteState.infoStream.isEnabled(BINARIZED_VECTOR_COMPONENT)) { + segmentWriteState.infoStream.message(BINARIZED_VECTOR_COMPONENT, "Vectors' count:" + vectorCount); + } + int descritizedDimension = BQVectorUtils.discretize(field.fieldInfo.getVectorDimension(), 64); + BinaryQuantizer quantizer = new BinaryQuantizer( + field.fieldInfo.getVectorDimension(), + descritizedDimension, + field.fieldInfo.getVectorSimilarityFunction() + ); + if (sortMap == null) { + writeField(field, clusterCenter, maxDoc, quantizer); + } else { + writeSortingField(field, clusterCenter, maxDoc, sortMap, quantizer); + } + field.finish(); + } + } + + private void writeField(FieldWriter fieldData, float[] clusterCenter, int maxDoc, BinaryQuantizer quantizer) throws IOException { + // write vector values + long vectorDataOffset = binarizedVectorData.alignFilePointer(Float.BYTES); + writeBinarizedVectors(fieldData, clusterCenter, quantizer); + long vectorDataLength = binarizedVectorData.getFilePointer() - vectorDataOffset; + float centroidDp = fieldData.getVectors().size() > 0 ? VectorUtil.dotProduct(clusterCenter, clusterCenter) : 0; + + writeMeta( + fieldData.fieldInfo, + maxDoc, + vectorDataOffset, + vectorDataLength, + clusterCenter, + centroidDp, + fieldData.getDocsWithFieldSet() + ); + } + + private void writeBinarizedVectors(FieldWriter fieldData, float[] clusterCenter, BinaryQuantizer scalarQuantizer) throws IOException { + byte[] vector = new byte[BQVectorUtils.discretize(fieldData.fieldInfo.getVectorDimension(), 64) / 8]; + int correctionsCount = scalarQuantizer.getSimilarity() != EUCLIDEAN ? 3 : 2; + final ByteBuffer correctionsBuffer = ByteBuffer.allocate(Float.BYTES * correctionsCount).order(ByteOrder.LITTLE_ENDIAN); + for (int i = 0; i < fieldData.getVectors().size(); i++) { + float[] v = fieldData.getVectors().get(i); + float[] corrections = scalarQuantizer.quantizeForIndex(v, vector, clusterCenter); + binarizedVectorData.writeBytes(vector, vector.length); + for (int j = 0; j < corrections.length; j++) { + correctionsBuffer.putFloat(corrections[j]); + } + binarizedVectorData.writeBytes(correctionsBuffer.array(), correctionsBuffer.array().length); + correctionsBuffer.rewind(); + } + } + + private void writeSortingField( + FieldWriter fieldData, + float[] clusterCenter, + int maxDoc, + Sorter.DocMap sortMap, + BinaryQuantizer scalarQuantizer + ) throws IOException { + final int[] ordMap = new int[fieldData.getDocsWithFieldSet().cardinality()]; // new ord to old ord + + DocsWithFieldSet newDocsWithField = new DocsWithFieldSet(); + mapOldOrdToNewOrd(fieldData.getDocsWithFieldSet(), sortMap, null, ordMap, newDocsWithField); + + // write vector values + long vectorDataOffset = binarizedVectorData.alignFilePointer(Float.BYTES); + writeSortedBinarizedVectors(fieldData, clusterCenter, ordMap, scalarQuantizer); + long quantizedVectorLength = binarizedVectorData.getFilePointer() - vectorDataOffset; + + float centroidDp = VectorUtil.dotProduct(clusterCenter, clusterCenter); + writeMeta(fieldData.fieldInfo, maxDoc, vectorDataOffset, quantizedVectorLength, clusterCenter, centroidDp, newDocsWithField); + } + + private void writeSortedBinarizedVectors(FieldWriter fieldData, float[] clusterCenter, int[] ordMap, BinaryQuantizer scalarQuantizer) + throws IOException { + byte[] vector = new byte[BQVectorUtils.discretize(fieldData.fieldInfo.getVectorDimension(), 64) / 8]; + int correctionsCount = scalarQuantizer.getSimilarity() != EUCLIDEAN ? 3 : 2; + final ByteBuffer correctionsBuffer = ByteBuffer.allocate(Float.BYTES * correctionsCount).order(ByteOrder.LITTLE_ENDIAN); + for (int ordinal : ordMap) { + float[] v = fieldData.getVectors().get(ordinal); + float[] corrections = scalarQuantizer.quantizeForIndex(v, vector, clusterCenter); + binarizedVectorData.writeBytes(vector, vector.length); + for (int i = 0; i < corrections.length; i++) { + correctionsBuffer.putFloat(corrections[i]); + } + binarizedVectorData.writeBytes(correctionsBuffer.array(), correctionsBuffer.array().length); + correctionsBuffer.rewind(); + } + } + + private void writeMeta( + FieldInfo field, + int maxDoc, + long vectorDataOffset, + long vectorDataLength, + float[] clusterCenter, + float centroidDp, + DocsWithFieldSet docsWithField + ) throws IOException { + meta.writeInt(field.number); + meta.writeInt(field.getVectorEncoding().ordinal()); + meta.writeInt(field.getVectorSimilarityFunction().ordinal()); + meta.writeVInt(field.getVectorDimension()); + meta.writeVLong(vectorDataOffset); + meta.writeVLong(vectorDataLength); + int count = docsWithField.cardinality(); + meta.writeVInt(count); + if (count > 0) { + final ByteBuffer buffer = ByteBuffer.allocate(field.getVectorDimension() * Float.BYTES).order(ByteOrder.LITTLE_ENDIAN); + buffer.asFloatBuffer().put(clusterCenter); + meta.writeBytes(buffer.array(), buffer.array().length); + meta.writeInt(Float.floatToIntBits(centroidDp)); + } + OrdToDocDISIReaderConfiguration.writeStoredMeta( + DIRECT_MONOTONIC_BLOCK_SHIFT, + meta, + binarizedVectorData, + count, + maxDoc, + docsWithField + ); + } + + @Override + public void finish() throws IOException { + if (finished) { + throw new IllegalStateException("already finished"); + } + finished = true; + rawVectorDelegate.finish(); + if (meta != null) { + // write end of fields marker + meta.writeInt(-1); + CodecUtil.writeFooter(meta); + } + if (binarizedVectorData != null) { + CodecUtil.writeFooter(binarizedVectorData); + } + } + + @Override + public void mergeOneField(FieldInfo fieldInfo, MergeState mergeState) throws IOException { + if (fieldInfo.getVectorEncoding().equals(VectorEncoding.FLOAT32)) { + final float[] centroid; + final float[] mergedCentroid = new float[fieldInfo.getVectorDimension()]; + int vectorCount = mergeAndRecalculateCentroids(mergeState, fieldInfo, mergedCentroid); + // Don't need access to the random vectors, we can just use the merged + rawVectorDelegate.mergeOneField(fieldInfo, mergeState); + centroid = mergedCentroid; + if (segmentWriteState.infoStream.isEnabled(BINARIZED_VECTOR_COMPONENT)) { + segmentWriteState.infoStream.message(BINARIZED_VECTOR_COMPONENT, "Vectors' count:" + vectorCount); + } + int descritizedDimension = BQVectorUtils.discretize(fieldInfo.getVectorDimension(), 64); + FloatVectorValues floatVectorValues = KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState); + if (fieldInfo.getVectorSimilarityFunction() == COSINE) { + floatVectorValues = new NormalizedFloatVectorValues(floatVectorValues); + } + BinarizedFloatVectorValues binarizedVectorValues = new BinarizedFloatVectorValues( + floatVectorValues, + new BinaryQuantizer(fieldInfo.getVectorDimension(), descritizedDimension, fieldInfo.getVectorSimilarityFunction()), + centroid + ); + long vectorDataOffset = binarizedVectorData.alignFilePointer(Float.BYTES); + DocsWithFieldSet docsWithField = writeBinarizedVectorData(binarizedVectorData, binarizedVectorValues); + long vectorDataLength = binarizedVectorData.getFilePointer() - vectorDataOffset; + float centroidDp = docsWithField.cardinality() > 0 ? VectorUtil.dotProduct(centroid, centroid) : 0; + writeMeta( + fieldInfo, + segmentWriteState.segmentInfo.maxDoc(), + vectorDataOffset, + vectorDataLength, + centroid, + centroidDp, + docsWithField + ); + } else { + rawVectorDelegate.mergeOneField(fieldInfo, mergeState); + } + } + + static DocsWithFieldSet writeBinarizedVectorAndQueryData( + IndexOutput binarizedVectorData, + IndexOutput binarizedQueryData, + FloatVectorValues floatVectorValues, + float[] centroid, + BinaryQuantizer binaryQuantizer + ) throws IOException { + DocsWithFieldSet docsWithField = new DocsWithFieldSet(); + byte[] toIndex = new byte[BQVectorUtils.discretize(floatVectorValues.dimension(), 64) / 8]; + byte[] toQuery = new byte[(BQVectorUtils.discretize(floatVectorValues.dimension(), 64) / 8) * BQSpaceUtils.B_QUERY]; + int queryCorrectionCount = binaryQuantizer.getSimilarity() != EUCLIDEAN ? 5 : 3; + final ByteBuffer queryCorrectionsBuffer = ByteBuffer.allocate(Float.BYTES * queryCorrectionCount + Short.BYTES) + .order(ByteOrder.LITTLE_ENDIAN); + for (int docV = floatVectorValues.nextDoc(); docV != NO_MORE_DOCS; docV = floatVectorValues.nextDoc()) { + // write index vector + BinaryQuantizer.QueryAndIndexResults r = binaryQuantizer.quantizeQueryAndIndex( + floatVectorValues.vectorValue(), + toIndex, + toQuery, + centroid + ); + binarizedVectorData.writeBytes(toIndex, toIndex.length); + float[] corrections = r.indexFeatures(); + for (int i = 0; i < corrections.length; i++) { + binarizedVectorData.writeInt(Float.floatToIntBits(corrections[i])); + } + docsWithField.add(docV); + + // write query vector + binarizedQueryData.writeBytes(toQuery, toQuery.length); + BinaryQuantizer.QueryFactors factors = r.queryFeatures(); + queryCorrectionsBuffer.putFloat(factors.distToC()); + queryCorrectionsBuffer.putFloat(factors.lower()); + queryCorrectionsBuffer.putFloat(factors.width()); + + if (binaryQuantizer.getSimilarity() != EUCLIDEAN) { + queryCorrectionsBuffer.putFloat(factors.normVmC()); + queryCorrectionsBuffer.putFloat(factors.vDotC()); + } + // ensure we are positive and fit within an unsigned short value. + assert factors.quantizedSum() >= 0 && factors.quantizedSum() <= 0xffff; + queryCorrectionsBuffer.putShort((short) factors.quantizedSum()); + + binarizedQueryData.writeBytes(queryCorrectionsBuffer.array(), queryCorrectionsBuffer.array().length); + queryCorrectionsBuffer.rewind(); + } + return docsWithField; + } + + static DocsWithFieldSet writeBinarizedVectorData(IndexOutput output, BinarizedByteVectorValues binarizedByteVectorValues) + throws IOException { + DocsWithFieldSet docsWithField = new DocsWithFieldSet(); + for (int docV = binarizedByteVectorValues.nextDoc(); docV != NO_MORE_DOCS; docV = binarizedByteVectorValues.nextDoc()) { + // write vector + byte[] binaryValue = binarizedByteVectorValues.vectorValue(); + output.writeBytes(binaryValue, binaryValue.length); + float[] corrections = binarizedByteVectorValues.getCorrectiveTerms(); + for (int i = 0; i < corrections.length; i++) { + output.writeInt(Float.floatToIntBits(corrections[i])); + } + docsWithField.add(docV); + } + return docsWithField; + } + + @Override + public CloseableRandomVectorScorerSupplier mergeOneFieldToIndex(FieldInfo fieldInfo, MergeState mergeState) throws IOException { + if (fieldInfo.getVectorEncoding().equals(VectorEncoding.FLOAT32)) { + final float[] centroid; + final float cDotC; + final float[] mergedCentroid = new float[fieldInfo.getVectorDimension()]; + int vectorCount = mergeAndRecalculateCentroids(mergeState, fieldInfo, mergedCentroid); + + // Don't need access to the random vectors, we can just use the merged + rawVectorDelegate.mergeOneField(fieldInfo, mergeState); + centroid = mergedCentroid; + cDotC = vectorCount > 0 ? VectorUtil.dotProduct(centroid, centroid) : 0; + if (segmentWriteState.infoStream.isEnabled(BINARIZED_VECTOR_COMPONENT)) { + segmentWriteState.infoStream.message(BINARIZED_VECTOR_COMPONENT, "Vectors' count:" + vectorCount); + } + return mergeOneFieldToIndex(segmentWriteState, fieldInfo, mergeState, centroid, cDotC); + } + return rawVectorDelegate.mergeOneFieldToIndex(fieldInfo, mergeState); + } + + private CloseableRandomVectorScorerSupplier mergeOneFieldToIndex( + SegmentWriteState segmentWriteState, + FieldInfo fieldInfo, + MergeState mergeState, + float[] centroid, + float cDotC + ) throws IOException { + long vectorDataOffset = binarizedVectorData.alignFilePointer(Float.BYTES); + final IndexOutput tempQuantizedVectorData = segmentWriteState.directory.createTempOutput( + binarizedVectorData.getName(), + "temp", + segmentWriteState.context + ); + final IndexOutput tempScoreQuantizedVectorData = segmentWriteState.directory.createTempOutput( + binarizedVectorData.getName(), + "score_temp", + segmentWriteState.context + ); + IndexInput binarizedDataInput = null; + IndexInput binarizedScoreDataInput = null; + boolean success = false; + int descritizedDimension = BQVectorUtils.discretize(fieldInfo.getVectorDimension(), 64); + BinaryQuantizer quantizer = new BinaryQuantizer( + fieldInfo.getVectorDimension(), + descritizedDimension, + fieldInfo.getVectorSimilarityFunction() + ); + try { + FloatVectorValues floatVectorValues = KnnVectorsWriter.MergedVectorValues.mergeFloatVectorValues(fieldInfo, mergeState); + if (fieldInfo.getVectorSimilarityFunction() == COSINE) { + floatVectorValues = new NormalizedFloatVectorValues(floatVectorValues); + } + DocsWithFieldSet docsWithField = writeBinarizedVectorAndQueryData( + tempQuantizedVectorData, + tempScoreQuantizedVectorData, + floatVectorValues, + centroid, + quantizer + ); + CodecUtil.writeFooter(tempQuantizedVectorData); + IOUtils.close(tempQuantizedVectorData); + binarizedDataInput = segmentWriteState.directory.openInput(tempQuantizedVectorData.getName(), segmentWriteState.context); + binarizedVectorData.copyBytes(binarizedDataInput, binarizedDataInput.length() - CodecUtil.footerLength()); + long vectorDataLength = binarizedVectorData.getFilePointer() - vectorDataOffset; + CodecUtil.retrieveChecksum(binarizedDataInput); + CodecUtil.writeFooter(tempScoreQuantizedVectorData); + IOUtils.close(tempScoreQuantizedVectorData); + binarizedScoreDataInput = segmentWriteState.directory.openInput( + tempScoreQuantizedVectorData.getName(), + segmentWriteState.context + ); + writeMeta( + fieldInfo, + segmentWriteState.segmentInfo.maxDoc(), + vectorDataOffset, + vectorDataLength, + centroid, + cDotC, + docsWithField + ); + success = true; + final IndexInput finalBinarizedDataInput = binarizedDataInput; + final IndexInput finalBinarizedScoreDataInput = binarizedScoreDataInput; + OffHeapBinarizedVectorValues vectorValues = new OffHeapBinarizedVectorValues.DenseOffHeapVectorValues( + fieldInfo.getVectorDimension(), + docsWithField.cardinality(), + centroid, + cDotC, + quantizer, + fieldInfo.getVectorSimilarityFunction(), + vectorsScorer, + finalBinarizedDataInput + ); + RandomVectorScorerSupplier scorerSupplier = vectorsScorer.getRandomVectorScorerSupplier( + fieldInfo.getVectorSimilarityFunction(), + new OffHeapBinarizedQueryVectorValues( + finalBinarizedScoreDataInput, + fieldInfo.getVectorDimension(), + docsWithField.cardinality(), + fieldInfo.getVectorSimilarityFunction() + ), + vectorValues + ); + return new BinarizedCloseableRandomVectorScorerSupplier(scorerSupplier, vectorValues, () -> { + IOUtils.close(finalBinarizedDataInput, finalBinarizedScoreDataInput); + IOUtils.deleteFilesIgnoringExceptions( + segmentWriteState.directory, + tempQuantizedVectorData.getName(), + tempScoreQuantizedVectorData.getName() + ); + }); + } finally { + if (success == false) { + IOUtils.closeWhileHandlingException( + tempQuantizedVectorData, + tempScoreQuantizedVectorData, + binarizedDataInput, + binarizedScoreDataInput + ); + IOUtils.deleteFilesIgnoringExceptions( + segmentWriteState.directory, + tempQuantizedVectorData.getName(), + tempScoreQuantizedVectorData.getName() + ); + } + } + } + + @Override + public void close() throws IOException { + IOUtils.close(meta, binarizedVectorData, rawVectorDelegate); + } + + static float[] getCentroid(KnnVectorsReader vectorsReader, String fieldName) { + if (vectorsReader instanceof PerFieldKnnVectorsFormat.FieldsReader candidateReader) { + vectorsReader = candidateReader.getFieldReader(fieldName); + } + if (vectorsReader instanceof ES816BinaryQuantizedVectorsReader reader) { + return reader.getCentroid(fieldName); + } + return null; + } + + static int mergeAndRecalculateCentroids(MergeState mergeState, FieldInfo fieldInfo, float[] mergedCentroid) throws IOException { + boolean recalculate = false; + int totalVectorCount = 0; + for (int i = 0; i < mergeState.knnVectorsReaders.length; i++) { + KnnVectorsReader knnVectorsReader = mergeState.knnVectorsReaders[i]; + if (knnVectorsReader == null || knnVectorsReader.getFloatVectorValues(fieldInfo.name) == null) { + continue; + } + float[] centroid = getCentroid(knnVectorsReader, fieldInfo.name); + int vectorCount = knnVectorsReader.getFloatVectorValues(fieldInfo.name).size(); + if (vectorCount == 0) { + continue; + } + totalVectorCount += vectorCount; + // If there aren't centroids, or previously clustered with more than one cluster + // or if there are deleted docs, we must recalculate the centroid + if (centroid == null || mergeState.liveDocs[i] != null) { + recalculate = true; + break; + } + for (int j = 0; j < centroid.length; j++) { + mergedCentroid[j] += centroid[j] * vectorCount; + } + } + if (recalculate) { + return calculateCentroid(mergeState, fieldInfo, mergedCentroid); + } else { + for (int j = 0; j < mergedCentroid.length; j++) { + mergedCentroid[j] = mergedCentroid[j] / totalVectorCount; + } + if (fieldInfo.getVectorSimilarityFunction() == COSINE) { + VectorUtil.l2normalize(mergedCentroid); + } + return totalVectorCount; + } + } + + static int calculateCentroid(MergeState mergeState, FieldInfo fieldInfo, float[] centroid) throws IOException { + assert fieldInfo.getVectorEncoding().equals(VectorEncoding.FLOAT32); + // clear out the centroid + Arrays.fill(centroid, 0); + int count = 0; + for (int i = 0; i < mergeState.knnVectorsReaders.length; i++) { + KnnVectorsReader knnVectorsReader = mergeState.knnVectorsReaders[i]; + if (knnVectorsReader == null) continue; + FloatVectorValues vectorValues = mergeState.knnVectorsReaders[i].getFloatVectorValues(fieldInfo.name); + if (vectorValues == null) { + continue; + } + for (int doc = vectorValues.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = vectorValues.nextDoc()) { + float[] vector = vectorValues.vectorValue(); + // TODO Panama sum + for (int j = 0; j < vector.length; j++) { + centroid[j] += vector[j]; + } + } + count += vectorValues.size(); + } + if (count == 0) { + return count; + } + // TODO Panama div + for (int i = 0; i < centroid.length; i++) { + centroid[i] /= count; + } + if (fieldInfo.getVectorSimilarityFunction() == COSINE) { + VectorUtil.l2normalize(centroid); + } + return count; + } + + @Override + public long ramBytesUsed() { + long total = SHALLOW_RAM_BYTES_USED; + for (FieldWriter field : fields) { + // the field tracks the delegate field usage + total += field.ramBytesUsed(); + } + return total; + } + + static class FieldWriter extends FlatFieldVectorsWriter { + private static final long SHALLOW_SIZE = shallowSizeOfInstance(FieldWriter.class); + private final FieldInfo fieldInfo; + private boolean finished; + private final FlatFieldVectorsWriter flatFieldVectorsWriter; + private final float[] dimensionSums; + private final FloatArrayList magnitudes = new FloatArrayList(); + + FieldWriter(FieldInfo fieldInfo, FlatFieldVectorsWriter flatFieldVectorsWriter) { + this.fieldInfo = fieldInfo; + this.flatFieldVectorsWriter = flatFieldVectorsWriter; + this.dimensionSums = new float[fieldInfo.getVectorDimension()]; + } + + @Override + public List getVectors() { + return flatFieldVectorsWriter.getVectors(); + } + + public void normalizeVectors() { + for (int i = 0; i < flatFieldVectorsWriter.getVectors().size(); i++) { + float[] vector = flatFieldVectorsWriter.getVectors().get(i); + float magnitude = magnitudes.get(i); + for (int j = 0; j < vector.length; j++) { + vector[j] /= magnitude; + } + } + } + + @Override + public DocsWithFieldSet getDocsWithFieldSet() { + return flatFieldVectorsWriter.getDocsWithFieldSet(); + } + + @Override + public void finish() throws IOException { + if (finished) { + return; + } + assert flatFieldVectorsWriter.isFinished(); + finished = true; + } + + @Override + public boolean isFinished() { + return finished && flatFieldVectorsWriter.isFinished(); + } + + @Override + public void addValue(int docID, float[] vectorValue) throws IOException { + flatFieldVectorsWriter.addValue(docID, vectorValue); + if (fieldInfo.getVectorSimilarityFunction() == COSINE) { + float dp = VectorUtil.dotProduct(vectorValue, vectorValue); + float divisor = (float) Math.sqrt(dp); + magnitudes.add(divisor); + for (int i = 0; i < vectorValue.length; i++) { + dimensionSums[i] += (vectorValue[i] / divisor); + } + } else { + for (int i = 0; i < vectorValue.length; i++) { + dimensionSums[i] += vectorValue[i]; + } + } + } + + @Override + public float[] copyValue(float[] vectorValue) { + throw new UnsupportedOperationException(); + } + + @Override + public long ramBytesUsed() { + long size = SHALLOW_SIZE; + size += flatFieldVectorsWriter.ramBytesUsed(); + size += RamUsageEstimator.sizeOf(dimensionSums); + size += magnitudes.ramBytesUsed(); + return size; + } + } + + // When accessing vectorValue method, targerOrd here means a row ordinal. + static class OffHeapBinarizedQueryVectorValues { + private final IndexInput slice; + private final int dimension; + private final int size; + protected final byte[] binaryValue; + protected final ByteBuffer byteBuffer; + private final int byteSize; + protected final float[] correctiveValues; + private int sumQuantizationValues; + private int lastOrd = -1; + private final int correctiveValuesSize; + private final VectorSimilarityFunction vectorSimilarityFunction; + + OffHeapBinarizedQueryVectorValues(IndexInput data, int dimension, int size, VectorSimilarityFunction vectorSimilarityFunction) { + this.slice = data; + this.dimension = dimension; + this.size = size; + this.vectorSimilarityFunction = vectorSimilarityFunction; + this.correctiveValuesSize = vectorSimilarityFunction != EUCLIDEAN ? 5 : 3; + // 4x the quantized binary dimensions + int binaryDimensions = (BQVectorUtils.discretize(dimension, 64) / 8) * BQSpaceUtils.B_QUERY; + this.byteBuffer = ByteBuffer.allocate(binaryDimensions); + this.binaryValue = byteBuffer.array(); + this.correctiveValues = new float[correctiveValuesSize]; + this.byteSize = binaryDimensions + Float.BYTES * correctiveValuesSize + Short.BYTES; + } + + public float getCentroidDistance(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[0]; + } + readCorrectiveValues(targetOrd); + return correctiveValues[0]; + } + + public float getLower(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[1]; + } + readCorrectiveValues(targetOrd); + return correctiveValues[1]; + } + + public float getWidth(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[2]; + } + readCorrectiveValues(targetOrd); + return correctiveValues[2]; + } + + public float getNormVmC(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[3]; + } + readCorrectiveValues(targetOrd); + return correctiveValues[3]; + } + + public float getVDotC(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[4]; + } + readCorrectiveValues(targetOrd); + return correctiveValues[4]; + } + + private void readCorrectiveValues(int targetOrd) throws IOException { + // load values + vectorValue(targetOrd); + } + + public int sumQuantizedValues(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return sumQuantizationValues; + } + // load values + vectorValue(targetOrd); + return sumQuantizationValues; + } + + public int size() { + return size; + } + + public int dimension() { + return dimension; + } + + public OffHeapBinarizedQueryVectorValues copy() throws IOException { + return new OffHeapBinarizedQueryVectorValues(slice.clone(), dimension, size, vectorSimilarityFunction); + } + + public IndexInput getSlice() { + return slice; + } + + public byte[] vectorValue(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return binaryValue; + } + slice.seek((long) targetOrd * byteSize); + slice.readBytes(binaryValue, 0, binaryValue.length); + slice.readFloats(correctiveValues, 0, correctiveValuesSize); + sumQuantizationValues = Short.toUnsignedInt(slice.readShort()); + lastOrd = targetOrd; + return binaryValue; + } + } + + static class BinarizedFloatVectorValues extends BinarizedByteVectorValues { + private float[] corrections; + private final byte[] binarized; + private final float[] centroid; + private final FloatVectorValues values; + private final BinaryQuantizer quantizer; + private int lastDoc; + + BinarizedFloatVectorValues(FloatVectorValues delegate, BinaryQuantizer quantizer, float[] centroid) { + this.values = delegate; + this.quantizer = quantizer; + this.binarized = new byte[BQVectorUtils.discretize(delegate.dimension(), 64) / 8]; + this.centroid = centroid; + lastDoc = -1; + } + + @Override + public float[] getCorrectiveTerms() { + return corrections; + } + + @Override + public byte[] vectorValue() throws IOException { + return binarized; + } + + @Override + public int dimension() { + return values.dimension(); + } + + @Override + public int size() { + return values.size(); + } + + @Override + public int docID() { + return values.docID(); + } + + @Override + public int nextDoc() throws IOException { + int doc = values.nextDoc(); + if (doc != NO_MORE_DOCS) { + binarize(); + } + lastDoc = doc; + return doc; + } + + @Override + public int advance(int target) throws IOException { + int doc = values.advance(target); + if (doc != NO_MORE_DOCS) { + binarize(); + } + lastDoc = doc; + return doc; + } + + @Override + public VectorScorer scorer(float[] target) throws IOException { + throw new UnsupportedOperationException(); + } + + private void binarize() throws IOException { + if (lastDoc == docID()) return; + corrections = quantizer.quantizeForIndex(values.vectorValue(), binarized, centroid); + } + } + + static class BinarizedCloseableRandomVectorScorerSupplier implements CloseableRandomVectorScorerSupplier { + private final RandomVectorScorerSupplier supplier; + private final RandomAccessVectorValues vectorValues; + private final Closeable onClose; + + BinarizedCloseableRandomVectorScorerSupplier( + RandomVectorScorerSupplier supplier, + RandomAccessVectorValues vectorValues, + Closeable onClose + ) { + this.supplier = supplier; + this.onClose = onClose; + this.vectorValues = vectorValues; + } + + @Override + public RandomVectorScorer scorer(int ord) throws IOException { + return supplier.scorer(ord); + } + + @Override + public RandomVectorScorerSupplier copy() throws IOException { + return supplier.copy(); + } + + @Override + public void close() throws IOException { + onClose.close(); + } + + @Override + public int totalVectorCount() { + return vectorValues.size(); + } + } + + static final class NormalizedFloatVectorValues extends FloatVectorValues { + private final FloatVectorValues values; + private final float[] normalizedVector; + int curDoc = -1; + + NormalizedFloatVectorValues(FloatVectorValues values) { + this.values = values; + this.normalizedVector = new float[values.dimension()]; + } + + @Override + public int dimension() { + return values.dimension(); + } + + @Override + public int size() { + return values.size(); + } + + @Override + public float[] vectorValue() { + return normalizedVector; + } + + @Override + public VectorScorer scorer(float[] query) { + throw new UnsupportedOperationException(); + } + + @Override + public int docID() { + return values.docID(); + } + + @Override + public int nextDoc() throws IOException { + curDoc = values.nextDoc(); + if (curDoc != NO_MORE_DOCS) { + System.arraycopy(values.vectorValue(), 0, normalizedVector, 0, normalizedVector.length); + VectorUtil.l2normalize(normalizedVector); + } + return curDoc; + } + + @Override + public int advance(int target) throws IOException { + curDoc = values.advance(target); + if (curDoc != NO_MORE_DOCS) { + System.arraycopy(values.vectorValue(), 0, normalizedVector, 0, normalizedVector.length); + VectorUtil.l2normalize(normalizedVector); + } + return curDoc; + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816HnswBinaryQuantizedVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816HnswBinaryQuantizedVectorsFormat.java new file mode 100644 index 0000000000000..989f88e0a7857 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES816HnswBinaryQuantizedVectorsFormat.java @@ -0,0 +1,144 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.KnnVectorsReader; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsWriter; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.search.TaskExecutor; +import org.apache.lucene.util.hnsw.HnswGraph; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; + +import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH; +import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN; +import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat.DEFAULT_NUM_MERGE_WORKER; +import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat.MAXIMUM_BEAM_WIDTH; +import static org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat.MAXIMUM_MAX_CONN; + +/** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + */ +public class ES816HnswBinaryQuantizedVectorsFormat extends KnnVectorsFormat { + + public static final String NAME = "ES816HnswBinaryQuantizedVectorsFormat"; + + /** + * Controls how many of the nearest neighbor candidates are connected to the new node. Defaults to + * {@link Lucene99HnswVectorsFormat#DEFAULT_MAX_CONN}. See {@link HnswGraph} for more details. + */ + private final int maxConn; + + /** + * The number of candidate neighbors to track while searching the graph for each newly inserted + * node. Defaults to {@link Lucene99HnswVectorsFormat#DEFAULT_BEAM_WIDTH}. See {@link HnswGraph} + * for details. + */ + private final int beamWidth; + + /** The format for storing, reading, merging vectors on disk */ + private static final FlatVectorsFormat flatVectorsFormat = new ES816BinaryQuantizedVectorsFormat(); + + private final int numMergeWorkers; + private final TaskExecutor mergeExec; + + /** Constructs a format using default graph construction parameters */ + public ES816HnswBinaryQuantizedVectorsFormat() { + this(DEFAULT_MAX_CONN, DEFAULT_BEAM_WIDTH, DEFAULT_NUM_MERGE_WORKER, null); + } + + /** + * Constructs a format using the given graph construction parameters. + * + * @param maxConn the maximum number of connections to a node in the HNSW graph + * @param beamWidth the size of the queue maintained during graph construction. + */ + public ES816HnswBinaryQuantizedVectorsFormat(int maxConn, int beamWidth) { + this(maxConn, beamWidth, DEFAULT_NUM_MERGE_WORKER, null); + } + + /** + * Constructs a format using the given graph construction parameters and scalar quantization. + * + * @param maxConn the maximum number of connections to a node in the HNSW graph + * @param beamWidth the size of the queue maintained during graph construction. + * @param numMergeWorkers number of workers (threads) that will be used when doing merge. If + * larger than 1, a non-null {@link ExecutorService} must be passed as mergeExec + * @param mergeExec the {@link ExecutorService} that will be used by ALL vector writers that are + * generated by this format to do the merge + */ + public ES816HnswBinaryQuantizedVectorsFormat(int maxConn, int beamWidth, int numMergeWorkers, ExecutorService mergeExec) { + super(NAME); + if (maxConn <= 0 || maxConn > MAXIMUM_MAX_CONN) { + throw new IllegalArgumentException( + "maxConn must be positive and less than or equal to " + MAXIMUM_MAX_CONN + "; maxConn=" + maxConn + ); + } + if (beamWidth <= 0 || beamWidth > MAXIMUM_BEAM_WIDTH) { + throw new IllegalArgumentException( + "beamWidth must be positive and less than or equal to " + MAXIMUM_BEAM_WIDTH + "; beamWidth=" + beamWidth + ); + } + this.maxConn = maxConn; + this.beamWidth = beamWidth; + if (numMergeWorkers == 1 && mergeExec != null) { + throw new IllegalArgumentException("No executor service is needed as we'll use single thread to merge"); + } + this.numMergeWorkers = numMergeWorkers; + if (mergeExec != null) { + this.mergeExec = new TaskExecutor(mergeExec); + } else { + this.mergeExec = null; + } + } + + @Override + public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { + return new Lucene99HnswVectorsWriter(state, maxConn, beamWidth, flatVectorsFormat.fieldsWriter(state), numMergeWorkers, mergeExec); + } + + @Override + public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException { + return new Lucene99HnswVectorsReader(state, flatVectorsFormat.fieldsReader(state)); + } + + @Override + public int getMaxDimensions(String fieldName) { + return 1024; + } + + @Override + public String toString() { + return "ES816HnswBinaryQuantizedVectorsFormat(name=ES816HnswBinaryQuantizedVectorsFormat, maxConn=" + + maxConn + + ", beamWidth=" + + beamWidth + + ", flatVectorFormat=" + + flatVectorsFormat + + ")"; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/OffHeapBinarizedVectorValues.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/OffHeapBinarizedVectorValues.java new file mode 100644 index 0000000000000..2a3c3aca60e54 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/OffHeapBinarizedVectorValues.java @@ -0,0 +1,456 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.hnsw.FlatVectorsScorer; +import org.apache.lucene.codecs.lucene90.IndexedDISI; +import org.apache.lucene.codecs.lucene95.OrdToDocDISIReaderConfiguration; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.VectorScorer; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.util.Bits; +import org.apache.lucene.util.hnsw.RandomVectorScorer; +import org.apache.lucene.util.packed.DirectMonotonicReader; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; + +/** Binarized vector values loaded from off-heap */ +public abstract class OffHeapBinarizedVectorValues extends BinarizedByteVectorValues implements RandomAccessBinarizedByteVectorValues { + + protected final int dimension; + protected final int size; + protected final int numBytes; + protected final VectorSimilarityFunction similarityFunction; + protected final FlatVectorsScorer vectorsScorer; + + protected final IndexInput slice; + protected final byte[] binaryValue; + protected final ByteBuffer byteBuffer; + protected final int byteSize; + private int lastOrd = -1; + protected final float[] correctiveValues; + protected final BinaryQuantizer binaryQuantizer; + protected final float[] centroid; + protected final float centroidDp; + private final int correctionsCount; + + OffHeapBinarizedVectorValues( + int dimension, + int size, + float[] centroid, + float centroidDp, + BinaryQuantizer quantizer, + VectorSimilarityFunction similarityFunction, + FlatVectorsScorer vectorsScorer, + IndexInput slice + ) { + this.dimension = dimension; + this.size = size; + this.similarityFunction = similarityFunction; + this.vectorsScorer = vectorsScorer; + this.slice = slice; + this.centroid = centroid; + this.centroidDp = centroidDp; + this.numBytes = BQVectorUtils.discretize(dimension, 64) / 8; + this.correctionsCount = similarityFunction != EUCLIDEAN ? 3 : 2; + this.correctiveValues = new float[this.correctionsCount]; + this.byteSize = numBytes + (Float.BYTES * correctionsCount); + this.byteBuffer = ByteBuffer.allocate(numBytes); + this.binaryValue = byteBuffer.array(); + this.binaryQuantizer = quantizer; + } + + @Override + public int dimension() { + return dimension; + } + + @Override + public int size() { + return size; + } + + @Override + public byte[] vectorValue(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return binaryValue; + } + slice.seek((long) targetOrd * byteSize); + slice.readBytes(byteBuffer.array(), byteBuffer.arrayOffset(), numBytes); + slice.readFloats(correctiveValues, 0, correctionsCount); + lastOrd = targetOrd; + return binaryValue; + } + + @Override + public float getCentroidDP() { + return centroidDp; + } + + @Override + public float[] getCorrectiveTerms() { + return correctiveValues; + } + + @Override + public float getCentroidDistance(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[0]; + } + slice.seek(((long) targetOrd * byteSize) + numBytes); + slice.readFloats(correctiveValues, 0, correctionsCount); + return correctiveValues[0]; + } + + @Override + public float getVectorMagnitude(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[1]; + } + slice.seek(((long) targetOrd * byteSize) + numBytes); + slice.readFloats(correctiveValues, 0, correctionsCount); + return correctiveValues[1]; + } + + @Override + public float getOOQ(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[0]; + } + slice.seek(((long) targetOrd * byteSize) + numBytes); + slice.readFloats(correctiveValues, 0, correctionsCount); + return correctiveValues[0]; + } + + @Override + public float getNormOC(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[1]; + } + slice.seek(((long) targetOrd * byteSize) + numBytes); + slice.readFloats(correctiveValues, 0, correctionsCount); + return correctiveValues[1]; + } + + @Override + public float getODotC(int targetOrd) throws IOException { + if (lastOrd == targetOrd) { + return correctiveValues[2]; + } + slice.seek(((long) targetOrd * byteSize) + numBytes); + slice.readFloats(correctiveValues, 0, correctionsCount); + return correctiveValues[2]; + } + + @Override + public BinaryQuantizer getQuantizer() { + return binaryQuantizer; + } + + @Override + public float[] getCentroid() { + return centroid; + } + + @Override + public IndexInput getSlice() { + return slice; + } + + @Override + public int getVectorByteLength() { + return numBytes; + } + + public static OffHeapBinarizedVectorValues load( + OrdToDocDISIReaderConfiguration configuration, + int dimension, + int size, + BinaryQuantizer binaryQuantizer, + VectorSimilarityFunction similarityFunction, + FlatVectorsScorer vectorsScorer, + float[] centroid, + float centroidDp, + long quantizedVectorDataOffset, + long quantizedVectorDataLength, + IndexInput vectorData + ) throws IOException { + if (configuration.isEmpty()) { + return new EmptyOffHeapVectorValues(dimension, similarityFunction, vectorsScorer); + } + assert centroid != null; + IndexInput bytesSlice = vectorData.slice("quantized-vector-data", quantizedVectorDataOffset, quantizedVectorDataLength); + if (configuration.isDense()) { + return new DenseOffHeapVectorValues( + dimension, + size, + centroid, + centroidDp, + binaryQuantizer, + similarityFunction, + vectorsScorer, + bytesSlice + ); + } else { + return new SparseOffHeapVectorValues( + configuration, + dimension, + size, + centroid, + centroidDp, + binaryQuantizer, + vectorData, + similarityFunction, + vectorsScorer, + bytesSlice + ); + } + } + + /** Dense off-heap binarized vector values */ + public static class DenseOffHeapVectorValues extends OffHeapBinarizedVectorValues { + private int doc = -1; + + public DenseOffHeapVectorValues( + int dimension, + int size, + float[] centroid, + float centroidDp, + BinaryQuantizer binaryQuantizer, + VectorSimilarityFunction similarityFunction, + FlatVectorsScorer vectorsScorer, + IndexInput slice + ) { + super(dimension, size, centroid, centroidDp, binaryQuantizer, similarityFunction, vectorsScorer, slice); + } + + @Override + public byte[] vectorValue() throws IOException { + return vectorValue(doc); + } + + @Override + public int docID() { + return doc; + } + + @Override + public int nextDoc() { + return advance(doc + 1); + } + + @Override + public int advance(int target) { + assert docID() < target; + if (target >= size) { + return doc = NO_MORE_DOCS; + } + return doc = target; + } + + @Override + public DenseOffHeapVectorValues copy() throws IOException { + return new DenseOffHeapVectorValues( + dimension, + size, + centroid, + centroidDp, + binaryQuantizer, + similarityFunction, + vectorsScorer, + slice.clone() + ); + } + + @Override + public Bits getAcceptOrds(Bits acceptDocs) { + return acceptDocs; + } + + @Override + public VectorScorer scorer(float[] target) throws IOException { + DenseOffHeapVectorValues copy = copy(); + RandomVectorScorer scorer = vectorsScorer.getRandomVectorScorer(similarityFunction, copy, target); + return new VectorScorer() { + @Override + public float score() throws IOException { + return scorer.score(copy.doc); + } + + @Override + public DocIdSetIterator iterator() { + return copy; + } + }; + } + } + + /** Sparse off-heap binarized vector values */ + private static class SparseOffHeapVectorValues extends OffHeapBinarizedVectorValues { + private final DirectMonotonicReader ordToDoc; + private final IndexedDISI disi; + // dataIn was used to init a new IndexedDIS for #randomAccess() + private final IndexInput dataIn; + private final OrdToDocDISIReaderConfiguration configuration; + + SparseOffHeapVectorValues( + OrdToDocDISIReaderConfiguration configuration, + int dimension, + int size, + float[] centroid, + float centroidDp, + BinaryQuantizer binaryQuantizer, + IndexInput dataIn, + VectorSimilarityFunction similarityFunction, + FlatVectorsScorer vectorsScorer, + IndexInput slice + ) throws IOException { + super(dimension, size, centroid, centroidDp, binaryQuantizer, similarityFunction, vectorsScorer, slice); + this.configuration = configuration; + this.dataIn = dataIn; + this.ordToDoc = configuration.getDirectMonotonicReader(dataIn); + this.disi = configuration.getIndexedDISI(dataIn); + } + + @Override + public byte[] vectorValue() throws IOException { + return vectorValue(disi.index()); + } + + @Override + public int docID() { + return disi.docID(); + } + + @Override + public int nextDoc() throws IOException { + return disi.nextDoc(); + } + + @Override + public int advance(int target) throws IOException { + assert docID() < target; + return disi.advance(target); + } + + @Override + public SparseOffHeapVectorValues copy() throws IOException { + return new SparseOffHeapVectorValues( + configuration, + dimension, + size, + centroid, + centroidDp, + binaryQuantizer, + dataIn, + similarityFunction, + vectorsScorer, + slice.clone() + ); + } + + @Override + public int ordToDoc(int ord) { + return (int) ordToDoc.get(ord); + } + + @Override + public Bits getAcceptOrds(Bits acceptDocs) { + if (acceptDocs == null) { + return null; + } + return new Bits() { + @Override + public boolean get(int index) { + return acceptDocs.get(ordToDoc(index)); + } + + @Override + public int length() { + return size; + } + }; + } + + @Override + public VectorScorer scorer(float[] target) throws IOException { + SparseOffHeapVectorValues copy = copy(); + RandomVectorScorer scorer = vectorsScorer.getRandomVectorScorer(similarityFunction, copy, target); + return new VectorScorer() { + @Override + public float score() throws IOException { + return scorer.score(copy.disi.index()); + } + + @Override + public DocIdSetIterator iterator() { + return copy; + } + }; + } + } + + private static class EmptyOffHeapVectorValues extends OffHeapBinarizedVectorValues { + private int doc = -1; + + EmptyOffHeapVectorValues(int dimension, VectorSimilarityFunction similarityFunction, FlatVectorsScorer vectorsScorer) { + super(dimension, 0, null, Float.NaN, null, similarityFunction, vectorsScorer, null); + } + + @Override + public int docID() { + return doc; + } + + @Override + public int nextDoc() { + return advance(doc + 1); + } + + @Override + public int advance(int target) { + return doc = NO_MORE_DOCS; + } + + @Override + public byte[] vectorValue() { + throw new UnsupportedOperationException(); + } + + @Override + public DenseOffHeapVectorValues copy() { + throw new UnsupportedOperationException(); + } + + @Override + public Bits getAcceptOrds(Bits acceptDocs) { + return null; + } + + @Override + public VectorScorer scorer(float[] target) throws IOException { + return null; + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/RandomAccessBinarizedByteVectorValues.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/RandomAccessBinarizedByteVectorValues.java new file mode 100644 index 0000000000000..2417353373ba5 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/RandomAccessBinarizedByteVectorValues.java @@ -0,0 +1,70 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.util.VectorUtil; +import org.apache.lucene.util.hnsw.RandomAccessVectorValues; + +import java.io.IOException; + +/** + * Copied from Lucene, replace with Lucene's implementation sometime after Lucene 10 + */ +public interface RandomAccessBinarizedByteVectorValues extends RandomAccessVectorValues.Bytes { + /** Returns the centroid distance for the vector */ + float getCentroidDistance(int vectorOrd) throws IOException; + + /** Returns the vector magnitude for the vector */ + float getVectorMagnitude(int vectorOrd) throws IOException; + + /** Returns OOQ corrective factor for the given vector ordinal */ + float getOOQ(int targetOrd) throws IOException; + + /** + * Returns the norm of the target vector w the centroid corrective factor for the given vector + * ordinal + */ + float getNormOC(int targetOrd) throws IOException; + + /** + * Returns the target vector dot product the centroid corrective factor for the given vector + * ordinal + */ + float getODotC(int targetOrd) throws IOException; + + /** + * @return the quantizer used to quantize the vectors + */ + BinaryQuantizer getQuantizer(); + + /** + * @return coarse grained centroids for the vectors + */ + float[] getCentroid() throws IOException; + + @Override + RandomAccessBinarizedByteVectorValues copy() throws IOException; + + default float getCentroidDP() throws IOException { + // this only gets executed on-merge + float[] centroid = getCentroid(); + return VectorUtil.dotProduct(centroid, centroid); + } +} diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java index 19bd4f9980baf..0ff754d953934 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java @@ -799,17 +799,30 @@ private static void parseNonDynamicArray( String fullPath = context.path().pathAsText(arrayFieldName); // Check if we need to record the array source. This only applies to synthetic source. + boolean canRemoveSingleLeafElement = false; if (context.canAddIgnoredField()) { - boolean objectRequiresStoringSource = mapper instanceof ObjectMapper objectMapper - && (getSourceKeepMode(context, objectMapper.sourceKeepMode()) == Mapper.SourceKeepMode.ALL - || (getSourceKeepMode(context, objectMapper.sourceKeepMode()) == Mapper.SourceKeepMode.ARRAYS - && objectMapper instanceof NestedObjectMapper == false)); - boolean fieldWithFallbackSyntheticSource = mapper instanceof FieldMapper fieldMapper - && fieldMapper.syntheticSourceMode() == FieldMapper.SyntheticSourceMode.FALLBACK; - boolean fieldWithStoredArraySource = mapper instanceof FieldMapper fieldMapper - && getSourceKeepMode(context, fieldMapper.sourceKeepMode()) != Mapper.SourceKeepMode.NONE; + Mapper.SourceKeepMode mode = Mapper.SourceKeepMode.NONE; + boolean objectWithFallbackSyntheticSource = false; + if (mapper instanceof ObjectMapper objectMapper) { + mode = getSourceKeepMode(context, objectMapper.sourceKeepMode()); + objectWithFallbackSyntheticSource = (mode == Mapper.SourceKeepMode.ALL + || (mode == Mapper.SourceKeepMode.ARRAYS && objectMapper instanceof NestedObjectMapper == false)); + } + boolean fieldWithFallbackSyntheticSource = false; + boolean fieldWithStoredArraySource = false; + if (mapper instanceof FieldMapper fieldMapper) { + mode = getSourceKeepMode(context, fieldMapper.sourceKeepMode()); + fieldWithFallbackSyntheticSource = fieldMapper.syntheticSourceMode() == FieldMapper.SyntheticSourceMode.FALLBACK; + fieldWithStoredArraySource = mode != Mapper.SourceKeepMode.NONE; + } boolean copyToFieldHasValuesInDocument = context.isWithinCopyTo() == false && context.isCopyToDestinationField(fullPath); - if (objectRequiresStoringSource + + canRemoveSingleLeafElement = mapper instanceof FieldMapper + && mode == Mapper.SourceKeepMode.ARRAYS + && fieldWithFallbackSyntheticSource == false + && copyToFieldHasValuesInDocument == false; + + if (objectWithFallbackSyntheticSource || fieldWithFallbackSyntheticSource || fieldWithStoredArraySource || copyToFieldHasValuesInDocument) { @@ -829,20 +842,28 @@ private static void parseNonDynamicArray( XContentParser parser = context.parser(); XContentParser.Token token; + int elements = 0; while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { if (token == XContentParser.Token.START_OBJECT) { + elements = Integer.MAX_VALUE; parseObject(context, lastFieldName); } else if (token == XContentParser.Token.START_ARRAY) { + elements = Integer.MAX_VALUE; parseArray(context, lastFieldName); } else if (token == XContentParser.Token.VALUE_NULL) { + elements++; parseNullValue(context, lastFieldName); } else if (token == null) { throwEOFOnParseArray(arrayFieldName, context); } else { assert token.isValue(); + elements++; parseValue(context, lastFieldName); } } + if (elements <= 1 && canRemoveSingleLeafElement) { + context.removeLastIgnoredField(fullPath); + } postProcessDynamicArrayMapping(context, lastFieldName); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java index eebe95e260dcf..ac236e5a7e5fd 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParserContext.java @@ -296,6 +296,12 @@ public final void addIgnoredField(IgnoredSourceFieldMapper.NameValue values) { } } + final void removeLastIgnoredField(String name) { + if (ignoredFieldValues.isEmpty() == false && ignoredFieldValues.getLast().name().equals(name)) { + ignoredFieldValues.removeLast(); + } + } + /** * Return the collection of values for fields that have been ignored so far. */ diff --git a/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java b/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java index 59394c0a8e6f9..5eaf79ad42bde 100644 --- a/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java +++ b/server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java @@ -199,6 +199,9 @@ public Query newPrefixQuery(String text) { if (disjuncts.size() == 1) { return disjuncts.get(0); } + if (disjuncts.size() == 0) { + return null; + } return new DisjunctionMaxQuery(disjuncts, 1.0f); } diff --git a/server/src/main/java/org/elasticsearch/index/snapshots/IndexShardSnapshotStatus.java b/server/src/main/java/org/elasticsearch/index/snapshots/IndexShardSnapshotStatus.java index b5607c31641d3..70ba9950f7689 100644 --- a/server/src/main/java/org/elasticsearch/index/snapshots/IndexShardSnapshotStatus.java +++ b/server/src/main/java/org/elasticsearch/index/snapshots/IndexShardSnapshotStatus.java @@ -184,6 +184,10 @@ public synchronized void moveToDone(final long endTime, final ShardSnapshotResul } } + public Stage getStage() { + return stage.get(); + } + public void addAbortListener(ActionListener listener) { abortListeners.addListener(listener); } @@ -429,4 +433,31 @@ public String toString() { + ')'; } } + + @Override + public String toString() { + return "index shard snapshot status (" + + "stage=" + + stage + + ", startTime=" + + startTime + + ", totalTime=" + + totalTime + + ", incrementalFileCount=" + + incrementalFileCount + + ", totalFileCount=" + + totalFileCount + + ", processedFileCount=" + + processedFileCount + + ", incrementalSize=" + + incrementalSize + + ", totalSize=" + + totalSize + + ", processedSize=" + + processedSize + + ", failure='" + + failure + + '\'' + + ')'; + } } diff --git a/server/src/main/java/org/elasticsearch/ingest/CompoundProcessor.java b/server/src/main/java/org/elasticsearch/ingest/CompoundProcessor.java index 9620becd49d59..873f334d0a650 100644 --- a/server/src/main/java/org/elasticsearch/ingest/CompoundProcessor.java +++ b/server/src/main/java/org/elasticsearch/ingest/CompoundProcessor.java @@ -148,7 +148,7 @@ public void execute(IngestDocument ingestDocument, BiConsumer handler) { assert currentProcessor <= processorsWithMetrics.size(); - if (currentProcessor == processorsWithMetrics.size() || ingestDocument.isReroute()) { + if (currentProcessor == processorsWithMetrics.size() || ingestDocument.isReroute() || ingestDocument.isTerminate()) { handler.accept(ingestDocument, null); return; } @@ -159,7 +159,8 @@ void innerExecute(int currentProcessor, IngestDocument ingestDocument, final BiC // iteratively execute any sync processors while (currentProcessor < processorsWithMetrics.size() && processorsWithMetrics.get(currentProcessor).v1().isAsync() == false - && ingestDocument.isReroute() == false) { + && ingestDocument.isReroute() == false + && ingestDocument.isTerminate() == false) { processorWithMetric = processorsWithMetrics.get(currentProcessor); processor = processorWithMetric.v1(); metric = processorWithMetric.v2(); @@ -185,7 +186,7 @@ void innerExecute(int currentProcessor, IngestDocument ingestDocument, final BiC } assert currentProcessor <= processorsWithMetrics.size(); - if (currentProcessor == processorsWithMetrics.size() || ingestDocument.isReroute()) { + if (currentProcessor == processorsWithMetrics.size() || ingestDocument.isReroute() || ingestDocument.isTerminate()) { handler.accept(ingestDocument, null); return; } diff --git a/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java b/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java index 0bc1c0d2932df..280c7684a8553 100644 --- a/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java +++ b/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java @@ -82,6 +82,7 @@ public final class IngestDocument { private boolean doNoSelfReferencesCheck = false; private boolean reroute = false; + private boolean terminate = false; public IngestDocument(String index, String id, long version, String routing, VersionType versionType, Map source) { this.ctxMap = new IngestCtxMap(index, id, version, routing, versionType, ZonedDateTime.now(ZoneOffset.UTC), source); @@ -935,6 +936,27 @@ void resetReroute() { reroute = false; } + /** + * Sets the terminate flag to true, to indicate that no further processors in the current pipeline should be run for this document. + */ + public void terminate() { + terminate = true; + } + + /** + * Returns whether the {@link #terminate()} flag was set. + */ + boolean isTerminate() { + return terminate; + } + + /** + * Resets the {@link #terminate()} flag. + */ + void resetTerminate() { + terminate = false; + } + public enum Metadata { INDEX(IndexFieldMapper.NAME), TYPE("_type"), diff --git a/server/src/main/java/org/elasticsearch/ingest/Pipeline.java b/server/src/main/java/org/elasticsearch/ingest/Pipeline.java index 6153d45bce779..a8e8fbb5d3217 100644 --- a/server/src/main/java/org/elasticsearch/ingest/Pipeline.java +++ b/server/src/main/java/org/elasticsearch/ingest/Pipeline.java @@ -133,6 +133,9 @@ public void execute(IngestDocument ingestDocument, BiConsumer registerAsyncMetrics(MeterRegistry registry, metrics.add( registry.registerLongGauge( "es.indices." + name + ".query.total", - "total queries of " + name + " indices", + "current queries of " + name + " indices", "unit", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).search.getQueryCount()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).search.getQueryCount()) ) ); metrics.add( registry.registerLongGauge( "es.indices." + name + ".query.time", - "total query time of " + name + " indices", + "current query time of " + name + " indices", "ms", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).search.getQueryTimeInMillis()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).search.getQueryTimeInMillis()) ) ); metrics.add( registry.registerLongGauge( "es.indices." + name + ".query.failure.total", - "total query failures of " + name + " indices", + "current query failures of " + name + " indices", "unit", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).search.getQueryFailure()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).search.getQueryFailure()) ) ); // fetch (count, took, failures) - use gauges as shards can be removed metrics.add( registry.registerLongGauge( "es.indices." + name + ".fetch.total", - "total fetches of " + name + " indices", + "current fetches of " + name + " indices", "unit", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).search.getFetchCount()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).search.getFetchCount()) ) ); metrics.add( registry.registerLongGauge( "es.indices." + name + ".fetch.time", - "total fetch time of " + name + " indices", + "current fetch time of " + name + " indices", "ms", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).search.getFetchTimeInMillis()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).search.getFetchTimeInMillis()) ) ); metrics.add( registry.registerLongGauge( "es.indices." + name + ".fetch.failure.total", - "total fetch failures of " + name + " indices", + "current fetch failures of " + name + " indices", "unit", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).search.getFetchFailure()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).search.getFetchFailure()) ) ); // indexing metrics.add( registry.registerLongGauge( "es.indices." + name + ".indexing.total", - "total indexing operations of " + name + " indices", + "current indexing operations of " + name + " indices", "unit", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).indexing.getIndexCount()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).indexing.getIndexCount()) ) ); metrics.add( registry.registerLongGauge( "es.indices." + name + ".indexing.time", - "total indexing time of " + name + " indices", + "current indexing time of " + name + " indices", "ms", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).indexing.getIndexTime().millis()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).indexing.getIndexTime().millis()) ) ); metrics.add( registry.registerLongGauge( "es.indices." + name + ".indexing.failure.total", - "total indexing failures of " + name + " indices", + "current indexing failures of " + name + " indices", "unit", - () -> new LongWithAttributes(cache.getOrRefresh().get(indexMode).indexing.getIndexFailedCount()) + diffGauge(() -> cache.getOrRefresh().get(indexMode).indexing.getIndexFailedCount()) ) ); } @@ -160,6 +163,15 @@ private static List registerAsyncMetrics(MeterRegistry registry, return metrics; } + static Supplier diffGauge(Supplier currentValue) { + final AtomicLong counter = new AtomicLong(); + return () -> { + var curr = currentValue.get(); + long prev = counter.getAndUpdate(v -> Math.max(curr, v)); + return new LongWithAttributes(Math.max(0, curr - prev)); + }; + } + @Override protected void doStart() { metrics.addAll(registerAsyncMetrics(registry, stateCache)); @@ -218,22 +230,19 @@ private Map internalGetIndicesStats() { continue; // skip system indices } final ShardRouting shardRouting = indexShard.routingEntry(); - if (shardRouting.primary() == false) { - continue; // count primaries only - } - if (shardRouting.recoverySource() != null) { - continue; // exclude relocating shards - } final IndexMode indexMode = indexShard.indexSettings().getMode(); final IndexStats indexStats = stats.get(indexMode); - if (shardRouting.shardId().id() == 0) { - indexStats.numIndices++; - } try { - indexStats.numDocs += indexShard.commitStats().getNumDocs(); - indexStats.numBytes += indexShard.storeStats().sizeInBytes(); + if (shardRouting.primary() && shardRouting.recoverySource() == null) { + if (shardRouting.shardId().id() == 0) { + indexStats.numIndices++; + } + final DocsStats docStats = indexShard.docStats(); + indexStats.numDocs += docStats.getCount(); + indexStats.numBytes += docStats.getTotalSizeInBytes(); + indexStats.indexing.add(indexShard.indexingStats().getTotal()); + } indexStats.search.add(indexShard.searchStats().getTotal()); - indexStats.indexing.add(indexShard.indexingStats().getTotal()); } catch (IllegalIndexShardStateException | AlreadyClosedException ignored) { // ignored } diff --git a/server/src/main/java/org/elasticsearch/rest/action/ingest/RestSimulateIngestAction.java b/server/src/main/java/org/elasticsearch/rest/action/ingest/RestSimulateIngestAction.java index 6de15b0046f1b..680860332fe74 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/ingest/RestSimulateIngestAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/ingest/RestSimulateIngestAction.java @@ -76,7 +76,8 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC Map sourceMap = XContentHelper.convertToMap(sourceTuple.v2(), false, sourceTuple.v1()).v2(); SimulateBulkRequest bulkRequest = new SimulateBulkRequest( (Map>) sourceMap.remove("pipeline_substitutions"), - (Map>) sourceMap.remove("component_template_substitutions") + (Map>) sourceMap.remove("component_template_substitutions"), + (Map>) sourceMap.remove("index_template_substitutions") ); BytesReference transformedData = convertToBulkRequestXContentBytes(sourceMap); bulkRequest.add( diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java index abc5f36eef7da..7b2066f243771 100644 --- a/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java @@ -21,6 +21,8 @@ import org.elasticsearch.cluster.SnapshotsInProgress; import org.elasticsearch.cluster.SnapshotsInProgress.ShardSnapshotStatus; import org.elasticsearch.cluster.SnapshotsInProgress.ShardState; +import org.elasticsearch.cluster.metadata.NodesShutdownMetadata; +import org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; @@ -82,6 +84,8 @@ public final class SnapshotShardsService extends AbstractLifecycleComponent impl private final ThreadPool threadPool; + private final SnapshotShutdownProgressTracker snapshotShutdownProgressTracker; + private final Map> shardSnapshots = new HashMap<>(); // A map of snapshots to the shardIds that we already reported to the master as failed @@ -102,6 +106,11 @@ public SnapshotShardsService( this.transportService = transportService; this.clusterService = clusterService; this.threadPool = transportService.getThreadPool(); + this.snapshotShutdownProgressTracker = new SnapshotShutdownProgressTracker( + () -> clusterService.state().nodes().getLocalNodeId(), + clusterService.getClusterSettings(), + threadPool + ); this.remoteFailedRequestDeduplicator = new ResultDeduplicator<>(threadPool.getThreadContext()); if (DiscoveryNode.canContainData(settings)) { // this is only useful on the nodes that can hold data @@ -130,11 +139,38 @@ protected void doClose() { @Override public void clusterChanged(ClusterChangedEvent event) { try { + final var localNodeId = clusterService.localNode().getId(); + + // Track when this node enters and leaves shutdown mode because we pause shard snapshots for shutdown. + // The snapshotShutdownProgressTracker will report (via logging) on the progress shard snapshots make + // towards either completing (successfully or otherwise) or pausing. + NodesShutdownMetadata currentShutdownMetadata = event.state().metadata().custom(NodesShutdownMetadata.TYPE); + NodesShutdownMetadata previousShutdownMetadata = event.previousState().metadata().custom(NodesShutdownMetadata.TYPE); + SingleNodeShutdownMetadata currentLocalNodeShutdownMetadata = currentShutdownMetadata != null + ? currentShutdownMetadata.get(localNodeId) + : null; + SingleNodeShutdownMetadata previousLocalNodeShutdownMetadata = previousShutdownMetadata != null + ? previousShutdownMetadata.get(localNodeId) + : null; + + boolean isLocalNodeAddingShutdown = false; + if (isPausingProgressTrackedShutdown(previousLocalNodeShutdownMetadata) == false + && isPausingProgressTrackedShutdown(currentLocalNodeShutdownMetadata)) { + snapshotShutdownProgressTracker.onClusterStateAddShutdown(); + isLocalNodeAddingShutdown = true; + } else if (isPausingProgressTrackedShutdown(previousLocalNodeShutdownMetadata) + && isPausingProgressTrackedShutdown(currentLocalNodeShutdownMetadata) == false) { + snapshotShutdownProgressTracker.onClusterStateRemoveShutdown(); + } + final var currentSnapshots = SnapshotsInProgress.get(event.state()); + if (SnapshotsInProgress.get(event.previousState()).equals(currentSnapshots) == false) { - final var localNodeId = clusterService.localNode().getId(); synchronized (shardSnapshots) { + // Cancel any snapshots that have been removed from the cluster state. cancelRemoved(currentSnapshots); + + // Update running snapshots or start any snapshots that are set to run. for (final var oneRepoSnapshotsInProgress : currentSnapshots.entriesByRepo()) { for (final var snapshotsInProgressEntry : oneRepoSnapshotsInProgress) { handleUpdatedSnapshotsInProgressEntry( @@ -147,6 +183,11 @@ public void clusterChanged(ClusterChangedEvent event) { } } + if (isLocalNodeAddingShutdown) { + // Any active snapshots would have been signalled to pause in the previous code block. + snapshotShutdownProgressTracker.onClusterStatePausingSetForAllShardSnapshots(); + } + String previousMasterNodeId = event.previousState().nodes().getMasterNodeId(); String currentMasterNodeId = event.state().nodes().getMasterNodeId(); if (currentMasterNodeId != null && currentMasterNodeId.equals(previousMasterNodeId) == false) { @@ -164,6 +205,17 @@ public void clusterChanged(ClusterChangedEvent event) { } } + /** + * Determines whether we want to track this kind of shutdown for snapshot pausing progress. + * We want tracking is shutdown metadata is set, and not type RESTART. + * Note that the Shutdown API is idempotent and the type of shutdown may change to / from RESTART to / from some other type of interest. + * + * @return true if snapshots will be paused during this type of local node shutdown. + */ + private static boolean isPausingProgressTrackedShutdown(@Nullable SingleNodeShutdownMetadata localNodeShutdownMetadata) { + return localNodeShutdownMetadata != null && localNodeShutdownMetadata.getType() != SingleNodeShutdownMetadata.Type.RESTART; + } + @Override public void beforeIndexShardClosed(ShardId shardId, @Nullable IndexShard indexShard, Settings indexSettings) { // abort any snapshots occurring on the soon-to-be closed shard @@ -231,6 +283,9 @@ private void cancelRemoved(SnapshotsInProgress snapshotsInProgress) { } } + /** + * Starts new snapshots and pauses or aborts active shard snapshot based on the updated {@link SnapshotsInProgress} entry. + */ private void handleUpdatedSnapshotsInProgressEntry(String localNodeId, boolean removingLocalNode, SnapshotsInProgress.Entry entry) { if (entry.isClone()) { // This is a snapshot clone, it will be executed on the current master @@ -364,8 +419,7 @@ private Runnable newShardSnapshotTask( final IndexVersion entryVersion, final long entryStartTime ) { - // separate method to make sure this lambda doesn't capture any heavy local objects like a SnapshotsInProgress.Entry - return () -> snapshot(shardId, snapshot, indexId, snapshotStatus, entryVersion, entryStartTime, new ActionListener<>() { + ActionListener snapshotResultListener = new ActionListener<>() { @Override public void onResponse(ShardSnapshotResult shardSnapshotResult) { final ShardGeneration newGeneration = shardSnapshotResult.getGeneration(); @@ -405,7 +459,15 @@ public void onFailure(Exception e) { final var shardState = snapshotStatus.moveToUnsuccessful(nextStage, failure, threadPool.absoluteTimeInMillis()); notifyUnsuccessfulSnapshotShard(snapshot, shardId, shardState, failure, snapshotStatus.generation()); } + }; + + snapshotShutdownProgressTracker.incNumberOfShardSnapshotsInProgress(shardId, snapshot); + var decTrackerRunsBeforeResultListener = ActionListener.runAfter(snapshotResultListener, () -> { + snapshotShutdownProgressTracker.decNumberOfShardSnapshotsInProgress(shardId, snapshot, snapshotStatus); }); + + // separate method to make sure this lambda doesn't capture any heavy local objects like a SnapshotsInProgress.Entry + return () -> snapshot(shardId, snapshot, indexId, snapshotStatus, entryVersion, entryStartTime, decTrackerRunsBeforeResultListener); } // package private for testing @@ -665,19 +727,25 @@ private void notifyUnsuccessfulSnapshotShard( /** Updates the shard snapshot status by sending a {@link UpdateIndexShardSnapshotStatusRequest} to the master node */ private void sendSnapshotShardUpdate(final Snapshot snapshot, final ShardId shardId, final ShardSnapshotStatus status) { + ActionListener updateResultListener = new ActionListener<>() { + @Override + public void onResponse(Void aVoid) { + logger.trace("[{}][{}] updated snapshot state to [{}]", shardId, snapshot, status); + } + + @Override + public void onFailure(Exception e) { + logger.warn(() -> format("[%s][%s] failed to update snapshot state to [%s]", shardId, snapshot, status), e); + } + }; + snapshotShutdownProgressTracker.trackRequestSentToMaster(snapshot, shardId); + var releaseTrackerRequestRunsBeforeResultListener = ActionListener.runBefore(updateResultListener, () -> { + snapshotShutdownProgressTracker.releaseRequestSentToMaster(snapshot, shardId); + }); + remoteFailedRequestDeduplicator.executeOnce( new UpdateIndexShardSnapshotStatusRequest(snapshot, shardId, status), - new ActionListener<>() { - @Override - public void onResponse(Void aVoid) { - logger.trace("[{}][{}] updated snapshot state to [{}]", shardId, snapshot, status); - } - - @Override - public void onFailure(Exception e) { - logger.warn(() -> format("[%s][%s] failed to update snapshot state to [%s]", shardId, snapshot, status), e); - } - }, + releaseTrackerRequestRunsBeforeResultListener, (req, reqListener) -> transportService.sendRequest( transportService.getLocalNode(), SnapshotsService.UPDATE_SNAPSHOT_STATUS_ACTION_NAME, diff --git a/server/src/main/java/org/elasticsearch/snapshots/SnapshotShutdownProgressTracker.java b/server/src/main/java/org/elasticsearch/snapshots/SnapshotShutdownProgressTracker.java new file mode 100644 index 0000000000000..5d81e3c4e46af --- /dev/null +++ b/server/src/main/java/org/elasticsearch/snapshots/SnapshotShutdownProgressTracker.java @@ -0,0 +1,270 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.snapshots; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ResultDeduplicator; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.util.concurrent.ConcurrentCollections; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.index.snapshots.IndexShardSnapshotStatus; +import org.elasticsearch.threadpool.Scheduler; +import org.elasticsearch.threadpool.ThreadPool; + +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; + +/** + * Tracks progress of shard snapshots during shutdown, on this single data node. Periodically reports progress via logging, the interval for + * which see {@link #SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING}. + */ +public class SnapshotShutdownProgressTracker { + + /** How frequently shard snapshot progress is logged after receiving local node shutdown metadata. */ + public static final Setting SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING = Setting.timeSetting( + "snapshots.shutdown.progress.interval", + TimeValue.timeValueSeconds(5), + TimeValue.MINUS_ONE, + Setting.Property.NodeScope, + Setting.Property.Dynamic + ); + + private static final Logger logger = LogManager.getLogger(SnapshotShutdownProgressTracker.class); + + private final Supplier getLocalNodeId; + private final ThreadPool threadPool; + + private volatile TimeValue progressLoggerInterval; + private Scheduler.Cancellable scheduledProgressLoggerFuture; + + /** + * The time at which the cluster state update began that found a shutdown signal for this node. Negative value means unset (node is not + * shutting down). + */ + private volatile long shutdownStartMillis = -1; + + /** + * The time at which the cluster state finished setting shard snapshot states to PAUSING, which the shard snapshot operations will + * discover asynchronously. Negative value means unset (node is not shutting down) + */ + private volatile long shutdownFinishedSignallingPausingMillis = -1; + + /** + * Tracks the number of shard snapshots that have started on the data node but not yet finished. + */ + private final AtomicLong numberOfShardSnapshotsInProgressOnDataNode = new AtomicLong(); + + /** + * The logic to track shard snapshot status update requests to master can result in duplicate requests (see + * {@link ResultDeduplicator}), as well as resending requests if the elected master changes. + * Tracking specific requests uniquely by snapshot ID + shard ID de-duplicates requests for tracking. + * Also tracks the absolute start time of registration, to report duration on de-registration. + */ + private final Map shardSnapshotRequests = ConcurrentCollections.newConcurrentMap(); + + /** + * Track how the shard snapshots reach completion during shutdown: did they fail, succeed or pause? + */ + private final AtomicLong doneCount = new AtomicLong(); + private final AtomicLong failureCount = new AtomicLong(); + private final AtomicLong abortedCount = new AtomicLong(); + private final AtomicLong pausedCount = new AtomicLong(); + + public SnapshotShutdownProgressTracker(Supplier localNodeIdSupplier, ClusterSettings clusterSettings, ThreadPool threadPool) { + this.getLocalNodeId = localNodeIdSupplier; + clusterSettings.initializeAndWatch( + SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING, + value -> this.progressLoggerInterval = value + ); + this.threadPool = threadPool; + } + + private void scheduleProgressLogger() { + if (progressLoggerInterval.millis() > 0) { + scheduledProgressLoggerFuture = threadPool.scheduleWithFixedDelay( + this::logProgressReport, + progressLoggerInterval, + threadPool.executor(ThreadPool.Names.GENERIC) + ); + logger.debug( + () -> Strings.format( + "Starting shutdown snapshot progress logging on node [%s], runs every [%s]", + getLocalNodeId.get(), + progressLoggerInterval + ) + ); + } else { + logger.debug("Snapshot progress logging during shutdown is disabled"); + } + } + + private void cancelProgressLogger() { + assert scheduledProgressLoggerFuture != null : "Somehow shutdown mode was removed before it was added."; + scheduledProgressLoggerFuture.cancel(); + if (progressLoggerInterval.millis() > 0) { + // Only log cancellation if it was most likely started. Theoretically the interval setting could be updated during shutdown, + // such that the progress logger is already running and ignores the new value, but that does not currently happen. + logger.debug(() -> Strings.format("Cancelling shutdown snapshot progress logging on node [%s]", getLocalNodeId.get())); + } + } + + /** + * Logs some statistics about shard snapshot progress. + */ + private void logProgressReport() { + logger.info( + """ + Current active shard snapshot stats on data node [{}]. \ + Node shutdown cluster state update received at [{}]. \ + Finished signalling shard snapshots to pause at [{}]. \ + Number shard snapshots running [{}]. \ + Number shard snapshots waiting for master node reply to status update request [{}] \ + Shard snapshot completion stats since shutdown began: Done [{}]; Failed [{}]; Aborted [{}]; Paused [{}]\ + """, + getLocalNodeId.get(), + shutdownStartMillis, + shutdownFinishedSignallingPausingMillis, + numberOfShardSnapshotsInProgressOnDataNode.get(), + shardSnapshotRequests.size(), + doneCount.get(), + failureCount.get(), + abortedCount.get(), + pausedCount.get() + ); + } + + /** + * Called as soon as a node shutdown signal is received. + */ + public void onClusterStateAddShutdown() { + assert this.shutdownStartMillis == -1 : "Expected not to be tracking anything. Call shutdown remove before adding shutdown again"; + + // Reset these values when a new shutdown occurs, to minimize/eliminate chances of racing if shutdown is later removed and async + // shard snapshots updates continue to occur. + doneCount.set(0); + failureCount.set(0); + abortedCount.set(0); + pausedCount.set(0); + + // Track the timestamp of shutdown signal, on which to base periodic progress logging. + this.shutdownStartMillis = threadPool.relativeTimeInMillis(); + + // Start logging periodic progress reports. + scheduleProgressLogger(); + } + + /** + * Called when the cluster state update processing a shutdown signal has finished signalling (setting PAUSING) all shard snapshots to + * pause. + */ + public void onClusterStatePausingSetForAllShardSnapshots() { + assert this.shutdownStartMillis != -1 + : "Should not have left shutdown mode before finishing processing the cluster state update with shutdown"; + this.shutdownFinishedSignallingPausingMillis = threadPool.relativeTimeInMillis(); + logger.debug(() -> Strings.format("Pause signals have been set for all shard snapshots on data node [%s]", getLocalNodeId.get())); + } + + /** + * The cluster state indicating that a node is to be shutdown may be cleared instead of following through with node shutdown. In that + * case, no further shutdown shard snapshot progress reporting is desired. + */ + public void onClusterStateRemoveShutdown() { + assert shutdownStartMillis != -1 : "Expected a call to add shutdown mode before a call to remove shutdown mode."; + + // Reset the shutdown specific trackers. + this.shutdownStartMillis = -1; + this.shutdownFinishedSignallingPausingMillis = -1; + + // Turn off the progress logger, which we only want to run during shutdown. + cancelProgressLogger(); + } + + /** + * Tracks how many shard snapshots are started. + */ + public void incNumberOfShardSnapshotsInProgress(ShardId shardId, Snapshot snapshot) { + logger.debug(() -> Strings.format("Started shard (shard ID: [%s]) in snapshot ([%s])", shardId, snapshot)); + numberOfShardSnapshotsInProgressOnDataNode.incrementAndGet(); + } + + /** + * Tracks how many shard snapshots have finished since shutdown mode began. + */ + public void decNumberOfShardSnapshotsInProgress(ShardId shardId, Snapshot snapshot, IndexShardSnapshotStatus shardSnapshotStatus) { + logger.debug( + () -> Strings.format( + "Finished shard (shard ID: [%s]) in snapshot ([%s]) with status ([%s]): ", + shardId, + snapshot, + shardSnapshotStatus.toString() + ) + ); + + numberOfShardSnapshotsInProgressOnDataNode.decrementAndGet(); + if (shutdownStartMillis != -1) { + switch (shardSnapshotStatus.getStage()) { + case DONE -> doneCount.incrementAndGet(); + case FAILURE -> failureCount.incrementAndGet(); + case ABORTED -> abortedCount.incrementAndGet(); + case PAUSED -> pausedCount.incrementAndGet(); + // The other stages are active, we should only see the end result because this method is called upon completion. + default -> { + assert false : "unexpected shard snapshot stage transition during shutdown: " + shardSnapshotStatus.getStage(); + } + } + } + } + + /** + * Uniquely tracks a request to update a shard snapshot status sent to the master node. Idempotent, safe to call multiple times. + * + * @param snapshot first part of a unique tracking identifier + * @param shardId second part of a unique tracking identifier + */ + public void trackRequestSentToMaster(Snapshot snapshot, ShardId shardId) { + logger.debug(() -> Strings.format("Tracking shard (shard ID: [%s]) snapshot ([%s]) request to master", shardId, snapshot)); + shardSnapshotRequests.put(snapshot.toString() + shardId.getIndexName() + shardId.getId(), threadPool.relativeTimeInNanos()); + } + + /** + * Stops tracking a request to update a shard snapshot status sent to the master node. Idempotent, safe to call multiple times. + * + * @param snapshot first part of a unique tracking identifier + * @param shardId second part of a unique tracking identifier + */ + public void releaseRequestSentToMaster(Snapshot snapshot, ShardId shardId) { + var masterRequestStartTime = shardSnapshotRequests.remove(snapshot.toString() + shardId.getIndexName() + shardId.getId()); + // This method is may be called multiple times. Only log if this is the first time, and the entry hasn't already been removed. + if (masterRequestStartTime != null) { + logger.debug( + () -> Strings.format( + "Finished shard (shard ID: [%s]) snapshot ([%s]) update request to master in [%s]", + shardId, + snapshot, + new TimeValue(threadPool.relativeTimeInNanos() - masterRequestStartTime.longValue(), TimeUnit.NANOSECONDS) + ) + ); + } + } + + // Test only + void assertStatsForTesting(long done, long failure, long aborted, long paused) { + assert doneCount.get() == done : "doneCount is " + doneCount.get() + ", expected count was " + done; + assert failureCount.get() == failure : "failureCount is " + doneCount.get() + ", expected count was " + failure; + assert abortedCount.get() == aborted : "abortedCount is " + doneCount.get() + ", expected count was " + aborted; + assert pausedCount.get() == paused : "pausedCount is " + doneCount.get() + ", expected count was " + paused; + } +} diff --git a/server/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat b/server/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat index da2a0c4b90f30..c2201f5b1c319 100644 --- a/server/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat +++ b/server/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat @@ -3,3 +3,5 @@ org.elasticsearch.index.codec.vectors.ES813Int8FlatVectorFormat org.elasticsearch.index.codec.vectors.ES814HnswScalarQuantizedVectorsFormat org.elasticsearch.index.codec.vectors.ES815HnswBitVectorsFormat org.elasticsearch.index.codec.vectors.ES815BitFlatVectorFormat +org.elasticsearch.index.codec.vectors.ES816BinaryQuantizedVectorsFormat +org.elasticsearch.index.codec.vectors.ES816HnswBinaryQuantizedVectorsFormat diff --git a/server/src/test/java/org/elasticsearch/action/bulk/SimulateBulkRequestTests.java b/server/src/test/java/org/elasticsearch/action/bulk/SimulateBulkRequestTests.java index b6b1770e2ed5c..c94e4e46c9ee3 100644 --- a/server/src/test/java/org/elasticsearch/action/bulk/SimulateBulkRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/bulk/SimulateBulkRequestTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.cluster.metadata.ComponentTemplate; +import org.elasticsearch.cluster.metadata.ComposableIndexTemplate; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.test.ESTestCase; @@ -27,18 +28,27 @@ public class SimulateBulkRequestTests extends ESTestCase { public void testSerialization() throws Exception { - testSerialization(getTestPipelineSubstitutions(), getTestTemplateSubstitutions()); - testSerialization(getTestPipelineSubstitutions(), null); - testSerialization(null, getTestTemplateSubstitutions()); - testSerialization(null, null); - testSerialization(Map.of(), Map.of()); + testSerialization(getTestPipelineSubstitutions(), getTestComponentTemplateSubstitutions(), getTestIndexTemplateSubstitutions()); + testSerialization(getTestPipelineSubstitutions(), null, null); + testSerialization(getTestPipelineSubstitutions(), getTestComponentTemplateSubstitutions(), null); + testSerialization(getTestPipelineSubstitutions(), null, getTestIndexTemplateSubstitutions()); + testSerialization(null, getTestComponentTemplateSubstitutions(), getTestIndexTemplateSubstitutions()); + testSerialization(null, getTestComponentTemplateSubstitutions(), null); + testSerialization(null, null, getTestIndexTemplateSubstitutions()); + testSerialization(null, null, null); + testSerialization(Map.of(), Map.of(), Map.of()); } private void testSerialization( Map> pipelineSubstitutions, - Map> templateSubstitutions + Map> componentTemplateSubstitutions, + Map> indexTemplateSubstitutions ) throws IOException { - SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(pipelineSubstitutions, templateSubstitutions); + SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest( + pipelineSubstitutions, + componentTemplateSubstitutions, + indexTemplateSubstitutions + ); /* * Note: SimulateBulkRequest does not implement equals or hashCode, so we can't test serialization in the usual way for a * Writable @@ -49,7 +59,7 @@ private void testSerialization( @SuppressWarnings({ "unchecked", "rawtypes" }) public void testGetComponentTemplateSubstitutions() throws IOException { - SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(Map.of(), Map.of()); + SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); assertThat(simulateBulkRequest.getComponentTemplateSubstitutions(), equalTo(Map.of())); String substituteComponentTemplatesString = """ { @@ -83,7 +93,7 @@ public void testGetComponentTemplateSubstitutions() throws IOException { XContentType.JSON ).v2(); Map> substituteComponentTemplates = (Map>) tempMap; - simulateBulkRequest = new SimulateBulkRequest(Map.of(), substituteComponentTemplates); + simulateBulkRequest = new SimulateBulkRequest(Map.of(), substituteComponentTemplates, Map.of()); Map componentTemplateSubstitutions = simulateBulkRequest.getComponentTemplateSubstitutions(); assertThat(componentTemplateSubstitutions.size(), equalTo(2)); assertThat( @@ -107,8 +117,70 @@ public void testGetComponentTemplateSubstitutions() throws IOException { ); } + public void testGetIndexTemplateSubstitutions() throws IOException { + SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), Map.of()); + assertThat(simulateBulkRequest.getIndexTemplateSubstitutions(), equalTo(Map.of())); + String substituteIndexTemplatesString = """ + { + "foo_template": { + "index_patterns": ["foo*"], + "composed_of": ["foo_mapping_template", "foo_settings_template"], + "template": { + "mappings": { + "dynamic": "true", + "properties": { + "foo": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "default_pipeline": "foo-pipeline" + } + } + } + }, + "bar_template": { + "index_patterns": ["bar*"], + "composed_of": ["bar_mapping_template", "bar_settings_template"] + } + } + """; + + @SuppressWarnings("unchecked") + Map> substituteIndexTemplates = (Map>) (Map) XContentHelper.convertToMap( + new BytesArray(substituteIndexTemplatesString.getBytes(StandardCharsets.UTF_8)), + randomBoolean(), + XContentType.JSON + ).v2(); + simulateBulkRequest = new SimulateBulkRequest(Map.of(), Map.of(), substituteIndexTemplates); + Map indexTemplateSubstitutions = simulateBulkRequest.getIndexTemplateSubstitutions(); + assertThat(indexTemplateSubstitutions.size(), equalTo(2)); + assertThat( + XContentHelper.convertToMap( + XContentHelper.toXContent(indexTemplateSubstitutions.get("foo_template").template(), XContentType.JSON, randomBoolean()), + randomBoolean(), + XContentType.JSON + ).v2(), + equalTo(substituteIndexTemplates.get("foo_template").get("template")) + ); + + assertThat(indexTemplateSubstitutions.get("foo_template").template().settings().size(), equalTo(1)); + assertThat( + indexTemplateSubstitutions.get("foo_template").template().settings().get("index.default_pipeline"), + equalTo("foo-pipeline") + ); + assertNull(indexTemplateSubstitutions.get("bar_template").template()); + assertNull(indexTemplateSubstitutions.get("bar_template").template()); + } + public void testShallowClone() throws IOException { - SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(getTestPipelineSubstitutions(), getTestTemplateSubstitutions()); + SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest( + getTestPipelineSubstitutions(), + getTestComponentTemplateSubstitutions(), + getTestIndexTemplateSubstitutions() + ); simulateBulkRequest.setRefreshPolicy(randomFrom(WriteRequest.RefreshPolicy.values())); simulateBulkRequest.waitForActiveShards(randomIntBetween(1, 10)); simulateBulkRequest.timeout(randomTimeValue()); @@ -144,7 +216,7 @@ private static Map> getTestPipelineSubstitutions() { ); } - private static Map> getTestTemplateSubstitutions() { + private static Map> getTestComponentTemplateSubstitutions() { return Map.of( "template1", Map.of( @@ -155,4 +227,25 @@ private static Map> getTestTemplateSubstitutions() { Map.of("template", Map.of("mappings", Map.of(), "settings", Map.of())) ); } + + private static Map> getTestIndexTemplateSubstitutions() { + return Map.of( + "template1", + Map.of( + "template", + Map.of( + "index_patterns", + List.of("foo*", "bar*"), + "composed_of", + List.of("template_1", "template_2"), + "mappings", + Map.of("_source", Map.of("enabled", false), "properties", Map.of()), + "settings", + Map.of() + ) + ), + "template2", + Map.of("template", Map.of("index_patterns", List.of("foo*", "bar*"), "mappings", Map.of(), "settings", Map.of())) + ); + } } diff --git a/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java b/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java index f4e53912d09a7..71bc31334920e 100644 --- a/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java @@ -135,7 +135,7 @@ public void tearDown() throws Exception { public void testIndexData() throws IOException { Task task = mock(Task.class); // unused - BulkRequest bulkRequest = new SimulateBulkRequest(null, null); + BulkRequest bulkRequest = new SimulateBulkRequest(null, null, null); int bulkItemCount = randomIntBetween(0, 200); for (int i = 0; i < bulkItemCount; i++) { Map source = Map.of(randomAlphaOfLength(10), randomAlphaOfLength(5)); @@ -218,7 +218,7 @@ public void testIndexDataWithValidation() throws IOException { * (7) An indexing request to a nonexistent index that matches no templates */ Task task = mock(Task.class); // unused - BulkRequest bulkRequest = new SimulateBulkRequest(null, null); + BulkRequest bulkRequest = new SimulateBulkRequest(null, null, null); int bulkItemCount = randomIntBetween(0, 200); Map indicesMap = new HashMap<>(); Map v1Templates = new HashMap<>(); diff --git a/server/src/test/java/org/elasticsearch/index/IndexingSlowLogTests.java b/server/src/test/java/org/elasticsearch/index/IndexingSlowLogTests.java index 03550ca7fc03f..753602e73a30a 100644 --- a/server/src/test/java/org/elasticsearch/index/IndexingSlowLogTests.java +++ b/server/src/test/java/org/elasticsearch/index/IndexingSlowLogTests.java @@ -58,18 +58,23 @@ public class IndexingSlowLogTests extends ESTestCase { static MockAppender appender; static Releasable appenderRelease; static Logger testLogger1 = LogManager.getLogger(IndexingSlowLog.INDEX_INDEXING_SLOWLOG_PREFIX + ".index"); + static Level origLogLevel = testLogger1.getLevel(); @BeforeClass public static void init() throws IllegalAccessException { appender = new MockAppender("trace_appender"); appender.start(); Loggers.addAppender(testLogger1, appender); + + Loggers.setLevel(testLogger1, Level.TRACE); } @AfterClass public static void cleanup() { - appender.stop(); Loggers.removeAppender(testLogger1, appender); + appender.stop(); + + Loggers.setLevel(testLogger1, origLogLevel); } public void testLevelPrecedence() { diff --git a/server/src/test/java/org/elasticsearch/index/SearchSlowLogTests.java b/server/src/test/java/org/elasticsearch/index/SearchSlowLogTests.java index dd1790cc786af..50e3269a6b9ba 100644 --- a/server/src/test/java/org/elasticsearch/index/SearchSlowLogTests.java +++ b/server/src/test/java/org/elasticsearch/index/SearchSlowLogTests.java @@ -50,6 +50,8 @@ public class SearchSlowLogTests extends ESSingleNodeTestCase { static MockAppender appender; static Logger queryLog = LogManager.getLogger(SearchSlowLog.INDEX_SEARCH_SLOWLOG_PREFIX + ".query"); static Logger fetchLog = LogManager.getLogger(SearchSlowLog.INDEX_SEARCH_SLOWLOG_PREFIX + ".fetch"); + static Level origQueryLogLevel = queryLog.getLevel(); + static Level origFetchLogLevel = fetchLog.getLevel(); @BeforeClass public static void init() throws IllegalAccessException { @@ -57,13 +59,19 @@ public static void init() throws IllegalAccessException { appender.start(); Loggers.addAppender(queryLog, appender); Loggers.addAppender(fetchLog, appender); + + Loggers.setLevel(queryLog, Level.TRACE); + Loggers.setLevel(fetchLog, Level.TRACE); } @AfterClass public static void cleanup() { - appender.stop(); Loggers.removeAppender(queryLog, appender); Loggers.removeAppender(fetchLog, appender); + appender.stop(); + + Loggers.setLevel(queryLog, origQueryLogLevel); + Loggers.setLevel(fetchLog, origFetchLogLevel); } @Override diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/BQVectorUtilsTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/BQVectorUtilsTests.java new file mode 100644 index 0000000000000..9f9114c70b6db --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/BQVectorUtilsTests.java @@ -0,0 +1,90 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.tests.util.LuceneTestCase; + +public class BQVectorUtilsTests extends LuceneTestCase { + + public static int popcount(byte[] a, int aOffset, byte[] b, int length) { + int res = 0; + for (int j = 0; j < length; j++) { + int value = (a[aOffset + j] & b[j]) & 0xFF; + for (int k = 0; k < Byte.SIZE; k++) { + if ((value & (1 << k)) != 0) { + ++res; + } + } + } + return res; + } + + private static float DELTA = Float.MIN_VALUE; + + public void testPadFloat() { + assertArrayEquals(new float[] { 1, 2, 3, 4 }, BQVectorUtils.pad(new float[] { 1, 2, 3, 4 }, 4), DELTA); + assertArrayEquals(new float[] { 1, 2, 3, 4 }, BQVectorUtils.pad(new float[] { 1, 2, 3, 4 }, 3), DELTA); + assertArrayEquals(new float[] { 1, 2, 3, 4, 0 }, BQVectorUtils.pad(new float[] { 1, 2, 3, 4 }, 5), DELTA); + } + + public void testPadByte() { + assertArrayEquals(new byte[] { 1, 2, 3, 4 }, BQVectorUtils.pad(new byte[] { 1, 2, 3, 4 }, 4)); + assertArrayEquals(new byte[] { 1, 2, 3, 4 }, BQVectorUtils.pad(new byte[] { 1, 2, 3, 4 }, 3)); + assertArrayEquals(new byte[] { 1, 2, 3, 4, 0 }, BQVectorUtils.pad(new byte[] { 1, 2, 3, 4 }, 5)); + } + + public void testPopCount() { + assertEquals(0, BQVectorUtils.popcount(new byte[] {})); + assertEquals(1, BQVectorUtils.popcount(new byte[] { 1 })); + assertEquals(2, BQVectorUtils.popcount(new byte[] { 2, 1 })); + assertEquals(2, BQVectorUtils.popcount(new byte[] { 8, 0, 1 })); + assertEquals(4, BQVectorUtils.popcount(new byte[] { 7, 1 })); + + int iterations = atLeast(50); + for (int i = 0; i < iterations; i++) { + int size = random().nextInt(5000); + var a = new byte[size]; + random().nextBytes(a); + assertEquals(popcount(a, 0, a, size), BQVectorUtils.popcount(a)); + } + } + + public void testNorm() { + assertEquals(3.0f, BQVectorUtils.norm(new float[] { 3 }), DELTA); + assertEquals(5.0f, BQVectorUtils.norm(new float[] { 5 }), DELTA); + assertEquals(4.0f, BQVectorUtils.norm(new float[] { 2, 2, 2, 2 }), DELTA); + assertEquals(9.0f, BQVectorUtils.norm(new float[] { 3, 3, 3, 3, 3, 3, 3, 3, 3 }), DELTA); + } + + public void testSubtract() { + assertArrayEquals(new float[] { 1 }, BQVectorUtils.subtract(new float[] { 3 }, new float[] { 2 }), DELTA); + assertArrayEquals(new float[] { 2, 1, 0 }, BQVectorUtils.subtract(new float[] { 3, 3, 3 }, new float[] { 1, 2, 3 }), DELTA); + } + + public void testSubtractInPlace() { + var a = new float[] { 3 }; + BQVectorUtils.subtractInPlace(a, new float[] { 2 }); + assertArrayEquals(new float[] { 1 }, a, DELTA); + + a = new float[] { 3, 3, 3 }; + BQVectorUtils.subtractInPlace(a, new float[] { 1, 2, 3 }); + assertArrayEquals(new float[] { 2, 1, 0 }, a, DELTA); + } +} diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/BinaryQuantizationTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/BinaryQuantizationTests.java new file mode 100644 index 0000000000000..32d717bd76f91 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/BinaryQuantizationTests.java @@ -0,0 +1,1856 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.tests.util.LuceneTestCase; +import org.apache.lucene.util.VectorUtil; + +import java.util.Random; + +public class BinaryQuantizationTests extends LuceneTestCase { + + public void testQuantizeForIndex() { + int dimensions = random().nextInt(1, 4097); + int discretizedDimensions = BQVectorUtils.discretize(dimensions, 64); + + int randIdx = random().nextInt(VectorSimilarityFunction.values().length); + VectorSimilarityFunction similarityFunction = VectorSimilarityFunction.values()[randIdx]; + + BinaryQuantizer quantizer = new BinaryQuantizer(discretizedDimensions, similarityFunction); + + float[] centroid = new float[dimensions]; + for (int i = 0; i < dimensions; i++) { + centroid[i] = random().nextFloat(-50f, 50f); + } + + float[] vector = new float[dimensions]; + for (int i = 0; i < dimensions; i++) { + vector[i] = random().nextFloat(-50f, 50f); + } + if (similarityFunction == VectorSimilarityFunction.COSINE) { + VectorUtil.l2normalize(vector); + VectorUtil.l2normalize(centroid); + } + + byte[] destination = new byte[discretizedDimensions / 8]; + float[] corrections = quantizer.quantizeForIndex(vector, destination, centroid); + + for (float correction : corrections) { + assertFalse(Float.isNaN(correction)); + } + + if (similarityFunction != VectorSimilarityFunction.EUCLIDEAN) { + assertEquals(3, corrections.length); + assertTrue(corrections[0] >= 0); + assertTrue(corrections[1] > 0); + } else { + assertEquals(2, corrections.length); + assertTrue(corrections[0] > 0); + assertTrue(corrections[1] > 0); + } + } + + public void testQuantizeForQuery() { + int dimensions = random().nextInt(1, 4097); + int discretizedDimensions = BQVectorUtils.discretize(dimensions, 64); + + int randIdx = random().nextInt(VectorSimilarityFunction.values().length); + VectorSimilarityFunction similarityFunction = VectorSimilarityFunction.values()[randIdx]; + + BinaryQuantizer quantizer = new BinaryQuantizer(discretizedDimensions, similarityFunction); + + float[] centroid = new float[dimensions]; + for (int i = 0; i < dimensions; i++) { + centroid[i] = random().nextFloat(-50f, 50f); + } + + float[] vector = new float[dimensions]; + for (int i = 0; i < dimensions; i++) { + vector[i] = random().nextFloat(-50f, 50f); + } + if (similarityFunction == VectorSimilarityFunction.COSINE) { + VectorUtil.l2normalize(vector); + VectorUtil.l2normalize(centroid); + } + float cDotC = VectorUtil.dotProduct(centroid, centroid); + byte[] destination = new byte[discretizedDimensions / 8 * BQSpaceUtils.B_QUERY]; + BinaryQuantizer.QueryFactors corrections = quantizer.quantizeForQuery(vector, destination, centroid); + + if (similarityFunction != VectorSimilarityFunction.EUCLIDEAN) { + int sumQ = corrections.quantizedSum(); + float distToC = corrections.distToC(); + float lower = corrections.lower(); + float width = corrections.width(); + float normVmC = corrections.normVmC(); + float vDotC = corrections.vDotC(); + assertTrue(sumQ >= 0); + assertTrue(distToC >= 0); + assertFalse(Float.isNaN(lower)); + assertTrue(width >= 0); + assertTrue(normVmC >= 0); + assertFalse(Float.isNaN(vDotC)); + assertTrue(cDotC >= 0); + } else { + int sumQ = corrections.quantizedSum(); + float distToC = corrections.distToC(); + float lower = corrections.lower(); + float width = corrections.width(); + assertTrue(sumQ >= 0); + assertTrue(distToC >= 0); + assertFalse(Float.isNaN(lower)); + assertTrue(width >= 0); + assertEquals(corrections.normVmC(), 0.0f, 0.01f); + assertEquals(corrections.vDotC(), 0.0f, 0.01f); + } + } + + public void testQuantizeForIndexEuclidean() { + int dimensions = 128; + + BinaryQuantizer quantizer = new BinaryQuantizer(dimensions, VectorSimilarityFunction.EUCLIDEAN); + float[] vector = new float[] { + 0f, + 0.0f, + 16.0f, + 35.0f, + 5.0f, + 32.0f, + 31.0f, + 14.0f, + 10.0f, + 11.0f, + 78.0f, + 55.0f, + 10.0f, + 45.0f, + 83.0f, + 11.0f, + 6.0f, + 14.0f, + 57.0f, + 102.0f, + 75.0f, + 20.0f, + 8.0f, + 3.0f, + 5.0f, + 67.0f, + 17.0f, + 19.0f, + 26.0f, + 5.0f, + 0.0f, + 1.0f, + 22.0f, + 60.0f, + 26.0f, + 7.0f, + 1.0f, + 18.0f, + 22.0f, + 84.0f, + 53.0f, + 85.0f, + 119.0f, + 119.0f, + 4.0f, + 24.0f, + 18.0f, + 7.0f, + 7.0f, + 1.0f, + 81.0f, + 106.0f, + 102.0f, + 72.0f, + 30.0f, + 6.0f, + 0.0f, + 9.0f, + 1.0f, + 9.0f, + 119.0f, + 72.0f, + 1.0f, + 4.0f, + 33.0f, + 119.0f, + 29.0f, + 6.0f, + 1.0f, + 0.0f, + 1.0f, + 14.0f, + 52.0f, + 119.0f, + 30.0f, + 3.0f, + 0.0f, + 0.0f, + 55.0f, + 92.0f, + 111.0f, + 2.0f, + 5.0f, + 4.0f, + 9.0f, + 22.0f, + 89.0f, + 96.0f, + 14.0f, + 1.0f, + 0.0f, + 1.0f, + 82.0f, + 59.0f, + 16.0f, + 20.0f, + 5.0f, + 25.0f, + 14.0f, + 11.0f, + 4.0f, + 0.0f, + 0.0f, + 1.0f, + 26.0f, + 47.0f, + 23.0f, + 4.0f, + 0.0f, + 0.0f, + 4.0f, + 38.0f, + 83.0f, + 30.0f, + 14.0f, + 9.0f, + 4.0f, + 9.0f, + 17.0f, + 23.0f, + 41.0f, + 0.0f, + 0.0f, + 2.0f, + 8.0f, + 19.0f, + 25.0f, + 23.0f }; + byte[] destination = new byte[dimensions / 8]; + float[] centroid = new float[] { + 27.054054f, + 22.252253f, + 25.027027f, + 23.55856f, + 31.099098f, + 28.765766f, + 31.64865f, + 30.981981f, + 24.675676f, + 21.81982f, + 26.72973f, + 25.486486f, + 30.504505f, + 35.216217f, + 28.306307f, + 24.486486f, + 29.675676f, + 26.153152f, + 31.315315f, + 25.225225f, + 29.234234f, + 30.855856f, + 24.495495f, + 29.828829f, + 31.54955f, + 24.36937f, + 25.108109f, + 24.873875f, + 22.918919f, + 24.918919f, + 29.027027f, + 25.513514f, + 27.64865f, + 28.405405f, + 23.603603f, + 17.900902f, + 22.522522f, + 24.855856f, + 31.396397f, + 32.585587f, + 26.297297f, + 27.468468f, + 19.675676f, + 19.018019f, + 24.801802f, + 30.27928f, + 27.945946f, + 25.324324f, + 29.918919f, + 27.864864f, + 28.081081f, + 23.45946f, + 28.828829f, + 28.387388f, + 25.387388f, + 27.90991f, + 25.621622f, + 21.585585f, + 26.378378f, + 24.144144f, + 21.666666f, + 22.72973f, + 26.837837f, + 22.747747f, + 29.0f, + 28.414415f, + 24.612612f, + 21.594595f, + 19.117117f, + 24.045046f, + 30.612612f, + 27.55856f, + 25.117117f, + 27.783783f, + 21.639639f, + 19.36937f, + 21.252253f, + 29.153152f, + 29.216217f, + 24.747747f, + 28.252253f, + 25.288288f, + 25.738739f, + 23.44144f, + 24.423424f, + 23.693693f, + 26.306307f, + 29.162163f, + 28.684685f, + 34.648647f, + 25.576576f, + 25.288288f, + 29.63063f, + 20.225225f, + 25.72973f, + 29.009008f, + 28.666666f, + 29.243244f, + 26.36937f, + 25.864864f, + 21.522522f, + 21.414415f, + 25.963964f, + 26.054054f, + 25.099098f, + 30.477478f, + 29.55856f, + 24.837837f, + 24.801802f, + 21.18018f, + 24.027027f, + 26.360361f, + 33.153152f, + 29.135136f, + 30.486486f, + 28.639639f, + 27.576576f, + 24.486486f, + 26.297297f, + 21.774775f, + 25.936937f, + 35.36937f, + 25.171171f, + 30.405405f, + 31.522522f, + 29.765766f, + 22.324324f, + 26.09009f }; + float[] corrections = quantizer.quantizeForIndex(vector, destination, centroid); + + assertEquals(2, corrections.length); + float distToCentroid = corrections[0]; + float magnitude = corrections[1]; + + assertEquals(387.90204f, distToCentroid, 0.0003f); + assertEquals(0.75916624f, magnitude, 0.0000001f); + assertArrayEquals(new byte[] { 20, 54, 56, 72, 97, -16, 62, 12, -32, -29, -125, 12, 0, -63, -63, -126 }, destination); + } + + public void testQuantizeForQueryEuclidean() { + int dimensions = 128; + + BinaryQuantizer quantizer = new BinaryQuantizer(dimensions, VectorSimilarityFunction.EUCLIDEAN); + float[] vector = new float[] { + 0.0f, + 8.0f, + 69.0f, + 45.0f, + 2.0f, + 0f, + 16.0f, + 52.0f, + 32.0f, + 13.0f, + 2.0f, + 6.0f, + 34.0f, + 49.0f, + 45.0f, + 83.0f, + 6.0f, + 2.0f, + 26.0f, + 57.0f, + 14.0f, + 46.0f, + 19.0f, + 9.0f, + 4.0f, + 13.0f, + 53.0f, + 104.0f, + 33.0f, + 11.0f, + 25.0f, + 19.0f, + 30.0f, + 10.0f, + 7.0f, + 2.0f, + 8.0f, + 7.0f, + 25.0f, + 1.0f, + 2.0f, + 25.0f, + 24.0f, + 28.0f, + 61.0f, + 83.0f, + 41.0f, + 9.0f, + 14.0f, + 3.0f, + 7.0f, + 114.0f, + 114.0f, + 114.0f, + 114.0f, + 5.0f, + 5.0f, + 1.0f, + 5.0f, + 114.0f, + 73.0f, + 75.0f, + 106.0f, + 3.0f, + 5.0f, + 6.0f, + 6.0f, + 8.0f, + 15.0f, + 45.0f, + 2.0f, + 15.0f, + 7.0f, + 114.0f, + 103.0f, + 6.0f, + 5.0f, + 4.0f, + 9.0f, + 67.0f, + 47.0f, + 22.0f, + 32.0f, + 27.0f, + 41.0f, + 10.0f, + 114.0f, + 36.0f, + 43.0f, + 42.0f, + 23.0f, + 9.0f, + 7.0f, + 30.0f, + 114.0f, + 19.0f, + 7.0f, + 5.0f, + 6.0f, + 6.0f, + 21.0f, + 48.0f, + 2.0f, + 1.0f, + 0.0f, + 8.0f, + 114.0f, + 13.0f, + 0.0f, + 1.0f, + 53.0f, + 83.0f, + 14.0f, + 8.0f, + 16.0f, + 12.0f, + 16.0f, + 20.0f, + 27.0f, + 87.0f, + 45.0f, + 50.0f, + 15.0f, + 5.0f, + 5.0f, + 6.0f, + 32.0f, + 49.0f }; + byte[] destination = new byte[dimensions / 8 * BQSpaceUtils.B_QUERY]; + float[] centroid = new float[] { + 26.7f, + 16.2f, + 10.913f, + 10.314f, + 12.12f, + 14.045f, + 15.887f, + 16.864f, + 32.232f, + 31.567f, + 34.922f, + 21.624f, + 16.349f, + 29.625f, + 31.994f, + 22.044f, + 37.847f, + 24.622f, + 36.299f, + 27.966f, + 14.368f, + 19.248f, + 30.778f, + 35.927f, + 27.019f, + 16.381f, + 17.325f, + 16.517f, + 13.272f, + 9.154f, + 9.242f, + 17.995f, + 53.777f, + 23.011f, + 12.929f, + 16.128f, + 22.16f, + 28.643f, + 25.861f, + 27.197f, + 59.883f, + 40.878f, + 34.153f, + 22.795f, + 24.402f, + 37.427f, + 34.19f, + 29.288f, + 61.812f, + 26.355f, + 39.071f, + 37.789f, + 23.33f, + 22.299f, + 28.64f, + 47.828f, + 52.457f, + 21.442f, + 24.039f, + 29.781f, + 27.707f, + 19.484f, + 14.642f, + 28.757f, + 54.567f, + 20.936f, + 25.112f, + 25.521f, + 22.077f, + 18.272f, + 14.526f, + 29.054f, + 61.803f, + 24.509f, + 37.517f, + 35.906f, + 24.106f, + 22.64f, + 32.1f, + 48.788f, + 60.102f, + 39.625f, + 34.766f, + 22.497f, + 24.397f, + 41.599f, + 38.419f, + 30.99f, + 55.647f, + 25.115f, + 14.96f, + 18.882f, + 26.918f, + 32.442f, + 26.231f, + 27.107f, + 26.828f, + 15.968f, + 18.668f, + 14.071f, + 10.906f, + 8.989f, + 9.721f, + 17.294f, + 36.32f, + 21.854f, + 35.509f, + 27.106f, + 14.067f, + 19.82f, + 33.582f, + 35.997f, + 33.528f, + 30.369f, + 36.955f, + 21.23f, + 15.2f, + 30.252f, + 34.56f, + 22.295f, + 29.413f, + 16.576f, + 11.226f, + 10.754f, + 12.936f, + 15.525f, + 15.868f, + 16.43f }; + BinaryQuantizer.QueryFactors corrections = quantizer.quantizeForQuery(vector, destination, centroid); + + int sumQ = corrections.quantizedSum(); + float lower = corrections.lower(); + float width = corrections.width(); + + assertEquals(729, sumQ); + assertEquals(-57.883f, lower, 0.001f); + assertEquals(9.972266f, width, 0.000001f); + assertArrayEquals( + new byte[] { + -77, + -49, + 73, + -17, + -89, + 9, + -43, + -27, + 40, + 15, + 42, + 76, + -122, + 38, + -22, + -37, + -96, + 111, + -63, + -102, + -123, + 23, + 110, + 127, + 32, + 95, + 29, + 106, + -120, + -121, + -32, + -94, + 78, + -98, + 42, + 95, + 122, + 114, + 30, + 18, + 91, + 97, + -5, + -9, + 123, + 122, + 31, + -66, + 49, + 1, + 20, + 48, + 0, + 12, + 30, + 30, + 4, + 96, + 2, + 2, + 4, + 33, + 1, + 65 }, + destination + ); + } + + private float[] generateRandomFloatArray(Random random, int dimensions, float lowerBoundInclusive, float upperBoundExclusive) { + float[] data = new float[dimensions]; + for (int i = 0; i < dimensions; i++) { + data[i] = random.nextFloat(lowerBoundInclusive, upperBoundExclusive); + } + return data; + } + + public void testQuantizeForIndexMIP() { + int dimensions = 768; + + // we want fixed values for these arrays so define our own random generation here to track + // quantization changes + Random random = new Random(42); + + float[] mipVectorToIndex = generateRandomFloatArray(random, dimensions, -1f, 1f); + float[] mipCentroid = generateRandomFloatArray(random, dimensions, -1f, 1f); + + VectorSimilarityFunction[] similarityFunctionsActingLikeEucllidean = new VectorSimilarityFunction[] { + VectorSimilarityFunction.MAXIMUM_INNER_PRODUCT, + VectorSimilarityFunction.DOT_PRODUCT }; + int randIdx = random().nextInt(similarityFunctionsActingLikeEucllidean.length); + VectorSimilarityFunction similarityFunction = similarityFunctionsActingLikeEucllidean[randIdx]; + + BinaryQuantizer quantizer = new BinaryQuantizer(dimensions, similarityFunction); + float[] vector = mipVectorToIndex; + byte[] destination = new byte[dimensions / 8]; + float[] centroid = mipCentroid; + float[] corrections = quantizer.quantizeForIndex(vector, destination, centroid); + + assertEquals(3, corrections.length); + float ooq = corrections[0]; + float normOC = corrections[1]; + float oDotC = corrections[2]; + + assertEquals(0.8141399f, ooq, 0.0000001f); + assertEquals(21.847124f, normOC, 0.00001f); + assertEquals(6.4300356f, oDotC, 0.0001f); + assertArrayEquals( + new byte[] { + -83, + -91, + -71, + 97, + 32, + -96, + 89, + -80, + -19, + -108, + 3, + 113, + -111, + 12, + -86, + 32, + -43, + 76, + 122, + -106, + -83, + -37, + -122, + 118, + 84, + -72, + 34, + 20, + 57, + -29, + 119, + -8, + -10, + -100, + -109, + 62, + -54, + 53, + -44, + 8, + -16, + 80, + 58, + 50, + 105, + -25, + 47, + 115, + -106, + -92, + -122, + -44, + 8, + 18, + -23, + 24, + -15, + 62, + 58, + 111, + 99, + -116, + -111, + -5, + 101, + -69, + -32, + -74, + -105, + 113, + -89, + 44, + 100, + -93, + -80, + 82, + -64, + 91, + -87, + -95, + 115, + 6, + 76, + 110, + 101, + 39, + 108, + 72, + 2, + 112, + -63, + -43, + 105, + -42, + 9, + -128 }, + destination + ); + } + + public void testQuantizeForQueryMIP() { + int dimensions = 768; + + // we want fixed values for these arrays so define our own random generation here to track + // quantization changes + Random random = new Random(42); + + float[] mipVectorToQuery = generateRandomFloatArray(random, dimensions, -1f, 1f); + float[] mipCentroid = generateRandomFloatArray(random, dimensions, -1f, 1f); + + VectorSimilarityFunction[] similarityFunctionsActingLikeEucllidean = new VectorSimilarityFunction[] { + VectorSimilarityFunction.MAXIMUM_INNER_PRODUCT, + VectorSimilarityFunction.DOT_PRODUCT }; + int randIdx = random().nextInt(similarityFunctionsActingLikeEucllidean.length); + VectorSimilarityFunction similarityFunction = similarityFunctionsActingLikeEucllidean[randIdx]; + + BinaryQuantizer quantizer = new BinaryQuantizer(dimensions, similarityFunction); + float[] vector = mipVectorToQuery; + byte[] destination = new byte[dimensions / 8 * BQSpaceUtils.B_QUERY]; + float[] centroid = mipCentroid; + float cDotC = VectorUtil.dotProduct(centroid, centroid); + BinaryQuantizer.QueryFactors corrections = quantizer.quantizeForQuery(vector, destination, centroid); + + int sumQ = corrections.quantizedSum(); + float lower = corrections.lower(); + float width = corrections.width(); + float normVmC = corrections.normVmC(); + float vDotC = corrections.vDotC(); + + assertEquals(5272, sumQ); + assertEquals(-0.08603752f, lower, 0.00000001f); + assertEquals(0.011431276f, width, 0.00000001f); + assertEquals(21.847124f, normVmC, 0.00001f); + assertEquals(6.4300356f, vDotC, 0.0001f); + assertEquals(252.37146f, cDotC, 0.0001f); + assertArrayEquals( + new byte[] { + -81, + 19, + 67, + 33, + 112, + 8, + 40, + -5, + -19, + 115, + -87, + -63, + -59, + 12, + -2, + -127, + -23, + 43, + 24, + 16, + -69, + 112, + -22, + 75, + -81, + -50, + 100, + -41, + 3, + -120, + -93, + -4, + 4, + 125, + 34, + -57, + -109, + 89, + -63, + -35, + -116, + 4, + 35, + 93, + -26, + -88, + -55, + -86, + 63, + -46, + -122, + -96, + -26, + 124, + -64, + 21, + 96, + 46, + 98, + 97, + 88, + -98, + -83, + 121, + 16, + -14, + -89, + -118, + 65, + -39, + -111, + -35, + 113, + 108, + 111, + 86, + 17, + -69, + -47, + 72, + 1, + 36, + 17, + 113, + -87, + -5, + -46, + -37, + -2, + 93, + -123, + 118, + 4, + -12, + -33, + 95, + 32, + -63, + -97, + -109, + 27, + 111, + 42, + -57, + -87, + -41, + -73, + -106, + 27, + -31, + 32, + -1, + 9, + -88, + -35, + -11, + -103, + 5, + 27, + -127, + 108, + 127, + -119, + 58, + 38, + 18, + -103, + -27, + -63, + 56, + 77, + -13, + 3, + -40, + -127, + 37, + 82, + -87, + -26, + -45, + -14, + 18, + -50, + 76, + 25, + 37, + -12, + 106, + 17, + 115, + 0, + 23, + -109, + 26, + -110, + 17, + -35, + 111, + 4, + 60, + 58, + -64, + -104, + -125, + 23, + -58, + 89, + -117, + 104, + -71, + 3, + -89, + -26, + 46, + 15, + 82, + -83, + -75, + -72, + -69, + 20, + -38, + -47, + 109, + -66, + -66, + -89, + 108, + -122, + -3, + -69, + -85, + 18, + 59, + 85, + -97, + -114, + 95, + 2, + -84, + -77, + 121, + -6, + 10, + 110, + -13, + -123, + -34, + 106, + -71, + -107, + 123, + 67, + -111, + 58, + 52, + -53, + 87, + -113, + -21, + -44, + 26, + 10, + -62, + 56, + 111, + 36, + -126, + 26, + 94, + -88, + -13, + -113, + -50, + -9, + -115, + 84, + 8, + -32, + -102, + -4, + 89, + 29, + 75, + -73, + -19, + 22, + -90, + 76, + -61, + 4, + -48, + -100, + -11, + 107, + 20, + -39, + -98, + 123, + 77, + 104, + 9, + 9, + 91, + -105, + -40, + -106, + -87, + 38, + 48, + 60, + 29, + -68, + 124, + -78, + -63, + -101, + -115, + 67, + -17, + 101, + -53, + 121, + 44, + -78, + -12, + 110, + 91, + -83, + -92, + -72, + 96, + 32, + -96, + 89, + 48, + 76, + -124, + 3, + 113, + -111, + 12, + -86, + 32, + -43, + 68, + 106, + -122, + -84, + -37, + -124, + 118, + 84, + -72, + 34, + 20, + 57, + -29, + 119, + 56, + -10, + -108, + -109, + 60, + -56, + 37, + 84, + 8, + -16, + 80, + 24, + 50, + 41, + -25, + 47, + 115, + -122, + -92, + -126, + -44, + 8, + 18, + -23, + 24, + -15, + 60, + 58, + 111, + 99, + -120, + -111, + -21, + 101, + 59, + -32, + -74, + -105, + 113, + -90, + 36, + 100, + -93, + -80, + 82, + -64, + 91, + -87, + -95, + 115, + 6, + 76, + 110, + 101, + 39, + 44, + 0, + 2, + 112, + -64, + -47, + 105, + 2, + 1, + -128 }, + destination + ); + } + + public void testQuantizeForIndexCosine() { + int dimensions = 768; + + // we want fixed values for these arrays so define our own random generation here to track + // quantization changes + Random random = new Random(42); + + float[] mipVectorToIndex = generateRandomFloatArray(random, dimensions, -1f, 1f); + float[] mipCentroid = generateRandomFloatArray(random, dimensions, -1f, 1f); + + mipVectorToIndex = VectorUtil.l2normalize(mipVectorToIndex); + mipCentroid = VectorUtil.l2normalize(mipCentroid); + + BinaryQuantizer quantizer = new BinaryQuantizer(dimensions, VectorSimilarityFunction.COSINE); + float[] vector = mipVectorToIndex; + byte[] destination = new byte[dimensions / 8]; + float[] centroid = mipCentroid; + float[] corrections = quantizer.quantizeForIndex(vector, destination, centroid); + + assertEquals(3, corrections.length); + float ooq = corrections[0]; + float normOC = corrections[1]; + float oDotC = corrections[2]; + + assertEquals(0.8145253f, ooq, 0.000001f); + assertEquals(1.3955297f, normOC, 0.00001f); + assertEquals(0.026248248f, oDotC, 0.0001f); + assertArrayEquals( + new byte[] { + -83, + -91, + -71, + 97, + 32, + -96, + 89, + -80, + -20, + -108, + 3, + 113, + -111, + 12, + -86, + 32, + -43, + 76, + 122, + -106, + -83, + -37, + -122, + 118, + 84, + -72, + 34, + 20, + 57, + -29, + 119, + -72, + -10, + -100, + -109, + 62, + -54, + 117, + -44, + 8, + -16, + 80, + 58, + 50, + 41, + -25, + 47, + 115, + -106, + -92, + -122, + -44, + 8, + 18, + -23, + 24, + -15, + 62, + 58, + 111, + 99, + -116, + -111, + -21, + 101, + -69, + -32, + -74, + -105, + 113, + -90, + 44, + 100, + -93, + -80, + 82, + -64, + 91, + -87, + -95, + 115, + 6, + 76, + 110, + 101, + 39, + 44, + 72, + 2, + 112, + -63, + -43, + 105, + -42, + 9, + -126 }, + destination + ); + } + + public void testQuantizeForQueryCosine() { + int dimensions = 768; + + // we want fixed values for these arrays so define our own random generation here to track + // quantization changes + Random random = new Random(42); + + float[] mipVectorToQuery = generateRandomFloatArray(random, dimensions, -1f, 1f); + float[] mipCentroid = generateRandomFloatArray(random, dimensions, -1f, 1f); + + mipVectorToQuery = VectorUtil.l2normalize(mipVectorToQuery); + mipCentroid = VectorUtil.l2normalize(mipCentroid); + + BinaryQuantizer quantizer = new BinaryQuantizer(dimensions, VectorSimilarityFunction.COSINE); + float[] vector = mipVectorToQuery; + byte[] destination = new byte[dimensions / 8 * BQSpaceUtils.B_QUERY]; + float[] centroid = mipCentroid; + float cDotC = VectorUtil.dotProduct(centroid, centroid); + BinaryQuantizer.QueryFactors corrections = quantizer.quantizeForQuery(vector, destination, centroid); + + int sumQ = corrections.quantizedSum(); + float lower = corrections.lower(); + float width = corrections.width(); + float normVmC = corrections.normVmC(); + float vDotC = corrections.vDotC(); + + assertEquals(5277, sumQ); + assertEquals(-0.086002514f, lower, 0.00000001f); + assertEquals(0.011431345f, width, 0.00000001f); + assertEquals(1.3955297f, normVmC, 0.00001f); + assertEquals(0.026248248f, vDotC, 0.0001f); + assertEquals(1.0f, cDotC, 0.0001f); + assertArrayEquals( + new byte[] { + -83, + 18, + 67, + 37, + 80, + 8, + 40, + -1, + -19, + 115, + -87, + -63, + -59, + 12, + -2, + -63, + -19, + 43, + -104, + 16, + -69, + 80, + -22, + 75, + -81, + -50, + 100, + -41, + 7, + -88, + -93, + -4, + 4, + 117, + 34, + -57, + -109, + 89, + -63, + -35, + -116, + 4, + 35, + 93, + -26, + -88, + -56, + -82, + 63, + -46, + -122, + -96, + -26, + 124, + -64, + 21, + 96, + 46, + 114, + 101, + 92, + -98, + -83, + 121, + 48, + -14, + -89, + -118, + 65, + -47, + -79, + -35, + 113, + 110, + 111, + 70, + 17, + -69, + -47, + 64, + 1, + 102, + 19, + 113, + -87, + -5, + -46, + -34, + -2, + 93, + -123, + 102, + 4, + -12, + 127, + 95, + 32, + -64, + -97, + -105, + 59, + 111, + 42, + -57, + -87, + -41, + -73, + -106, + 27, + -31, + 32, + -65, + 9, + -88, + 93, + -11, + -103, + 37, + 27, + -127, + 108, + 127, + -119, + 58, + 38, + 18, + -103, + -27, + -63, + 48, + 77, + -13, + 3, + -40, + -127, + 37, + 82, + -87, + -26, + -45, + -14, + 18, + -49, + 76, + 25, + 37, + -12, + 106, + 17, + 115, + 0, + 23, + -109, + 26, + -126, + 21, + -35, + 111, + 4, + 60, + 58, + -64, + -104, + -125, + 23, + -58, + 121, + -117, + 104, + -69, + 3, + -89, + -26, + 46, + 15, + 90, + -83, + -73, + -72, + -69, + 20, + -38, + -47, + 109, + -66, + -66, + -89, + 108, + -122, + -3, + 59, + -85, + 18, + 58, + 85, + -101, + -114, + 95, + 2, + -84, + -77, + 121, + -6, + 10, + 110, + -13, + -123, + -34, + 106, + -71, + -107, + 123, + 67, + -111, + 58, + 52, + -53, + 87, + -113, + -21, + -44, + 26, + 10, + -62, + 56, + 103, + 36, + -126, + 26, + 94, + -88, + -13, + -113, + -50, + -9, + -115, + 84, + 8, + -32, + -102, + -4, + 89, + 29, + 75, + -73, + -19, + 22, + -90, + 76, + -61, + 4, + -44, + -100, + -11, + 107, + 20, + -39, + -98, + 123, + 77, + 104, + 9, + 41, + 91, + -105, + -38, + -106, + -87, + 38, + 48, + 60, + 29, + -68, + 126, + -78, + -63, + -101, + -115, + 67, + -17, + 101, + -53, + 121, + 44, + -78, + -12, + -18, + 91, + -83, + -91, + -72, + 96, + 32, + -96, + 89, + 48, + 76, + -124, + 3, + 113, + -111, + 12, + -86, + 32, + -43, + 68, + 106, + -122, + -84, + -37, + -124, + 118, + 84, + -72, + 34, + 20, + 57, + -29, + 119, + 56, + -10, + -100, + -109, + 60, + -56, + 37, + 84, + 8, + -16, + 80, + 24, + 50, + 41, + -25, + 47, + 115, + -122, + -92, + -126, + -44, + 8, + 18, + -23, + 24, + -15, + 60, + 58, + 107, + 99, + -120, + -111, + -21, + 101, + 59, + -32, + -74, + -105, + 113, + -122, + 36, + 100, + -95, + -80, + 82, + -64, + 91, + -87, + -95, + 115, + 4, + 76, + 110, + 101, + 39, + 44, + 0, + 2, + 112, + -64, + -47, + 105, + 2, + 1, + -128 }, + destination + ); + } +} diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816BinaryFlatVectorsScorerTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816BinaryFlatVectorsScorerTests.java new file mode 100644 index 0000000000000..4ac66a9f63a3f --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816BinaryFlatVectorsScorerTests.java @@ -0,0 +1,1746 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.tests.util.LuceneTestCase; +import org.apache.lucene.util.VectorUtil; +import org.elasticsearch.common.logging.LogConfigurator; + +import java.io.IOException; + +public class ES816BinaryFlatVectorsScorerTests extends LuceneTestCase { + + static { + LogConfigurator.loadLog4jPlugins(); + LogConfigurator.configureESLogging(); // native access requires logging to be initialized + } + + public void testScore() throws IOException { + int dimensions = random().nextInt(1, 4097); + int discretizedDimensions = BQVectorUtils.discretize(dimensions, 64); + + int randIdx = random().nextInt(VectorSimilarityFunction.values().length); + VectorSimilarityFunction similarityFunction = VectorSimilarityFunction.values()[randIdx]; + + float[] centroid = new float[dimensions]; + for (int j = 0; j < dimensions; j++) { + centroid[j] = random().nextFloat(-50f, 50f); + } + if (similarityFunction == VectorSimilarityFunction.COSINE) { + VectorUtil.l2normalize(centroid); + } + + byte[] vector = new byte[discretizedDimensions / 8 * BQSpaceUtils.B_QUERY]; + random().nextBytes(vector); + float distanceToCentroid = random().nextFloat(0f, 10_000.0f); + float vl = random().nextFloat(-1000f, 1000f); + float width = random().nextFloat(0f, 1000f); + short quantizedSum = (short) random().nextInt(0, 4097); + float normVmC = random().nextFloat(-1000f, 1000f); + float vDotC = random().nextFloat(-1000f, 1000f); + ES816BinaryFlatVectorsScorer.BinaryQueryVector queryVector = new ES816BinaryFlatVectorsScorer.BinaryQueryVector( + vector, + new BinaryQuantizer.QueryFactors(quantizedSum, distanceToCentroid, vl, width, normVmC, vDotC) + ); + + RandomAccessBinarizedByteVectorValues targetVectors = new RandomAccessBinarizedByteVectorValues() { + @Override + public float getCentroidDistance(int vectorOrd) throws IOException { + return random().nextFloat(0f, 1000f); + } + + @Override + public float getVectorMagnitude(int vectorOrd) throws IOException { + return random().nextFloat(0f, 100f); + } + + @Override + public float getOOQ(int targetOrd) throws IOException { + return random().nextFloat(-1000f, 1000f); + } + + @Override + public float getNormOC(int targetOrd) throws IOException { + return random().nextFloat(-1000f, 1000f); + } + + @Override + public float getODotC(int targetOrd) throws IOException { + return random().nextFloat(-1000f, 1000f); + } + + @Override + public BinaryQuantizer getQuantizer() { + int dimensions = 128; + return new BinaryQuantizer(dimensions, dimensions, VectorSimilarityFunction.EUCLIDEAN); + } + + @Override + public float[] getCentroid() throws IOException { + return centroid; + } + + @Override + public RandomAccessBinarizedByteVectorValues copy() throws IOException { + return null; + } + + @Override + public byte[] vectorValue(int targetOrd) throws IOException { + byte[] vectorBytes = new byte[discretizedDimensions / 8]; + random().nextBytes(vectorBytes); + return vectorBytes; + } + + @Override + public int size() { + return 1; + } + + @Override + public int dimension() { + return dimensions; + } + }; + + ES816BinaryFlatVectorsScorer.BinarizedRandomVectorScorer scorer = new ES816BinaryFlatVectorsScorer.BinarizedRandomVectorScorer( + queryVector, + targetVectors, + similarityFunction + ); + + float score = scorer.score(0); + + assertTrue(score >= 0f); + } + + public void testScoreEuclidean() throws IOException { + int dimensions = 128; + + byte[] vector = new byte[] { + -8, + 10, + -27, + 112, + -83, + 36, + -36, + -122, + -114, + 82, + 55, + 33, + -33, + 120, + 55, + -99, + -93, + -86, + -55, + 21, + -121, + 30, + 111, + 30, + 0, + 82, + 21, + 38, + -120, + -127, + 40, + -32, + 78, + -37, + 42, + -43, + 122, + 115, + 30, + 115, + 123, + 108, + -13, + -65, + 123, + 124, + -33, + -68, + 49, + 5, + 20, + 58, + 0, + 12, + 30, + 30, + 4, + 97, + 10, + 66, + 4, + 35, + 1, + 67 }; + float distanceToCentroid = 157799.12f; + float vl = -57.883f; + float width = 9.972266f; + short quantizedSum = 795; + ES816BinaryFlatVectorsScorer.BinaryQueryVector queryVector = new ES816BinaryFlatVectorsScorer.BinaryQueryVector( + vector, + new BinaryQuantizer.QueryFactors(quantizedSum, distanceToCentroid, vl, width, 0f, 0f) + ); + + RandomAccessBinarizedByteVectorValues targetVectors = new RandomAccessBinarizedByteVectorValues() { + @Override + public float getCentroidDistance(int vectorOrd) { + return 355.78073f; + } + + @Override + public float getVectorMagnitude(int vectorOrd) { + return 0.7636705f; + } + + @Override + public float getOOQ(int targetOrd) { + return 0; + } + + @Override + public float getNormOC(int targetOrd) { + return 0; + } + + @Override + public float getODotC(int targetOrd) { + return 0; + } + + @Override + public BinaryQuantizer getQuantizer() { + int dimensions = 128; + return new BinaryQuantizer(dimensions, dimensions, VectorSimilarityFunction.EUCLIDEAN); + } + + @Override + public float[] getCentroid() { + return new float[] { + 26.7f, + 16.2f, + 10.913f, + 10.314f, + 12.12f, + 14.045f, + 15.887f, + 16.864f, + 32.232f, + 31.567f, + 34.922f, + 21.624f, + 16.349f, + 29.625f, + 31.994f, + 22.044f, + 37.847f, + 24.622f, + 36.299f, + 27.966f, + 14.368f, + 19.248f, + 30.778f, + 35.927f, + 27.019f, + 16.381f, + 17.325f, + 16.517f, + 13.272f, + 9.154f, + 9.242f, + 17.995f, + 53.777f, + 23.011f, + 12.929f, + 16.128f, + 22.16f, + 28.643f, + 25.861f, + 27.197f, + 59.883f, + 40.878f, + 34.153f, + 22.795f, + 24.402f, + 37.427f, + 34.19f, + 29.288f, + 61.812f, + 26.355f, + 39.071f, + 37.789f, + 23.33f, + 22.299f, + 28.64f, + 47.828f, + 52.457f, + 21.442f, + 24.039f, + 29.781f, + 27.707f, + 19.484f, + 14.642f, + 28.757f, + 54.567f, + 20.936f, + 25.112f, + 25.521f, + 22.077f, + 18.272f, + 14.526f, + 29.054f, + 61.803f, + 24.509f, + 37.517f, + 35.906f, + 24.106f, + 22.64f, + 32.1f, + 48.788f, + 60.102f, + 39.625f, + 34.766f, + 22.497f, + 24.397f, + 41.599f, + 38.419f, + 30.99f, + 55.647f, + 25.115f, + 14.96f, + 18.882f, + 26.918f, + 32.442f, + 26.231f, + 27.107f, + 26.828f, + 15.968f, + 18.668f, + 14.071f, + 10.906f, + 8.989f, + 9.721f, + 17.294f, + 36.32f, + 21.854f, + 35.509f, + 27.106f, + 14.067f, + 19.82f, + 33.582f, + 35.997f, + 33.528f, + 30.369f, + 36.955f, + 21.23f, + 15.2f, + 30.252f, + 34.56f, + 22.295f, + 29.413f, + 16.576f, + 11.226f, + 10.754f, + 12.936f, + 15.525f, + 15.868f, + 16.43f }; + } + + @Override + public RandomAccessBinarizedByteVectorValues copy() { + return null; + } + + @Override + public byte[] vectorValue(int targetOrd) { + return new byte[] { 44, 108, 120, -15, -61, -32, 124, 25, -63, -57, 6, 24, 1, -61, 1, 14 }; + } + + @Override + public int size() { + return 1; + } + + @Override + public int dimension() { + return dimensions; + } + }; + + VectorSimilarityFunction similarityFunction = VectorSimilarityFunction.EUCLIDEAN; + + ES816BinaryFlatVectorsScorer.BinarizedRandomVectorScorer scorer = new ES816BinaryFlatVectorsScorer.BinarizedRandomVectorScorer( + queryVector, + targetVectors, + similarityFunction + ); + + assertEquals(1f / (1f + 245482.47f), scorer.score(0), 0.1f); + } + + public void testScoreMIP() throws IOException { + int dimensions = 768; + + byte[] vector = new byte[] { + -76, + 44, + 81, + 31, + 30, + -59, + 56, + -118, + -36, + 45, + -11, + 8, + -61, + 95, + -100, + 18, + -91, + -98, + -46, + 31, + -8, + 82, + -42, + 121, + 75, + -61, + 125, + -21, + -82, + 16, + 21, + 40, + -1, + 12, + -92, + -22, + -49, + -92, + -19, + -32, + -56, + -34, + 60, + -100, + 69, + 13, + 60, + -51, + 90, + 4, + -77, + 63, + 124, + 69, + 88, + 73, + -72, + 29, + -96, + 44, + 69, + -123, + -59, + -94, + 84, + 80, + -61, + 27, + -37, + -92, + -51, + -86, + 19, + -55, + -36, + -2, + 68, + -37, + -128, + 59, + -47, + 119, + -53, + 56, + -12, + 37, + 27, + 119, + -37, + 125, + 78, + 19, + 15, + -9, + 94, + 100, + -72, + 55, + 86, + -48, + 26, + 10, + -112, + 28, + -15, + -64, + -34, + 55, + -42, + -31, + -96, + -18, + 60, + -44, + 69, + 106, + -20, + 15, + 47, + 49, + -122, + -45, + 119, + 101, + 22, + 77, + 108, + -15, + -71, + -28, + -43, + -68, + -127, + -86, + -118, + -51, + 121, + -65, + -10, + -49, + 115, + -6, + -61, + -98, + 21, + 41, + 56, + 29, + -16, + -82, + 4, + 72, + -77, + 23, + 23, + -32, + -98, + 112, + 27, + -4, + 91, + -69, + 102, + -114, + 16, + -20, + -76, + -124, + 43, + 12, + 3, + -30, + 42, + -44, + -88, + -72, + -76, + -94, + -73, + 46, + -17, + 4, + -74, + -44, + 53, + -11, + -117, + -105, + -113, + -37, + -43, + -128, + -70, + 56, + -68, + -100, + 56, + -20, + 77, + 12, + 17, + -119, + -17, + 59, + -10, + -26, + 29, + 42, + -59, + -28, + -28, + 60, + -34, + 60, + -24, + 80, + -81, + 24, + 122, + 127, + 62, + 124, + -5, + -11, + 59, + -52, + 74, + -29, + -116, + 3, + -40, + -99, + -24, + 11, + -10, + 95, + 21, + -38, + 59, + -52, + 29, + 58, + 112, + 100, + -106, + -90, + 71, + 72, + 57, + 95, + 98, + 96, + -41, + -16, + 50, + -18, + 123, + -36, + 74, + -101, + 17, + 50, + 48, + 96, + 57, + 7, + 81, + -16, + -32, + -102, + -24, + -71, + -10, + 37, + -22, + 94, + -36, + -52, + -71, + -47, + 47, + -1, + -31, + -10, + -126, + -15, + -123, + -59, + 71, + -49, + 67, + 99, + -57, + 21, + -93, + -13, + -18, + 54, + -112, + -60, + 9, + 25, + -30, + -47, + 26, + 27, + 26, + -63, + 1, + -63, + 18, + -114, + 80, + 110, + -123, + 0, + -63, + -126, + -128, + 10, + -60, + 51, + -71, + 28, + 114, + -4, + 53, + 10, + 23, + -96, + 9, + 32, + -22, + 5, + -108, + 33, + 98, + -59, + -106, + -126, + 73, + 72, + -72, + -73, + -60, + -96, + -99, + 31, + 40, + 15, + -19, + 17, + -128, + 33, + -75, + 96, + -18, + -47, + 75, + 27, + -60, + -16, + -82, + 13, + 21, + 37, + 23, + 70, + 9, + -39, + 16, + -127, + 35, + -78, + 64, + 99, + -46, + 1, + 28, + 65, + 125, + 14, + 42, + 26 }; + float distanceToCentroid = 95.39032f; + float vl = -0.10079563f; + float width = 0.014609014f; + short quantizedSum = 5306; + float normVmC = 9.766797f; + float vDotC = 133.56123f; + float cDotC = 132.20227f; + ES816BinaryFlatVectorsScorer.BinaryQueryVector queryVector = new ES816BinaryFlatVectorsScorer.BinaryQueryVector( + vector, + new BinaryQuantizer.QueryFactors(quantizedSum, distanceToCentroid, vl, width, normVmC, vDotC) + ); + + RandomAccessBinarizedByteVectorValues targetVectors = new RandomAccessBinarizedByteVectorValues() { + @Override + public float getCentroidDistance(int vectorOrd) { + return 0f; + } + + @Override + public float getCentroidDP() { + return cDotC; + } + + @Override + public float getVectorMagnitude(int vectorOrd) { + return 0f; + } + + @Override + public float getOOQ(int targetOrd) { + return 0.7882396f; + } + + @Override + public float getNormOC(int targetOrd) { + return 5.0889387f; + } + + @Override + public float getODotC(int targetOrd) { + return 131.485660f; + } + + @Override + public BinaryQuantizer getQuantizer() { + int dimensions = 768; + return new BinaryQuantizer(dimensions, dimensions, VectorSimilarityFunction.MAXIMUM_INNER_PRODUCT); + } + + @Override + public float[] getCentroid() { + return new float[] { + 0.16672021f, + 0.11700719f, + 0.013227397f, + 0.09305186f, + -0.029422699f, + 0.17622353f, + 0.4267106f, + -0.297038f, + 0.13915674f, + 0.38441318f, + -0.486725f, + -0.15987667f, + -0.19712289f, + 0.1349074f, + -0.19016947f, + -0.026179956f, + 0.4129807f, + 0.14325741f, + -0.09106042f, + 0.06876218f, + -0.19389102f, + 0.4467732f, + 0.03169017f, + -0.066950575f, + -0.044301506f, + -0.0059755715f, + -0.33196586f, + 0.18213534f, + -0.25065416f, + 0.30251458f, + 0.3448419f, + -0.14900115f, + -0.07782894f, + 0.3568707f, + -0.46595258f, + 0.37295088f, + -0.088741764f, + 0.17248306f, + -0.0072736046f, + 0.32928637f, + 0.13216197f, + 0.032092985f, + 0.21553043f, + 0.016091486f, + 0.31958902f, + 0.0133126f, + 0.1579258f, + 0.018537233f, + 0.046248164f, + -0.0048194043f, + -0.2184672f, + -0.26273906f, + -0.110678785f, + -0.04542999f, + -0.41625032f, + 0.46025568f, + -0.16116948f, + 0.4091706f, + 0.18427321f, + 0.004736977f, + 0.16289745f, + -0.05330932f, + -0.2694863f, + -0.14762327f, + 0.17744702f, + 0.2445075f, + 0.14377175f, + 0.37390858f, + 0.16165806f, + 0.17177118f, + 0.097307935f, + 0.36326465f, + 0.23221572f, + 0.15579978f, + -0.065486655f, + -0.29006517f, + -0.009194494f, + 0.009019374f, + 0.32154799f, + -0.23186184f, + 0.46485493f, + -0.110756285f, + -0.18604982f, + 0.35027295f, + 0.19815539f, + 0.47386464f, + -0.031379268f, + 0.124035835f, + 0.11556784f, + 0.4304302f, + -0.24455063f, + 0.1816723f, + 0.034300473f, + -0.034347706f, + 0.040140998f, + 0.1389901f, + 0.22840638f, + -0.19911191f, + 0.07563166f, + -0.2744902f, + 0.13114859f, + -0.23862572f, + -0.31404558f, + 0.41355187f, + 0.12970817f, + -0.35403475f, + -0.2714075f, + 0.07231573f, + 0.043893218f, + 0.30324167f, + 0.38928393f, + -0.1567055f, + -0.0083288215f, + 0.0487653f, + 0.12073729f, + -0.01582117f, + 0.13381198f, + -0.084824145f, + -0.15329859f, + -1.120622f, + 0.3972598f, + 0.36022213f, + -0.29826534f, + -0.09468781f, + 0.03550699f, + -0.21630692f, + 0.55655843f, + -0.14842057f, + 0.5924833f, + 0.38791573f, + 0.1502777f, + 0.111737385f, + 0.1926823f, + 0.66021144f, + 0.25601995f, + 0.28220543f, + 0.10194068f, + 0.013066262f, + -0.09348819f, + -0.24085014f, + -0.17843121f, + -0.012598432f, + 0.18757571f, + 0.48543528f, + -0.059388146f, + 0.1548026f, + 0.041945867f, + 0.3322589f, + 0.012830887f, + 0.16621992f, + 0.22606649f, + 0.13959105f, + -0.16688728f, + 0.47194278f, + -0.12767595f, + 0.037815034f, + 0.441938f, + 0.07875027f, + 0.08625042f, + 0.053454693f, + 0.74093896f, + 0.34662113f, + 0.009829135f, + -0.033400282f, + 0.030965377f, + 0.17645596f, + 0.083803624f, + 0.32578796f, + 0.49538168f, + -0.13212465f, + -0.39596975f, + 0.109529115f, + 0.2815771f, + -0.051440604f, + 0.21889819f, + 0.25598505f, + 0.012208843f, + -0.012405662f, + 0.3248759f, + 0.00997502f, + 0.05999008f, + 0.03562817f, + 0.19007418f, + 0.24805716f, + 0.5926766f, + 0.26937613f, + 0.25856f, + -0.05798439f, + -0.29168302f, + 0.14050555f, + 0.084851265f, + -0.03763504f, + 0.8265359f, + -0.23383066f, + -0.042164285f, + 0.19120507f, + -0.12189065f, + 0.3864055f, + -0.19823311f, + 0.30280992f, + 0.10814344f, + -0.164514f, + -0.22905481f, + 0.13680641f, + 0.4513772f, + -0.514546f, + -0.061746247f, + 0.11598224f, + -0.23093395f, + -0.09735358f, + 0.02767051f, + 0.11594536f, + 0.17106244f, + 0.21301728f, + -0.048222974f, + 0.2212131f, + -0.018857865f, + -0.09783516f, + 0.42156664f, + -0.14032331f, + -0.103861615f, + 0.4190284f, + 0.068923555f, + -0.015083771f, + 0.083590426f, + -0.15759592f, + -0.19096768f, + -0.4275228f, + 0.12626286f, + 0.12192557f, + 0.4157616f, + 0.048780657f, + 0.008426048f, + -0.0869124f, + 0.054927208f, + 0.28417027f, + 0.29765493f, + 0.09203619f, + -0.14446871f, + -0.117514975f, + 0.30662632f, + 0.24904715f, + -0.19551662f, + -0.0045785015f, + 0.4217626f, + -0.31457824f, + 0.23381722f, + 0.089111514f, + -0.27170828f, + -0.06662652f, + 0.10011391f, + -0.090274535f, + 0.101849966f, + 0.26554734f, + -0.1722843f, + 0.23296228f, + 0.25112453f, + -0.16790418f, + 0.010348314f, + 0.05061285f, + 0.38003662f, + 0.0804625f, + 0.3450673f, + 0.364368f, + -0.2529952f, + -0.034065288f, + 0.22796603f, + 0.5457553f, + 0.11120353f, + 0.24596325f, + 0.42822433f, + -0.19215727f, + -0.06974534f, + 0.19388479f, + -0.17598474f, + -0.08769705f, + 0.12769659f, + 0.1371616f, + -0.4636819f, + 0.16870509f, + 0.14217548f, + 0.04412187f, + -0.20930687f, + 0.0075530168f, + 0.10065227f, + 0.45334083f, + -0.1097471f, + -0.11139921f, + -0.31835595f, + -0.057386875f, + 0.16285825f, + 0.5088513f, + -0.06318843f, + -0.34759882f, + 0.21132466f, + 0.33609292f, + 0.04858872f, + -0.058759f, + 0.22845529f, + -0.07641319f, + 0.5452827f, + -0.5050389f, + 0.1788054f, + 0.37428045f, + 0.066334985f, + -0.28162515f, + -0.15629752f, + 0.33783385f, + -0.0832242f, + 0.29144394f, + 0.47892854f, + -0.47006592f, + -0.07867588f, + 0.3872869f, + 0.28053126f, + 0.52399015f, + 0.21979983f, + 0.076880336f, + 0.47866163f, + 0.252952f, + -0.1323851f, + -0.22225754f, + -0.38585815f, + 0.12967427f, + 0.20340872f, + -0.326928f, + 0.09636557f, + -0.35929212f, + 0.5413311f, + 0.019960884f, + 0.33512768f, + 0.15133342f, + -0.14124066f, + -0.1868793f, + -0.07862198f, + 0.22739467f, + 0.19598985f, + 0.34314656f, + -0.05071516f, + -0.21107961f, + 0.19934991f, + 0.04822684f, + 0.15060754f, + 0.26586458f, + -0.15528078f, + 0.123646654f, + 0.14450715f, + -0.12574252f, + 0.30608323f, + 0.018549249f, + 0.36323825f, + 0.06762097f, + 0.08562406f, + -0.07863075f, + 0.15975896f, + 0.008347004f, + 0.37931192f, + 0.22957338f, + 0.33606857f, + -0.25204057f, + 0.18126069f, + 0.41903302f, + 0.20244692f, + -0.053850617f, + 0.23088565f, + 0.16085246f, + 0.1077502f, + -0.12445943f, + 0.115779735f, + 0.124704875f, + 0.13076028f, + -0.11628619f, + -0.12580182f, + 0.065204754f, + -0.26290357f, + -0.23539798f, + -0.1855292f, + 0.39872098f, + 0.44495568f, + 0.05491784f, + 0.05135692f, + 0.624011f, + 0.22839564f, + 0.0022447354f, + -0.27169296f, + -0.1694988f, + -0.19106841f, + 0.0110123325f, + 0.15464798f, + -0.16269256f, + 0.04033836f, + -0.11792753f, + 0.17172396f, + -0.08912173f, + -0.30929542f, + -0.03446989f, + -0.21738084f, + 0.39657044f, + 0.33550346f, + -0.06839139f, + 0.053675443f, + 0.33783767f, + 0.22576828f, + 0.38280004f, + 4.1448855f, + 0.14225426f, + 0.24038498f, + 0.072373435f, + -0.09465926f, + -0.016144043f, + 0.40864578f, + -0.2583055f, + 0.031816103f, + 0.062555805f, + 0.06068663f, + 0.25858644f, + -0.10598804f, + 0.18201788f, + -0.00090025424f, + 0.085680895f, + 0.4304161f, + 0.028686283f, + 0.027298616f, + 0.27473378f, + -0.3888415f, + 0.44825438f, + 0.3600378f, + 0.038944595f, + 0.49292335f, + 0.18556066f, + 0.15779617f, + 0.29989767f, + 0.39233804f, + 0.39759228f, + 0.3850708f, + -0.0526475f, + 0.18572918f, + 0.09667526f, + -0.36111078f, + 0.3439669f, + 0.1724522f, + 0.14074509f, + 0.26097745f, + 0.16626832f, + -0.3062964f, + -0.054877423f, + 0.21702516f, + 0.4736452f, + 0.2298038f, + -0.2983771f, + 0.118479654f, + 0.35940516f, + 0.12212727f, + 0.17234904f, + 0.30632678f, + 0.09207966f, + -0.14084268f, + -0.19737118f, + 0.12442629f, + 0.52454203f, + 0.1266684f, + 0.3062802f, + 0.121598125f, + -0.09156268f, + 0.11491686f, + -0.105715364f, + 0.19831072f, + 0.061421417f, + -0.41778997f, + 0.14488487f, + 0.023310646f, + 0.27257463f, + 0.16821945f, + -0.16702746f, + 0.263203f, + 0.33512688f, + 0.35117313f, + -0.31740817f, + -0.14203706f, + 0.061256267f, + -0.19764185f, + 0.04822579f, + -0.0016218472f, + -0.025792575f, + 0.4885193f, + -0.16942391f, + -0.04156327f, + 0.15908112f, + -0.06998626f, + 0.53907114f, + 0.10317832f, + -0.365468f, + 0.4729886f, + 0.14291425f, + 0.32812154f, + -0.0273262f, + 0.31760117f, + 0.16925456f, + 0.21820979f, + 0.085142255f, + 0.16118735f, + -3.7089362f, + 0.251577f, + 0.18394576f, + 0.027926167f, + 0.15720351f, + 0.13084261f, + 0.16240814f, + 0.23045056f, + -0.3966458f, + 0.22822891f, + -0.061541352f, + 0.028320132f, + -0.14736478f, + 0.184569f, + 0.084853746f, + 0.15172474f, + 0.08277542f, + 0.27751622f, + 0.23450488f, + -0.15349835f, + 0.29665688f, + 0.32045734f, + 0.20012043f, + -0.2749372f, + 0.011832386f, + 0.05976605f, + 0.018300122f, + -0.07855043f, + -0.075900674f, + 0.0384252f, + -0.15101928f, + 0.10922137f, + 0.47396383f, + -0.1771141f, + 0.2203417f, + 0.33174303f, + 0.36640546f, + 0.10906258f, + 0.13765177f, + 0.2488032f, + -0.061588854f, + 0.20347528f, + 0.2574979f, + 0.22369152f, + 0.18777567f, + -0.0772263f, + -0.1353299f, + 0.087077625f, + -0.05409276f, + 0.027534787f, + 0.08053508f, + 0.3403908f, + -0.15362988f, + 0.07499862f, + 0.54367846f, + -0.045938436f, + 0.12206868f, + 0.031069376f, + 0.2972343f, + 0.3235321f, + -0.053970363f, + -0.0042564687f, + 0.21447177f, + 0.023565233f, + -0.1286087f, + -0.047359955f, + 0.23021339f, + 0.059837278f, + 0.19709614f, + -0.17340347f, + 0.11572943f, + 0.21720429f, + 0.29375625f, + -0.045433592f, + 0.033339307f, + 0.24594454f, + -0.021661613f, + -0.12823369f, + 0.41809165f, + 0.093840264f, + -0.007481906f, + 0.22441079f, + -0.45719734f, + 0.2292629f, + 2.675806f, + 0.3690025f, + 2.1311781f, + 0.07818368f, + -0.17055893f, + 0.3162922f, + -0.2983149f, + 0.21211359f, + 0.037087034f, + 0.021580033f, + 0.086415835f, + 0.13541797f, + -0.12453424f, + 0.04563163f, + -0.082379065f, + -0.15938349f, + 0.38595748f, + -0.8796574f, + -0.080991246f, + 0.078572094f, + 0.20274459f, + 0.009252143f, + -0.12719384f, + 0.105845824f, + 0.1592398f, + -0.08656061f, + -0.053054806f, + 0.090986334f, + -0.02223379f, + -0.18215932f, + -0.018316114f, + 0.1806707f, + 0.24788831f, + -0.041049056f, + 0.01839475f, + 0.19160001f, + -0.04827654f, + 4.4070687f, + 0.12640671f, + -0.11171499f, + -0.015480781f, + 0.14313947f, + 0.10024215f, + 0.4129662f, + 0.038836367f, + -0.030228542f, + 0.2948598f, + 0.32946473f, + 0.2237934f, + 0.14260699f, + -0.044821896f, + 0.23791742f, + 0.079720296f, + 0.27059034f, + 0.32129505f, + 0.2725177f, + 0.06883333f, + 0.1478041f, + 0.07598411f, + 0.27230525f, + -0.04704308f, + 0.045167264f, + 0.215413f, + 0.20359069f, + -0.092178136f, + -0.09523752f, + 0.21427691f, + 0.10512272f, + 5.1295033f, + 0.040909242f, + 0.007160441f, + -0.192866f, + -0.102640584f, + 0.21103396f, + -0.006780398f, + -0.049653083f, + -0.29426834f, + -0.0038102255f, + -0.13842082f, + 0.06620181f, + -0.3196518f, + 0.33279592f, + 0.13845938f, + 0.16162738f, + -0.24798508f, + -0.06672485f, + 0.195944f, + -0.11957207f, + 0.44237947f, + -0.07617347f, + 0.13575341f, + -0.35074243f, + -0.093798876f, + 0.072853446f, + -0.20490398f, + 0.26504788f, + -0.046076056f, + 0.16488416f, + 0.36007464f, + 0.20955376f, + -0.3082038f, + 0.46533757f, + -0.27326992f, + -0.14167665f, + 0.25017953f, + 0.062622115f, + 0.14057694f, + -0.102370486f, + 0.33898357f, + 0.36456722f, + -0.10120469f, + -0.27838466f, + -0.11779602f, + 0.18517569f, + -0.05942488f, + 0.076405466f, + 0.007960496f, + 0.0443746f, + 0.098998964f, + -0.01897129f, + 0.8059487f, + 0.06991939f, + 0.26562217f, + 0.26942885f, + 0.11432197f, + -0.0055776504f, + 0.054493718f, + -0.13086213f, + 0.6841702f, + 0.121975765f, + 0.02787146f, + 0.29039973f, + 0.30943078f, + 0.21762547f, + 0.28751117f, + 0.027524523f, + 0.5315654f, + -0.22451901f, + -0.13782433f, + 0.08228316f, + 0.07808882f, + 0.17445615f, + -0.042489477f, + 0.13232234f, + 0.2756272f, + -0.18824948f, + 0.14326479f, + -0.119312495f, + 0.011788091f, + -0.22103515f, + -0.2477118f, + -0.10513839f, + 0.034028634f, + 0.10693818f, + 0.03057979f, + 0.04634646f, + 0.2289361f, + 0.09981585f, + 0.26901972f, + 0.1561221f, + -0.10639886f, + 0.36466748f, + 0.06350991f, + 0.027927283f, + 0.11919768f, + 0.23290513f, + -0.03417105f, + 0.16698854f, + -0.19243467f, + 0.28430334f, + 0.03754995f, + -0.08697018f, + 0.20413163f, + -0.27218238f, + 0.13707504f, + -0.082289375f, + 0.03479585f, + 0.2298305f, + 0.4983682f, + 0.34522808f, + -0.05711886f, + -0.10568684f, + -0.07771385f }; + } + + @Override + public RandomAccessBinarizedByteVectorValues copy() { + return null; + } + + @Override + public byte[] vectorValue(int targetOrd) { + return new byte[] { + -88, + -3, + 60, + -75, + -38, + 79, + 84, + -53, + -116, + -126, + 19, + -19, + -21, + -80, + 69, + 101, + -71, + 53, + 101, + -124, + -24, + -76, + 92, + -45, + 108, + -107, + -18, + 102, + 23, + -80, + -47, + 116, + 87, + -50, + 27, + -31, + -10, + -13, + 117, + -88, + -27, + -93, + -98, + -39, + 30, + -109, + -114, + 5, + -15, + 98, + -82, + 81, + 83, + 118, + 30, + -118, + -12, + -95, + 121, + 125, + -13, + -88, + 75, + -85, + -56, + -126, + 82, + -59, + 48, + -81, + 67, + -63, + 81, + 24, + -83, + 95, + -44, + 103, + 3, + -40, + -13, + -41, + -29, + -60, + 1, + 65, + -4, + -110, + -40, + 34, + 118, + 51, + -76, + 75, + 70, + -51 }; + } + + @Override + public int size() { + return 1; + } + + @Override + public int dimension() { + return dimensions; + } + }; + + VectorSimilarityFunction similarityFunction = VectorSimilarityFunction.MAXIMUM_INNER_PRODUCT; + + ES816BinaryFlatVectorsScorer.BinarizedRandomVectorScorer scorer = new ES816BinaryFlatVectorsScorer.BinarizedRandomVectorScorer( + queryVector, + targetVectors, + similarityFunction + ); + + assertEquals(132.30249f, scorer.score(0), 0.0001f); + } +} diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsFormatTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsFormatTests.java new file mode 100644 index 0000000000000..0892436891ff1 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816BinaryQuantizedVectorsFormatTests.java @@ -0,0 +1,175 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.lucene912.Lucene912Codec; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.KnnFloatVectorQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TotalHits; +import org.apache.lucene.store.Directory; +import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; +import org.elasticsearch.common.logging.LogConfigurator; + +import java.io.IOException; +import java.util.Locale; + +import static java.lang.String.format; +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.oneOf; + +public class ES816BinaryQuantizedVectorsFormatTests extends BaseKnnVectorsFormatTestCase { + + static { + LogConfigurator.loadLog4jPlugins(); + LogConfigurator.configureESLogging(); // native access requires logging to be initialized + } + + @Override + protected Codec getCodec() { + return new Lucene912Codec() { + @Override + public KnnVectorsFormat getKnnVectorsFormatForField(String field) { + return new ES816BinaryQuantizedVectorsFormat(); + } + }; + } + + public void testSearch() throws Exception { + String fieldName = "field"; + int numVectors = random().nextInt(99, 500); + int dims = random().nextInt(4, 65); + float[] vector = randomVector(dims); + VectorSimilarityFunction similarityFunction = randomSimilarity(); + KnnFloatVectorField knnField = new KnnFloatVectorField(fieldName, vector, similarityFunction); + IndexWriterConfig iwc = newIndexWriterConfig(); + try (Directory dir = newDirectory()) { + try (IndexWriter w = new IndexWriter(dir, iwc)) { + for (int i = 0; i < numVectors; i++) { + Document doc = new Document(); + knnField.setVectorValue(randomVector(dims)); + doc.add(knnField); + w.addDocument(doc); + } + w.commit(); + + try (IndexReader reader = DirectoryReader.open(w)) { + IndexSearcher searcher = new IndexSearcher(reader); + final int k = random().nextInt(5, 50); + float[] queryVector = randomVector(dims); + Query q = new KnnFloatVectorQuery(fieldName, queryVector, k); + TopDocs collectedDocs = searcher.search(q, k); + assertEquals(k, collectedDocs.totalHits.value); + assertEquals(TotalHits.Relation.EQUAL_TO, collectedDocs.totalHits.relation); + } + } + } + } + + public void testToString() { + FilterCodec customCodec = new FilterCodec("foo", Codec.getDefault()) { + @Override + public KnnVectorsFormat knnVectorsFormat() { + return new ES816BinaryQuantizedVectorsFormat(); + } + }; + String expectedPattern = "ES816BinaryQuantizedVectorsFormat(" + + "name=ES816BinaryQuantizedVectorsFormat, " + + "flatVectorScorer=ES816BinaryFlatVectorsScorer(nonQuantizedDelegate=%s()))"; + var defaultScorer = format(Locale.ROOT, expectedPattern, "DefaultFlatVectorScorer"); + var memSegScorer = format(Locale.ROOT, expectedPattern, "Lucene99MemorySegmentFlatVectorsScorer"); + assertThat(customCodec.knnVectorsFormat().toString(), is(oneOf(defaultScorer, memSegScorer))); + } + + @Override + public void testRandomWithUpdatesAndGraph() { + // graph not supported + } + + @Override + public void testSearchWithVisitedLimit() { + // visited limit is not respected, as it is brute force search + } + + public void testQuantizedVectorsWriteAndRead() throws IOException { + String fieldName = "field"; + int numVectors = random().nextInt(99, 500); + int dims = random().nextInt(4, 65); + + float[] vector = randomVector(dims); + VectorSimilarityFunction similarityFunction = randomSimilarity(); + KnnFloatVectorField knnField = new KnnFloatVectorField(fieldName, vector, similarityFunction); + try (Directory dir = newDirectory()) { + try (IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) { + for (int i = 0; i < numVectors; i++) { + Document doc = new Document(); + knnField.setVectorValue(randomVector(dims)); + doc.add(knnField); + w.addDocument(doc); + if (i % 101 == 0) { + w.commit(); + } + } + w.commit(); + w.forceMerge(1); + + try (IndexReader reader = DirectoryReader.open(w)) { + LeafReader r = getOnlyLeafReader(reader); + FloatVectorValues vectorValues = r.getFloatVectorValues(fieldName); + assertEquals(vectorValues.size(), numVectors); + OffHeapBinarizedVectorValues qvectorValues = ((ES816BinaryQuantizedVectorsReader.BinarizedVectorValues) vectorValues) + .getQuantizedVectorValues(); + float[] centroid = qvectorValues.getCentroid(); + assertEquals(centroid.length, dims); + + int descritizedDimension = BQVectorUtils.discretize(dims, 64); + BinaryQuantizer quantizer = new BinaryQuantizer(dims, descritizedDimension, similarityFunction); + byte[] expectedVector = new byte[BQVectorUtils.discretize(dims, 64) / 8]; + if (similarityFunction == VectorSimilarityFunction.COSINE) { + vectorValues = new ES816BinaryQuantizedVectorsWriter.NormalizedFloatVectorValues(vectorValues); + } + + while (vectorValues.nextDoc() != NO_MORE_DOCS) { + float[] corrections = quantizer.quantizeForIndex(vectorValues.vectorValue(), expectedVector, centroid); + assertArrayEquals(expectedVector, qvectorValues.vectorValue()); + assertEquals(corrections.length, qvectorValues.getCorrectiveTerms().length); + for (int i = 0; i < corrections.length; i++) { + assertEquals(corrections[i], qvectorValues.getCorrectiveTerms()[i], 0.00001f); + } + } + } + } + } + } +} diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816HnswBinaryQuantizedVectorsFormatTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816HnswBinaryQuantizedVectorsFormatTests.java new file mode 100644 index 0000000000000..f607de57e1fd5 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/ES816HnswBinaryQuantizedVectorsFormatTests.java @@ -0,0 +1,126 @@ +/* + * @notice + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. + */ +package org.elasticsearch.index.codec.vectors; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.lucene912.Lucene912Codec; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.Directory; +import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; +import org.apache.lucene.util.SameThreadExecutorService; +import org.elasticsearch.common.logging.LogConfigurator; + +import java.util.Arrays; +import java.util.Locale; + +import static java.lang.String.format; +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.oneOf; + +public class ES816HnswBinaryQuantizedVectorsFormatTests extends BaseKnnVectorsFormatTestCase { + + static { + LogConfigurator.loadLog4jPlugins(); + LogConfigurator.configureESLogging(); // native access requires logging to be initialized + } + + @Override + protected Codec getCodec() { + return new Lucene912Codec() { + @Override + public KnnVectorsFormat getKnnVectorsFormatForField(String field) { + return new ES816HnswBinaryQuantizedVectorsFormat(); + } + }; + } + + public void testToString() { + FilterCodec customCodec = new FilterCodec("foo", Codec.getDefault()) { + @Override + public KnnVectorsFormat knnVectorsFormat() { + return new ES816HnswBinaryQuantizedVectorsFormat(10, 20, 1, null); + } + }; + String expectedPattern = + "ES816HnswBinaryQuantizedVectorsFormat(name=ES816HnswBinaryQuantizedVectorsFormat, maxConn=10, beamWidth=20," + + " flatVectorFormat=ES816BinaryQuantizedVectorsFormat(name=ES816BinaryQuantizedVectorsFormat," + + " flatVectorScorer=ES816BinaryFlatVectorsScorer(nonQuantizedDelegate=%s())))"; + + var defaultScorer = format(Locale.ROOT, expectedPattern, "DefaultFlatVectorScorer"); + var memSegScorer = format(Locale.ROOT, expectedPattern, "Lucene99MemorySegmentFlatVectorsScorer"); + assertThat(customCodec.knnVectorsFormat().toString(), is(oneOf(defaultScorer, memSegScorer))); + } + + public void testSingleVectorCase() throws Exception { + float[] vector = randomVector(random().nextInt(12, 500)); + for (VectorSimilarityFunction similarityFunction : VectorSimilarityFunction.values()) { + try (Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) { + Document doc = new Document(); + doc.add(new KnnFloatVectorField("f", vector, similarityFunction)); + w.addDocument(doc); + w.commit(); + try (IndexReader reader = DirectoryReader.open(w)) { + LeafReader r = getOnlyLeafReader(reader); + FloatVectorValues vectorValues = r.getFloatVectorValues("f"); + assert (vectorValues.size() == 1); + while (vectorValues.nextDoc() != NO_MORE_DOCS) { + assertArrayEquals(vector, vectorValues.vectorValue(), 0.00001f); + } + TopDocs td = r.searchNearestVectors("f", randomVector(vector.length), 1, null, Integer.MAX_VALUE); + assertEquals(1, td.totalHits.value); + assertTrue(td.scoreDocs[0].score >= 0); + } + } + } + } + + public void testLimits() { + expectThrows(IllegalArgumentException.class, () -> new ES816HnswBinaryQuantizedVectorsFormat(-1, 20)); + expectThrows(IllegalArgumentException.class, () -> new ES816HnswBinaryQuantizedVectorsFormat(0, 20)); + expectThrows(IllegalArgumentException.class, () -> new ES816HnswBinaryQuantizedVectorsFormat(20, 0)); + expectThrows(IllegalArgumentException.class, () -> new ES816HnswBinaryQuantizedVectorsFormat(20, -1)); + expectThrows(IllegalArgumentException.class, () -> new ES816HnswBinaryQuantizedVectorsFormat(512 + 1, 20)); + expectThrows(IllegalArgumentException.class, () -> new ES816HnswBinaryQuantizedVectorsFormat(20, 3201)); + expectThrows( + IllegalArgumentException.class, + () -> new ES816HnswBinaryQuantizedVectorsFormat(20, 100, 1, new SameThreadExecutorService()) + ); + } + + // Ensures that all expected vector similarity functions are translatable in the format. + public void testVectorSimilarityFuncs() { + // This does not necessarily have to be all similarity functions, but + // differences should be considered carefully. + var expectedValues = Arrays.stream(VectorSimilarityFunction.values()).toList(); + assertEquals(Lucene99HnswVectorsReader.SIMILARITY_FUNCTIONS, expectedValues); + } +} diff --git a/server/src/test/java/org/elasticsearch/index/mapper/IgnoredSourceFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/IgnoredSourceFieldMapperTests.java index 8c65424fb8560..205ff08c397b2 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/IgnoredSourceFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/IgnoredSourceFieldMapperTests.java @@ -532,6 +532,38 @@ public void testIndexStoredArraySourceRootValueArrayDisabled() throws IOExceptio {"bool_value":true,"int_value":[10,20,30]}""", syntheticSource); } + public void testIndexStoredArraySourceSingleLeafElement() throws IOException { + DocumentMapper documentMapper = createMapperServiceWithStoredArraySource(syntheticSourceMapping(b -> { + b.startObject("int_value").field("type", "integer").endObject(); + })).documentMapper(); + var syntheticSource = syntheticSource(documentMapper, b -> b.array("int_value", new int[] { 10 })); + assertEquals("{\"int_value\":10}", syntheticSource); + ParsedDocument doc = documentMapper.parse(source(syntheticSource)); + assertNull(doc.rootDoc().getField("_ignored_source")); + } + + public void testIndexStoredArraySourceSingleLeafElementAndNull() throws IOException { + DocumentMapper documentMapper = createMapperServiceWithStoredArraySource(syntheticSourceMapping(b -> { + b.startObject("value").field("type", "keyword").endObject(); + })).documentMapper(); + var syntheticSource = syntheticSource(documentMapper, b -> b.array("value", new String[] { "foo", null })); + assertEquals("{\"value\":[\"foo\",null]}", syntheticSource); + } + + public void testIndexStoredArraySourceSingleObjectElement() throws IOException { + DocumentMapper documentMapper = createMapperServiceWithStoredArraySource(syntheticSourceMapping(b -> { + b.startObject("path").startObject("properties"); + { + b.startObject("int_value").field("type", "integer").endObject(); + } + b.endObject().endObject(); + })).documentMapper(); + var syntheticSource = syntheticSource(documentMapper, b -> { + b.startArray("path").startObject().field("int_value", 10).endObject().endArray(); + }); + assertEquals("{\"path\":[{\"int_value\":10}]}", syntheticSource); + } + public void testFieldStoredArraySourceRootValueArray() throws IOException { DocumentMapper documentMapper = createMapperService(syntheticSourceMapping(b -> { b.startObject("int_value").field("type", "integer").field(Mapper.SYNTHETIC_SOURCE_KEEP_PARAM, "arrays").endObject(); diff --git a/server/src/test/java/org/elasticsearch/ingest/SimulateIngestServiceTests.java b/server/src/test/java/org/elasticsearch/ingest/SimulateIngestServiceTests.java index 332a04e40e43d..3b3f5bdc747b5 100644 --- a/server/src/test/java/org/elasticsearch/ingest/SimulateIngestServiceTests.java +++ b/server/src/test/java/org/elasticsearch/ingest/SimulateIngestServiceTests.java @@ -65,7 +65,7 @@ public void testGetPipeline() { ingestService.innerUpdatePipelines(ingestMetadata); { // First we make sure that if there are no substitutions that we get our original pipeline back: - SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(null, null); + SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(null, null, null); SimulateIngestService simulateIngestService = new SimulateIngestService(ingestService, simulateBulkRequest); Pipeline pipeline = simulateIngestService.getPipeline("pipeline1"); assertThat(pipeline.getProcessors(), contains(transformedMatch(Processor::getType, equalTo("processor1")))); @@ -83,7 +83,7 @@ public void testGetPipeline() { ); pipelineSubstitutions.put("pipeline2", newHashMap("processors", List.of(newHashMap("processor3", Collections.emptyMap())))); - SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(pipelineSubstitutions, null); + SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(pipelineSubstitutions, null, null); SimulateIngestService simulateIngestService = new SimulateIngestService(ingestService, simulateBulkRequest); Pipeline pipeline1 = simulateIngestService.getPipeline("pipeline1"); assertThat( @@ -103,7 +103,7 @@ public void testGetPipeline() { */ Map> pipelineSubstitutions = new HashMap<>(); pipelineSubstitutions.put("pipeline2", newHashMap("processors", List.of(newHashMap("processor3", Collections.emptyMap())))); - SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(pipelineSubstitutions, null); + SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(pipelineSubstitutions, null, null); SimulateIngestService simulateIngestService = new SimulateIngestService(ingestService, simulateBulkRequest); Pipeline pipeline1 = simulateIngestService.getPipeline("pipeline1"); assertThat(pipeline1.getProcessors(), contains(transformedMatch(Processor::getType, equalTo("processor1")))); diff --git a/server/src/test/java/org/elasticsearch/snapshots/SnapshotShutdownProgressTrackerTests.java b/server/src/test/java/org/elasticsearch/snapshots/SnapshotShutdownProgressTrackerTests.java new file mode 100644 index 0000000000000..fbf742ae2ea57 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/snapshots/SnapshotShutdownProgressTrackerTests.java @@ -0,0 +1,407 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.snapshots; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.cluster.coordination.Coordinator; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.concurrent.DeterministicTaskQueue; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.index.snapshots.IndexShardSnapshotStatus; +import org.elasticsearch.repositories.ShardGeneration; +import org.elasticsearch.repositories.ShardSnapshotResult; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.MockLog; +import org.elasticsearch.test.junit.annotations.TestLogging; +import org.elasticsearch.threadpool.ThreadPool; +import org.junit.Before; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class SnapshotShutdownProgressTrackerTests extends ESTestCase { + private static final Logger logger = LogManager.getLogger(SnapshotShutdownProgressTrackerTests.class); + + final Settings settings = Settings.builder() + .put( + SnapshotShutdownProgressTracker.SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING.getKey(), + TimeValue.timeValueMillis(500) + ) + .build(); + final Settings disabledTrackerLoggingSettings = Settings.builder() + .put(SnapshotShutdownProgressTracker.SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING.getKey(), TimeValue.MINUS_ONE) + .build(); + ClusterSettings clusterSettings = new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); + + DeterministicTaskQueue deterministicTaskQueue; + + // Construction parameters for the Tracker. + ThreadPool testThreadPool; + private final Supplier getLocalNodeIdSupplier = () -> "local-node-id-for-test"; + private final BiConsumer, Consumer> addSettingsUpdateConsumerNoOp = (setting, updateMethod) -> {}; + + // Set up some dummy shard snapshot information to feed the Tracker. + private final ShardId dummyShardId = new ShardId(new Index("index-name-for-test", "index-uuid-for-test"), 0); + private final Snapshot dummySnapshot = new Snapshot( + "snapshot-repo-name-for-test", + new SnapshotId("snapshot-name-for-test", "snapshot-uuid-for-test") + ); + Function dummyShardSnapshotStatusSupplier = (stage) -> { + var shardGen = new ShardGeneration("shard-gen-string-for-test"); + IndexShardSnapshotStatus newStatus = IndexShardSnapshotStatus.newInitializing(new ShardGeneration("shard-gen-string-for-test")); + switch (stage) { + case DONE -> { + newStatus.moveToStarted(0L, 1, 10, 2L, 20L); + newStatus.moveToFinalize(); + newStatus.moveToDone(10L, new ShardSnapshotResult(shardGen, ByteSizeValue.MINUS_ONE, 2)); + } + case ABORTED -> newStatus.abortIfNotCompleted("snapshot-aborted-for-test", (listener) -> {}); + case FAILURE -> newStatus.moveToFailed(300, "shard-snapshot-failure-string for-test"); + case PAUSED -> { + newStatus.pauseIfNotCompleted((listener) -> {}); + newStatus.moveToUnsuccessful(IndexShardSnapshotStatus.Stage.PAUSED, "shard-paused-string-for-test", 100L); + } + default -> newStatus.pauseIfNotCompleted((listener) -> {}); + } + return newStatus; + }; + + @Before + public void setUpThreadPool() { + deterministicTaskQueue = new DeterministicTaskQueue(); + testThreadPool = deterministicTaskQueue.getThreadPool(); + } + + /** + * Increments the tracker's shard snapshot completion stats. Evenly adds to each type of {@link IndexShardSnapshotStatus.Stage} stat + * supported by the tracker. + */ + void simulateShardSnapshotsCompleting(SnapshotShutdownProgressTracker tracker, int numShardSnapshots) { + for (int i = 0; i < numShardSnapshots; ++i) { + tracker.incNumberOfShardSnapshotsInProgress(dummyShardId, dummySnapshot); + IndexShardSnapshotStatus status; + switch (i % 4) { + case 0 -> status = dummyShardSnapshotStatusSupplier.apply(IndexShardSnapshotStatus.Stage.DONE); + case 1 -> status = dummyShardSnapshotStatusSupplier.apply(IndexShardSnapshotStatus.Stage.ABORTED); + case 2 -> status = dummyShardSnapshotStatusSupplier.apply(IndexShardSnapshotStatus.Stage.FAILURE); + case 3 -> status = dummyShardSnapshotStatusSupplier.apply(IndexShardSnapshotStatus.Stage.PAUSED); + // decNumberOfShardSnapshotsInProgress will throw an assertion if this value is ever set. + default -> status = dummyShardSnapshotStatusSupplier.apply(IndexShardSnapshotStatus.Stage.PAUSING); + } + logger.info("---> Generated shard snapshot status in stage (" + status.getStage() + ") for switch case (" + (i % 4) + ")"); + tracker.decNumberOfShardSnapshotsInProgress(dummyShardId, dummySnapshot, status); + } + } + + public void testTrackerLogsStats() { + SnapshotShutdownProgressTracker tracker = new SnapshotShutdownProgressTracker( + getLocalNodeIdSupplier, + clusterSettings, + testThreadPool + ); + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.UnseenEventExpectation( + "unset shard snapshot completion stats", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.INFO, + "*snapshots to pause [-1]*Done [0]; Failed [0]; Aborted [0]; Paused [0]*" + ) + ); + + // Simulate starting shutdown -- should reset the completion stats and start logging + tracker.onClusterStateAddShutdown(); + + // Wait for the initial progress log message with no shard snapshot completions. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "shard snapshot completed stats", + SnapshotShutdownProgressTracker.class.getCanonicalName(), + Level.INFO, + "*Shard snapshot completion stats since shutdown began: Done [2]; Failed [1]; Aborted [1]; Paused [1]*" + ) + ); + + // Simulate updating the shard snapshot completion stats. + simulateShardSnapshotsCompleting(tracker, 5); + tracker.assertStatsForTesting(2, 1, 1, 1); + + // Wait for the next periodic log message to include the new completion stats. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + } + + /** + * Test that {@link SnapshotShutdownProgressTracker#SNAPSHOT_PROGRESS_DURING_SHUTDOWN_LOG_INTERVAL_SETTING} can be disabled by setting + * a value of {@link TimeValue#MINUS_ONE}. This will disable progress logging, though the Tracker will continue to track things. + */ + @TestLogging( + value = "org.elasticsearch.snapshots.SnapshotShutdownProgressTracker:DEBUG", + reason = "Test checks for DEBUG-level log message" + ) + public void testTrackerProgressLoggingIntervalSettingCanBeDisabled() { + ClusterSettings clusterSettingsDisabledLogging = new ClusterSettings( + disabledTrackerLoggingSettings, + ClusterSettings.BUILT_IN_CLUSTER_SETTINGS + ); + SnapshotShutdownProgressTracker tracker = new SnapshotShutdownProgressTracker( + getLocalNodeIdSupplier, + clusterSettingsDisabledLogging, + testThreadPool + ); + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "disabled logging message", + SnapshotShutdownProgressTracker.class.getName(), + Level.DEBUG, + "Snapshot progress logging during shutdown is disabled" + ) + ); + mockLog.addExpectation( + new MockLog.UnseenEventExpectation( + "no progress logging message", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "Current active shard snapshot stats on data node*" + ) + ); + + // Simulate starting shutdown -- no logging will start because the Tracker logging is disabled. + tracker.onClusterStateAddShutdown(); + tracker.onClusterStatePausingSetForAllShardSnapshots(); + + // Wait for the logging disabled message. + deterministicTaskQueue.runAllTasks(); + mockLog.awaitAllExpectationsMatched(); + } + } + + @TestLogging( + value = "org.elasticsearch.snapshots.SnapshotShutdownProgressTracker:DEBUG", + reason = "Test checks for DEBUG-level log message" + ) + public void testTrackerIntervalSettingDynamically() { + ClusterSettings clusterSettingsDisabledLogging = new ClusterSettings( + disabledTrackerLoggingSettings, + ClusterSettings.BUILT_IN_CLUSTER_SETTINGS + ); + SnapshotShutdownProgressTracker tracker = new SnapshotShutdownProgressTracker( + getLocalNodeIdSupplier, + clusterSettingsDisabledLogging, + testThreadPool + ); + // Re-enable the progress logging + clusterSettingsDisabledLogging.applySettings(settings); + + // Check that the logging is active. + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.UnseenEventExpectation( + "disabled logging message", + SnapshotShutdownProgressTracker.class.getName(), + Level.DEBUG, + "Snapshot progress logging during shutdown is disabled" + ) + ); + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "progress logging message", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "Current active shard snapshot stats on data node*" + ) + ); + + // Simulate starting shutdown -- progress logging should begin. + tracker.onClusterStateAddShutdown(); + tracker.onClusterStatePausingSetForAllShardSnapshots(); + + // Wait for the progress logging message + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + } + + public void testTrackerPauseTimestamp() { + SnapshotShutdownProgressTracker tracker = new SnapshotShutdownProgressTracker( + getLocalNodeIdSupplier, + clusterSettings, + testThreadPool + ); + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "pausing timestamp should be set", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "*Finished signalling shard snapshots to pause at [" + testThreadPool.relativeTimeInMillis() + "]*" + ) + ); + + // Simulate starting shutdown -- start logging. + tracker.onClusterStateAddShutdown(); + + // Set a pausing complete timestamp. + tracker.onClusterStatePausingSetForAllShardSnapshots(); + + // Wait for the first log message to ensure the pausing timestamp was set. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + } + + public void testTrackerRequestsToMaster() { + SnapshotShutdownProgressTracker tracker = new SnapshotShutdownProgressTracker( + getLocalNodeIdSupplier, + clusterSettings, + testThreadPool + ); + Snapshot snapshot = new Snapshot("repositoryName", new SnapshotId("snapshotName", "snapshotUUID")); + ShardId shardId = new ShardId(new Index("indexName", "indexUUID"), 0); + + // Simulate starting shutdown -- start logging. + tracker.onClusterStateAddShutdown(); + + // Set a pausing complete timestamp. + tracker.onClusterStatePausingSetForAllShardSnapshots(); + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "one master status update request", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "*master node reply to status update request [1]*" + ) + ); + + tracker.trackRequestSentToMaster(snapshot, shardId); + + // Wait for the first log message to ensure the pausing timestamp was set. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "no master status update requests", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "*master node reply to status update request [0]*" + ) + ); + + tracker.releaseRequestSentToMaster(snapshot, shardId); + + // Wait for the first log message to ensure the pausing timestamp was set. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + } + + public void testTrackerClearShutdown() { + SnapshotShutdownProgressTracker tracker = new SnapshotShutdownProgressTracker( + getLocalNodeIdSupplier, + clusterSettings, + testThreadPool + ); + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.UnseenEventExpectation( + "pausing timestamp should be unset", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "*Finished signalling shard snapshots to pause at [-1]*" + ) + ); + + // Simulate starting shutdown -- start logging. + tracker.onClusterStateAddShutdown(); + + // Set a pausing complete timestamp. + tracker.onClusterStatePausingSetForAllShardSnapshots(); + + // Wait for the first log message to ensure the pausing timestamp was set. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "logging completed shard snapshot stats", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "*Done [2]; Failed [2]; Aborted [2]; Paused [1]*" + ) + ); + + // Simulate updating the shard snapshot completion stats. + simulateShardSnapshotsCompleting(tracker, 7); + tracker.assertStatsForTesting(2, 2, 2, 1); + + // Wait for the first log message to ensure the pausing timestamp was set. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + + // Clear start and pause timestamps + tracker.onClusterStateRemoveShutdown(); + + try (var mockLog = MockLog.capture(Coordinator.class, SnapshotShutdownProgressTracker.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "completed shard snapshot stats are reset", + SnapshotShutdownProgressTracker.class.getName(), + Level.INFO, + "*Done [0]; Failed [0]; Aborted [0]; Paused [0]" + ) + ); + + // Start logging again and check that the pause timestamp was reset from the last time. + tracker.onClusterStateAddShutdown(); + + // Wait for the first log message to ensure the pausing timestamp was set. + deterministicTaskQueue.advanceTime(); + deterministicTaskQueue.runAllRunnableTasks(); + mockLog.awaitAllExpectationsMatched(); + } + + } +} diff --git a/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java b/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java index a24bd91206ac0..38b3dd4bd7e30 100644 --- a/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java +++ b/test/external-modules/esql-heap-attack/src/javaRestTest/java/org/elasticsearch/xpack/esql/heap_attack/HeapAttackIT.java @@ -166,7 +166,7 @@ public void testSortByManyLongsTooMuchMemoryAsync() throws IOException { "error", matchesMap().extraOk() .entry("bytes_wanted", greaterThan(1000)) - .entry("reason", matchesRegex("\\[request] Data too large, data for \\[topn] would .+")) + .entry("reason", matchesRegex("\\[request] Data too large, data for \\[(topn|esql_block_factory)] would .+")) .entry("durability", "TRANSIENT") .entry("type", "circuit_breaking_exception") .entry("bytes_limit", greaterThan(1000)) diff --git a/test/framework/src/main/java/org/elasticsearch/cluster/coordination/AbstractCoordinatorTestCase.java b/test/framework/src/main/java/org/elasticsearch/cluster/coordination/AbstractCoordinatorTestCase.java index 262dba80caa24..ddfa61b53a0af 100644 --- a/test/framework/src/main/java/org/elasticsearch/cluster/coordination/AbstractCoordinatorTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/cluster/coordination/AbstractCoordinatorTestCase.java @@ -1250,7 +1250,7 @@ ClusterNode restartedNode( .roles(localNode.isMasterNode() && DiscoveryNode.isMasterNode(settings) ? ALL_ROLES_EXCEPT_VOTING_ONLY : emptySet()) .build(); try { - return new ClusterNode( + final var restartedNode = new ClusterNode( nodeIndex, newLocalNode, (node, threadPool) -> createPersistedStateFromExistingState( @@ -1263,6 +1263,8 @@ ClusterNode restartedNode( settings, nodeHealthService ); + restartedNode.blackholedRegisterOperations.addAll(blackholedRegisterOperations); + return restartedNode; } finally { clearableRecycler.clear(); } diff --git a/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java index ca26779f3376d..eef7fc4e50080 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperTestCase.java @@ -1580,7 +1580,11 @@ public void testSyntheticSourceKeepArrays() throws IOException { buildInput.accept(builder); builder.endObject(); String expected = Strings.toString(builder); - assertThat(syntheticSource(mapperAll, buildInput), equalTo(expected)); + String actual = syntheticSource(mapperAll, buildInput); + // Check for single-element array, the array source is not stored in this case. + if (expected.replace("[", "").replace("]", "").equals(actual) == false) { + assertThat(actual, equalTo(expected)); + } } @Override diff --git a/test/framework/src/main/java/org/elasticsearch/logsdb/datageneration/datasource/DefaultMappingParametersHandler.java b/test/framework/src/main/java/org/elasticsearch/logsdb/datageneration/datasource/DefaultMappingParametersHandler.java index 81bd80f464525..4b33f3fefcf1b 100644 --- a/test/framework/src/main/java/org/elasticsearch/logsdb/datageneration/datasource/DefaultMappingParametersHandler.java +++ b/test/framework/src/main/java/org/elasticsearch/logsdb/datageneration/datasource/DefaultMappingParametersHandler.java @@ -102,18 +102,12 @@ public DataSourceResponse.ObjectMappingParametersGenerator handle(DataSourceRequ // TODO enable subobjects: auto // It is disabled because it currently does not have auto flattening and that results in asserts being triggered when using // copy_to. - if (ESTestCase.randomBoolean()) { - parameters.put( - "subobjects", - ESTestCase.randomValueOtherThan( - ObjectMapper.Subobjects.AUTO, - () -> ESTestCase.randomFrom(ObjectMapper.Subobjects.values()) - ).toString() - ); - } + var subobjects = ESTestCase.randomValueOtherThan( + ObjectMapper.Subobjects.AUTO, + () -> ESTestCase.randomFrom(ObjectMapper.Subobjects.values()) + ); - if (request.parentSubobjects() == ObjectMapper.Subobjects.DISABLED - || parameters.getOrDefault("subobjects", "true").equals("false")) { + if (request.parentSubobjects() == ObjectMapper.Subobjects.DISABLED || subobjects == ObjectMapper.Subobjects.DISABLED) { // "enabled: false" is not compatible with subobjects: false // changing "dynamic" from parent context is not compatible with subobjects: false // changing subobjects value is not compatible with subobjects: false @@ -124,6 +118,9 @@ public DataSourceResponse.ObjectMappingParametersGenerator handle(DataSourceRequ return parameters; } + if (ESTestCase.randomBoolean()) { + parameters.put("subobjects", subobjects.toString()); + } if (ESTestCase.randomBoolean()) { parameters.put("dynamic", ESTestCase.randomFrom("true", "false", "strict", "runtime")); } diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java index 5a40816c94beb..87a834d6424b7 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java @@ -45,6 +45,7 @@ import org.elasticsearch.action.admin.indices.segments.IndicesSegmentResponse; import org.elasticsearch.action.admin.indices.segments.ShardSegments; import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequestBuilder; +import org.elasticsearch.action.admin.indices.stats.ShardStats; import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequestBuilder; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; @@ -1545,6 +1546,37 @@ protected final DocWriteResponse indexDoc(String index, String id, Object... sou return prepareIndex(index).setId(id).setSource(source).get(); } + /** + * Runs random indexing until each shard in the given index is at least minBytesPerShard in size. + * Force merges all cluster shards down to one segment, and then invokes refresh to ensure all shard data is visible for readers, + * before returning. + * + * @return The final {@link ShardStats} for all shards of the index. + */ + protected ShardStats[] indexAllShardsToAnEqualOrGreaterMinimumSize(final String indexName, long minBytesPerShard) { + while (true) { + indexRandom(false, indexName, scaledRandomIntBetween(100, 10000)); + forceMerge(); + refresh(); + + final ShardStats[] shardStats = indicesAdmin().prepareStats(indexName) + .clear() + .setStore(true) + .setTranslog(true) + .get() + .getShards(); + + var smallestShardSize = Arrays.stream(shardStats) + .mapToLong(it -> it.getStats().getStore().sizeInBytes()) + .min() + .orElseThrow(() -> new AssertionError("no shards")); + + if (smallestShardSize >= minBytesPerShard) { + return shardStats; + } + } + } + /** * Syntactic sugar for: *
diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java
index 5adf01a2a0e7d..aa72d3248812e 100644
--- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java
+++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/FeatureFlag.java
@@ -19,7 +19,6 @@ public enum FeatureFlag {
     TIME_SERIES_MODE("es.index_mode_feature_flag_registered=true", Version.fromString("8.0.0"), null),
     FAILURE_STORE_ENABLED("es.failure_store_feature_flag_enabled=true", Version.fromString("8.12.0"), null),
     CHUNKING_SETTINGS_ENABLED("es.inference_chunking_settings_feature_flag_enabled=true", Version.fromString("8.16.0"), null),
-    INFERENCE_SCALE_TO_ZERO("es.inference_scale_to_zero_feature_flag_enabled=true", Version.fromString("8.16.0"), null),
     INFERENCE_DEFAULT_ELSER("es.inference_default_elser_feature_flag_enabled=true", Version.fromString("8.16.0"), null);
 
     public final String systemProperty;
diff --git a/x-pack/plugin/build.gradle b/x-pack/plugin/build.gradle
index 158cccb1b6ea2..3e5aaea43a9b9 100644
--- a/x-pack/plugin/build.gradle
+++ b/x-pack/plugin/build.gradle
@@ -84,5 +84,6 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
   task.skipTest("security/10_forbidden/Test bulk response with invalid credentials", "warning does not exist for compatibility")
   task.skipTest("wildcard/30_ignore_above_synthetic_source/wildcard field type ignore_above", "Temporary until backported")
   task.skipTest("inference/inference_crud/Test get all", "Assertions on number of inference models break due to default configs")
+  task.skipTest("esql/60_usage/Basic ESQL usage output (telemetry)", "The telemetry output changed. We dropped a column. That's safe.")
 })
 
diff --git a/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java b/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java
index 08b4794b740d6..d0d6d5fa49c42 100644
--- a/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java
+++ b/x-pack/plugin/esql/qa/server/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/mixed/MixedClusterEsqlSpecIT.java
@@ -72,10 +72,6 @@ public MixedClusterEsqlSpecIT(
     protected void shouldSkipTest(String testName) throws IOException {
         super.shouldSkipTest(testName);
         assumeTrue("Test " + testName + " is skipped on " + bwcVersion, isEnabled(testName, instructions, bwcVersion));
-        assumeFalse(
-            "Skip META tests on mixed version clusters because we change it too quickly",
-            testCase.requiredCapabilities.contains("meta")
-        );
         if (mode == ASYNC) {
             assumeTrue("Async is not supported on " + bwcVersion, supportsAsync());
         }
diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java
index 8d54dc63598f0..3e799730f7269 100644
--- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java
+++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java
@@ -112,10 +112,6 @@ protected void shouldSkipTest(String testName) throws IOException {
         );
         assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains("inlinestats"));
         assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains("inlinestats_v2"));
-        assumeFalse(
-            "Skip META tests on mixed version clusters because we change it too quickly",
-            testCase.requiredCapabilities.contains("meta")
-        );
     }
 
     private TestFeatureService remoteFeaturesService() throws IOException {
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec
deleted file mode 100644
index 6e8d5fba67cee..0000000000000
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec
+++ /dev/null
@@ -1,552 +0,0 @@
-metaFunctionsSynopsis
-required_capability: date_nanos_type
-required_capability: meta
-
-meta functions | keep synopsis;
-
-synopsis:keyword
-"double|integer|long|unsigned_long abs(number:double|integer|long|unsigned_long)"
-"double acos(number:double|integer|long|unsigned_long)"
-"double asin(number:double|integer|long|unsigned_long)"
-"double atan(number:double|integer|long|unsigned_long)"
-"double atan2(y_coordinate:double|integer|long|unsigned_long, x_coordinate:double|integer|long|unsigned_long)"
-"double avg(number:double|integer|long)"
-"double|date bin(field:integer|long|double|date, buckets:integer|long|double|date_period|time_duration, ?from:integer|long|double|date|keyword|text, ?to:integer|long|double|date|keyword|text)"
-"double|date bucket(field:integer|long|double|date, buckets:integer|long|double|date_period|time_duration, ?from:integer|long|double|date|keyword|text, ?to:integer|long|double|date|keyword|text)"
-"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version case(condition:boolean, trueValue...:boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version)"
-"double cbrt(number:double|integer|long|unsigned_long)"
-"double|integer|long|unsigned_long ceil(number:double|integer|long|unsigned_long)"
-"boolean cidr_match(ip:ip, blockX...:keyword|text)"
-"boolean|cartesian_point|cartesian_shape|date|geo_point|geo_shape|integer|ip|keyword|long|text|version coalesce(first:boolean|cartesian_point|cartesian_shape|date|geo_point|geo_shape|integer|ip|keyword|long|text|version, ?rest...:boolean|cartesian_point|cartesian_shape|date|geo_point|geo_shape|integer|ip|keyword|long|text|version)"
-"keyword concat(string1:keyword|text, string2...:keyword|text)"
-"double cos(angle:double|integer|long|unsigned_long)"
-"double cosh(number:double|integer|long|unsigned_long)"
-"long count(?field:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version)"
-"long count_distinct(field:boolean|date|double|integer|ip|keyword|long|text|version, ?precision:integer|long|unsigned_long)"
-"integer date_diff(unit:keyword|text, startTimestamp:date, endTimestamp:date)"
-"long date_extract(datePart:keyword|text, date:date)"
-"keyword date_format(?dateFormat:keyword|text, date:date)"
-"date date_parse(?datePattern:keyword|text, dateString:keyword|text)"
-"date date_trunc(interval:date_period|time_duration, date:date)"
-double e()
-"boolean ends_with(str:keyword|text, suffix:keyword|text)"
-"double exp(number:double|integer|long|unsigned_long)"
-"double|integer|long|unsigned_long floor(number:double|integer|long|unsigned_long)"
-"keyword from_base64(string:keyword|text)"
-"boolean|date|double|integer|ip|keyword|long|text|version greatest(first:boolean|date|double|integer|ip|keyword|long|text|version, ?rest...:boolean|date|double|integer|ip|keyword|long|text|version)"
-"ip ip_prefix(ip:ip, prefixLengthV4:integer, prefixLengthV6:integer)"
-"boolean|date|double|integer|ip|keyword|long|text|version least(first:boolean|date|double|integer|ip|keyword|long|text|version, ?rest...:boolean|date|double|integer|ip|keyword|long|text|version)"
-"keyword left(string:keyword|text, length:integer)"
-"integer length(string:keyword|text)"
-"integer locate(string:keyword|text, substring:keyword|text, ?start:integer)"
-"double log(?base:integer|unsigned_long|long|double, number:integer|unsigned_long|long|double)"
-"double log10(number:double|integer|long|unsigned_long)"
-"keyword|text ltrim(string:keyword|text)"
-"boolean|double|integer|long|date|ip|keyword|text|long|version max(field:boolean|double|integer|long|date|ip|keyword|text|long|version)"
-"double median(number:double|integer|long)"
-"double median_absolute_deviation(number:double|integer|long)"
-"boolean|double|integer|long|date|ip|keyword|text|long|version min(field:boolean|double|integer|long|date|ip|keyword|text|long|version)"
-"boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version mv_append(field1:boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version, field2:boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version)"
-"double mv_avg(number:double|integer|long|unsigned_long)"
-"keyword mv_concat(string:text|keyword, delim:text|keyword)"
-"integer mv_count(field:boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version)"
-"boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version mv_dedupe(field:boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version)"
-"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version mv_first(field:boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version)"
-"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version mv_last(field:boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version)"
-"boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version mv_max(field:boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version)"
-"double|integer|long|unsigned_long mv_median(number:double|integer|long|unsigned_long)"
-"double|integer|long|unsigned_long mv_median_absolute_deviation(number:double|integer|long|unsigned_long)"
-"boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version mv_min(field:boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version)"
-"double|integer|long mv_percentile(number:double|integer|long, percentile:double|integer|long)"
-"double mv_pseries_weighted_sum(number:double, p:double)"
-"boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version mv_slice(field:boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version, start:integer, ?end:integer)"
-"boolean|date|double|integer|ip|keyword|long|text|version mv_sort(field:boolean|date|double|integer|ip|keyword|long|text|version, ?order:keyword)"
-"double|integer|long|unsigned_long mv_sum(number:double|integer|long|unsigned_long)"
-"keyword mv_zip(string1:keyword|text, string2:keyword|text, ?delim:keyword|text)"
-date now()
-"double percentile(number:double|integer|long, percentile:double|integer|long)"
-double pi()
-"double pow(base:double|integer|long|unsigned_long, exponent:double|integer|long|unsigned_long)"
-"keyword repeat(string:keyword|text, number:integer)"
-"keyword replace(string:keyword|text, regex:keyword|text, newString:keyword|text)"
-"keyword|text reverse(str:keyword|text)"
-"keyword right(string:keyword|text, length:integer)"
-"double|integer|long|unsigned_long round(number:double|integer|long|unsigned_long, ?decimals:integer)"
-"keyword|text rtrim(string:keyword|text)"
-"double signum(number:double|integer|long|unsigned_long)"
-"double sin(angle:double|integer|long|unsigned_long)"
-"double sinh(number:double|integer|long|unsigned_long)"
-"keyword space(number:integer)"
-"keyword split(string:keyword|text, delim:keyword|text)"
-"double sqrt(number:double|integer|long|unsigned_long)"
-"geo_point|cartesian_point st_centroid_agg(field:geo_point|cartesian_point)"
-"boolean st_contains(geomA:geo_point|cartesian_point|geo_shape|cartesian_shape, geomB:geo_point|cartesian_point|geo_shape|cartesian_shape)"
-"boolean st_disjoint(geomA:geo_point|cartesian_point|geo_shape|cartesian_shape, geomB:geo_point|cartesian_point|geo_shape|cartesian_shape)"
-"double st_distance(geomA:geo_point|cartesian_point, geomB:geo_point|cartesian_point)"
-"boolean st_intersects(geomA:geo_point|cartesian_point|geo_shape|cartesian_shape, geomB:geo_point|cartesian_point|geo_shape|cartesian_shape)"
-"boolean st_within(geomA:geo_point|cartesian_point|geo_shape|cartesian_shape, geomB:geo_point|cartesian_point|geo_shape|cartesian_shape)"
-"double st_x(point:geo_point|cartesian_point)"
-"double st_y(point:geo_point|cartesian_point)"
-"boolean starts_with(str:keyword|text, prefix:keyword|text)"
-"keyword substring(string:keyword|text, start:integer, ?length:integer)"
-"long|double sum(number:double|integer|long)"
-"double tan(angle:double|integer|long|unsigned_long)"
-"double tanh(number:double|integer|long|unsigned_long)"
-double tau()
-"keyword to_base64(string:keyword|text)"
-"boolean to_bool(field:boolean|keyword|text|double|long|unsigned_long|integer)"
-"boolean to_boolean(field:boolean|keyword|text|double|long|unsigned_long|integer)"
-"cartesian_point to_cartesianpoint(field:cartesian_point|keyword|text)"
-"cartesian_shape to_cartesianshape(field:cartesian_point|cartesian_shape|keyword|text)"
-"date_nanos to_date_nanos(field:date|date_nanos|keyword|text|double|long|unsigned_long)"
-"date_nanos to_datenanos(field:date|date_nanos|keyword|text|double|long|unsigned_long)"
-"date_period to_dateperiod(field:date_period|keyword|text)"
-"date to_datetime(field:date|date_nanos|keyword|text|double|long|unsigned_long|integer)"
-"double to_dbl(field:boolean|date|keyword|text|double|long|unsigned_long|integer|counter_double|counter_integer|counter_long)"
-"double to_degrees(number:double|integer|long|unsigned_long)"
-"double to_double(field:boolean|date|keyword|text|double|long|unsigned_long|integer|counter_double|counter_integer|counter_long)"
-"date to_dt(field:date|date_nanos|keyword|text|double|long|unsigned_long|integer)"
-"geo_point to_geopoint(field:geo_point|keyword|text)"
-"geo_shape to_geoshape(field:geo_point|geo_shape|keyword|text)"
-"integer to_int(field:boolean|date|keyword|text|double|long|unsigned_long|integer|counter_integer)"
-"integer to_integer(field:boolean|date|keyword|text|double|long|unsigned_long|integer|counter_integer)"
-"ip to_ip(field:ip|keyword|text)"
-"long to_long(field:boolean|date|date_nanos|keyword|text|double|long|unsigned_long|integer|counter_integer|counter_long)"
-"keyword|text to_lower(str:keyword|text)"
-"double to_radians(number:double|integer|long|unsigned_long)"
-"keyword to_str(field:boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version)"
-"keyword to_string(field:boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version)"
-"time_duration to_timeduration(field:time_duration|keyword|text)"
-"unsigned_long to_ul(field:boolean|date|keyword|text|double|long|unsigned_long|integer)"
-"unsigned_long to_ulong(field:boolean|date|keyword|text|double|long|unsigned_long|integer)"
-"unsigned_long to_unsigned_long(field:boolean|date|keyword|text|double|long|unsigned_long|integer)"
-"keyword|text to_upper(str:keyword|text)"
-"version to_ver(field:keyword|text|version)"
-"version to_version(field:keyword|text|version)"
-"boolean|double|integer|long|date|ip|keyword|text top(field:boolean|double|integer|long|date|ip|keyword|text, limit:integer, order:keyword)"
-"keyword|text trim(string:keyword|text)"
-"boolean|date|double|integer|ip|keyword|long|text|version values(field:boolean|date|double|integer|ip|keyword|long|text|version)"
-"double weighted_avg(number:double|integer|long, weight:double|integer|long)"
-;
-
-metaFunctionsArgs
-required_capability: meta
-required_capability: date_nanos_type
-
-  META functions
-| EVAL name = SUBSTRING(name, 0, 14)
-| KEEP name, argNames, argTypes, argDescriptions;
-
- name:keyword |          argNames:keyword          |                                               argTypes:keyword                                                                   |             argDescriptions:keyword
-abs           |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-acos          |number                              |"double|integer|long|unsigned_long"                                                                                               |Number between -1 and 1. If `null`, the function returns `null`.
-asin          |number                              |"double|integer|long|unsigned_long"                                                                                               |Number between -1 and 1. If `null`, the function returns `null`.
-atan          |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-atan2         |[y_coordinate, x_coordinate]        |["double|integer|long|unsigned_long", "double|integer|long|unsigned_long"]                                                        |[y coordinate. If `null`\, the function returns `null`., x coordinate. If `null`\, the function returns `null`.]
-avg           |number                              |"double|integer|long"                                                                                                             |[""]
-bin           |[field, buckets, from, to]          |["integer|long|double|date", "integer|long|double|date_period|time_duration", "integer|long|double|date|keyword|text", "integer|long|double|date|keyword|text"]  |[Numeric or date expression from which to derive buckets., Target number of buckets\, or desired bucket size if `from` and `to` parameters are omitted., Start of the range. Can be a number\, a date or a date expressed as a string., End of the range. Can be a number\, a date or a date expressed as a string.]
-bucket        |[field, buckets, from, to]          |["integer|long|double|date", "integer|long|double|date_period|time_duration", "integer|long|double|date|keyword|text", "integer|long|double|date|keyword|text"]  |[Numeric or date expression from which to derive buckets., Target number of buckets\, or desired bucket size if `from` and `to` parameters are omitted., Start of the range. Can be a number\, a date or a date expressed as a string., End of the range. Can be a number\, a date or a date expressed as a string.]
-case          |[condition, trueValue]              |[boolean, "boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"] |[A condition., The value that's returned when the corresponding condition is the first to evaluate to `true`. The default value is returned when no condition matches.]
-cbrt          |number                              |"double|integer|long|unsigned_long"                                                                                               |"Numeric expression. If `null`, the function returns `null`."
-ceil          |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-cidr_match    |[ip, blockX]                        |[ip, "keyword|text"]                                                                                                              |[IP address of type `ip` (both IPv4 and IPv6 are supported)., CIDR block to test the IP against.]
-coalesce      |first                               |"boolean|cartesian_point|cartesian_shape|date|geo_point|geo_shape|integer|ip|keyword|long|text|version"                                   |Expression to evaluate.
-concat        |[string1, string2]                  |["keyword|text", "keyword|text"]                                                                                                  |[Strings to concatenate., Strings to concatenate.]
-cos           |angle                               |"double|integer|long|unsigned_long"                                                                                               |An angle, in radians. If `null`, the function returns `null`.
-cosh          |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-count         |field                               |"boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version"                                |Expression that outputs values to be counted. If omitted, equivalent to `COUNT(*)` (the number of rows).
-count_distinct|[field, precision]                  |["boolean|date|double|integer|ip|keyword|long|text|version", "integer|long|unsigned_long"]                                        |[Column or literal for which to count the number of distinct values., Precision threshold. Refer to <>. The maximum supported value is 40000. Thresholds above this number will have the same effect as a threshold of 40000. The default value is 3000.]
-date_diff     |[unit, startTimestamp, endTimestamp]|["keyword|text", date, date]                                                                                                      |[Time difference unit, A string representing a start timestamp, A string representing an end timestamp]
-date_extract  |[datePart, date]                    |["keyword|text", date]                                                                                                            |[Part of the date to extract.  Can be: `aligned_day_of_week_in_month`\, `aligned_day_of_week_in_year`\, `aligned_week_of_month`\, `aligned_week_of_year`\, `ampm_of_day`\, `clock_hour_of_ampm`\, `clock_hour_of_day`\, `day_of_month`\, `day_of_week`\, `day_of_year`\, `epoch_day`\, `era`\, `hour_of_ampm`\, `hour_of_day`\, `instant_seconds`\, `micro_of_day`\, `micro_of_second`\, `milli_of_day`\, `milli_of_second`\, `minute_of_day`\, `minute_of_hour`\, `month_of_year`\, `nano_of_day`\, `nano_of_second`\, `offset_seconds`\, `proleptic_month`\, `second_of_day`\, `second_of_minute`\, `year`\, or `year_of_era`. Refer to https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html[java.time.temporal.ChronoField] for a description of these values.  If `null`\, the function returns `null`., Date expression. If `null`\, the function returns `null`.]
-date_format   |[dateFormat, date]                  |["keyword|text", date]                                                                                                            |[Date format (optional).  If no format is specified\, the `yyyy-MM-dd'T'HH:mm:ss.SSSZ` format is used. If `null`\, the function returns `null`., Date expression. If `null`\, the function returns `null`.]
-date_parse    |[datePattern, dateString]           |["keyword|text", "keyword|text"]                                                                                                  |[The date format. Refer to the https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/time/format/DateTimeFormatter.html[`DateTimeFormatter` documentation] for the syntax. If `null`\, the function returns `null`., Date expression as a string. If `null` or an empty string\, the function returns `null`.]
-date_trunc    |[interval, date]                    |["date_period|time_duration", date]                                                                                               |[Interval; expressed using the timespan literal syntax., Date expression]
-e             |null                                |null                                                                                                                              |null
-ends_with     |[str, suffix]                       |["keyword|text", "keyword|text"]                                                                                                  |[String expression. If `null`\, the function returns `null`., String expression. If `null`\, the function returns `null`.]
-exp           |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-floor         |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-from_base64   |string                              |"keyword|text"                                                                                                                    |A base64 string.
-greatest      |first                               |"boolean|date|double|integer|ip|keyword|long|text|version"                                                                        |First of the columns to evaluate.
-ip_prefix     |[ip, prefixLengthV4, prefixLengthV6]|[ip, integer, integer]                                                                                                            |[IP address of type `ip` (both IPv4 and IPv6 are supported)., Prefix length for IPv4 addresses., Prefix length for IPv6 addresses.]
-least         |first                               |"boolean|date|double|integer|ip|keyword|long|text|version"                                                                        |First of the columns to evaluate.
-left          |[string, length]                    |["keyword|text", integer]                                                                                                         |[The string from which to return a substring., The number of characters to return.]
-length        |string                              |"keyword|text"                                                                                                                    |String expression. If `null`, the function returns `null`.
-locate        |[string, substring, start]          |["keyword|text", "keyword|text", "integer"]                                                                                       |[An input string, A substring to locate in the input string, The start index]
-log           |[base, number]                      |["integer|unsigned_long|long|double", "integer|unsigned_long|long|double"]                                                        |["Base of logarithm. If `null`\, the function returns `null`. If not provided\, this function returns the natural logarithm (base e) of a value.", "Numeric expression. If `null`\, the function returns `null`."]
-log10         |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-ltrim         |string                              |"keyword|text"                                                                                                                    |String expression. If `null`, the function returns `null`.
-max           |field                               |"boolean|double|integer|long|date|ip|keyword|text|long|version"                                                                   |[""]
-median        |number                              |"double|integer|long"                                                                                                             |[""]
-median_absolut|number                              |"double|integer|long"                                                                                                             |[""]
-min           |field                               |"boolean|double|integer|long|date|ip|keyword|text|long|version"                                                                   |[""]
-mv_append     |[field1, field2]                    |["boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version", "boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version"]                 | ["", ""]
-mv_avg        |number                              |"double|integer|long|unsigned_long"                                                                                               |Multivalue expression.
-mv_concat     |[string, delim]                     |["text|keyword", "text|keyword"]                                                                                                  |[Multivalue expression., Delimiter.]
-mv_count      |field                               |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"      |Multivalue expression.
-mv_dedupe     |field                               |"boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version"                    |Multivalue expression.
-mv_first      |field                               |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"      |Multivalue expression.
-mv_last       |field                               |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"      |Multivalue expression.
-mv_max        |field                               |"boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version"                                               |Multivalue expression.
-mv_median     |number                              |"double|integer|long|unsigned_long"                                                                                               |Multivalue expression.
-mv_median_abso|number                              |"double|integer|long|unsigned_long"                                                                                               |Multivalue expression.
-mv_min        |field                               |"boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version"                                               |Multivalue expression.
-mv_percentile |[number, percentile]                |["double|integer|long", "double|integer|long"]                                                                                    |[Multivalue expression., The percentile to calculate. Must be a number between 0 and 100. Numbers out of range will return a null instead.]
-mv_pseries_wei|[number, p]                         |[double, double]                                                                                                                  |[Multivalue expression., It is a constant number that represents the 'p' parameter in the P-Series. It impacts every element's contribution to the weighted sum.]
-mv_slice      |[field, start, end]                 |["boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version", integer, integer]|[Multivalue expression. If `null`\, the function returns `null`., Start position. If `null`\, the function returns `null`. The start argument can be negative. An index of -1 is used to specify the last value in the list., End position(included). Optional; if omitted\, the position at `start` is returned. The end argument can be negative. An index of -1 is used to specify the last value in the list.]
-mv_sort       |[field, order]                      |["boolean|date|double|integer|ip|keyword|long|text|version", keyword]                                                             |[Multivalue expression. If `null`\, the function returns `null`., Sort order. The valid options are ASC and DESC\, the default is ASC.]
-mv_sum        |number                              |"double|integer|long|unsigned_long"                                                                                               |Multivalue expression.
-mv_zip        |[string1, string2, delim]           |["keyword|text", "keyword|text", "keyword|text"]                                                                                  |[Multivalue expression., Multivalue expression., Delimiter. Optional; if omitted\, `\,` is used as a default delimiter.]
-now           |null                                |null                                                                                                                              |null
-percentile    |[number, percentile]                |["double|integer|long", "double|integer|long"]                                                                                    |[, ]
-pi            |null                                |null                                                                                                                              |null
-pow           |[base, exponent]                    |["double|integer|long|unsigned_long", "double|integer|long|unsigned_long"]                                                        |["Numeric expression for the base. If `null`\, the function returns `null`.", "Numeric expression for the exponent. If `null`\, the function returns `null`."]
-repeat        |[string, number]                    |["keyword|text", integer]                                                                                                         |[String expression., Number times to repeat.]
-replace       |[string, regex, newString]          |["keyword|text", "keyword|text", "keyword|text"]                                                                                  |[String expression., Regular expression., Replacement string.]
-reverse       |str                                 |"keyword|text"                                                                                                                    |String expression. If `null`, the function returns `null`.
-right         |[string, length]                    |["keyword|text", integer]                                                                                                         |[The string from which to returns a substring., The number of characters to return.]
-round         |[number, decimals]                  |["double|integer|long|unsigned_long", integer]                                                                                    |["The numeric value to round. If `null`\, the function returns `null`.", "The number of decimal places to round to. Defaults to 0. If `null`\, the function returns `null`."]
-rtrim         |string                              |"keyword|text"                                                                                                                    |String expression. If `null`, the function returns `null`.
-signum        |number                              |"double|integer|long|unsigned_long"                                                                                               |"Numeric expression. If `null`, the function returns `null`."
-sin           |angle                               |"double|integer|long|unsigned_long"                                                                                               |An angle, in radians. If `null`, the function returns `null`.
-sinh          |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-space         |number                              |"integer"                                                                                                                         |Number of spaces in result.
-split         |[string, delim]                     |["keyword|text", "keyword|text"]                                                                                                  |[String expression. If `null`\, the function returns `null`., Delimiter. Only single byte delimiters are currently supported.]
-sqrt          |number                              |"double|integer|long|unsigned_long"                                                                                               |"Numeric expression. If `null`, the function returns `null`."
-st_centroid_ag|field                               |"geo_point|cartesian_point"                                                                                                       |[""]
-st_contains   |[geomA, geomB]                      |["geo_point|cartesian_point|geo_shape|cartesian_shape", "geo_point|cartesian_point|geo_shape|cartesian_shape"]                    |[Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`., Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`. The second parameter must also have the same coordinate system as the first. This means it is not possible to combine `geo_*` and `cartesian_*` parameters.]
-st_disjoint   |[geomA, geomB]                      |["geo_point|cartesian_point|geo_shape|cartesian_shape", "geo_point|cartesian_point|geo_shape|cartesian_shape"]                    |[Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`., Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`. The second parameter must also have the same coordinate system as the first. This means it is not possible to combine `geo_*` and `cartesian_*` parameters.]
-st_distance   |[geomA, geomB]                      |["geo_point|cartesian_point", "geo_point|cartesian_point"]                                                                        |[Expression of type `geo_point` or `cartesian_point`. If `null`\, the function returns `null`., Expression of type `geo_point` or `cartesian_point`. If `null`\, the function returns `null`. The second parameter must also have the same coordinate system as the first. This means it is not possible to combine `geo_point` and `cartesian_point` parameters.]
-st_intersects |[geomA, geomB]                      |["geo_point|cartesian_point|geo_shape|cartesian_shape", "geo_point|cartesian_point|geo_shape|cartesian_shape"]                    |[Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`., Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`. The second parameter must also have the same coordinate system as the first. This means it is not possible to combine `geo_*` and `cartesian_*` parameters.]
-st_within     |[geomA, geomB]                      |["geo_point|cartesian_point|geo_shape|cartesian_shape", "geo_point|cartesian_point|geo_shape|cartesian_shape"]                    |[Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`., Expression of type `geo_point`\, `cartesian_point`\, `geo_shape` or `cartesian_shape`. If `null`\, the function returns `null`. The second parameter must also have the same coordinate system as the first. This means it is not possible to combine `geo_*` and `cartesian_*` parameters.]
-st_x          |point                               |"geo_point|cartesian_point"                                                                                                       |Expression of type `geo_point` or `cartesian_point`. If `null`, the function returns `null`.
-st_y          |point                               |"geo_point|cartesian_point"                                                                                                       |Expression of type `geo_point` or `cartesian_point`. If `null`, the function returns `null`.
-starts_with   |[str, prefix]                       |["keyword|text", "keyword|text"]                                                                                                  |[String expression. If `null`\, the function returns `null`., String expression. If `null`\, the function returns `null`.]
-substring     |[string, start, length]             |["keyword|text", integer, integer]                                                                                                |[String expression. If `null`\, the function returns `null`., Start position., Length of the substring from the start position. Optional; if omitted\, all positions after `start` are returned.]
-sum           |number                              |"double|integer|long"                                                                                                             |[""]
-tan           |angle                               |"double|integer|long|unsigned_long"                                                                                               |An angle, in radians. If `null`, the function returns `null`.
-tanh          |number                              |"double|integer|long|unsigned_long"                                                                                               |Numeric expression. If `null`, the function returns `null`.
-tau           |null                                |null                                                                                                                              |null
-to_base64     |string                              |"keyword|text"                                                                                                                    |A string.
-to_bool       |field                               |"boolean|keyword|text|double|long|unsigned_long|integer"                                                                          |Input value. The input can be a single- or multi-valued column or an expression.
-to_boolean    |field                               |"boolean|keyword|text|double|long|unsigned_long|integer"                                                                          |Input value. The input can be a single- or multi-valued column or an expression.
-to_cartesianpo|field                               |"cartesian_point|keyword|text"                                                                                                    |Input value. The input can be a single- or multi-valued column or an expression.
-to_cartesiansh|field                               |"cartesian_point|cartesian_shape|keyword|text"                                                                                    |Input value. The input can be a single- or multi-valued column or an expression.
-to_date_nanos |field                               |"date|date_nanos|keyword|text|double|long|unsigned_long"                                                                          |Input value. The input can be a single- or multi-valued column or an expression.
-to_datenanos  |field                               |"date|date_nanos|keyword|text|double|long|unsigned_long"                                                                          |Input value. The input can be a single- or multi-valued column or an expression.
-to_dateperiod |field                               |"date_period|keyword|text"                                                                                                        |Input value. The input is a valid constant date period expression.
-to_datetime   |field                               |"date|date_nanos|keyword|text|double|long|unsigned_long|integer"                                                                             |Input value. The input can be a single- or multi-valued column or an expression.
-to_dbl        |field                               |"boolean|date|keyword|text|double|long|unsigned_long|integer|counter_double|counter_integer|counter_long"                         |Input value. The input can be a single- or multi-valued column or an expression.
-to_degrees    |number                              |"double|integer|long|unsigned_long"                                                                                               |Input value. The input can be a single- or multi-valued column or an expression.
-to_double     |field                               |"boolean|date|keyword|text|double|long|unsigned_long|integer|counter_double|counter_integer|counter_long"                         |Input value. The input can be a single- or multi-valued column or an expression.
-to_dt         |field                               |"date|date_nanos|keyword|text|double|long|unsigned_long|integer"                                                                             |Input value. The input can be a single- or multi-valued column or an expression.
-to_geopoint   |field                               |"geo_point|keyword|text"                                                                                                          |Input value. The input can be a single- or multi-valued column or an expression.
-to_geoshape   |field                               |"geo_point|geo_shape|keyword|text"                                                                                                |Input value. The input can be a single- or multi-valued column or an expression.
-to_int        |field                               |"boolean|date|keyword|text|double|long|unsigned_long|integer|counter_integer"                                                     |Input value. The input can be a single- or multi-valued column or an expression.
-to_integer    |field                               |"boolean|date|keyword|text|double|long|unsigned_long|integer|counter_integer"                                                     |Input value. The input can be a single- or multi-valued column or an expression.
-to_ip         |field                               |"ip|keyword|text"                                                                                                                 |Input value. The input can be a single- or multi-valued column or an expression.
-to_long       |field                               |"boolean|date|date_nanos|keyword|text|double|long|unsigned_long|integer|counter_integer|counter_long"                                        |Input value. The input can be a single- or multi-valued column or an expression.
-to_lower      |str                                 |"keyword|text"                                                                                                                    |String expression. If `null`, the function returns `null`.
-to_radians    |number                              |"double|integer|long|unsigned_long"                                                                                               |Input value. The input can be a single- or multi-valued column or an expression.
-to_str        |field                               |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"      |Input value. The input can be a single- or multi-valued column or an expression.
-to_string     |field                               |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"      |Input value. The input can be a single- or multi-valued column or an expression.
-to_timeduratio|field                               |"time_duration|keyword|text"                                                                                                      |Input value. The input is a valid constant time duration expression.
-to_ul         |field                               |"boolean|date|keyword|text|double|long|unsigned_long|integer"                                                                     |Input value. The input can be a single- or multi-valued column or an expression.
-to_ulong      |field                               |"boolean|date|keyword|text|double|long|unsigned_long|integer"                                                                     |Input value. The input can be a single- or multi-valued column or an expression.
-to_unsigned_lo|field                               |"boolean|date|keyword|text|double|long|unsigned_long|integer"                                                                     |Input value. The input can be a single- or multi-valued column or an expression.
-to_upper      |str                                 |"keyword|text"                                                                                                                    |String expression. If `null`, the function returns `null`.
-to_ver        |field                               |"keyword|text|version"                                                                                                            |Input value. The input can be a single- or multi-valued column or an expression.
-to_version    |field                               |"keyword|text|version"                                                                                                            |Input value. The input can be a single- or multi-valued column or an expression.
-top           |[field, limit, order]               |["boolean|double|integer|long|date|ip|keyword|text", integer, keyword]                                                                         |[The field to collect the top values for.,The maximum number of values to collect.,The order to calculate the top values. Either `asc` or `desc`.]
-trim          |string                              |"keyword|text"                                                                                                                    |String expression. If `null`, the function returns `null`.
-values        |field                               |"boolean|date|double|integer|ip|keyword|long|text|version"                                                                        |[""]
-weighted_avg  |[number, weight]                    |["double|integer|long", "double|integer|long"]                                                                                    |[A numeric value., A numeric weight.]
-;
-
-metaFunctionsDescription
-required_capability: meta
-
-  META functions
-| EVAL name = SUBSTRING(name, 0, 14)
-| KEEP name, description
-;
-
- name:keyword | description:keyword
-abs           |Returns the absolute value.
-acos          |Returns the {wikipedia}/Inverse_trigonometric_functions[arccosine] of `n` as an angle, expressed in radians.
-asin          |Returns the {wikipedia}/Inverse_trigonometric_functions[arcsine] of the input numeric expression as an angle, expressed in radians.
-atan          |Returns the {wikipedia}/Inverse_trigonometric_functions[arctangent] of the input numeric expression as an angle, expressed in radians.
-atan2         |The {wikipedia}/Atan2[angle] between the positive x-axis and the ray from the origin to the point (x , y) in the Cartesian plane, expressed in radians.
-avg           |The average of a numeric field.
-bin           |Creates groups of values - buckets - out of a datetime or numeric input. The size of the buckets can either be provided directly, or chosen based on a recommended count and values range.
-bucket        |Creates groups of values - buckets - out of a datetime or numeric input. The size of the buckets can either be provided directly, or chosen based on a recommended count and values range.
-case          |Accepts pairs of conditions and values. The function returns the value that belongs to the first condition that evaluates to `true`.  If the number of arguments is odd, the last argument is the default value which is returned when no condition matches. If the number of arguments is even, and no condition matches, the function returns `null`.
-cbrt          |Returns the cube root of a number. The input can be any numeric value, the return value is always a double. Cube roots of infinities are null.
-ceil          |Round a number up to the nearest integer.
-cidr_match    |Returns true if the provided IP is contained in one of the provided CIDR blocks.
-coalesce      |Returns the first of its arguments that is not null. If all arguments are null, it returns `null`.
-concat        |Concatenates two or more strings.
-cos           |Returns the {wikipedia}/Sine_and_cosine[cosine] of an angle.
-cosh          |Returns the {wikipedia}/Hyperbolic_functions[hyperbolic cosine] of a number.
-count         |Returns the total number (count) of input values.
-count_distinct|Returns the approximate number of distinct values.
-date_diff     |Subtracts the `startTimestamp` from the `endTimestamp` and returns the difference in multiples of `unit`. If `startTimestamp` is later than the `endTimestamp`, negative values are returned.
-date_extract  |Extracts parts of a date, like year, month, day, hour.
-date_format   |Returns a string representation of a date, in the provided format.
-date_parse    |Returns a date by parsing the second argument using the format specified in the first argument.
-date_trunc    |Rounds down a date to the closest interval.
-e             |Returns {wikipedia}/E_(mathematical_constant)[Euler's number].
-ends_with     |Returns a boolean that indicates whether a keyword string ends with another string.
-exp           |Returns the value of e raised to the power of the given number.
-floor         |Round a number down to the nearest integer.
-from_base64   |Decode a base64 string.
-greatest      |Returns the maximum value from multiple columns. This is similar to <> except it is intended to run on multiple columns at once.
-ip_prefix     |Truncates an IP to a given prefix length.
-least         |Returns the minimum value from multiple columns. This is similar to <> except it is intended to run on multiple columns at once.
-left          |Returns the substring that extracts 'length' chars from 'string' starting from the left.
-length        |Returns the character length of a string.
-locate        |Returns an integer that indicates the position of a keyword substring within another string. Returns `0` if the substring cannot be found. Note that string positions start from `1`.
-log           |Returns the logarithm of a value to a base. The input can be any numeric value, the return value is always a double.  Logs of zero, negative numbers, and base of one return `null` as well as a warning.
-log10         |Returns the logarithm of a value to base 10. The input can be any numeric value, the return value is always a double.  Logs of 0 and negative numbers return `null` as well as a warning.
-ltrim         |Removes leading whitespaces from a string.
-max           |The maximum value of a field.
-median        |The value that is greater than half of all values and less than half of all values, also known as the 50% <>.
-median_absolut|"Returns the median absolute deviation, a measure of variability. It is a robust statistic, meaning that it is useful for describing data that may have outliers, or may not be normally distributed. For such data it can be more descriptive than standard deviation.  It is calculated as the median of each data point's deviation from the median of the entire sample. That is, for a random variable `X`, the median absolute deviation is `median(|median(X) - X|)`."
-min           |The minimum value of a field.
-mv_append     |Concatenates values of two multi-value fields.
-mv_avg        |Converts a multivalued field into a single valued field containing the average of all of the values.
-mv_concat     |Converts a multivalued string expression into a single valued column containing the concatenation of all values separated by a delimiter.
-mv_count      |Converts a multivalued expression into a single valued column containing a count of the number of values.
-mv_dedupe     |Remove duplicate values from a multivalued field.
-mv_first      |Converts a multivalued expression into a single valued column containing the first value. This is most useful when reading from a function that emits multivalued columns in a known order like <>.  The order that <> are read from underlying storage is not guaranteed. It is *frequently* ascending, but don't rely on that. If you need the minimum value use <> instead of `MV_FIRST`. `MV_MIN` has optimizations for sorted values so there isn't a performance benefit to `MV_FIRST`.
-mv_last       |Converts a multivalue expression into a single valued column containing the last value. This is most useful when reading from a function that emits multivalued columns in a known order like <>.  The order that <> are read from underlying storage is not guaranteed. It is *frequently* ascending, but don't rely on that. If you need the maximum value use <> instead of `MV_LAST`. `MV_MAX` has optimizations for sorted values so there isn't a performance benefit to `MV_LAST`.
-mv_max        |Converts a multivalued expression into a single valued column containing the maximum value.
-mv_median     |Converts a multivalued field into a single valued field containing the median value.
-mv_median_abso|"Converts a multivalued field into a single valued field containing the median absolute deviation.  It is calculated as the median of each data point's deviation from the median of the entire sample. That is, for a random variable `X`, the median absolute deviation is `median(|median(X) - X|)`."
-mv_min        |Converts a multivalued expression into a single valued column containing the minimum value.
-mv_percentile |Converts a multivalued field into a single valued field containing the value at which a certain percentage of observed values occur.
-mv_pseries_wei|Converts a multivalued expression into a single-valued column by multiplying every element on the input list by its corresponding term in P-Series and computing the sum.
-mv_slice      |Returns a subset of the multivalued field using the start and end index values.
-mv_sort       |Sorts a multivalued field in lexicographical order.
-mv_sum        |Converts a multivalued field into a single valued field containing the sum of all of the values.
-mv_zip        |Combines the values from two multivalued fields with a delimiter that joins them together.
-now           |Returns current date and time.
-percentile    |Returns the value at which a certain percentage of observed values occur. For example, the 95th percentile is the value which is greater than 95% of the observed values and the 50th percentile is the `MEDIAN`.
-pi            |Returns {wikipedia}/Pi[Pi], the ratio of a circle's circumference to its diameter.
-pow           |Returns the value of `base` raised to the power of `exponent`.
-repeat        |Returns a string constructed by concatenating `string` with itself the specified `number` of times.
-replace       |The function substitutes in the string `str` any match of the regular expression `regex` with the replacement string `newStr`.
-reverse       |Returns a new string representing the input string in reverse order.
-right         |Return the substring that extracts 'length' chars from 'str' starting from the right.
-round         |Rounds a number to the specified number of decimal places. Defaults to 0, which returns the nearest integer. If the precision is a negative number, rounds to the number of digits left of the decimal point.
-rtrim         |Removes trailing whitespaces from a string.
-signum        |Returns the sign of the given number. It returns `-1` for negative numbers, `0` for `0` and `1` for positive numbers.
-sin           |Returns the {wikipedia}/Sine_and_cosine[sine] of an angle.
-sinh          |Returns the {wikipedia}/Hyperbolic_functions[hyperbolic sine] of a number.
-space         |Returns a string made of `number` spaces.
-split         |Split a single valued string into multiple strings.
-sqrt          |Returns the square root of a number. The input can be any numeric value, the return value is always a double. Square roots of negative numbers and infinities are null.
-st_centroid_ag|Calculate the spatial centroid over a field with spatial point geometry type.
-st_contains   |Returns whether the first geometry contains the second geometry. This is the inverse of the <> function.
-st_disjoint   |Returns whether the two geometries or geometry columns are disjoint. This is the inverse of the <> function. In mathematical terms: ST_Disjoint(A, B) ⇔ A ⋂ B = ∅
-st_distance   |Computes the distance between two points. For cartesian geometries, this is the pythagorean distance in the same units as the original coordinates. For geographic geometries, this is the circular distance along the great circle in meters.
-st_intersects |Returns true if two geometries intersect. They intersect if they have any point in common, including their interior points (points along lines or within polygons). This is the inverse of the <> function. In mathematical terms: ST_Intersects(A, B) ⇔ A ⋂ B ≠ ∅
-st_within     |Returns whether the first geometry is within the second geometry. This is the inverse of the <> function.
-st_x          |Extracts the `x` coordinate from the supplied point. If the points is of type `geo_point` this is equivalent to extracting the `longitude` value.
-st_y          |Extracts the `y` coordinate from the supplied point. If the points is of type `geo_point` this is equivalent to extracting the `latitude` value.
-starts_with   |Returns a boolean that indicates whether a keyword string starts with another string.
-substring     |Returns a substring of a string, specified by a start position and an optional length.
-sum           |The sum of a numeric expression.
-tan           |Returns the {wikipedia}/Sine_and_cosine[tangent] of an angle.
-tanh          |Returns the {wikipedia}/Hyperbolic_functions[hyperbolic tangent] of a number.
-tau           |Returns the https://tauday.com/tau-manifesto[ratio] of a circle's circumference to its radius.
-to_base64     |Encode a string to a base64 string.
-to_bool       |Converts an input value to a boolean value. A string value of *true* will be case-insensitive converted to the Boolean *true*. For anything else, including the empty string, the function will return *false*. The numerical value of *0* will be converted to *false*, anything else will be converted to *true*.
-to_boolean    |Converts an input value to a boolean value. A string value of *true* will be case-insensitive converted to the Boolean *true*. For anything else, including the empty string, the function will return *false*. The numerical value of *0* will be converted to *false*, anything else will be converted to *true*.
-to_cartesianpo|Converts an input value to a `cartesian_point` value. A string will only be successfully converted if it respects the {wikipedia}/Well-known_text_representation_of_geometry[WKT Point] format.
-to_cartesiansh|Converts an input value to a `cartesian_shape` value. A string will only be successfully converted if it respects the {wikipedia}/Well-known_text_representation_of_geometry[WKT] format.
-to_date_nanos |Converts an input to a nanosecond-resolution date value (aka date_nanos).
-to_datenanos  |Converts an input to a nanosecond-resolution date value (aka date_nanos).
-to_dateperiod |Converts an input value into a `date_period` value.
-to_datetime   |Converts an input value to a date value. A string will only be successfully converted if it's respecting the format `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`. To convert dates in other formats, use <>.
-to_dbl        |Converts an input value to a double value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to double. Boolean *true* will be converted to double *1.0*, *false* to *0.0*.
-to_degrees    |Converts a number in {wikipedia}/Radian[radians] to {wikipedia}/Degree_(angle)[degrees].
-to_double     |Converts an input value to a double value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to double. Boolean *true* will be converted to double *1.0*, *false* to *0.0*.
-to_dt         |Converts an input value to a date value. A string will only be successfully converted if it's respecting the format `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`. To convert dates in other formats, use <>.
-to_geopoint   |Converts an input value to a `geo_point` value. A string will only be successfully converted if it respects the {wikipedia}/Well-known_text_representation_of_geometry[WKT Point] format.
-to_geoshape   |Converts an input value to a `geo_shape` value. A string will only be successfully converted if it respects the {wikipedia}/Well-known_text_representation_of_geometry[WKT] format.
-to_int        |Converts an input value to an integer value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to integer. Boolean *true* will be converted to integer *1*, *false* to *0*.
-to_integer    |Converts an input value to an integer value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to integer. Boolean *true* will be converted to integer *1*, *false* to *0*.
-to_ip         |Converts an input string to an IP value.
-to_long       |Converts an input value to a long value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to long. Boolean *true* will be converted to long *1*, *false* to *0*.
-to_lower      |Returns a new string representing the input string converted to lower case.
-to_radians    |Converts a number in {wikipedia}/Degree_(angle)[degrees] to {wikipedia}/Radian[radians].
-to_str        |Converts an input value into a string.
-to_string     |Converts an input value into a string.
-to_timeduratio|Converts an input value into a `time_duration` value.
-to_ul         |Converts an input value to an unsigned long value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to unsigned long. Boolean *true* will be converted to unsigned long *1*, *false* to *0*.
-to_ulong      |Converts an input value to an unsigned long value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to unsigned long. Boolean *true* will be converted to unsigned long *1*, *false* to *0*.
-to_unsigned_lo|Converts an input value to an unsigned long value. If the input parameter is of a date type, its value will be interpreted as milliseconds since the {wikipedia}/Unix_time[Unix epoch], converted to unsigned long. Boolean *true* will be converted to unsigned long *1*, *false* to *0*.
-to_upper      |Returns a new string representing the input string converted to upper case.
-to_ver        |Converts an input string to a version value.
-to_version    |Converts an input string to a version value.
-top           |Collects the top values for a field. Includes repeated values.
-trim          |Removes leading and trailing whitespaces from a string.
-values        |Returns all values in a group as a multivalued field. The order of the returned values isn't guaranteed. If you need the values returned in order use <>.
-weighted_avg  |The weighted average of a numeric expression.
-;
-
-metaFunctionsRemaining
-required_capability: meta
-required_capability: date_nanos_type
-
-  META functions
-| EVAL name = SUBSTRING(name, 0, 14)
-| KEEP name, *
-| DROP synopsis, description, argNames, argTypes, argDescriptions
-;
-
- name:keyword |                                                    returnType:keyword                                                      |    optionalArgs:boolean    |variadic:boolean|isAggregation:boolean
-abs           |"double|integer|long|unsigned_long"                                                                                         |false                       |false           |false
-acos          |double                                                                                                                      |false                       |false           |false
-asin          |double                                                                                                                      |false                       |false           |false
-atan          |double                                                                                                                      |false                       |false           |false
-atan2         |double                                                                                                                      |[false, false]              |false           |false
-avg           |double                                                                                                                      |false                       |false           |true
-bin           |"double|date"                                                                                                               |[false, false, true, true]  |false           |false
-bucket        |"double|date"                                                                                                               |[false, false, true, true]  |false           |false
-case          |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"                          |[false, false]              |true            |false
-cbrt          |double                                                                                                                      |false                       |false           |false
-ceil          |"double|integer|long|unsigned_long"                                                                                         |false                       |false           |false
-cidr_match    |boolean                                                                                                                     |[false, false]              |true            |false
-coalesce      |"boolean|cartesian_point|cartesian_shape|date|geo_point|geo_shape|integer|ip|keyword|long|text|version"                     |false                       |true            |false
-concat        |keyword                                                                                                                     |[false, false]              |true            |false
-cos           |double                                                                                                                      |false                       |false           |false
-cosh          |double                                                                                                                      |false                       |false           |false
-count         |long                                                                                                                        |true                        |false           |true
-count_distinct|long                                                                                                                        |[false, true]               |false           |true
-date_diff     |integer                                                                                                                     |[false, false, false]       |false           |false
-date_extract  |long                                                                                                                        |[false, false]              |false           |false
-date_format   |keyword                                                                                                                     |[true, false]               |false           |false
-date_parse    |date                                                                                                                        |[true, false]               |false           |false
-date_trunc    |date                                                                                                                        |[false, false]              |false           |false
-e             |double                                                                                                                      |null                        |false           |false
-ends_with     |boolean                                                                                                                     |[false, false]              |false           |false
-exp           |double                                                                                         |false                       |false           |false
-floor         |"double|integer|long|unsigned_long"                                                                                         |false                       |false           |false
-from_base64   |keyword                                                                                                                     |false                       |false           |false
-greatest      |"boolean|date|double|integer|ip|keyword|long|text|version"                                                                  |false                       |true            |false
-ip_prefix     |ip                                                                                                                          |[false, false, false]       |false           |false
-least         |"boolean|date|double|integer|ip|keyword|long|text|version"                                                                  |false                       |true            |false
-left          |keyword                                                                                                                     |[false, false]              |false           |false
-length        |integer                                                                                                                     |false                       |false           |false
-locate        |integer                                                                                                                     |[false, false, true]        |false           |false
-log           |double                                                                                                                      |[true, false]               |false           |false
-log10         |double                                                                                                                      |false                       |false           |false
-ltrim         |"keyword|text"                                                                                                              |false                       |false           |false
-max           |"boolean|double|integer|long|date|ip|keyword|text|long|version"                                                             |false                       |false           |true
-median        |double                                                                                                                      |false                       |false           |true
-median_absolut|double                                                                                                                      |false                       |false           |true
-min           |"boolean|double|integer|long|date|ip|keyword|text|long|version"                                                             |false                       |false           |true
-mv_append     |"boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version"              |[false, false]              |false 		     |false
-mv_avg        |double                                                                                                                      |false                       |false           |false
-mv_concat     |keyword                                                                                                                     |[false, false]              |false           |false
-mv_count      |integer                                                                                                                     |false                       |false           |false
-mv_dedupe     |"boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version"              |false                       |false           |false
-mv_first      |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"|false                       |false           |false
-mv_last       |"boolean|cartesian_point|cartesian_shape|date|date_nanos|double|geo_point|geo_shape|integer|ip|keyword|long|text|unsigned_long|version"|false                       |false           |false
-mv_max        |"boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version"                                         |false                       |false           |false
-mv_median     |"double|integer|long|unsigned_long"                                                                                         |false                       |false           |false
-mv_median_abso|"double|integer|long|unsigned_long"                                                                                         |false                       |false           |false
-mv_min        |"boolean|date|date_nanos|double|integer|ip|keyword|long|text|unsigned_long|version"                                         |false                       |false           |false
-mv_percentile |"double|integer|long"                                                                                                       |[false, false]              |false           |false
-mv_pseries_wei|"double"                                                                                                                    |[false, false]              |false           |false 
-mv_slice      |"boolean|cartesian_point|cartesian_shape|date|double|geo_point|geo_shape|integer|ip|keyword|long|text|version"              |[false, false, true]        |false           |false
-mv_sort       |"boolean|date|double|integer|ip|keyword|long|text|version"                                                                  |[false, true]               |false           |false
-mv_sum        |"double|integer|long|unsigned_long"                                                                                         |false                       |false           |false
-mv_zip        |keyword                                                                                                                     |[false, false, true]        |false           |false
-now           |date                                                                                                                        |null                        |false           |false
-percentile    |double                                                                                                                      |[false, false]              |false           |true
-pi            |double                                                                                                                      |null                        |false           |false
-pow           |double                                                                                                                      |[false, false]              |false           |false
-repeat        |keyword                                                                                                                     |[false, false]              |false           |false
-replace       |keyword                                                                                                                     |[false, false, false]       |false           |false
-reverse       |"keyword|text"                                                                                                              |false                       |false           |false
-right         |keyword                                                                                                                     |[false, false]              |false           |false
-round         |"double|integer|long|unsigned_long"                                                                                         |[false, true]               |false           |false
-rtrim         |"keyword|text"                                                                                                              |false                       |false           |false
-signum        |double                                                                                                                      |false                       |false           |false
-sin           |double                                                                                                                      |false                       |false           |false
-sinh          |double                                                                                                                      |false                       |false           |false
-space         |keyword                                                                                                                     |false                       |false           |false
-split         |keyword                                                                                                                     |[false, false]              |false           |false
-sqrt          |double                                                                                                                      |false                       |false           |false
-st_centroid_ag|"geo_point|cartesian_point"                                                                                                 |false                       |false           |true
-st_contains   |boolean                                                                                                                     |[false, false]              |false           |false
-st_disjoint   |boolean                                                                                                                     |[false, false]              |false           |false
-st_distance   |double                                                                                                                      |[false, false]              |false           |false
-st_intersects |boolean                                                                                                                     |[false, false]              |false           |false
-st_within     |boolean                                                                                                                     |[false, false]              |false           |false
-st_x          |double                                                                                                                      |false                       |false           |false
-st_y          |double                                                                                                                      |false                       |false           |false
-starts_with   |boolean                                                                                                                     |[false, false]              |false           |false
-substring     |keyword                                                                                                                     |[false, false, true]        |false           |false
-sum           |"long|double"                                                                                                               |false                       |false           |true
-tan           |double                                                                                                                      |false                       |false           |false
-tanh          |double                                                                                                                      |false                       |false           |false
-tau           |double                                                                                                                      |null                        |false           |false
-to_base64     |keyword                                                                                                                     |false                       |false           |false
-to_bool       |boolean                                                                                                                     |false                       |false           |false
-to_boolean    |boolean                                                                                                                     |false                       |false           |false
-to_cartesianpo|cartesian_point                                                                                                             |false                       |false           |false
-to_cartesiansh|cartesian_shape                                                                                                             |false                       |false           |false
-to_date_nanos |date_nanos                                                                                                                  |false                       |false           |false
-to_datenanos  |date_nanos                                                                                                                  |false                       |false           |false
-to_dateperiod |date_period                                                                                                                 |false                       |false           |false
-to_datetime   |date                                                                                                                        |false                       |false           |false
-to_dbl        |double                                                                                                                      |false                       |false           |false
-to_degrees    |double                                                                                                                      |false                       |false           |false
-to_double     |double                                                                                                                      |false                       |false           |false
-to_dt         |date                                                                                                                        |false                       |false           |false
-to_geopoint   |geo_point                                                                                                                   |false                       |false           |false
-to_geoshape   |geo_shape                                                                                                                   |false                       |false           |false
-to_int        |integer                                                                                                                     |false                       |false           |false
-to_integer    |integer                                                                                                                     |false                       |false           |false
-to_ip         |ip                                                                                                                          |false                       |false           |false
-to_long       |long                                                                                                                        |false                       |false           |false
-to_lower      |"keyword|text"                                                                                                              |false                       |false           |false
-to_radians    |double                                                                                                                      |false                       |false           |false
-to_str        |keyword                                                                                                                     |false                       |false           |false
-to_string     |keyword                                                                                                                     |false                       |false           |false
-to_timeduratio|time_duration                                                                                                               |false                       |false           |false
-to_ul         |unsigned_long                                                                                                               |false                       |false           |false
-to_ulong      |unsigned_long                                                                                                               |false                       |false           |false
-to_unsigned_lo|unsigned_long                                                                                                               |false                       |false           |false
-to_upper      |"keyword|text"                                                                                                              |false                       |false           |false
-to_ver        |version                                                                                                                     |false                       |false           |false
-to_version    |version                                                                                                                     |false                       |false           |false
-top           |"boolean|double|integer|long|date|ip|keyword|text"                                                                                       |[false, false, false]       |false           |true
-trim          |"keyword|text"                                                                                                              |false                       |false           |false
-values        |"boolean|date|double|integer|ip|keyword|long|text|version"                                                                  |false                       |false           |true
-weighted_avg  |"double"                                                                                                                    |[false, false]              |false           |true
-;
-
-metaFunctionsFiltered
-required_capability: meta
-
-META FUNCTIONS
-| WHERE STARTS_WITH(name, "sin")
-;
-
-name:keyword |                      synopsis:keyword                  |argNames:keyword  |        argTypes:keyword            |             argDescriptions:keyword                             | returnType:keyword |                                      description:keyword                     | optionalArgs:boolean | variadic:boolean | isAggregation:boolean
-sin          |"double sin(angle:double|integer|long|unsigned_long)"   |angle             |"double|integer|long|unsigned_long" | "An angle, in radians. If `null`, the function returns `null`." | double             | "Returns the {wikipedia}/Sine_and_cosine[sine] of an angle."                 | false                | false            | false
-sinh         |"double sinh(number:double|integer|long|unsigned_long)" |number            |"double|integer|long|unsigned_long" | "Numeric expression. If `null`, the function returns `null`."   | double             | "Returns the {wikipedia}/Hyperbolic_functions[hyperbolic sine] of a number." | false                | false            | false
-;
-
-countFunctions
-required_capability: meta
-
-meta functions |  stats  a = count(*), b = count(*), c = count(*) |  mv_expand c;
-
-a:long | b:long | c:long
-122    | 122    | 122
-;
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 e0bef22718d0d..147b13b36c44b 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
@@ -34,7 +34,6 @@
 import org.elasticsearch.xpack.core.esql.action.ColumnInfo;
 import org.elasticsearch.xpack.esql.VerificationException;
 import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
 import org.elasticsearch.xpack.esql.parser.ParsingException;
 import org.elasticsearch.xpack.esql.plugin.EsqlPlugin;
 import org.junit.Before;
@@ -1038,29 +1037,6 @@ public void testShowInfo() {
         }
     }
 
-    public void testMetaFunctions() {
-        try (EsqlQueryResponse results = run("meta functions")) {
-            assertThat(
-                results.columns(),
-                equalTo(
-                    List.of(
-                        new ColumnInfoImpl("name", "keyword"),
-                        new ColumnInfoImpl("synopsis", "keyword"),
-                        new ColumnInfoImpl("argNames", "keyword"),
-                        new ColumnInfoImpl("argTypes", "keyword"),
-                        new ColumnInfoImpl("argDescriptions", "keyword"),
-                        new ColumnInfoImpl("returnType", "keyword"),
-                        new ColumnInfoImpl("description", "keyword"),
-                        new ColumnInfoImpl("optionalArgs", "boolean"),
-                        new ColumnInfoImpl("variadic", "boolean"),
-                        new ColumnInfoImpl("isAggregation", "boolean")
-                    )
-                )
-            );
-            assertThat(getValuesList(results).size(), equalTo(new EsqlFunctionRegistry().listFunctions().size()));
-        }
-    }
-
     public void testInWithNullValue() {
         try (EsqlQueryResponse results = run("from test | where null in (data, 2) | keep data")) {
             assertThat(results.columns(), equalTo(List.of(new ColumnInfoImpl("data", "long"))));
diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.g4 b/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.g4
index 6570a25469971..e7f10d96c89ae 100644
--- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.g4
+++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.g4
@@ -66,7 +66,6 @@ FROM : 'from'                 -> pushMode(FROM_MODE);
 GROK : 'grok'                 -> pushMode(EXPRESSION_MODE);
 KEEP : 'keep'                 -> pushMode(PROJECT_MODE);
 LIMIT : 'limit'               -> pushMode(EXPRESSION_MODE);
-META : 'meta'                 -> pushMode(META_MODE);
 MV_EXPAND : 'mv_expand'       -> pushMode(MVEXPAND_MODE);
 RENAME : 'rename'             -> pushMode(RENAME_MODE);
 ROW : 'row'                   -> pushMode(EXPRESSION_MODE);
@@ -467,26 +466,6 @@ SHOW_WS
     : WS -> channel(HIDDEN)
     ;
 
-//
-// META commands
-//
-mode META_MODE;
-META_PIPE : PIPE -> type(PIPE), popMode;
-
-FUNCTIONS : 'functions';
-
-META_LINE_COMMENT
-    : LINE_COMMENT -> channel(HIDDEN)
-    ;
-
-META_MULTILINE_COMMENT
-    : MULTILINE_COMMENT -> channel(HIDDEN)
-    ;
-
-META_WS
-    : WS -> channel(HIDDEN)
-    ;
-
 mode SETTING_MODE;
 SETTING_CLOSING_BRACKET : CLOSING_BRACKET -> type(CLOSING_BRACKET), popMode;
 
diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens b/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens
index 747fbbc64cf5f..4fd37ab9900f2 100644
--- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens
+++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens
@@ -7,122 +7,117 @@ FROM=6
 GROK=7
 KEEP=8
 LIMIT=9
-META=10
-MV_EXPAND=11
-RENAME=12
-ROW=13
-SHOW=14
-SORT=15
-STATS=16
-WHERE=17
-DEV_INLINESTATS=18
-DEV_LOOKUP=19
-DEV_MATCH=20
-DEV_METRICS=21
-UNKNOWN_CMD=22
-LINE_COMMENT=23
-MULTILINE_COMMENT=24
-WS=25
-PIPE=26
-QUOTED_STRING=27
-INTEGER_LITERAL=28
-DECIMAL_LITERAL=29
-BY=30
-AND=31
-ASC=32
-ASSIGN=33
-CAST_OP=34
-COMMA=35
-DESC=36
-DOT=37
-FALSE=38
-FIRST=39
-IN=40
-IS=41
-LAST=42
-LIKE=43
-LP=44
-NOT=45
-NULL=46
-NULLS=47
-OR=48
-PARAM=49
-RLIKE=50
-RP=51
-TRUE=52
-EQ=53
-CIEQ=54
-NEQ=55
-LT=56
-LTE=57
-GT=58
-GTE=59
-PLUS=60
-MINUS=61
-ASTERISK=62
-SLASH=63
-PERCENT=64
-NAMED_OR_POSITIONAL_PARAM=65
-OPENING_BRACKET=66
-CLOSING_BRACKET=67
-UNQUOTED_IDENTIFIER=68
-QUOTED_IDENTIFIER=69
-EXPR_LINE_COMMENT=70
-EXPR_MULTILINE_COMMENT=71
-EXPR_WS=72
-EXPLAIN_WS=73
-EXPLAIN_LINE_COMMENT=74
-EXPLAIN_MULTILINE_COMMENT=75
-METADATA=76
-UNQUOTED_SOURCE=77
-FROM_LINE_COMMENT=78
-FROM_MULTILINE_COMMENT=79
-FROM_WS=80
-ID_PATTERN=81
-PROJECT_LINE_COMMENT=82
-PROJECT_MULTILINE_COMMENT=83
-PROJECT_WS=84
-AS=85
-RENAME_LINE_COMMENT=86
-RENAME_MULTILINE_COMMENT=87
-RENAME_WS=88
-ON=89
-WITH=90
-ENRICH_POLICY_NAME=91
-ENRICH_LINE_COMMENT=92
-ENRICH_MULTILINE_COMMENT=93
-ENRICH_WS=94
-ENRICH_FIELD_LINE_COMMENT=95
-ENRICH_FIELD_MULTILINE_COMMENT=96
-ENRICH_FIELD_WS=97
-MVEXPAND_LINE_COMMENT=98
-MVEXPAND_MULTILINE_COMMENT=99
-MVEXPAND_WS=100
-INFO=101
-SHOW_LINE_COMMENT=102
-SHOW_MULTILINE_COMMENT=103
-SHOW_WS=104
-FUNCTIONS=105
-META_LINE_COMMENT=106
-META_MULTILINE_COMMENT=107
-META_WS=108
-COLON=109
-SETTING=110
-SETTING_LINE_COMMENT=111
-SETTTING_MULTILINE_COMMENT=112
-SETTING_WS=113
-LOOKUP_LINE_COMMENT=114
-LOOKUP_MULTILINE_COMMENT=115
-LOOKUP_WS=116
-LOOKUP_FIELD_LINE_COMMENT=117
-LOOKUP_FIELD_MULTILINE_COMMENT=118
-LOOKUP_FIELD_WS=119
-METRICS_LINE_COMMENT=120
-METRICS_MULTILINE_COMMENT=121
-METRICS_WS=122
-CLOSING_METRICS_LINE_COMMENT=123
-CLOSING_METRICS_MULTILINE_COMMENT=124
-CLOSING_METRICS_WS=125
+MV_EXPAND=10
+RENAME=11
+ROW=12
+SHOW=13
+SORT=14
+STATS=15
+WHERE=16
+DEV_INLINESTATS=17
+DEV_LOOKUP=18
+DEV_MATCH=19
+DEV_METRICS=20
+UNKNOWN_CMD=21
+LINE_COMMENT=22
+MULTILINE_COMMENT=23
+WS=24
+PIPE=25
+QUOTED_STRING=26
+INTEGER_LITERAL=27
+DECIMAL_LITERAL=28
+BY=29
+AND=30
+ASC=31
+ASSIGN=32
+CAST_OP=33
+COMMA=34
+DESC=35
+DOT=36
+FALSE=37
+FIRST=38
+IN=39
+IS=40
+LAST=41
+LIKE=42
+LP=43
+NOT=44
+NULL=45
+NULLS=46
+OR=47
+PARAM=48
+RLIKE=49
+RP=50
+TRUE=51
+EQ=52
+CIEQ=53
+NEQ=54
+LT=55
+LTE=56
+GT=57
+GTE=58
+PLUS=59
+MINUS=60
+ASTERISK=61
+SLASH=62
+PERCENT=63
+NAMED_OR_POSITIONAL_PARAM=64
+OPENING_BRACKET=65
+CLOSING_BRACKET=66
+UNQUOTED_IDENTIFIER=67
+QUOTED_IDENTIFIER=68
+EXPR_LINE_COMMENT=69
+EXPR_MULTILINE_COMMENT=70
+EXPR_WS=71
+EXPLAIN_WS=72
+EXPLAIN_LINE_COMMENT=73
+EXPLAIN_MULTILINE_COMMENT=74
+METADATA=75
+UNQUOTED_SOURCE=76
+FROM_LINE_COMMENT=77
+FROM_MULTILINE_COMMENT=78
+FROM_WS=79
+ID_PATTERN=80
+PROJECT_LINE_COMMENT=81
+PROJECT_MULTILINE_COMMENT=82
+PROJECT_WS=83
+AS=84
+RENAME_LINE_COMMENT=85
+RENAME_MULTILINE_COMMENT=86
+RENAME_WS=87
+ON=88
+WITH=89
+ENRICH_POLICY_NAME=90
+ENRICH_LINE_COMMENT=91
+ENRICH_MULTILINE_COMMENT=92
+ENRICH_WS=93
+ENRICH_FIELD_LINE_COMMENT=94
+ENRICH_FIELD_MULTILINE_COMMENT=95
+ENRICH_FIELD_WS=96
+MVEXPAND_LINE_COMMENT=97
+MVEXPAND_MULTILINE_COMMENT=98
+MVEXPAND_WS=99
+INFO=100
+SHOW_LINE_COMMENT=101
+SHOW_MULTILINE_COMMENT=102
+SHOW_WS=103
+COLON=104
+SETTING=105
+SETTING_LINE_COMMENT=106
+SETTTING_MULTILINE_COMMENT=107
+SETTING_WS=108
+LOOKUP_LINE_COMMENT=109
+LOOKUP_MULTILINE_COMMENT=110
+LOOKUP_WS=111
+LOOKUP_FIELD_LINE_COMMENT=112
+LOOKUP_FIELD_MULTILINE_COMMENT=113
+LOOKUP_FIELD_WS=114
+METRICS_LINE_COMMENT=115
+METRICS_MULTILINE_COMMENT=116
+METRICS_WS=117
+CLOSING_METRICS_LINE_COMMENT=118
+CLOSING_METRICS_MULTILINE_COMMENT=119
+CLOSING_METRICS_WS=120
 'dissect'=1
 'drop'=2
 'enrich'=3
@@ -132,55 +127,53 @@ CLOSING_METRICS_WS=125
 'grok'=7
 'keep'=8
 'limit'=9
-'meta'=10
-'mv_expand'=11
-'rename'=12
-'row'=13
-'show'=14
-'sort'=15
-'stats'=16
-'where'=17
-'|'=26
-'by'=30
-'and'=31
-'asc'=32
-'='=33
-'::'=34
-','=35
-'desc'=36
-'.'=37
-'false'=38
-'first'=39
-'in'=40
-'is'=41
-'last'=42
-'like'=43
-'('=44
-'not'=45
-'null'=46
-'nulls'=47
-'or'=48
-'?'=49
-'rlike'=50
-')'=51
-'true'=52
-'=='=53
-'=~'=54
-'!='=55
-'<'=56
-'<='=57
-'>'=58
-'>='=59
-'+'=60
-'-'=61
-'*'=62
-'/'=63
-'%'=64
-']'=67
-'metadata'=76
-'as'=85
-'on'=89
-'with'=90
-'info'=101
-'functions'=105
-':'=109
+'mv_expand'=10
+'rename'=11
+'row'=12
+'show'=13
+'sort'=14
+'stats'=15
+'where'=16
+'|'=25
+'by'=29
+'and'=30
+'asc'=31
+'='=32
+'::'=33
+','=34
+'desc'=35
+'.'=36
+'false'=37
+'first'=38
+'in'=39
+'is'=40
+'last'=41
+'like'=42
+'('=43
+'not'=44
+'null'=45
+'nulls'=46
+'or'=47
+'?'=48
+'rlike'=49
+')'=50
+'true'=51
+'=='=52
+'=~'=53
+'!='=54
+'<'=55
+'<='=56
+'>'=57
+'>='=58
+'+'=59
+'-'=60
+'*'=61
+'/'=62
+'%'=63
+']'=66
+'metadata'=75
+'as'=84
+'on'=88
+'with'=89
+'info'=100
+':'=104
diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4
index a5691a16ca50b..eefe352e9cdd9 100644
--- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4
+++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4
@@ -32,7 +32,6 @@ query
 sourceCommand
     : explainCommand
     | fromCommand
-    | metaCommand
     | rowCommand
     | showCommand
     // in development
@@ -289,10 +288,6 @@ showCommand
     : SHOW INFO                                                           #showInfo
     ;
 
-metaCommand
-    : META FUNCTIONS                                                      #metaFunctions
-    ;
-
 enrichCommand
     : ENRICH policyName=ENRICH_POLICY_NAME (ON matchField=qualifiedNamePattern)? (WITH enrichWithClause (COMMA enrichWithClause)*)?
     ;
diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens
index 747fbbc64cf5f..4fd37ab9900f2 100644
--- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens
+++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens
@@ -7,122 +7,117 @@ FROM=6
 GROK=7
 KEEP=8
 LIMIT=9
-META=10
-MV_EXPAND=11
-RENAME=12
-ROW=13
-SHOW=14
-SORT=15
-STATS=16
-WHERE=17
-DEV_INLINESTATS=18
-DEV_LOOKUP=19
-DEV_MATCH=20
-DEV_METRICS=21
-UNKNOWN_CMD=22
-LINE_COMMENT=23
-MULTILINE_COMMENT=24
-WS=25
-PIPE=26
-QUOTED_STRING=27
-INTEGER_LITERAL=28
-DECIMAL_LITERAL=29
-BY=30
-AND=31
-ASC=32
-ASSIGN=33
-CAST_OP=34
-COMMA=35
-DESC=36
-DOT=37
-FALSE=38
-FIRST=39
-IN=40
-IS=41
-LAST=42
-LIKE=43
-LP=44
-NOT=45
-NULL=46
-NULLS=47
-OR=48
-PARAM=49
-RLIKE=50
-RP=51
-TRUE=52
-EQ=53
-CIEQ=54
-NEQ=55
-LT=56
-LTE=57
-GT=58
-GTE=59
-PLUS=60
-MINUS=61
-ASTERISK=62
-SLASH=63
-PERCENT=64
-NAMED_OR_POSITIONAL_PARAM=65
-OPENING_BRACKET=66
-CLOSING_BRACKET=67
-UNQUOTED_IDENTIFIER=68
-QUOTED_IDENTIFIER=69
-EXPR_LINE_COMMENT=70
-EXPR_MULTILINE_COMMENT=71
-EXPR_WS=72
-EXPLAIN_WS=73
-EXPLAIN_LINE_COMMENT=74
-EXPLAIN_MULTILINE_COMMENT=75
-METADATA=76
-UNQUOTED_SOURCE=77
-FROM_LINE_COMMENT=78
-FROM_MULTILINE_COMMENT=79
-FROM_WS=80
-ID_PATTERN=81
-PROJECT_LINE_COMMENT=82
-PROJECT_MULTILINE_COMMENT=83
-PROJECT_WS=84
-AS=85
-RENAME_LINE_COMMENT=86
-RENAME_MULTILINE_COMMENT=87
-RENAME_WS=88
-ON=89
-WITH=90
-ENRICH_POLICY_NAME=91
-ENRICH_LINE_COMMENT=92
-ENRICH_MULTILINE_COMMENT=93
-ENRICH_WS=94
-ENRICH_FIELD_LINE_COMMENT=95
-ENRICH_FIELD_MULTILINE_COMMENT=96
-ENRICH_FIELD_WS=97
-MVEXPAND_LINE_COMMENT=98
-MVEXPAND_MULTILINE_COMMENT=99
-MVEXPAND_WS=100
-INFO=101
-SHOW_LINE_COMMENT=102
-SHOW_MULTILINE_COMMENT=103
-SHOW_WS=104
-FUNCTIONS=105
-META_LINE_COMMENT=106
-META_MULTILINE_COMMENT=107
-META_WS=108
-COLON=109
-SETTING=110
-SETTING_LINE_COMMENT=111
-SETTTING_MULTILINE_COMMENT=112
-SETTING_WS=113
-LOOKUP_LINE_COMMENT=114
-LOOKUP_MULTILINE_COMMENT=115
-LOOKUP_WS=116
-LOOKUP_FIELD_LINE_COMMENT=117
-LOOKUP_FIELD_MULTILINE_COMMENT=118
-LOOKUP_FIELD_WS=119
-METRICS_LINE_COMMENT=120
-METRICS_MULTILINE_COMMENT=121
-METRICS_WS=122
-CLOSING_METRICS_LINE_COMMENT=123
-CLOSING_METRICS_MULTILINE_COMMENT=124
-CLOSING_METRICS_WS=125
+MV_EXPAND=10
+RENAME=11
+ROW=12
+SHOW=13
+SORT=14
+STATS=15
+WHERE=16
+DEV_INLINESTATS=17
+DEV_LOOKUP=18
+DEV_MATCH=19
+DEV_METRICS=20
+UNKNOWN_CMD=21
+LINE_COMMENT=22
+MULTILINE_COMMENT=23
+WS=24
+PIPE=25
+QUOTED_STRING=26
+INTEGER_LITERAL=27
+DECIMAL_LITERAL=28
+BY=29
+AND=30
+ASC=31
+ASSIGN=32
+CAST_OP=33
+COMMA=34
+DESC=35
+DOT=36
+FALSE=37
+FIRST=38
+IN=39
+IS=40
+LAST=41
+LIKE=42
+LP=43
+NOT=44
+NULL=45
+NULLS=46
+OR=47
+PARAM=48
+RLIKE=49
+RP=50
+TRUE=51
+EQ=52
+CIEQ=53
+NEQ=54
+LT=55
+LTE=56
+GT=57
+GTE=58
+PLUS=59
+MINUS=60
+ASTERISK=61
+SLASH=62
+PERCENT=63
+NAMED_OR_POSITIONAL_PARAM=64
+OPENING_BRACKET=65
+CLOSING_BRACKET=66
+UNQUOTED_IDENTIFIER=67
+QUOTED_IDENTIFIER=68
+EXPR_LINE_COMMENT=69
+EXPR_MULTILINE_COMMENT=70
+EXPR_WS=71
+EXPLAIN_WS=72
+EXPLAIN_LINE_COMMENT=73
+EXPLAIN_MULTILINE_COMMENT=74
+METADATA=75
+UNQUOTED_SOURCE=76
+FROM_LINE_COMMENT=77
+FROM_MULTILINE_COMMENT=78
+FROM_WS=79
+ID_PATTERN=80
+PROJECT_LINE_COMMENT=81
+PROJECT_MULTILINE_COMMENT=82
+PROJECT_WS=83
+AS=84
+RENAME_LINE_COMMENT=85
+RENAME_MULTILINE_COMMENT=86
+RENAME_WS=87
+ON=88
+WITH=89
+ENRICH_POLICY_NAME=90
+ENRICH_LINE_COMMENT=91
+ENRICH_MULTILINE_COMMENT=92
+ENRICH_WS=93
+ENRICH_FIELD_LINE_COMMENT=94
+ENRICH_FIELD_MULTILINE_COMMENT=95
+ENRICH_FIELD_WS=96
+MVEXPAND_LINE_COMMENT=97
+MVEXPAND_MULTILINE_COMMENT=98
+MVEXPAND_WS=99
+INFO=100
+SHOW_LINE_COMMENT=101
+SHOW_MULTILINE_COMMENT=102
+SHOW_WS=103
+COLON=104
+SETTING=105
+SETTING_LINE_COMMENT=106
+SETTTING_MULTILINE_COMMENT=107
+SETTING_WS=108
+LOOKUP_LINE_COMMENT=109
+LOOKUP_MULTILINE_COMMENT=110
+LOOKUP_WS=111
+LOOKUP_FIELD_LINE_COMMENT=112
+LOOKUP_FIELD_MULTILINE_COMMENT=113
+LOOKUP_FIELD_WS=114
+METRICS_LINE_COMMENT=115
+METRICS_MULTILINE_COMMENT=116
+METRICS_WS=117
+CLOSING_METRICS_LINE_COMMENT=118
+CLOSING_METRICS_MULTILINE_COMMENT=119
+CLOSING_METRICS_WS=120
 'dissect'=1
 'drop'=2
 'enrich'=3
@@ -132,55 +127,53 @@ CLOSING_METRICS_WS=125
 'grok'=7
 'keep'=8
 'limit'=9
-'meta'=10
-'mv_expand'=11
-'rename'=12
-'row'=13
-'show'=14
-'sort'=15
-'stats'=16
-'where'=17
-'|'=26
-'by'=30
-'and'=31
-'asc'=32
-'='=33
-'::'=34
-','=35
-'desc'=36
-'.'=37
-'false'=38
-'first'=39
-'in'=40
-'is'=41
-'last'=42
-'like'=43
-'('=44
-'not'=45
-'null'=46
-'nulls'=47
-'or'=48
-'?'=49
-'rlike'=50
-')'=51
-'true'=52
-'=='=53
-'=~'=54
-'!='=55
-'<'=56
-'<='=57
-'>'=58
-'>='=59
-'+'=60
-'-'=61
-'*'=62
-'/'=63
-'%'=64
-']'=67
-'metadata'=76
-'as'=85
-'on'=89
-'with'=90
-'info'=101
-'functions'=105
-':'=109
+'mv_expand'=10
+'rename'=11
+'row'=12
+'show'=13
+'sort'=14
+'stats'=15
+'where'=16
+'|'=25
+'by'=29
+'and'=30
+'asc'=31
+'='=32
+'::'=33
+','=34
+'desc'=35
+'.'=36
+'false'=37
+'first'=38
+'in'=39
+'is'=40
+'last'=41
+'like'=42
+'('=43
+'not'=44
+'null'=45
+'nulls'=46
+'or'=47
+'?'=48
+'rlike'=49
+')'=50
+'true'=51
+'=='=52
+'=~'=53
+'!='=54
+'<'=55
+'<='=56
+'>'=57
+'>='=58
+'+'=59
+'-'=60
+'*'=61
+'/'=62
+'%'=63
+']'=66
+'metadata'=75
+'as'=84
+'on'=88
+'with'=89
+'info'=100
+':'=104
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
index c39a2041a61be..5b4428ee24118 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
@@ -262,11 +262,9 @@ public enum Cap {
         MATCH_OPERATOR(true),
 
         /**
-         * Support for the {@code META} keyword. Tests with this tag are
-         * intentionally excluded from mixed version clusters because we
-         * continually add functions, so they constantly fail if we don't.
+         * Removing support for the {@code META} keyword.
          */
-        META,
+        NO_META,
 
         /**
          * Add CombineBinaryComparisons rule.
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
index 7c0f1fa3a8ad0..8e238f9ed760c 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
@@ -133,7 +133,6 @@
 import org.elasticsearch.xpack.esql.expression.function.scalar.string.ToLower;
 import org.elasticsearch.xpack.esql.expression.function.scalar.string.ToUpper;
 import org.elasticsearch.xpack.esql.expression.function.scalar.string.Trim;
-import org.elasticsearch.xpack.esql.plan.logical.meta.MetaFunctions;
 import org.elasticsearch.xpack.esql.session.Configuration;
 
 import java.lang.reflect.Constructor;
@@ -450,31 +449,6 @@ public record FunctionDescription(
         boolean variadic,
         boolean isAggregation
     ) {
-        public String fullSignature() {
-            StringBuilder builder = new StringBuilder();
-            builder.append(MetaFunctions.withPipes(returnType));
-            builder.append(" ");
-            builder.append(name);
-            builder.append("(");
-            for (int i = 0; i < args.size(); i++) {
-                ArgSignature arg = args.get(i);
-                if (i > 0) {
-                    builder.append(", ");
-                }
-                if (arg.optional()) {
-                    builder.append("?");
-                }
-                builder.append(arg.name());
-                if (i == args.size() - 1 && variadic) {
-                    builder.append("...");
-                }
-                builder.append(":");
-                builder.append(MetaFunctions.withPipes(arg.type()));
-            }
-            builder.append(")");
-            return builder.toString();
-        }
-
         /**
          * The name of every argument.
          */
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionInfo.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionInfo.java
index f275496c6787a..1491f5643e4f5 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionInfo.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/FunctionInfo.java
@@ -29,13 +29,18 @@
     boolean preview() default false;
 
     /**
-     * The description of the function rendered in {@code META FUNCTIONS}
-     * and the docs. These should be complete sentences.
+     * The description of the function rendered in the docs and kibana's
+     * json files that drive their IDE-like experience. These should be
+     * complete sentences but can contain asciidoc syntax. It is rendered
+     * as a single paragraph.
      */
     String description() default "";
 
     /**
-     * Detailed descriptions of the function rendered in the docs.
+     * Detailed descriptions of the function rendered in the docs. This is
+     * rendered as a single paragraph following {@link #description()} in
+     * the docs and is excluded from Kibana's IDE-like
+     * experience. It can contain asciidoc syntax.
      */
     String detailedDescription() default "";
 
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java
index 6e76888f72b1d..d5d203e7bb3d1 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java
@@ -59,8 +59,8 @@ public class MvFirst extends AbstractMultivalueFunction {
         description = """
             Converts a multivalued expression into a single valued column containing the
             first value. This is most useful when reading from a function that emits
-            multivalued columns in a known order like <>.
-
+            multivalued columns in a known order like <>.""",
+        detailedDescription = """
             The order that <> are read from
             underlying storage is not guaranteed. It is *frequently* ascending, but don't
             rely on that. If you need the minimum value use <> instead of
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java
index 198731ca601f4..21487f14817cd 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java
@@ -59,8 +59,8 @@ public class MvLast extends AbstractMultivalueFunction {
         description = """
             Converts a multivalue expression into a single valued column containing the last
             value. This is most useful when reading from a function that emits multivalued
-            columns in a known order like <>.
-
+            columns in a known order like <>.""",
+        detailedDescription = """
             The order that <> are read from
             underlying storage is not guaranteed. It is *frequently* ascending, but don't
             rely on that. If you need the maximum value use <> instead of
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java
index 9846ebe4111c0..a829b6f1417b9 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java
@@ -68,7 +68,14 @@ public class MvSlice extends EsqlScalarFunction implements OptionalArgument, Eva
             "long",
             "text",
             "version" },
-        description = "Returns a subset of the multivalued field using the start and end index values.",
+        description = """
+            Returns a subset of the multivalued field using the start and end index values.
+            This is most useful when reading from a function that emits multivalued columns
+            in a known order like <> or <>.""",
+        detailedDescription = """
+            The order that <> are read from
+            underlying storage is not guaranteed. It is *frequently* ascending, but don't
+            rely on that.""",
         examples = { @Example(file = "ints", tag = "mv_slice_positive"), @Example(file = "ints", tag = "mv_slice_negative") }
     )
     public MvSlice(
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp
index 8122a56884280..4f3a843ee09d1 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.interp
@@ -9,7 +9,6 @@ null
 'grok'
 'keep'
 'limit'
-'meta'
 'mv_expand'
 'rename'
 'row'
@@ -104,10 +103,6 @@ null
 null
 null
 null
-'functions'
-null
-null
-null
 ':'
 null
 null
@@ -137,7 +132,6 @@ FROM
 GROK
 KEEP
 LIMIT
-META
 MV_EXPAND
 RENAME
 ROW
@@ -232,10 +226,6 @@ INFO
 SHOW_LINE_COMMENT
 SHOW_MULTILINE_COMMENT
 SHOW_WS
-FUNCTIONS
-META_LINE_COMMENT
-META_MULTILINE_COMMENT
-META_WS
 COLON
 SETTING
 SETTING_LINE_COMMENT
@@ -264,7 +254,6 @@ FROM
 GROK
 KEEP
 LIMIT
-META
 MV_EXPAND
 RENAME
 ROW
@@ -408,11 +397,6 @@ INFO
 SHOW_LINE_COMMENT
 SHOW_MULTILINE_COMMENT
 SHOW_WS
-META_PIPE
-FUNCTIONS
-META_LINE_COMMENT
-META_MULTILINE_COMMENT
-META_WS
 SETTING_CLOSING_BRACKET
 COLON
 SETTING
@@ -467,7 +451,6 @@ ENRICH_MODE
 ENRICH_FIELD_MODE
 MVEXPAND_MODE
 SHOW_MODE
-META_MODE
 SETTING_MODE
 LOOKUP_MODE
 LOOKUP_FIELD_MODE
@@ -475,4 +458,4 @@ METRICS_MODE
 CLOSING_METRICS_MODE
 
 atn:
-[4, 0, 125, 1474, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 4, 21, 591, 8, 21, 11, 21, 12, 21, 592, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 601, 8, 22, 10, 22, 12, 22, 604, 9, 22, 1, 22, 3, 22, 607, 8, 22, 1, 22, 3, 22, 610, 8, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 5, 23, 619, 8, 23, 10, 23, 12, 23, 622, 9, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 4, 24, 630, 8, 24, 11, 24, 12, 24, 631, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 3, 30, 651, 8, 30, 1, 30, 4, 30, 654, 8, 30, 11, 30, 12, 30, 655, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 665, 8, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 3, 35, 672, 8, 35, 1, 36, 1, 36, 1, 36, 5, 36, 677, 8, 36, 10, 36, 12, 36, 680, 9, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 688, 8, 36, 10, 36, 12, 36, 691, 9, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 3, 36, 698, 8, 36, 1, 36, 3, 36, 701, 8, 36, 3, 36, 703, 8, 36, 1, 37, 4, 37, 706, 8, 37, 11, 37, 12, 37, 707, 1, 38, 4, 38, 711, 8, 38, 11, 38, 12, 38, 712, 1, 38, 1, 38, 5, 38, 717, 8, 38, 10, 38, 12, 38, 720, 9, 38, 1, 38, 1, 38, 4, 38, 724, 8, 38, 11, 38, 12, 38, 725, 1, 38, 4, 38, 729, 8, 38, 11, 38, 12, 38, 730, 1, 38, 1, 38, 5, 38, 735, 8, 38, 10, 38, 12, 38, 738, 9, 38, 3, 38, 740, 8, 38, 1, 38, 1, 38, 1, 38, 1, 38, 4, 38, 746, 8, 38, 11, 38, 12, 38, 747, 1, 38, 1, 38, 3, 38, 752, 8, 38, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 3, 75, 879, 8, 75, 1, 75, 5, 75, 882, 8, 75, 10, 75, 12, 75, 885, 9, 75, 1, 75, 1, 75, 4, 75, 889, 8, 75, 11, 75, 12, 75, 890, 3, 75, 893, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 5, 78, 907, 8, 78, 10, 78, 12, 78, 910, 9, 78, 1, 78, 1, 78, 3, 78, 914, 8, 78, 1, 78, 4, 78, 917, 8, 78, 11, 78, 12, 78, 918, 3, 78, 921, 8, 78, 1, 79, 1, 79, 4, 79, 925, 8, 79, 11, 79, 12, 79, 926, 1, 79, 1, 79, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 3, 96, 1004, 8, 96, 1, 97, 4, 97, 1007, 8, 97, 11, 97, 12, 97, 1008, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 3, 106, 1048, 8, 106, 1, 107, 1, 107, 3, 107, 1052, 8, 107, 1, 107, 5, 107, 1055, 8, 107, 10, 107, 12, 107, 1058, 9, 107, 1, 107, 1, 107, 3, 107, 1062, 8, 107, 1, 107, 4, 107, 1065, 8, 107, 11, 107, 12, 107, 1066, 3, 107, 1069, 8, 107, 1, 108, 1, 108, 4, 108, 1073, 8, 108, 11, 108, 12, 108, 1074, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 126, 4, 126, 1150, 8, 126, 11, 126, 12, 126, 1151, 1, 126, 1, 126, 3, 126, 1156, 8, 126, 1, 126, 4, 126, 1159, 8, 126, 11, 126, 12, 126, 1160, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 4, 160, 1311, 8, 160, 11, 160, 12, 160, 1312, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 2, 620, 689, 0, 196, 16, 1, 18, 2, 20, 3, 22, 4, 24, 5, 26, 6, 28, 7, 30, 8, 32, 9, 34, 10, 36, 11, 38, 12, 40, 13, 42, 14, 44, 15, 46, 16, 48, 17, 50, 18, 52, 19, 54, 20, 56, 21, 58, 22, 60, 23, 62, 24, 64, 25, 66, 26, 68, 0, 70, 0, 72, 0, 74, 0, 76, 0, 78, 0, 80, 0, 82, 0, 84, 0, 86, 0, 88, 27, 90, 28, 92, 29, 94, 30, 96, 31, 98, 32, 100, 33, 102, 34, 104, 35, 106, 36, 108, 37, 110, 38, 112, 39, 114, 40, 116, 41, 118, 42, 120, 43, 122, 44, 124, 45, 126, 46, 128, 47, 130, 48, 132, 49, 134, 50, 136, 51, 138, 52, 140, 53, 142, 54, 144, 55, 146, 56, 148, 57, 150, 58, 152, 59, 154, 60, 156, 61, 158, 62, 160, 63, 162, 64, 164, 0, 166, 65, 168, 66, 170, 67, 172, 68, 174, 0, 176, 69, 178, 70, 180, 71, 182, 72, 184, 0, 186, 0, 188, 73, 190, 74, 192, 75, 194, 0, 196, 0, 198, 0, 200, 0, 202, 0, 204, 0, 206, 76, 208, 0, 210, 77, 212, 0, 214, 0, 216, 78, 218, 79, 220, 80, 222, 0, 224, 0, 226, 0, 228, 0, 230, 0, 232, 81, 234, 82, 236, 83, 238, 84, 240, 0, 242, 0, 244, 0, 246, 0, 248, 85, 250, 0, 252, 86, 254, 87, 256, 88, 258, 0, 260, 0, 262, 89, 264, 90, 266, 0, 268, 91, 270, 0, 272, 92, 274, 93, 276, 94, 278, 0, 280, 0, 282, 0, 284, 0, 286, 0, 288, 0, 290, 0, 292, 95, 294, 96, 296, 97, 298, 0, 300, 0, 302, 0, 304, 0, 306, 98, 308, 99, 310, 100, 312, 0, 314, 101, 316, 102, 318, 103, 320, 104, 322, 0, 324, 105, 326, 106, 328, 107, 330, 108, 332, 0, 334, 109, 336, 110, 338, 111, 340, 112, 342, 113, 344, 0, 346, 0, 348, 0, 350, 0, 352, 0, 354, 0, 356, 0, 358, 114, 360, 115, 362, 116, 364, 0, 366, 0, 368, 0, 370, 0, 372, 117, 374, 118, 376, 119, 378, 0, 380, 0, 382, 0, 384, 120, 386, 121, 388, 122, 390, 0, 392, 0, 394, 123, 396, 124, 398, 125, 400, 0, 402, 0, 404, 0, 406, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1501, 0, 16, 1, 0, 0, 0, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 64, 1, 0, 0, 0, 1, 66, 1, 0, 0, 0, 1, 88, 1, 0, 0, 0, 1, 90, 1, 0, 0, 0, 1, 92, 1, 0, 0, 0, 1, 94, 1, 0, 0, 0, 1, 96, 1, 0, 0, 0, 1, 98, 1, 0, 0, 0, 1, 100, 1, 0, 0, 0, 1, 102, 1, 0, 0, 0, 1, 104, 1, 0, 0, 0, 1, 106, 1, 0, 0, 0, 1, 108, 1, 0, 0, 0, 1, 110, 1, 0, 0, 0, 1, 112, 1, 0, 0, 0, 1, 114, 1, 0, 0, 0, 1, 116, 1, 0, 0, 0, 1, 118, 1, 0, 0, 0, 1, 120, 1, 0, 0, 0, 1, 122, 1, 0, 0, 0, 1, 124, 1, 0, 0, 0, 1, 126, 1, 0, 0, 0, 1, 128, 1, 0, 0, 0, 1, 130, 1, 0, 0, 0, 1, 132, 1, 0, 0, 0, 1, 134, 1, 0, 0, 0, 1, 136, 1, 0, 0, 0, 1, 138, 1, 0, 0, 0, 1, 140, 1, 0, 0, 0, 1, 142, 1, 0, 0, 0, 1, 144, 1, 0, 0, 0, 1, 146, 1, 0, 0, 0, 1, 148, 1, 0, 0, 0, 1, 150, 1, 0, 0, 0, 1, 152, 1, 0, 0, 0, 1, 154, 1, 0, 0, 0, 1, 156, 1, 0, 0, 0, 1, 158, 1, 0, 0, 0, 1, 160, 1, 0, 0, 0, 1, 162, 1, 0, 0, 0, 1, 164, 1, 0, 0, 0, 1, 166, 1, 0, 0, 0, 1, 168, 1, 0, 0, 0, 1, 170, 1, 0, 0, 0, 1, 172, 1, 0, 0, 0, 1, 176, 1, 0, 0, 0, 1, 178, 1, 0, 0, 0, 1, 180, 1, 0, 0, 0, 1, 182, 1, 0, 0, 0, 2, 184, 1, 0, 0, 0, 2, 186, 1, 0, 0, 0, 2, 188, 1, 0, 0, 0, 2, 190, 1, 0, 0, 0, 2, 192, 1, 0, 0, 0, 3, 194, 1, 0, 0, 0, 3, 196, 1, 0, 0, 0, 3, 198, 1, 0, 0, 0, 3, 200, 1, 0, 0, 0, 3, 202, 1, 0, 0, 0, 3, 204, 1, 0, 0, 0, 3, 206, 1, 0, 0, 0, 3, 210, 1, 0, 0, 0, 3, 212, 1, 0, 0, 0, 3, 214, 1, 0, 0, 0, 3, 216, 1, 0, 0, 0, 3, 218, 1, 0, 0, 0, 3, 220, 1, 0, 0, 0, 4, 222, 1, 0, 0, 0, 4, 224, 1, 0, 0, 0, 4, 226, 1, 0, 0, 0, 4, 232, 1, 0, 0, 0, 4, 234, 1, 0, 0, 0, 4, 236, 1, 0, 0, 0, 4, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 5, 246, 1, 0, 0, 0, 5, 248, 1, 0, 0, 0, 5, 250, 1, 0, 0, 0, 5, 252, 1, 0, 0, 0, 5, 254, 1, 0, 0, 0, 5, 256, 1, 0, 0, 0, 6, 258, 1, 0, 0, 0, 6, 260, 1, 0, 0, 0, 6, 262, 1, 0, 0, 0, 6, 264, 1, 0, 0, 0, 6, 268, 1, 0, 0, 0, 6, 270, 1, 0, 0, 0, 6, 272, 1, 0, 0, 0, 6, 274, 1, 0, 0, 0, 6, 276, 1, 0, 0, 0, 7, 278, 1, 0, 0, 0, 7, 280, 1, 0, 0, 0, 7, 282, 1, 0, 0, 0, 7, 284, 1, 0, 0, 0, 7, 286, 1, 0, 0, 0, 7, 288, 1, 0, 0, 0, 7, 290, 1, 0, 0, 0, 7, 292, 1, 0, 0, 0, 7, 294, 1, 0, 0, 0, 7, 296, 1, 0, 0, 0, 8, 298, 1, 0, 0, 0, 8, 300, 1, 0, 0, 0, 8, 302, 1, 0, 0, 0, 8, 304, 1, 0, 0, 0, 8, 306, 1, 0, 0, 0, 8, 308, 1, 0, 0, 0, 8, 310, 1, 0, 0, 0, 9, 312, 1, 0, 0, 0, 9, 314, 1, 0, 0, 0, 9, 316, 1, 0, 0, 0, 9, 318, 1, 0, 0, 0, 9, 320, 1, 0, 0, 0, 10, 322, 1, 0, 0, 0, 10, 324, 1, 0, 0, 0, 10, 326, 1, 0, 0, 0, 10, 328, 1, 0, 0, 0, 10, 330, 1, 0, 0, 0, 11, 332, 1, 0, 0, 0, 11, 334, 1, 0, 0, 0, 11, 336, 1, 0, 0, 0, 11, 338, 1, 0, 0, 0, 11, 340, 1, 0, 0, 0, 11, 342, 1, 0, 0, 0, 12, 344, 1, 0, 0, 0, 12, 346, 1, 0, 0, 0, 12, 348, 1, 0, 0, 0, 12, 350, 1, 0, 0, 0, 12, 352, 1, 0, 0, 0, 12, 354, 1, 0, 0, 0, 12, 356, 1, 0, 0, 0, 12, 358, 1, 0, 0, 0, 12, 360, 1, 0, 0, 0, 12, 362, 1, 0, 0, 0, 13, 364, 1, 0, 0, 0, 13, 366, 1, 0, 0, 0, 13, 368, 1, 0, 0, 0, 13, 370, 1, 0, 0, 0, 13, 372, 1, 0, 0, 0, 13, 374, 1, 0, 0, 0, 13, 376, 1, 0, 0, 0, 14, 378, 1, 0, 0, 0, 14, 380, 1, 0, 0, 0, 14, 382, 1, 0, 0, 0, 14, 384, 1, 0, 0, 0, 14, 386, 1, 0, 0, 0, 14, 388, 1, 0, 0, 0, 15, 390, 1, 0, 0, 0, 15, 392, 1, 0, 0, 0, 15, 394, 1, 0, 0, 0, 15, 396, 1, 0, 0, 0, 15, 398, 1, 0, 0, 0, 15, 400, 1, 0, 0, 0, 15, 402, 1, 0, 0, 0, 15, 404, 1, 0, 0, 0, 15, 406, 1, 0, 0, 0, 16, 408, 1, 0, 0, 0, 18, 418, 1, 0, 0, 0, 20, 425, 1, 0, 0, 0, 22, 434, 1, 0, 0, 0, 24, 441, 1, 0, 0, 0, 26, 451, 1, 0, 0, 0, 28, 458, 1, 0, 0, 0, 30, 465, 1, 0, 0, 0, 32, 472, 1, 0, 0, 0, 34, 480, 1, 0, 0, 0, 36, 487, 1, 0, 0, 0, 38, 499, 1, 0, 0, 0, 40, 508, 1, 0, 0, 0, 42, 514, 1, 0, 0, 0, 44, 521, 1, 0, 0, 0, 46, 528, 1, 0, 0, 0, 48, 536, 1, 0, 0, 0, 50, 544, 1, 0, 0, 0, 52, 559, 1, 0, 0, 0, 54, 569, 1, 0, 0, 0, 56, 578, 1, 0, 0, 0, 58, 590, 1, 0, 0, 0, 60, 596, 1, 0, 0, 0, 62, 613, 1, 0, 0, 0, 64, 629, 1, 0, 0, 0, 66, 635, 1, 0, 0, 0, 68, 639, 1, 0, 0, 0, 70, 641, 1, 0, 0, 0, 72, 643, 1, 0, 0, 0, 74, 646, 1, 0, 0, 0, 76, 648, 1, 0, 0, 0, 78, 657, 1, 0, 0, 0, 80, 659, 1, 0, 0, 0, 82, 664, 1, 0, 0, 0, 84, 666, 1, 0, 0, 0, 86, 671, 1, 0, 0, 0, 88, 702, 1, 0, 0, 0, 90, 705, 1, 0, 0, 0, 92, 751, 1, 0, 0, 0, 94, 753, 1, 0, 0, 0, 96, 756, 1, 0, 0, 0, 98, 760, 1, 0, 0, 0, 100, 764, 1, 0, 0, 0, 102, 766, 1, 0, 0, 0, 104, 769, 1, 0, 0, 0, 106, 771, 1, 0, 0, 0, 108, 776, 1, 0, 0, 0, 110, 778, 1, 0, 0, 0, 112, 784, 1, 0, 0, 0, 114, 790, 1, 0, 0, 0, 116, 793, 1, 0, 0, 0, 118, 796, 1, 0, 0, 0, 120, 801, 1, 0, 0, 0, 122, 806, 1, 0, 0, 0, 124, 808, 1, 0, 0, 0, 126, 812, 1, 0, 0, 0, 128, 817, 1, 0, 0, 0, 130, 823, 1, 0, 0, 0, 132, 826, 1, 0, 0, 0, 134, 828, 1, 0, 0, 0, 136, 834, 1, 0, 0, 0, 138, 836, 1, 0, 0, 0, 140, 841, 1, 0, 0, 0, 142, 844, 1, 0, 0, 0, 144, 847, 1, 0, 0, 0, 146, 850, 1, 0, 0, 0, 148, 852, 1, 0, 0, 0, 150, 855, 1, 0, 0, 0, 152, 857, 1, 0, 0, 0, 154, 860, 1, 0, 0, 0, 156, 862, 1, 0, 0, 0, 158, 864, 1, 0, 0, 0, 160, 866, 1, 0, 0, 0, 162, 868, 1, 0, 0, 0, 164, 870, 1, 0, 0, 0, 166, 892, 1, 0, 0, 0, 168, 894, 1, 0, 0, 0, 170, 899, 1, 0, 0, 0, 172, 920, 1, 0, 0, 0, 174, 922, 1, 0, 0, 0, 176, 930, 1, 0, 0, 0, 178, 932, 1, 0, 0, 0, 180, 936, 1, 0, 0, 0, 182, 940, 1, 0, 0, 0, 184, 944, 1, 0, 0, 0, 186, 949, 1, 0, 0, 0, 188, 954, 1, 0, 0, 0, 190, 958, 1, 0, 0, 0, 192, 962, 1, 0, 0, 0, 194, 966, 1, 0, 0, 0, 196, 971, 1, 0, 0, 0, 198, 975, 1, 0, 0, 0, 200, 979, 1, 0, 0, 0, 202, 983, 1, 0, 0, 0, 204, 987, 1, 0, 0, 0, 206, 991, 1, 0, 0, 0, 208, 1003, 1, 0, 0, 0, 210, 1006, 1, 0, 0, 0, 212, 1010, 1, 0, 0, 0, 214, 1014, 1, 0, 0, 0, 216, 1018, 1, 0, 0, 0, 218, 1022, 1, 0, 0, 0, 220, 1026, 1, 0, 0, 0, 222, 1030, 1, 0, 0, 0, 224, 1035, 1, 0, 0, 0, 226, 1039, 1, 0, 0, 0, 228, 1047, 1, 0, 0, 0, 230, 1068, 1, 0, 0, 0, 232, 1072, 1, 0, 0, 0, 234, 1076, 1, 0, 0, 0, 236, 1080, 1, 0, 0, 0, 238, 1084, 1, 0, 0, 0, 240, 1088, 1, 0, 0, 0, 242, 1093, 1, 0, 0, 0, 244, 1097, 1, 0, 0, 0, 246, 1101, 1, 0, 0, 0, 248, 1105, 1, 0, 0, 0, 250, 1108, 1, 0, 0, 0, 252, 1112, 1, 0, 0, 0, 254, 1116, 1, 0, 0, 0, 256, 1120, 1, 0, 0, 0, 258, 1124, 1, 0, 0, 0, 260, 1129, 1, 0, 0, 0, 262, 1134, 1, 0, 0, 0, 264, 1139, 1, 0, 0, 0, 266, 1146, 1, 0, 0, 0, 268, 1155, 1, 0, 0, 0, 270, 1162, 1, 0, 0, 0, 272, 1166, 1, 0, 0, 0, 274, 1170, 1, 0, 0, 0, 276, 1174, 1, 0, 0, 0, 278, 1178, 1, 0, 0, 0, 280, 1184, 1, 0, 0, 0, 282, 1188, 1, 0, 0, 0, 284, 1192, 1, 0, 0, 0, 286, 1196, 1, 0, 0, 0, 288, 1200, 1, 0, 0, 0, 290, 1204, 1, 0, 0, 0, 292, 1208, 1, 0, 0, 0, 294, 1212, 1, 0, 0, 0, 296, 1216, 1, 0, 0, 0, 298, 1220, 1, 0, 0, 0, 300, 1225, 1, 0, 0, 0, 302, 1229, 1, 0, 0, 0, 304, 1233, 1, 0, 0, 0, 306, 1237, 1, 0, 0, 0, 308, 1241, 1, 0, 0, 0, 310, 1245, 1, 0, 0, 0, 312, 1249, 1, 0, 0, 0, 314, 1254, 1, 0, 0, 0, 316, 1259, 1, 0, 0, 0, 318, 1263, 1, 0, 0, 0, 320, 1267, 1, 0, 0, 0, 322, 1271, 1, 0, 0, 0, 324, 1276, 1, 0, 0, 0, 326, 1286, 1, 0, 0, 0, 328, 1290, 1, 0, 0, 0, 330, 1294, 1, 0, 0, 0, 332, 1298, 1, 0, 0, 0, 334, 1303, 1, 0, 0, 0, 336, 1310, 1, 0, 0, 0, 338, 1314, 1, 0, 0, 0, 340, 1318, 1, 0, 0, 0, 342, 1322, 1, 0, 0, 0, 344, 1326, 1, 0, 0, 0, 346, 1331, 1, 0, 0, 0, 348, 1335, 1, 0, 0, 0, 350, 1339, 1, 0, 0, 0, 352, 1343, 1, 0, 0, 0, 354, 1348, 1, 0, 0, 0, 356, 1352, 1, 0, 0, 0, 358, 1356, 1, 0, 0, 0, 360, 1360, 1, 0, 0, 0, 362, 1364, 1, 0, 0, 0, 364, 1368, 1, 0, 0, 0, 366, 1374, 1, 0, 0, 0, 368, 1378, 1, 0, 0, 0, 370, 1382, 1, 0, 0, 0, 372, 1386, 1, 0, 0, 0, 374, 1390, 1, 0, 0, 0, 376, 1394, 1, 0, 0, 0, 378, 1398, 1, 0, 0, 0, 380, 1403, 1, 0, 0, 0, 382, 1409, 1, 0, 0, 0, 384, 1415, 1, 0, 0, 0, 386, 1419, 1, 0, 0, 0, 388, 1423, 1, 0, 0, 0, 390, 1427, 1, 0, 0, 0, 392, 1433, 1, 0, 0, 0, 394, 1439, 1, 0, 0, 0, 396, 1443, 1, 0, 0, 0, 398, 1447, 1, 0, 0, 0, 400, 1451, 1, 0, 0, 0, 402, 1457, 1, 0, 0, 0, 404, 1463, 1, 0, 0, 0, 406, 1469, 1, 0, 0, 0, 408, 409, 7, 0, 0, 0, 409, 410, 7, 1, 0, 0, 410, 411, 7, 2, 0, 0, 411, 412, 7, 2, 0, 0, 412, 413, 7, 3, 0, 0, 413, 414, 7, 4, 0, 0, 414, 415, 7, 5, 0, 0, 415, 416, 1, 0, 0, 0, 416, 417, 6, 0, 0, 0, 417, 17, 1, 0, 0, 0, 418, 419, 7, 0, 0, 0, 419, 420, 7, 6, 0, 0, 420, 421, 7, 7, 0, 0, 421, 422, 7, 8, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 6, 1, 1, 0, 424, 19, 1, 0, 0, 0, 425, 426, 7, 3, 0, 0, 426, 427, 7, 9, 0, 0, 427, 428, 7, 6, 0, 0, 428, 429, 7, 1, 0, 0, 429, 430, 7, 4, 0, 0, 430, 431, 7, 10, 0, 0, 431, 432, 1, 0, 0, 0, 432, 433, 6, 2, 2, 0, 433, 21, 1, 0, 0, 0, 434, 435, 7, 3, 0, 0, 435, 436, 7, 11, 0, 0, 436, 437, 7, 12, 0, 0, 437, 438, 7, 13, 0, 0, 438, 439, 1, 0, 0, 0, 439, 440, 6, 3, 0, 0, 440, 23, 1, 0, 0, 0, 441, 442, 7, 3, 0, 0, 442, 443, 7, 14, 0, 0, 443, 444, 7, 8, 0, 0, 444, 445, 7, 13, 0, 0, 445, 446, 7, 12, 0, 0, 446, 447, 7, 1, 0, 0, 447, 448, 7, 9, 0, 0, 448, 449, 1, 0, 0, 0, 449, 450, 6, 4, 3, 0, 450, 25, 1, 0, 0, 0, 451, 452, 7, 15, 0, 0, 452, 453, 7, 6, 0, 0, 453, 454, 7, 7, 0, 0, 454, 455, 7, 16, 0, 0, 455, 456, 1, 0, 0, 0, 456, 457, 6, 5, 4, 0, 457, 27, 1, 0, 0, 0, 458, 459, 7, 17, 0, 0, 459, 460, 7, 6, 0, 0, 460, 461, 7, 7, 0, 0, 461, 462, 7, 18, 0, 0, 462, 463, 1, 0, 0, 0, 463, 464, 6, 6, 0, 0, 464, 29, 1, 0, 0, 0, 465, 466, 7, 18, 0, 0, 466, 467, 7, 3, 0, 0, 467, 468, 7, 3, 0, 0, 468, 469, 7, 8, 0, 0, 469, 470, 1, 0, 0, 0, 470, 471, 6, 7, 1, 0, 471, 31, 1, 0, 0, 0, 472, 473, 7, 13, 0, 0, 473, 474, 7, 1, 0, 0, 474, 475, 7, 16, 0, 0, 475, 476, 7, 1, 0, 0, 476, 477, 7, 5, 0, 0, 477, 478, 1, 0, 0, 0, 478, 479, 6, 8, 0, 0, 479, 33, 1, 0, 0, 0, 480, 481, 7, 16, 0, 0, 481, 482, 7, 3, 0, 0, 482, 483, 7, 5, 0, 0, 483, 484, 7, 12, 0, 0, 484, 485, 1, 0, 0, 0, 485, 486, 6, 9, 5, 0, 486, 35, 1, 0, 0, 0, 487, 488, 7, 16, 0, 0, 488, 489, 7, 11, 0, 0, 489, 490, 5, 95, 0, 0, 490, 491, 7, 3, 0, 0, 491, 492, 7, 14, 0, 0, 492, 493, 7, 8, 0, 0, 493, 494, 7, 12, 0, 0, 494, 495, 7, 9, 0, 0, 495, 496, 7, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 498, 6, 10, 6, 0, 498, 37, 1, 0, 0, 0, 499, 500, 7, 6, 0, 0, 500, 501, 7, 3, 0, 0, 501, 502, 7, 9, 0, 0, 502, 503, 7, 12, 0, 0, 503, 504, 7, 16, 0, 0, 504, 505, 7, 3, 0, 0, 505, 506, 1, 0, 0, 0, 506, 507, 6, 11, 7, 0, 507, 39, 1, 0, 0, 0, 508, 509, 7, 6, 0, 0, 509, 510, 7, 7, 0, 0, 510, 511, 7, 19, 0, 0, 511, 512, 1, 0, 0, 0, 512, 513, 6, 12, 0, 0, 513, 41, 1, 0, 0, 0, 514, 515, 7, 2, 0, 0, 515, 516, 7, 10, 0, 0, 516, 517, 7, 7, 0, 0, 517, 518, 7, 19, 0, 0, 518, 519, 1, 0, 0, 0, 519, 520, 6, 13, 8, 0, 520, 43, 1, 0, 0, 0, 521, 522, 7, 2, 0, 0, 522, 523, 7, 7, 0, 0, 523, 524, 7, 6, 0, 0, 524, 525, 7, 5, 0, 0, 525, 526, 1, 0, 0, 0, 526, 527, 6, 14, 0, 0, 527, 45, 1, 0, 0, 0, 528, 529, 7, 2, 0, 0, 529, 530, 7, 5, 0, 0, 530, 531, 7, 12, 0, 0, 531, 532, 7, 5, 0, 0, 532, 533, 7, 2, 0, 0, 533, 534, 1, 0, 0, 0, 534, 535, 6, 15, 0, 0, 535, 47, 1, 0, 0, 0, 536, 537, 7, 19, 0, 0, 537, 538, 7, 10, 0, 0, 538, 539, 7, 3, 0, 0, 539, 540, 7, 6, 0, 0, 540, 541, 7, 3, 0, 0, 541, 542, 1, 0, 0, 0, 542, 543, 6, 16, 0, 0, 543, 49, 1, 0, 0, 0, 544, 545, 4, 17, 0, 0, 545, 546, 7, 1, 0, 0, 546, 547, 7, 9, 0, 0, 547, 548, 7, 13, 0, 0, 548, 549, 7, 1, 0, 0, 549, 550, 7, 9, 0, 0, 550, 551, 7, 3, 0, 0, 551, 552, 7, 2, 0, 0, 552, 553, 7, 5, 0, 0, 553, 554, 7, 12, 0, 0, 554, 555, 7, 5, 0, 0, 555, 556, 7, 2, 0, 0, 556, 557, 1, 0, 0, 0, 557, 558, 6, 17, 0, 0, 558, 51, 1, 0, 0, 0, 559, 560, 4, 18, 1, 0, 560, 561, 7, 13, 0, 0, 561, 562, 7, 7, 0, 0, 562, 563, 7, 7, 0, 0, 563, 564, 7, 18, 0, 0, 564, 565, 7, 20, 0, 0, 565, 566, 7, 8, 0, 0, 566, 567, 1, 0, 0, 0, 567, 568, 6, 18, 9, 0, 568, 53, 1, 0, 0, 0, 569, 570, 4, 19, 2, 0, 570, 571, 7, 16, 0, 0, 571, 572, 7, 12, 0, 0, 572, 573, 7, 5, 0, 0, 573, 574, 7, 4, 0, 0, 574, 575, 7, 10, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 6, 19, 0, 0, 577, 55, 1, 0, 0, 0, 578, 579, 4, 20, 3, 0, 579, 580, 7, 16, 0, 0, 580, 581, 7, 3, 0, 0, 581, 582, 7, 5, 0, 0, 582, 583, 7, 6, 0, 0, 583, 584, 7, 1, 0, 0, 584, 585, 7, 4, 0, 0, 585, 586, 7, 2, 0, 0, 586, 587, 1, 0, 0, 0, 587, 588, 6, 20, 10, 0, 588, 57, 1, 0, 0, 0, 589, 591, 8, 21, 0, 0, 590, 589, 1, 0, 0, 0, 591, 592, 1, 0, 0, 0, 592, 590, 1, 0, 0, 0, 592, 593, 1, 0, 0, 0, 593, 594, 1, 0, 0, 0, 594, 595, 6, 21, 0, 0, 595, 59, 1, 0, 0, 0, 596, 597, 5, 47, 0, 0, 597, 598, 5, 47, 0, 0, 598, 602, 1, 0, 0, 0, 599, 601, 8, 22, 0, 0, 600, 599, 1, 0, 0, 0, 601, 604, 1, 0, 0, 0, 602, 600, 1, 0, 0, 0, 602, 603, 1, 0, 0, 0, 603, 606, 1, 0, 0, 0, 604, 602, 1, 0, 0, 0, 605, 607, 5, 13, 0, 0, 606, 605, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 609, 1, 0, 0, 0, 608, 610, 5, 10, 0, 0, 609, 608, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 610, 611, 1, 0, 0, 0, 611, 612, 6, 22, 11, 0, 612, 61, 1, 0, 0, 0, 613, 614, 5, 47, 0, 0, 614, 615, 5, 42, 0, 0, 615, 620, 1, 0, 0, 0, 616, 619, 3, 62, 23, 0, 617, 619, 9, 0, 0, 0, 618, 616, 1, 0, 0, 0, 618, 617, 1, 0, 0, 0, 619, 622, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 623, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 624, 5, 42, 0, 0, 624, 625, 5, 47, 0, 0, 625, 626, 1, 0, 0, 0, 626, 627, 6, 23, 11, 0, 627, 63, 1, 0, 0, 0, 628, 630, 7, 23, 0, 0, 629, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 629, 1, 0, 0, 0, 631, 632, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 634, 6, 24, 11, 0, 634, 65, 1, 0, 0, 0, 635, 636, 5, 124, 0, 0, 636, 637, 1, 0, 0, 0, 637, 638, 6, 25, 12, 0, 638, 67, 1, 0, 0, 0, 639, 640, 7, 24, 0, 0, 640, 69, 1, 0, 0, 0, 641, 642, 7, 25, 0, 0, 642, 71, 1, 0, 0, 0, 643, 644, 5, 92, 0, 0, 644, 645, 7, 26, 0, 0, 645, 73, 1, 0, 0, 0, 646, 647, 8, 27, 0, 0, 647, 75, 1, 0, 0, 0, 648, 650, 7, 3, 0, 0, 649, 651, 7, 28, 0, 0, 650, 649, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 653, 1, 0, 0, 0, 652, 654, 3, 68, 26, 0, 653, 652, 1, 0, 0, 0, 654, 655, 1, 0, 0, 0, 655, 653, 1, 0, 0, 0, 655, 656, 1, 0, 0, 0, 656, 77, 1, 0, 0, 0, 657, 658, 5, 64, 0, 0, 658, 79, 1, 0, 0, 0, 659, 660, 5, 96, 0, 0, 660, 81, 1, 0, 0, 0, 661, 665, 8, 29, 0, 0, 662, 663, 5, 96, 0, 0, 663, 665, 5, 96, 0, 0, 664, 661, 1, 0, 0, 0, 664, 662, 1, 0, 0, 0, 665, 83, 1, 0, 0, 0, 666, 667, 5, 95, 0, 0, 667, 85, 1, 0, 0, 0, 668, 672, 3, 70, 27, 0, 669, 672, 3, 68, 26, 0, 670, 672, 3, 84, 34, 0, 671, 668, 1, 0, 0, 0, 671, 669, 1, 0, 0, 0, 671, 670, 1, 0, 0, 0, 672, 87, 1, 0, 0, 0, 673, 678, 5, 34, 0, 0, 674, 677, 3, 72, 28, 0, 675, 677, 3, 74, 29, 0, 676, 674, 1, 0, 0, 0, 676, 675, 1, 0, 0, 0, 677, 680, 1, 0, 0, 0, 678, 676, 1, 0, 0, 0, 678, 679, 1, 0, 0, 0, 679, 681, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 681, 703, 5, 34, 0, 0, 682, 683, 5, 34, 0, 0, 683, 684, 5, 34, 0, 0, 684, 685, 5, 34, 0, 0, 685, 689, 1, 0, 0, 0, 686, 688, 8, 22, 0, 0, 687, 686, 1, 0, 0, 0, 688, 691, 1, 0, 0, 0, 689, 690, 1, 0, 0, 0, 689, 687, 1, 0, 0, 0, 690, 692, 1, 0, 0, 0, 691, 689, 1, 0, 0, 0, 692, 693, 5, 34, 0, 0, 693, 694, 5, 34, 0, 0, 694, 695, 5, 34, 0, 0, 695, 697, 1, 0, 0, 0, 696, 698, 5, 34, 0, 0, 697, 696, 1, 0, 0, 0, 697, 698, 1, 0, 0, 0, 698, 700, 1, 0, 0, 0, 699, 701, 5, 34, 0, 0, 700, 699, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 703, 1, 0, 0, 0, 702, 673, 1, 0, 0, 0, 702, 682, 1, 0, 0, 0, 703, 89, 1, 0, 0, 0, 704, 706, 3, 68, 26, 0, 705, 704, 1, 0, 0, 0, 706, 707, 1, 0, 0, 0, 707, 705, 1, 0, 0, 0, 707, 708, 1, 0, 0, 0, 708, 91, 1, 0, 0, 0, 709, 711, 3, 68, 26, 0, 710, 709, 1, 0, 0, 0, 711, 712, 1, 0, 0, 0, 712, 710, 1, 0, 0, 0, 712, 713, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 718, 3, 108, 46, 0, 715, 717, 3, 68, 26, 0, 716, 715, 1, 0, 0, 0, 717, 720, 1, 0, 0, 0, 718, 716, 1, 0, 0, 0, 718, 719, 1, 0, 0, 0, 719, 752, 1, 0, 0, 0, 720, 718, 1, 0, 0, 0, 721, 723, 3, 108, 46, 0, 722, 724, 3, 68, 26, 0, 723, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 723, 1, 0, 0, 0, 725, 726, 1, 0, 0, 0, 726, 752, 1, 0, 0, 0, 727, 729, 3, 68, 26, 0, 728, 727, 1, 0, 0, 0, 729, 730, 1, 0, 0, 0, 730, 728, 1, 0, 0, 0, 730, 731, 1, 0, 0, 0, 731, 739, 1, 0, 0, 0, 732, 736, 3, 108, 46, 0, 733, 735, 3, 68, 26, 0, 734, 733, 1, 0, 0, 0, 735, 738, 1, 0, 0, 0, 736, 734, 1, 0, 0, 0, 736, 737, 1, 0, 0, 0, 737, 740, 1, 0, 0, 0, 738, 736, 1, 0, 0, 0, 739, 732, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 3, 76, 30, 0, 742, 752, 1, 0, 0, 0, 743, 745, 3, 108, 46, 0, 744, 746, 3, 68, 26, 0, 745, 744, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 745, 1, 0, 0, 0, 747, 748, 1, 0, 0, 0, 748, 749, 1, 0, 0, 0, 749, 750, 3, 76, 30, 0, 750, 752, 1, 0, 0, 0, 751, 710, 1, 0, 0, 0, 751, 721, 1, 0, 0, 0, 751, 728, 1, 0, 0, 0, 751, 743, 1, 0, 0, 0, 752, 93, 1, 0, 0, 0, 753, 754, 7, 30, 0, 0, 754, 755, 7, 31, 0, 0, 755, 95, 1, 0, 0, 0, 756, 757, 7, 12, 0, 0, 757, 758, 7, 9, 0, 0, 758, 759, 7, 0, 0, 0, 759, 97, 1, 0, 0, 0, 760, 761, 7, 12, 0, 0, 761, 762, 7, 2, 0, 0, 762, 763, 7, 4, 0, 0, 763, 99, 1, 0, 0, 0, 764, 765, 5, 61, 0, 0, 765, 101, 1, 0, 0, 0, 766, 767, 5, 58, 0, 0, 767, 768, 5, 58, 0, 0, 768, 103, 1, 0, 0, 0, 769, 770, 5, 44, 0, 0, 770, 105, 1, 0, 0, 0, 771, 772, 7, 0, 0, 0, 772, 773, 7, 3, 0, 0, 773, 774, 7, 2, 0, 0, 774, 775, 7, 4, 0, 0, 775, 107, 1, 0, 0, 0, 776, 777, 5, 46, 0, 0, 777, 109, 1, 0, 0, 0, 778, 779, 7, 15, 0, 0, 779, 780, 7, 12, 0, 0, 780, 781, 7, 13, 0, 0, 781, 782, 7, 2, 0, 0, 782, 783, 7, 3, 0, 0, 783, 111, 1, 0, 0, 0, 784, 785, 7, 15, 0, 0, 785, 786, 7, 1, 0, 0, 786, 787, 7, 6, 0, 0, 787, 788, 7, 2, 0, 0, 788, 789, 7, 5, 0, 0, 789, 113, 1, 0, 0, 0, 790, 791, 7, 1, 0, 0, 791, 792, 7, 9, 0, 0, 792, 115, 1, 0, 0, 0, 793, 794, 7, 1, 0, 0, 794, 795, 7, 2, 0, 0, 795, 117, 1, 0, 0, 0, 796, 797, 7, 13, 0, 0, 797, 798, 7, 12, 0, 0, 798, 799, 7, 2, 0, 0, 799, 800, 7, 5, 0, 0, 800, 119, 1, 0, 0, 0, 801, 802, 7, 13, 0, 0, 802, 803, 7, 1, 0, 0, 803, 804, 7, 18, 0, 0, 804, 805, 7, 3, 0, 0, 805, 121, 1, 0, 0, 0, 806, 807, 5, 40, 0, 0, 807, 123, 1, 0, 0, 0, 808, 809, 7, 9, 0, 0, 809, 810, 7, 7, 0, 0, 810, 811, 7, 5, 0, 0, 811, 125, 1, 0, 0, 0, 812, 813, 7, 9, 0, 0, 813, 814, 7, 20, 0, 0, 814, 815, 7, 13, 0, 0, 815, 816, 7, 13, 0, 0, 816, 127, 1, 0, 0, 0, 817, 818, 7, 9, 0, 0, 818, 819, 7, 20, 0, 0, 819, 820, 7, 13, 0, 0, 820, 821, 7, 13, 0, 0, 821, 822, 7, 2, 0, 0, 822, 129, 1, 0, 0, 0, 823, 824, 7, 7, 0, 0, 824, 825, 7, 6, 0, 0, 825, 131, 1, 0, 0, 0, 826, 827, 5, 63, 0, 0, 827, 133, 1, 0, 0, 0, 828, 829, 7, 6, 0, 0, 829, 830, 7, 13, 0, 0, 830, 831, 7, 1, 0, 0, 831, 832, 7, 18, 0, 0, 832, 833, 7, 3, 0, 0, 833, 135, 1, 0, 0, 0, 834, 835, 5, 41, 0, 0, 835, 137, 1, 0, 0, 0, 836, 837, 7, 5, 0, 0, 837, 838, 7, 6, 0, 0, 838, 839, 7, 20, 0, 0, 839, 840, 7, 3, 0, 0, 840, 139, 1, 0, 0, 0, 841, 842, 5, 61, 0, 0, 842, 843, 5, 61, 0, 0, 843, 141, 1, 0, 0, 0, 844, 845, 5, 61, 0, 0, 845, 846, 5, 126, 0, 0, 846, 143, 1, 0, 0, 0, 847, 848, 5, 33, 0, 0, 848, 849, 5, 61, 0, 0, 849, 145, 1, 0, 0, 0, 850, 851, 5, 60, 0, 0, 851, 147, 1, 0, 0, 0, 852, 853, 5, 60, 0, 0, 853, 854, 5, 61, 0, 0, 854, 149, 1, 0, 0, 0, 855, 856, 5, 62, 0, 0, 856, 151, 1, 0, 0, 0, 857, 858, 5, 62, 0, 0, 858, 859, 5, 61, 0, 0, 859, 153, 1, 0, 0, 0, 860, 861, 5, 43, 0, 0, 861, 155, 1, 0, 0, 0, 862, 863, 5, 45, 0, 0, 863, 157, 1, 0, 0, 0, 864, 865, 5, 42, 0, 0, 865, 159, 1, 0, 0, 0, 866, 867, 5, 47, 0, 0, 867, 161, 1, 0, 0, 0, 868, 869, 5, 37, 0, 0, 869, 163, 1, 0, 0, 0, 870, 871, 4, 74, 4, 0, 871, 872, 3, 54, 19, 0, 872, 873, 1, 0, 0, 0, 873, 874, 6, 74, 13, 0, 874, 165, 1, 0, 0, 0, 875, 878, 3, 132, 58, 0, 876, 879, 3, 70, 27, 0, 877, 879, 3, 84, 34, 0, 878, 876, 1, 0, 0, 0, 878, 877, 1, 0, 0, 0, 879, 883, 1, 0, 0, 0, 880, 882, 3, 86, 35, 0, 881, 880, 1, 0, 0, 0, 882, 885, 1, 0, 0, 0, 883, 881, 1, 0, 0, 0, 883, 884, 1, 0, 0, 0, 884, 893, 1, 0, 0, 0, 885, 883, 1, 0, 0, 0, 886, 888, 3, 132, 58, 0, 887, 889, 3, 68, 26, 0, 888, 887, 1, 0, 0, 0, 889, 890, 1, 0, 0, 0, 890, 888, 1, 0, 0, 0, 890, 891, 1, 0, 0, 0, 891, 893, 1, 0, 0, 0, 892, 875, 1, 0, 0, 0, 892, 886, 1, 0, 0, 0, 893, 167, 1, 0, 0, 0, 894, 895, 5, 91, 0, 0, 895, 896, 1, 0, 0, 0, 896, 897, 6, 76, 0, 0, 897, 898, 6, 76, 0, 0, 898, 169, 1, 0, 0, 0, 899, 900, 5, 93, 0, 0, 900, 901, 1, 0, 0, 0, 901, 902, 6, 77, 12, 0, 902, 903, 6, 77, 12, 0, 903, 171, 1, 0, 0, 0, 904, 908, 3, 70, 27, 0, 905, 907, 3, 86, 35, 0, 906, 905, 1, 0, 0, 0, 907, 910, 1, 0, 0, 0, 908, 906, 1, 0, 0, 0, 908, 909, 1, 0, 0, 0, 909, 921, 1, 0, 0, 0, 910, 908, 1, 0, 0, 0, 911, 914, 3, 84, 34, 0, 912, 914, 3, 78, 31, 0, 913, 911, 1, 0, 0, 0, 913, 912, 1, 0, 0, 0, 914, 916, 1, 0, 0, 0, 915, 917, 3, 86, 35, 0, 916, 915, 1, 0, 0, 0, 917, 918, 1, 0, 0, 0, 918, 916, 1, 0, 0, 0, 918, 919, 1, 0, 0, 0, 919, 921, 1, 0, 0, 0, 920, 904, 1, 0, 0, 0, 920, 913, 1, 0, 0, 0, 921, 173, 1, 0, 0, 0, 922, 924, 3, 80, 32, 0, 923, 925, 3, 82, 33, 0, 924, 923, 1, 0, 0, 0, 925, 926, 1, 0, 0, 0, 926, 924, 1, 0, 0, 0, 926, 927, 1, 0, 0, 0, 927, 928, 1, 0, 0, 0, 928, 929, 3, 80, 32, 0, 929, 175, 1, 0, 0, 0, 930, 931, 3, 174, 79, 0, 931, 177, 1, 0, 0, 0, 932, 933, 3, 60, 22, 0, 933, 934, 1, 0, 0, 0, 934, 935, 6, 81, 11, 0, 935, 179, 1, 0, 0, 0, 936, 937, 3, 62, 23, 0, 937, 938, 1, 0, 0, 0, 938, 939, 6, 82, 11, 0, 939, 181, 1, 0, 0, 0, 940, 941, 3, 64, 24, 0, 941, 942, 1, 0, 0, 0, 942, 943, 6, 83, 11, 0, 943, 183, 1, 0, 0, 0, 944, 945, 3, 168, 76, 0, 945, 946, 1, 0, 0, 0, 946, 947, 6, 84, 14, 0, 947, 948, 6, 84, 15, 0, 948, 185, 1, 0, 0, 0, 949, 950, 3, 66, 25, 0, 950, 951, 1, 0, 0, 0, 951, 952, 6, 85, 16, 0, 952, 953, 6, 85, 12, 0, 953, 187, 1, 0, 0, 0, 954, 955, 3, 64, 24, 0, 955, 956, 1, 0, 0, 0, 956, 957, 6, 86, 11, 0, 957, 189, 1, 0, 0, 0, 958, 959, 3, 60, 22, 0, 959, 960, 1, 0, 0, 0, 960, 961, 6, 87, 11, 0, 961, 191, 1, 0, 0, 0, 962, 963, 3, 62, 23, 0, 963, 964, 1, 0, 0, 0, 964, 965, 6, 88, 11, 0, 965, 193, 1, 0, 0, 0, 966, 967, 3, 66, 25, 0, 967, 968, 1, 0, 0, 0, 968, 969, 6, 89, 16, 0, 969, 970, 6, 89, 12, 0, 970, 195, 1, 0, 0, 0, 971, 972, 3, 168, 76, 0, 972, 973, 1, 0, 0, 0, 973, 974, 6, 90, 14, 0, 974, 197, 1, 0, 0, 0, 975, 976, 3, 170, 77, 0, 976, 977, 1, 0, 0, 0, 977, 978, 6, 91, 17, 0, 978, 199, 1, 0, 0, 0, 979, 980, 3, 334, 159, 0, 980, 981, 1, 0, 0, 0, 981, 982, 6, 92, 18, 0, 982, 201, 1, 0, 0, 0, 983, 984, 3, 104, 44, 0, 984, 985, 1, 0, 0, 0, 985, 986, 6, 93, 19, 0, 986, 203, 1, 0, 0, 0, 987, 988, 3, 100, 42, 0, 988, 989, 1, 0, 0, 0, 989, 990, 6, 94, 20, 0, 990, 205, 1, 0, 0, 0, 991, 992, 7, 16, 0, 0, 992, 993, 7, 3, 0, 0, 993, 994, 7, 5, 0, 0, 994, 995, 7, 12, 0, 0, 995, 996, 7, 0, 0, 0, 996, 997, 7, 12, 0, 0, 997, 998, 7, 5, 0, 0, 998, 999, 7, 12, 0, 0, 999, 207, 1, 0, 0, 0, 1000, 1004, 8, 32, 0, 0, 1001, 1002, 5, 47, 0, 0, 1002, 1004, 8, 33, 0, 0, 1003, 1000, 1, 0, 0, 0, 1003, 1001, 1, 0, 0, 0, 1004, 209, 1, 0, 0, 0, 1005, 1007, 3, 208, 96, 0, 1006, 1005, 1, 0, 0, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1006, 1, 0, 0, 0, 1008, 1009, 1, 0, 0, 0, 1009, 211, 1, 0, 0, 0, 1010, 1011, 3, 210, 97, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1013, 6, 98, 21, 0, 1013, 213, 1, 0, 0, 0, 1014, 1015, 3, 88, 36, 0, 1015, 1016, 1, 0, 0, 0, 1016, 1017, 6, 99, 22, 0, 1017, 215, 1, 0, 0, 0, 1018, 1019, 3, 60, 22, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1021, 6, 100, 11, 0, 1021, 217, 1, 0, 0, 0, 1022, 1023, 3, 62, 23, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1025, 6, 101, 11, 0, 1025, 219, 1, 0, 0, 0, 1026, 1027, 3, 64, 24, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1029, 6, 102, 11, 0, 1029, 221, 1, 0, 0, 0, 1030, 1031, 3, 66, 25, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 103, 16, 0, 1033, 1034, 6, 103, 12, 0, 1034, 223, 1, 0, 0, 0, 1035, 1036, 3, 108, 46, 0, 1036, 1037, 1, 0, 0, 0, 1037, 1038, 6, 104, 23, 0, 1038, 225, 1, 0, 0, 0, 1039, 1040, 3, 104, 44, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 6, 105, 19, 0, 1042, 227, 1, 0, 0, 0, 1043, 1048, 3, 70, 27, 0, 1044, 1048, 3, 68, 26, 0, 1045, 1048, 3, 84, 34, 0, 1046, 1048, 3, 158, 71, 0, 1047, 1043, 1, 0, 0, 0, 1047, 1044, 1, 0, 0, 0, 1047, 1045, 1, 0, 0, 0, 1047, 1046, 1, 0, 0, 0, 1048, 229, 1, 0, 0, 0, 1049, 1052, 3, 70, 27, 0, 1050, 1052, 3, 158, 71, 0, 1051, 1049, 1, 0, 0, 0, 1051, 1050, 1, 0, 0, 0, 1052, 1056, 1, 0, 0, 0, 1053, 1055, 3, 228, 106, 0, 1054, 1053, 1, 0, 0, 0, 1055, 1058, 1, 0, 0, 0, 1056, 1054, 1, 0, 0, 0, 1056, 1057, 1, 0, 0, 0, 1057, 1069, 1, 0, 0, 0, 1058, 1056, 1, 0, 0, 0, 1059, 1062, 3, 84, 34, 0, 1060, 1062, 3, 78, 31, 0, 1061, 1059, 1, 0, 0, 0, 1061, 1060, 1, 0, 0, 0, 1062, 1064, 1, 0, 0, 0, 1063, 1065, 3, 228, 106, 0, 1064, 1063, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 1064, 1, 0, 0, 0, 1066, 1067, 1, 0, 0, 0, 1067, 1069, 1, 0, 0, 0, 1068, 1051, 1, 0, 0, 0, 1068, 1061, 1, 0, 0, 0, 1069, 231, 1, 0, 0, 0, 1070, 1073, 3, 230, 107, 0, 1071, 1073, 3, 174, 79, 0, 1072, 1070, 1, 0, 0, 0, 1072, 1071, 1, 0, 0, 0, 1073, 1074, 1, 0, 0, 0, 1074, 1072, 1, 0, 0, 0, 1074, 1075, 1, 0, 0, 0, 1075, 233, 1, 0, 0, 0, 1076, 1077, 3, 60, 22, 0, 1077, 1078, 1, 0, 0, 0, 1078, 1079, 6, 109, 11, 0, 1079, 235, 1, 0, 0, 0, 1080, 1081, 3, 62, 23, 0, 1081, 1082, 1, 0, 0, 0, 1082, 1083, 6, 110, 11, 0, 1083, 237, 1, 0, 0, 0, 1084, 1085, 3, 64, 24, 0, 1085, 1086, 1, 0, 0, 0, 1086, 1087, 6, 111, 11, 0, 1087, 239, 1, 0, 0, 0, 1088, 1089, 3, 66, 25, 0, 1089, 1090, 1, 0, 0, 0, 1090, 1091, 6, 112, 16, 0, 1091, 1092, 6, 112, 12, 0, 1092, 241, 1, 0, 0, 0, 1093, 1094, 3, 100, 42, 0, 1094, 1095, 1, 0, 0, 0, 1095, 1096, 6, 113, 20, 0, 1096, 243, 1, 0, 0, 0, 1097, 1098, 3, 104, 44, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1100, 6, 114, 19, 0, 1100, 245, 1, 0, 0, 0, 1101, 1102, 3, 108, 46, 0, 1102, 1103, 1, 0, 0, 0, 1103, 1104, 6, 115, 23, 0, 1104, 247, 1, 0, 0, 0, 1105, 1106, 7, 12, 0, 0, 1106, 1107, 7, 2, 0, 0, 1107, 249, 1, 0, 0, 0, 1108, 1109, 3, 232, 108, 0, 1109, 1110, 1, 0, 0, 0, 1110, 1111, 6, 117, 24, 0, 1111, 251, 1, 0, 0, 0, 1112, 1113, 3, 60, 22, 0, 1113, 1114, 1, 0, 0, 0, 1114, 1115, 6, 118, 11, 0, 1115, 253, 1, 0, 0, 0, 1116, 1117, 3, 62, 23, 0, 1117, 1118, 1, 0, 0, 0, 1118, 1119, 6, 119, 11, 0, 1119, 255, 1, 0, 0, 0, 1120, 1121, 3, 64, 24, 0, 1121, 1122, 1, 0, 0, 0, 1122, 1123, 6, 120, 11, 0, 1123, 257, 1, 0, 0, 0, 1124, 1125, 3, 66, 25, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 6, 121, 16, 0, 1127, 1128, 6, 121, 12, 0, 1128, 259, 1, 0, 0, 0, 1129, 1130, 3, 168, 76, 0, 1130, 1131, 1, 0, 0, 0, 1131, 1132, 6, 122, 14, 0, 1132, 1133, 6, 122, 25, 0, 1133, 261, 1, 0, 0, 0, 1134, 1135, 7, 7, 0, 0, 1135, 1136, 7, 9, 0, 0, 1136, 1137, 1, 0, 0, 0, 1137, 1138, 6, 123, 26, 0, 1138, 263, 1, 0, 0, 0, 1139, 1140, 7, 19, 0, 0, 1140, 1141, 7, 1, 0, 0, 1141, 1142, 7, 5, 0, 0, 1142, 1143, 7, 10, 0, 0, 1143, 1144, 1, 0, 0, 0, 1144, 1145, 6, 124, 26, 0, 1145, 265, 1, 0, 0, 0, 1146, 1147, 8, 34, 0, 0, 1147, 267, 1, 0, 0, 0, 1148, 1150, 3, 266, 125, 0, 1149, 1148, 1, 0, 0, 0, 1150, 1151, 1, 0, 0, 0, 1151, 1149, 1, 0, 0, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 1, 0, 0, 0, 1153, 1154, 3, 334, 159, 0, 1154, 1156, 1, 0, 0, 0, 1155, 1149, 1, 0, 0, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1158, 1, 0, 0, 0, 1157, 1159, 3, 266, 125, 0, 1158, 1157, 1, 0, 0, 0, 1159, 1160, 1, 0, 0, 0, 1160, 1158, 1, 0, 0, 0, 1160, 1161, 1, 0, 0, 0, 1161, 269, 1, 0, 0, 0, 1162, 1163, 3, 268, 126, 0, 1163, 1164, 1, 0, 0, 0, 1164, 1165, 6, 127, 27, 0, 1165, 271, 1, 0, 0, 0, 1166, 1167, 3, 60, 22, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1169, 6, 128, 11, 0, 1169, 273, 1, 0, 0, 0, 1170, 1171, 3, 62, 23, 0, 1171, 1172, 1, 0, 0, 0, 1172, 1173, 6, 129, 11, 0, 1173, 275, 1, 0, 0, 0, 1174, 1175, 3, 64, 24, 0, 1175, 1176, 1, 0, 0, 0, 1176, 1177, 6, 130, 11, 0, 1177, 277, 1, 0, 0, 0, 1178, 1179, 3, 66, 25, 0, 1179, 1180, 1, 0, 0, 0, 1180, 1181, 6, 131, 16, 0, 1181, 1182, 6, 131, 12, 0, 1182, 1183, 6, 131, 12, 0, 1183, 279, 1, 0, 0, 0, 1184, 1185, 3, 100, 42, 0, 1185, 1186, 1, 0, 0, 0, 1186, 1187, 6, 132, 20, 0, 1187, 281, 1, 0, 0, 0, 1188, 1189, 3, 104, 44, 0, 1189, 1190, 1, 0, 0, 0, 1190, 1191, 6, 133, 19, 0, 1191, 283, 1, 0, 0, 0, 1192, 1193, 3, 108, 46, 0, 1193, 1194, 1, 0, 0, 0, 1194, 1195, 6, 134, 23, 0, 1195, 285, 1, 0, 0, 0, 1196, 1197, 3, 264, 124, 0, 1197, 1198, 1, 0, 0, 0, 1198, 1199, 6, 135, 28, 0, 1199, 287, 1, 0, 0, 0, 1200, 1201, 3, 232, 108, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 6, 136, 24, 0, 1203, 289, 1, 0, 0, 0, 1204, 1205, 3, 176, 80, 0, 1205, 1206, 1, 0, 0, 0, 1206, 1207, 6, 137, 29, 0, 1207, 291, 1, 0, 0, 0, 1208, 1209, 3, 60, 22, 0, 1209, 1210, 1, 0, 0, 0, 1210, 1211, 6, 138, 11, 0, 1211, 293, 1, 0, 0, 0, 1212, 1213, 3, 62, 23, 0, 1213, 1214, 1, 0, 0, 0, 1214, 1215, 6, 139, 11, 0, 1215, 295, 1, 0, 0, 0, 1216, 1217, 3, 64, 24, 0, 1217, 1218, 1, 0, 0, 0, 1218, 1219, 6, 140, 11, 0, 1219, 297, 1, 0, 0, 0, 1220, 1221, 3, 66, 25, 0, 1221, 1222, 1, 0, 0, 0, 1222, 1223, 6, 141, 16, 0, 1223, 1224, 6, 141, 12, 0, 1224, 299, 1, 0, 0, 0, 1225, 1226, 3, 108, 46, 0, 1226, 1227, 1, 0, 0, 0, 1227, 1228, 6, 142, 23, 0, 1228, 301, 1, 0, 0, 0, 1229, 1230, 3, 176, 80, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1232, 6, 143, 29, 0, 1232, 303, 1, 0, 0, 0, 1233, 1234, 3, 172, 78, 0, 1234, 1235, 1, 0, 0, 0, 1235, 1236, 6, 144, 30, 0, 1236, 305, 1, 0, 0, 0, 1237, 1238, 3, 60, 22, 0, 1238, 1239, 1, 0, 0, 0, 1239, 1240, 6, 145, 11, 0, 1240, 307, 1, 0, 0, 0, 1241, 1242, 3, 62, 23, 0, 1242, 1243, 1, 0, 0, 0, 1243, 1244, 6, 146, 11, 0, 1244, 309, 1, 0, 0, 0, 1245, 1246, 3, 64, 24, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1248, 6, 147, 11, 0, 1248, 311, 1, 0, 0, 0, 1249, 1250, 3, 66, 25, 0, 1250, 1251, 1, 0, 0, 0, 1251, 1252, 6, 148, 16, 0, 1252, 1253, 6, 148, 12, 0, 1253, 313, 1, 0, 0, 0, 1254, 1255, 7, 1, 0, 0, 1255, 1256, 7, 9, 0, 0, 1256, 1257, 7, 15, 0, 0, 1257, 1258, 7, 7, 0, 0, 1258, 315, 1, 0, 0, 0, 1259, 1260, 3, 60, 22, 0, 1260, 1261, 1, 0, 0, 0, 1261, 1262, 6, 150, 11, 0, 1262, 317, 1, 0, 0, 0, 1263, 1264, 3, 62, 23, 0, 1264, 1265, 1, 0, 0, 0, 1265, 1266, 6, 151, 11, 0, 1266, 319, 1, 0, 0, 0, 1267, 1268, 3, 64, 24, 0, 1268, 1269, 1, 0, 0, 0, 1269, 1270, 6, 152, 11, 0, 1270, 321, 1, 0, 0, 0, 1271, 1272, 3, 66, 25, 0, 1272, 1273, 1, 0, 0, 0, 1273, 1274, 6, 153, 16, 0, 1274, 1275, 6, 153, 12, 0, 1275, 323, 1, 0, 0, 0, 1276, 1277, 7, 15, 0, 0, 1277, 1278, 7, 20, 0, 0, 1278, 1279, 7, 9, 0, 0, 1279, 1280, 7, 4, 0, 0, 1280, 1281, 7, 5, 0, 0, 1281, 1282, 7, 1, 0, 0, 1282, 1283, 7, 7, 0, 0, 1283, 1284, 7, 9, 0, 0, 1284, 1285, 7, 2, 0, 0, 1285, 325, 1, 0, 0, 0, 1286, 1287, 3, 60, 22, 0, 1287, 1288, 1, 0, 0, 0, 1288, 1289, 6, 155, 11, 0, 1289, 327, 1, 0, 0, 0, 1290, 1291, 3, 62, 23, 0, 1291, 1292, 1, 0, 0, 0, 1292, 1293, 6, 156, 11, 0, 1293, 329, 1, 0, 0, 0, 1294, 1295, 3, 64, 24, 0, 1295, 1296, 1, 0, 0, 0, 1296, 1297, 6, 157, 11, 0, 1297, 331, 1, 0, 0, 0, 1298, 1299, 3, 170, 77, 0, 1299, 1300, 1, 0, 0, 0, 1300, 1301, 6, 158, 17, 0, 1301, 1302, 6, 158, 12, 0, 1302, 333, 1, 0, 0, 0, 1303, 1304, 5, 58, 0, 0, 1304, 335, 1, 0, 0, 0, 1305, 1311, 3, 78, 31, 0, 1306, 1311, 3, 68, 26, 0, 1307, 1311, 3, 108, 46, 0, 1308, 1311, 3, 70, 27, 0, 1309, 1311, 3, 84, 34, 0, 1310, 1305, 1, 0, 0, 0, 1310, 1306, 1, 0, 0, 0, 1310, 1307, 1, 0, 0, 0, 1310, 1308, 1, 0, 0, 0, 1310, 1309, 1, 0, 0, 0, 1311, 1312, 1, 0, 0, 0, 1312, 1310, 1, 0, 0, 0, 1312, 1313, 1, 0, 0, 0, 1313, 337, 1, 0, 0, 0, 1314, 1315, 3, 60, 22, 0, 1315, 1316, 1, 0, 0, 0, 1316, 1317, 6, 161, 11, 0, 1317, 339, 1, 0, 0, 0, 1318, 1319, 3, 62, 23, 0, 1319, 1320, 1, 0, 0, 0, 1320, 1321, 6, 162, 11, 0, 1321, 341, 1, 0, 0, 0, 1322, 1323, 3, 64, 24, 0, 1323, 1324, 1, 0, 0, 0, 1324, 1325, 6, 163, 11, 0, 1325, 343, 1, 0, 0, 0, 1326, 1327, 3, 66, 25, 0, 1327, 1328, 1, 0, 0, 0, 1328, 1329, 6, 164, 16, 0, 1329, 1330, 6, 164, 12, 0, 1330, 345, 1, 0, 0, 0, 1331, 1332, 3, 334, 159, 0, 1332, 1333, 1, 0, 0, 0, 1333, 1334, 6, 165, 18, 0, 1334, 347, 1, 0, 0, 0, 1335, 1336, 3, 104, 44, 0, 1336, 1337, 1, 0, 0, 0, 1337, 1338, 6, 166, 19, 0, 1338, 349, 1, 0, 0, 0, 1339, 1340, 3, 108, 46, 0, 1340, 1341, 1, 0, 0, 0, 1341, 1342, 6, 167, 23, 0, 1342, 351, 1, 0, 0, 0, 1343, 1344, 3, 262, 123, 0, 1344, 1345, 1, 0, 0, 0, 1345, 1346, 6, 168, 31, 0, 1346, 1347, 6, 168, 32, 0, 1347, 353, 1, 0, 0, 0, 1348, 1349, 3, 210, 97, 0, 1349, 1350, 1, 0, 0, 0, 1350, 1351, 6, 169, 21, 0, 1351, 355, 1, 0, 0, 0, 1352, 1353, 3, 88, 36, 0, 1353, 1354, 1, 0, 0, 0, 1354, 1355, 6, 170, 22, 0, 1355, 357, 1, 0, 0, 0, 1356, 1357, 3, 60, 22, 0, 1357, 1358, 1, 0, 0, 0, 1358, 1359, 6, 171, 11, 0, 1359, 359, 1, 0, 0, 0, 1360, 1361, 3, 62, 23, 0, 1361, 1362, 1, 0, 0, 0, 1362, 1363, 6, 172, 11, 0, 1363, 361, 1, 0, 0, 0, 1364, 1365, 3, 64, 24, 0, 1365, 1366, 1, 0, 0, 0, 1366, 1367, 6, 173, 11, 0, 1367, 363, 1, 0, 0, 0, 1368, 1369, 3, 66, 25, 0, 1369, 1370, 1, 0, 0, 0, 1370, 1371, 6, 174, 16, 0, 1371, 1372, 6, 174, 12, 0, 1372, 1373, 6, 174, 12, 0, 1373, 365, 1, 0, 0, 0, 1374, 1375, 3, 104, 44, 0, 1375, 1376, 1, 0, 0, 0, 1376, 1377, 6, 175, 19, 0, 1377, 367, 1, 0, 0, 0, 1378, 1379, 3, 108, 46, 0, 1379, 1380, 1, 0, 0, 0, 1380, 1381, 6, 176, 23, 0, 1381, 369, 1, 0, 0, 0, 1382, 1383, 3, 232, 108, 0, 1383, 1384, 1, 0, 0, 0, 1384, 1385, 6, 177, 24, 0, 1385, 371, 1, 0, 0, 0, 1386, 1387, 3, 60, 22, 0, 1387, 1388, 1, 0, 0, 0, 1388, 1389, 6, 178, 11, 0, 1389, 373, 1, 0, 0, 0, 1390, 1391, 3, 62, 23, 0, 1391, 1392, 1, 0, 0, 0, 1392, 1393, 6, 179, 11, 0, 1393, 375, 1, 0, 0, 0, 1394, 1395, 3, 64, 24, 0, 1395, 1396, 1, 0, 0, 0, 1396, 1397, 6, 180, 11, 0, 1397, 377, 1, 0, 0, 0, 1398, 1399, 3, 66, 25, 0, 1399, 1400, 1, 0, 0, 0, 1400, 1401, 6, 181, 16, 0, 1401, 1402, 6, 181, 12, 0, 1402, 379, 1, 0, 0, 0, 1403, 1404, 3, 210, 97, 0, 1404, 1405, 1, 0, 0, 0, 1405, 1406, 6, 182, 21, 0, 1406, 1407, 6, 182, 12, 0, 1407, 1408, 6, 182, 33, 0, 1408, 381, 1, 0, 0, 0, 1409, 1410, 3, 88, 36, 0, 1410, 1411, 1, 0, 0, 0, 1411, 1412, 6, 183, 22, 0, 1412, 1413, 6, 183, 12, 0, 1413, 1414, 6, 183, 33, 0, 1414, 383, 1, 0, 0, 0, 1415, 1416, 3, 60, 22, 0, 1416, 1417, 1, 0, 0, 0, 1417, 1418, 6, 184, 11, 0, 1418, 385, 1, 0, 0, 0, 1419, 1420, 3, 62, 23, 0, 1420, 1421, 1, 0, 0, 0, 1421, 1422, 6, 185, 11, 0, 1422, 387, 1, 0, 0, 0, 1423, 1424, 3, 64, 24, 0, 1424, 1425, 1, 0, 0, 0, 1425, 1426, 6, 186, 11, 0, 1426, 389, 1, 0, 0, 0, 1427, 1428, 3, 334, 159, 0, 1428, 1429, 1, 0, 0, 0, 1429, 1430, 6, 187, 18, 0, 1430, 1431, 6, 187, 12, 0, 1431, 1432, 6, 187, 10, 0, 1432, 391, 1, 0, 0, 0, 1433, 1434, 3, 104, 44, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 188, 19, 0, 1436, 1437, 6, 188, 12, 0, 1437, 1438, 6, 188, 10, 0, 1438, 393, 1, 0, 0, 0, 1439, 1440, 3, 60, 22, 0, 1440, 1441, 1, 0, 0, 0, 1441, 1442, 6, 189, 11, 0, 1442, 395, 1, 0, 0, 0, 1443, 1444, 3, 62, 23, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 190, 11, 0, 1446, 397, 1, 0, 0, 0, 1447, 1448, 3, 64, 24, 0, 1448, 1449, 1, 0, 0, 0, 1449, 1450, 6, 191, 11, 0, 1450, 399, 1, 0, 0, 0, 1451, 1452, 3, 176, 80, 0, 1452, 1453, 1, 0, 0, 0, 1453, 1454, 6, 192, 12, 0, 1454, 1455, 6, 192, 0, 0, 1455, 1456, 6, 192, 29, 0, 1456, 401, 1, 0, 0, 0, 1457, 1458, 3, 172, 78, 0, 1458, 1459, 1, 0, 0, 0, 1459, 1460, 6, 193, 12, 0, 1460, 1461, 6, 193, 0, 0, 1461, 1462, 6, 193, 30, 0, 1462, 403, 1, 0, 0, 0, 1463, 1464, 3, 94, 39, 0, 1464, 1465, 1, 0, 0, 0, 1465, 1466, 6, 194, 12, 0, 1466, 1467, 6, 194, 0, 0, 1467, 1468, 6, 194, 34, 0, 1468, 405, 1, 0, 0, 0, 1469, 1470, 3, 66, 25, 0, 1470, 1471, 1, 0, 0, 0, 1471, 1472, 6, 195, 16, 0, 1472, 1473, 6, 195, 12, 0, 1473, 407, 1, 0, 0, 0, 66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 592, 602, 606, 609, 618, 620, 631, 650, 655, 664, 671, 676, 678, 689, 697, 700, 702, 707, 712, 718, 725, 730, 736, 739, 747, 751, 878, 883, 890, 892, 908, 913, 918, 920, 926, 1003, 1008, 1047, 1051, 1056, 1061, 1066, 1068, 1072, 1074, 1151, 1155, 1160, 1310, 1312, 35, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 10, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 12, 0, 5, 14, 0, 0, 1, 0, 4, 0, 0, 7, 20, 0, 7, 66, 0, 5, 0, 0, 7, 26, 0, 7, 67, 0, 7, 109, 0, 7, 35, 0, 7, 33, 0, 7, 77, 0, 7, 27, 0, 7, 37, 0, 7, 81, 0, 5, 11, 0, 5, 7, 0, 7, 91, 0, 7, 90, 0, 7, 69, 0, 7, 68, 0, 7, 89, 0, 5, 13, 0, 5, 15, 0, 7, 30, 0]
\ No newline at end of file
+[4, 0, 120, 1427, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 4, 20, 571, 8, 20, 11, 20, 12, 20, 572, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 581, 8, 21, 10, 21, 12, 21, 584, 9, 21, 1, 21, 3, 21, 587, 8, 21, 1, 21, 3, 21, 590, 8, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 599, 8, 22, 10, 22, 12, 22, 602, 9, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 4, 23, 610, 8, 23, 11, 23, 12, 23, 611, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 3, 29, 631, 8, 29, 1, 29, 4, 29, 634, 8, 29, 11, 29, 12, 29, 635, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 3, 32, 645, 8, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 652, 8, 34, 1, 35, 1, 35, 1, 35, 5, 35, 657, 8, 35, 10, 35, 12, 35, 660, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 668, 8, 35, 10, 35, 12, 35, 671, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 3, 35, 678, 8, 35, 1, 35, 3, 35, 681, 8, 35, 3, 35, 683, 8, 35, 1, 36, 4, 36, 686, 8, 36, 11, 36, 12, 36, 687, 1, 37, 4, 37, 691, 8, 37, 11, 37, 12, 37, 692, 1, 37, 1, 37, 5, 37, 697, 8, 37, 10, 37, 12, 37, 700, 9, 37, 1, 37, 1, 37, 4, 37, 704, 8, 37, 11, 37, 12, 37, 705, 1, 37, 4, 37, 709, 8, 37, 11, 37, 12, 37, 710, 1, 37, 1, 37, 5, 37, 715, 8, 37, 10, 37, 12, 37, 718, 9, 37, 3, 37, 720, 8, 37, 1, 37, 1, 37, 1, 37, 1, 37, 4, 37, 726, 8, 37, 11, 37, 12, 37, 727, 1, 37, 1, 37, 3, 37, 732, 8, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 3, 74, 859, 8, 74, 1, 74, 5, 74, 862, 8, 74, 10, 74, 12, 74, 865, 9, 74, 1, 74, 1, 74, 4, 74, 869, 8, 74, 11, 74, 12, 74, 870, 3, 74, 873, 8, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 5, 77, 887, 8, 77, 10, 77, 12, 77, 890, 9, 77, 1, 77, 1, 77, 3, 77, 894, 8, 77, 1, 77, 4, 77, 897, 8, 77, 11, 77, 12, 77, 898, 3, 77, 901, 8, 77, 1, 78, 1, 78, 4, 78, 905, 8, 78, 11, 78, 12, 78, 906, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 3, 95, 984, 8, 95, 1, 96, 4, 96, 987, 8, 96, 11, 96, 12, 96, 988, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 3, 105, 1028, 8, 105, 1, 106, 1, 106, 3, 106, 1032, 8, 106, 1, 106, 5, 106, 1035, 8, 106, 10, 106, 12, 106, 1038, 9, 106, 1, 106, 1, 106, 3, 106, 1042, 8, 106, 1, 106, 4, 106, 1045, 8, 106, 11, 106, 12, 106, 1046, 3, 106, 1049, 8, 106, 1, 107, 1, 107, 4, 107, 1053, 8, 107, 11, 107, 12, 107, 1054, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 125, 4, 125, 1130, 8, 125, 11, 125, 12, 125, 1131, 1, 125, 1, 125, 3, 125, 1136, 8, 125, 1, 125, 4, 125, 1139, 8, 125, 11, 125, 12, 125, 1140, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 4, 154, 1264, 8, 154, 11, 154, 12, 154, 1265, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 2, 600, 669, 0, 190, 15, 1, 17, 2, 19, 3, 21, 4, 23, 5, 25, 6, 27, 7, 29, 8, 31, 9, 33, 10, 35, 11, 37, 12, 39, 13, 41, 14, 43, 15, 45, 16, 47, 17, 49, 18, 51, 19, 53, 20, 55, 21, 57, 22, 59, 23, 61, 24, 63, 25, 65, 0, 67, 0, 69, 0, 71, 0, 73, 0, 75, 0, 77, 0, 79, 0, 81, 0, 83, 0, 85, 26, 87, 27, 89, 28, 91, 29, 93, 30, 95, 31, 97, 32, 99, 33, 101, 34, 103, 35, 105, 36, 107, 37, 109, 38, 111, 39, 113, 40, 115, 41, 117, 42, 119, 43, 121, 44, 123, 45, 125, 46, 127, 47, 129, 48, 131, 49, 133, 50, 135, 51, 137, 52, 139, 53, 141, 54, 143, 55, 145, 56, 147, 57, 149, 58, 151, 59, 153, 60, 155, 61, 157, 62, 159, 63, 161, 0, 163, 64, 165, 65, 167, 66, 169, 67, 171, 0, 173, 68, 175, 69, 177, 70, 179, 71, 181, 0, 183, 0, 185, 72, 187, 73, 189, 74, 191, 0, 193, 0, 195, 0, 197, 0, 199, 0, 201, 0, 203, 75, 205, 0, 207, 76, 209, 0, 211, 0, 213, 77, 215, 78, 217, 79, 219, 0, 221, 0, 223, 0, 225, 0, 227, 0, 229, 80, 231, 81, 233, 82, 235, 83, 237, 0, 239, 0, 241, 0, 243, 0, 245, 84, 247, 0, 249, 85, 251, 86, 253, 87, 255, 0, 257, 0, 259, 88, 261, 89, 263, 0, 265, 90, 267, 0, 269, 91, 271, 92, 273, 93, 275, 0, 277, 0, 279, 0, 281, 0, 283, 0, 285, 0, 287, 0, 289, 94, 291, 95, 293, 96, 295, 0, 297, 0, 299, 0, 301, 0, 303, 97, 305, 98, 307, 99, 309, 0, 311, 100, 313, 101, 315, 102, 317, 103, 319, 0, 321, 104, 323, 105, 325, 106, 327, 107, 329, 108, 331, 0, 333, 0, 335, 0, 337, 0, 339, 0, 341, 0, 343, 0, 345, 109, 347, 110, 349, 111, 351, 0, 353, 0, 355, 0, 357, 0, 359, 112, 361, 113, 363, 114, 365, 0, 367, 0, 369, 0, 371, 115, 373, 116, 375, 117, 377, 0, 379, 0, 381, 118, 383, 119, 385, 120, 387, 0, 389, 0, 391, 0, 393, 0, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1455, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 1, 63, 1, 0, 0, 0, 1, 85, 1, 0, 0, 0, 1, 87, 1, 0, 0, 0, 1, 89, 1, 0, 0, 0, 1, 91, 1, 0, 0, 0, 1, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 1, 97, 1, 0, 0, 0, 1, 99, 1, 0, 0, 0, 1, 101, 1, 0, 0, 0, 1, 103, 1, 0, 0, 0, 1, 105, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 1, 109, 1, 0, 0, 0, 1, 111, 1, 0, 0, 0, 1, 113, 1, 0, 0, 0, 1, 115, 1, 0, 0, 0, 1, 117, 1, 0, 0, 0, 1, 119, 1, 0, 0, 0, 1, 121, 1, 0, 0, 0, 1, 123, 1, 0, 0, 0, 1, 125, 1, 0, 0, 0, 1, 127, 1, 0, 0, 0, 1, 129, 1, 0, 0, 0, 1, 131, 1, 0, 0, 0, 1, 133, 1, 0, 0, 0, 1, 135, 1, 0, 0, 0, 1, 137, 1, 0, 0, 0, 1, 139, 1, 0, 0, 0, 1, 141, 1, 0, 0, 0, 1, 143, 1, 0, 0, 0, 1, 145, 1, 0, 0, 0, 1, 147, 1, 0, 0, 0, 1, 149, 1, 0, 0, 0, 1, 151, 1, 0, 0, 0, 1, 153, 1, 0, 0, 0, 1, 155, 1, 0, 0, 0, 1, 157, 1, 0, 0, 0, 1, 159, 1, 0, 0, 0, 1, 161, 1, 0, 0, 0, 1, 163, 1, 0, 0, 0, 1, 165, 1, 0, 0, 0, 1, 167, 1, 0, 0, 0, 1, 169, 1, 0, 0, 0, 1, 173, 1, 0, 0, 0, 1, 175, 1, 0, 0, 0, 1, 177, 1, 0, 0, 0, 1, 179, 1, 0, 0, 0, 2, 181, 1, 0, 0, 0, 2, 183, 1, 0, 0, 0, 2, 185, 1, 0, 0, 0, 2, 187, 1, 0, 0, 0, 2, 189, 1, 0, 0, 0, 3, 191, 1, 0, 0, 0, 3, 193, 1, 0, 0, 0, 3, 195, 1, 0, 0, 0, 3, 197, 1, 0, 0, 0, 3, 199, 1, 0, 0, 0, 3, 201, 1, 0, 0, 0, 3, 203, 1, 0, 0, 0, 3, 207, 1, 0, 0, 0, 3, 209, 1, 0, 0, 0, 3, 211, 1, 0, 0, 0, 3, 213, 1, 0, 0, 0, 3, 215, 1, 0, 0, 0, 3, 217, 1, 0, 0, 0, 4, 219, 1, 0, 0, 0, 4, 221, 1, 0, 0, 0, 4, 223, 1, 0, 0, 0, 4, 229, 1, 0, 0, 0, 4, 231, 1, 0, 0, 0, 4, 233, 1, 0, 0, 0, 4, 235, 1, 0, 0, 0, 5, 237, 1, 0, 0, 0, 5, 239, 1, 0, 0, 0, 5, 241, 1, 0, 0, 0, 5, 243, 1, 0, 0, 0, 5, 245, 1, 0, 0, 0, 5, 247, 1, 0, 0, 0, 5, 249, 1, 0, 0, 0, 5, 251, 1, 0, 0, 0, 5, 253, 1, 0, 0, 0, 6, 255, 1, 0, 0, 0, 6, 257, 1, 0, 0, 0, 6, 259, 1, 0, 0, 0, 6, 261, 1, 0, 0, 0, 6, 265, 1, 0, 0, 0, 6, 267, 1, 0, 0, 0, 6, 269, 1, 0, 0, 0, 6, 271, 1, 0, 0, 0, 6, 273, 1, 0, 0, 0, 7, 275, 1, 0, 0, 0, 7, 277, 1, 0, 0, 0, 7, 279, 1, 0, 0, 0, 7, 281, 1, 0, 0, 0, 7, 283, 1, 0, 0, 0, 7, 285, 1, 0, 0, 0, 7, 287, 1, 0, 0, 0, 7, 289, 1, 0, 0, 0, 7, 291, 1, 0, 0, 0, 7, 293, 1, 0, 0, 0, 8, 295, 1, 0, 0, 0, 8, 297, 1, 0, 0, 0, 8, 299, 1, 0, 0, 0, 8, 301, 1, 0, 0, 0, 8, 303, 1, 0, 0, 0, 8, 305, 1, 0, 0, 0, 8, 307, 1, 0, 0, 0, 9, 309, 1, 0, 0, 0, 9, 311, 1, 0, 0, 0, 9, 313, 1, 0, 0, 0, 9, 315, 1, 0, 0, 0, 9, 317, 1, 0, 0, 0, 10, 319, 1, 0, 0, 0, 10, 321, 1, 0, 0, 0, 10, 323, 1, 0, 0, 0, 10, 325, 1, 0, 0, 0, 10, 327, 1, 0, 0, 0, 10, 329, 1, 0, 0, 0, 11, 331, 1, 0, 0, 0, 11, 333, 1, 0, 0, 0, 11, 335, 1, 0, 0, 0, 11, 337, 1, 0, 0, 0, 11, 339, 1, 0, 0, 0, 11, 341, 1, 0, 0, 0, 11, 343, 1, 0, 0, 0, 11, 345, 1, 0, 0, 0, 11, 347, 1, 0, 0, 0, 11, 349, 1, 0, 0, 0, 12, 351, 1, 0, 0, 0, 12, 353, 1, 0, 0, 0, 12, 355, 1, 0, 0, 0, 12, 357, 1, 0, 0, 0, 12, 359, 1, 0, 0, 0, 12, 361, 1, 0, 0, 0, 12, 363, 1, 0, 0, 0, 13, 365, 1, 0, 0, 0, 13, 367, 1, 0, 0, 0, 13, 369, 1, 0, 0, 0, 13, 371, 1, 0, 0, 0, 13, 373, 1, 0, 0, 0, 13, 375, 1, 0, 0, 0, 14, 377, 1, 0, 0, 0, 14, 379, 1, 0, 0, 0, 14, 381, 1, 0, 0, 0, 14, 383, 1, 0, 0, 0, 14, 385, 1, 0, 0, 0, 14, 387, 1, 0, 0, 0, 14, 389, 1, 0, 0, 0, 14, 391, 1, 0, 0, 0, 14, 393, 1, 0, 0, 0, 15, 395, 1, 0, 0, 0, 17, 405, 1, 0, 0, 0, 19, 412, 1, 0, 0, 0, 21, 421, 1, 0, 0, 0, 23, 428, 1, 0, 0, 0, 25, 438, 1, 0, 0, 0, 27, 445, 1, 0, 0, 0, 29, 452, 1, 0, 0, 0, 31, 459, 1, 0, 0, 0, 33, 467, 1, 0, 0, 0, 35, 479, 1, 0, 0, 0, 37, 488, 1, 0, 0, 0, 39, 494, 1, 0, 0, 0, 41, 501, 1, 0, 0, 0, 43, 508, 1, 0, 0, 0, 45, 516, 1, 0, 0, 0, 47, 524, 1, 0, 0, 0, 49, 539, 1, 0, 0, 0, 51, 549, 1, 0, 0, 0, 53, 558, 1, 0, 0, 0, 55, 570, 1, 0, 0, 0, 57, 576, 1, 0, 0, 0, 59, 593, 1, 0, 0, 0, 61, 609, 1, 0, 0, 0, 63, 615, 1, 0, 0, 0, 65, 619, 1, 0, 0, 0, 67, 621, 1, 0, 0, 0, 69, 623, 1, 0, 0, 0, 71, 626, 1, 0, 0, 0, 73, 628, 1, 0, 0, 0, 75, 637, 1, 0, 0, 0, 77, 639, 1, 0, 0, 0, 79, 644, 1, 0, 0, 0, 81, 646, 1, 0, 0, 0, 83, 651, 1, 0, 0, 0, 85, 682, 1, 0, 0, 0, 87, 685, 1, 0, 0, 0, 89, 731, 1, 0, 0, 0, 91, 733, 1, 0, 0, 0, 93, 736, 1, 0, 0, 0, 95, 740, 1, 0, 0, 0, 97, 744, 1, 0, 0, 0, 99, 746, 1, 0, 0, 0, 101, 749, 1, 0, 0, 0, 103, 751, 1, 0, 0, 0, 105, 756, 1, 0, 0, 0, 107, 758, 1, 0, 0, 0, 109, 764, 1, 0, 0, 0, 111, 770, 1, 0, 0, 0, 113, 773, 1, 0, 0, 0, 115, 776, 1, 0, 0, 0, 117, 781, 1, 0, 0, 0, 119, 786, 1, 0, 0, 0, 121, 788, 1, 0, 0, 0, 123, 792, 1, 0, 0, 0, 125, 797, 1, 0, 0, 0, 127, 803, 1, 0, 0, 0, 129, 806, 1, 0, 0, 0, 131, 808, 1, 0, 0, 0, 133, 814, 1, 0, 0, 0, 135, 816, 1, 0, 0, 0, 137, 821, 1, 0, 0, 0, 139, 824, 1, 0, 0, 0, 141, 827, 1, 0, 0, 0, 143, 830, 1, 0, 0, 0, 145, 832, 1, 0, 0, 0, 147, 835, 1, 0, 0, 0, 149, 837, 1, 0, 0, 0, 151, 840, 1, 0, 0, 0, 153, 842, 1, 0, 0, 0, 155, 844, 1, 0, 0, 0, 157, 846, 1, 0, 0, 0, 159, 848, 1, 0, 0, 0, 161, 850, 1, 0, 0, 0, 163, 872, 1, 0, 0, 0, 165, 874, 1, 0, 0, 0, 167, 879, 1, 0, 0, 0, 169, 900, 1, 0, 0, 0, 171, 902, 1, 0, 0, 0, 173, 910, 1, 0, 0, 0, 175, 912, 1, 0, 0, 0, 177, 916, 1, 0, 0, 0, 179, 920, 1, 0, 0, 0, 181, 924, 1, 0, 0, 0, 183, 929, 1, 0, 0, 0, 185, 934, 1, 0, 0, 0, 187, 938, 1, 0, 0, 0, 189, 942, 1, 0, 0, 0, 191, 946, 1, 0, 0, 0, 193, 951, 1, 0, 0, 0, 195, 955, 1, 0, 0, 0, 197, 959, 1, 0, 0, 0, 199, 963, 1, 0, 0, 0, 201, 967, 1, 0, 0, 0, 203, 971, 1, 0, 0, 0, 205, 983, 1, 0, 0, 0, 207, 986, 1, 0, 0, 0, 209, 990, 1, 0, 0, 0, 211, 994, 1, 0, 0, 0, 213, 998, 1, 0, 0, 0, 215, 1002, 1, 0, 0, 0, 217, 1006, 1, 0, 0, 0, 219, 1010, 1, 0, 0, 0, 221, 1015, 1, 0, 0, 0, 223, 1019, 1, 0, 0, 0, 225, 1027, 1, 0, 0, 0, 227, 1048, 1, 0, 0, 0, 229, 1052, 1, 0, 0, 0, 231, 1056, 1, 0, 0, 0, 233, 1060, 1, 0, 0, 0, 235, 1064, 1, 0, 0, 0, 237, 1068, 1, 0, 0, 0, 239, 1073, 1, 0, 0, 0, 241, 1077, 1, 0, 0, 0, 243, 1081, 1, 0, 0, 0, 245, 1085, 1, 0, 0, 0, 247, 1088, 1, 0, 0, 0, 249, 1092, 1, 0, 0, 0, 251, 1096, 1, 0, 0, 0, 253, 1100, 1, 0, 0, 0, 255, 1104, 1, 0, 0, 0, 257, 1109, 1, 0, 0, 0, 259, 1114, 1, 0, 0, 0, 261, 1119, 1, 0, 0, 0, 263, 1126, 1, 0, 0, 0, 265, 1135, 1, 0, 0, 0, 267, 1142, 1, 0, 0, 0, 269, 1146, 1, 0, 0, 0, 271, 1150, 1, 0, 0, 0, 273, 1154, 1, 0, 0, 0, 275, 1158, 1, 0, 0, 0, 277, 1164, 1, 0, 0, 0, 279, 1168, 1, 0, 0, 0, 281, 1172, 1, 0, 0, 0, 283, 1176, 1, 0, 0, 0, 285, 1180, 1, 0, 0, 0, 287, 1184, 1, 0, 0, 0, 289, 1188, 1, 0, 0, 0, 291, 1192, 1, 0, 0, 0, 293, 1196, 1, 0, 0, 0, 295, 1200, 1, 0, 0, 0, 297, 1205, 1, 0, 0, 0, 299, 1209, 1, 0, 0, 0, 301, 1213, 1, 0, 0, 0, 303, 1217, 1, 0, 0, 0, 305, 1221, 1, 0, 0, 0, 307, 1225, 1, 0, 0, 0, 309, 1229, 1, 0, 0, 0, 311, 1234, 1, 0, 0, 0, 313, 1239, 1, 0, 0, 0, 315, 1243, 1, 0, 0, 0, 317, 1247, 1, 0, 0, 0, 319, 1251, 1, 0, 0, 0, 321, 1256, 1, 0, 0, 0, 323, 1263, 1, 0, 0, 0, 325, 1267, 1, 0, 0, 0, 327, 1271, 1, 0, 0, 0, 329, 1275, 1, 0, 0, 0, 331, 1279, 1, 0, 0, 0, 333, 1284, 1, 0, 0, 0, 335, 1288, 1, 0, 0, 0, 337, 1292, 1, 0, 0, 0, 339, 1296, 1, 0, 0, 0, 341, 1301, 1, 0, 0, 0, 343, 1305, 1, 0, 0, 0, 345, 1309, 1, 0, 0, 0, 347, 1313, 1, 0, 0, 0, 349, 1317, 1, 0, 0, 0, 351, 1321, 1, 0, 0, 0, 353, 1327, 1, 0, 0, 0, 355, 1331, 1, 0, 0, 0, 357, 1335, 1, 0, 0, 0, 359, 1339, 1, 0, 0, 0, 361, 1343, 1, 0, 0, 0, 363, 1347, 1, 0, 0, 0, 365, 1351, 1, 0, 0, 0, 367, 1356, 1, 0, 0, 0, 369, 1362, 1, 0, 0, 0, 371, 1368, 1, 0, 0, 0, 373, 1372, 1, 0, 0, 0, 375, 1376, 1, 0, 0, 0, 377, 1380, 1, 0, 0, 0, 379, 1386, 1, 0, 0, 0, 381, 1392, 1, 0, 0, 0, 383, 1396, 1, 0, 0, 0, 385, 1400, 1, 0, 0, 0, 387, 1404, 1, 0, 0, 0, 389, 1410, 1, 0, 0, 0, 391, 1416, 1, 0, 0, 0, 393, 1422, 1, 0, 0, 0, 395, 396, 7, 0, 0, 0, 396, 397, 7, 1, 0, 0, 397, 398, 7, 2, 0, 0, 398, 399, 7, 2, 0, 0, 399, 400, 7, 3, 0, 0, 400, 401, 7, 4, 0, 0, 401, 402, 7, 5, 0, 0, 402, 403, 1, 0, 0, 0, 403, 404, 6, 0, 0, 0, 404, 16, 1, 0, 0, 0, 405, 406, 7, 0, 0, 0, 406, 407, 7, 6, 0, 0, 407, 408, 7, 7, 0, 0, 408, 409, 7, 8, 0, 0, 409, 410, 1, 0, 0, 0, 410, 411, 6, 1, 1, 0, 411, 18, 1, 0, 0, 0, 412, 413, 7, 3, 0, 0, 413, 414, 7, 9, 0, 0, 414, 415, 7, 6, 0, 0, 415, 416, 7, 1, 0, 0, 416, 417, 7, 4, 0, 0, 417, 418, 7, 10, 0, 0, 418, 419, 1, 0, 0, 0, 419, 420, 6, 2, 2, 0, 420, 20, 1, 0, 0, 0, 421, 422, 7, 3, 0, 0, 422, 423, 7, 11, 0, 0, 423, 424, 7, 12, 0, 0, 424, 425, 7, 13, 0, 0, 425, 426, 1, 0, 0, 0, 426, 427, 6, 3, 0, 0, 427, 22, 1, 0, 0, 0, 428, 429, 7, 3, 0, 0, 429, 430, 7, 14, 0, 0, 430, 431, 7, 8, 0, 0, 431, 432, 7, 13, 0, 0, 432, 433, 7, 12, 0, 0, 433, 434, 7, 1, 0, 0, 434, 435, 7, 9, 0, 0, 435, 436, 1, 0, 0, 0, 436, 437, 6, 4, 3, 0, 437, 24, 1, 0, 0, 0, 438, 439, 7, 15, 0, 0, 439, 440, 7, 6, 0, 0, 440, 441, 7, 7, 0, 0, 441, 442, 7, 16, 0, 0, 442, 443, 1, 0, 0, 0, 443, 444, 6, 5, 4, 0, 444, 26, 1, 0, 0, 0, 445, 446, 7, 17, 0, 0, 446, 447, 7, 6, 0, 0, 447, 448, 7, 7, 0, 0, 448, 449, 7, 18, 0, 0, 449, 450, 1, 0, 0, 0, 450, 451, 6, 6, 0, 0, 451, 28, 1, 0, 0, 0, 452, 453, 7, 18, 0, 0, 453, 454, 7, 3, 0, 0, 454, 455, 7, 3, 0, 0, 455, 456, 7, 8, 0, 0, 456, 457, 1, 0, 0, 0, 457, 458, 6, 7, 1, 0, 458, 30, 1, 0, 0, 0, 459, 460, 7, 13, 0, 0, 460, 461, 7, 1, 0, 0, 461, 462, 7, 16, 0, 0, 462, 463, 7, 1, 0, 0, 463, 464, 7, 5, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 6, 8, 0, 0, 466, 32, 1, 0, 0, 0, 467, 468, 7, 16, 0, 0, 468, 469, 7, 11, 0, 0, 469, 470, 5, 95, 0, 0, 470, 471, 7, 3, 0, 0, 471, 472, 7, 14, 0, 0, 472, 473, 7, 8, 0, 0, 473, 474, 7, 12, 0, 0, 474, 475, 7, 9, 0, 0, 475, 476, 7, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 478, 6, 9, 5, 0, 478, 34, 1, 0, 0, 0, 479, 480, 7, 6, 0, 0, 480, 481, 7, 3, 0, 0, 481, 482, 7, 9, 0, 0, 482, 483, 7, 12, 0, 0, 483, 484, 7, 16, 0, 0, 484, 485, 7, 3, 0, 0, 485, 486, 1, 0, 0, 0, 486, 487, 6, 10, 6, 0, 487, 36, 1, 0, 0, 0, 488, 489, 7, 6, 0, 0, 489, 490, 7, 7, 0, 0, 490, 491, 7, 19, 0, 0, 491, 492, 1, 0, 0, 0, 492, 493, 6, 11, 0, 0, 493, 38, 1, 0, 0, 0, 494, 495, 7, 2, 0, 0, 495, 496, 7, 10, 0, 0, 496, 497, 7, 7, 0, 0, 497, 498, 7, 19, 0, 0, 498, 499, 1, 0, 0, 0, 499, 500, 6, 12, 7, 0, 500, 40, 1, 0, 0, 0, 501, 502, 7, 2, 0, 0, 502, 503, 7, 7, 0, 0, 503, 504, 7, 6, 0, 0, 504, 505, 7, 5, 0, 0, 505, 506, 1, 0, 0, 0, 506, 507, 6, 13, 0, 0, 507, 42, 1, 0, 0, 0, 508, 509, 7, 2, 0, 0, 509, 510, 7, 5, 0, 0, 510, 511, 7, 12, 0, 0, 511, 512, 7, 5, 0, 0, 512, 513, 7, 2, 0, 0, 513, 514, 1, 0, 0, 0, 514, 515, 6, 14, 0, 0, 515, 44, 1, 0, 0, 0, 516, 517, 7, 19, 0, 0, 517, 518, 7, 10, 0, 0, 518, 519, 7, 3, 0, 0, 519, 520, 7, 6, 0, 0, 520, 521, 7, 3, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, 6, 15, 0, 0, 523, 46, 1, 0, 0, 0, 524, 525, 4, 16, 0, 0, 525, 526, 7, 1, 0, 0, 526, 527, 7, 9, 0, 0, 527, 528, 7, 13, 0, 0, 528, 529, 7, 1, 0, 0, 529, 530, 7, 9, 0, 0, 530, 531, 7, 3, 0, 0, 531, 532, 7, 2, 0, 0, 532, 533, 7, 5, 0, 0, 533, 534, 7, 12, 0, 0, 534, 535, 7, 5, 0, 0, 535, 536, 7, 2, 0, 0, 536, 537, 1, 0, 0, 0, 537, 538, 6, 16, 0, 0, 538, 48, 1, 0, 0, 0, 539, 540, 4, 17, 1, 0, 540, 541, 7, 13, 0, 0, 541, 542, 7, 7, 0, 0, 542, 543, 7, 7, 0, 0, 543, 544, 7, 18, 0, 0, 544, 545, 7, 20, 0, 0, 545, 546, 7, 8, 0, 0, 546, 547, 1, 0, 0, 0, 547, 548, 6, 17, 8, 0, 548, 50, 1, 0, 0, 0, 549, 550, 4, 18, 2, 0, 550, 551, 7, 16, 0, 0, 551, 552, 7, 12, 0, 0, 552, 553, 7, 5, 0, 0, 553, 554, 7, 4, 0, 0, 554, 555, 7, 10, 0, 0, 555, 556, 1, 0, 0, 0, 556, 557, 6, 18, 0, 0, 557, 52, 1, 0, 0, 0, 558, 559, 4, 19, 3, 0, 559, 560, 7, 16, 0, 0, 560, 561, 7, 3, 0, 0, 561, 562, 7, 5, 0, 0, 562, 563, 7, 6, 0, 0, 563, 564, 7, 1, 0, 0, 564, 565, 7, 4, 0, 0, 565, 566, 7, 2, 0, 0, 566, 567, 1, 0, 0, 0, 567, 568, 6, 19, 9, 0, 568, 54, 1, 0, 0, 0, 569, 571, 8, 21, 0, 0, 570, 569, 1, 0, 0, 0, 571, 572, 1, 0, 0, 0, 572, 570, 1, 0, 0, 0, 572, 573, 1, 0, 0, 0, 573, 574, 1, 0, 0, 0, 574, 575, 6, 20, 0, 0, 575, 56, 1, 0, 0, 0, 576, 577, 5, 47, 0, 0, 577, 578, 5, 47, 0, 0, 578, 582, 1, 0, 0, 0, 579, 581, 8, 22, 0, 0, 580, 579, 1, 0, 0, 0, 581, 584, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 585, 587, 5, 13, 0, 0, 586, 585, 1, 0, 0, 0, 586, 587, 1, 0, 0, 0, 587, 589, 1, 0, 0, 0, 588, 590, 5, 10, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 6, 21, 10, 0, 592, 58, 1, 0, 0, 0, 593, 594, 5, 47, 0, 0, 594, 595, 5, 42, 0, 0, 595, 600, 1, 0, 0, 0, 596, 599, 3, 59, 22, 0, 597, 599, 9, 0, 0, 0, 598, 596, 1, 0, 0, 0, 598, 597, 1, 0, 0, 0, 599, 602, 1, 0, 0, 0, 600, 601, 1, 0, 0, 0, 600, 598, 1, 0, 0, 0, 601, 603, 1, 0, 0, 0, 602, 600, 1, 0, 0, 0, 603, 604, 5, 42, 0, 0, 604, 605, 5, 47, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 6, 22, 10, 0, 607, 60, 1, 0, 0, 0, 608, 610, 7, 23, 0, 0, 609, 608, 1, 0, 0, 0, 610, 611, 1, 0, 0, 0, 611, 609, 1, 0, 0, 0, 611, 612, 1, 0, 0, 0, 612, 613, 1, 0, 0, 0, 613, 614, 6, 23, 10, 0, 614, 62, 1, 0, 0, 0, 615, 616, 5, 124, 0, 0, 616, 617, 1, 0, 0, 0, 617, 618, 6, 24, 11, 0, 618, 64, 1, 0, 0, 0, 619, 620, 7, 24, 0, 0, 620, 66, 1, 0, 0, 0, 621, 622, 7, 25, 0, 0, 622, 68, 1, 0, 0, 0, 623, 624, 5, 92, 0, 0, 624, 625, 7, 26, 0, 0, 625, 70, 1, 0, 0, 0, 626, 627, 8, 27, 0, 0, 627, 72, 1, 0, 0, 0, 628, 630, 7, 3, 0, 0, 629, 631, 7, 28, 0, 0, 630, 629, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 633, 1, 0, 0, 0, 632, 634, 3, 65, 25, 0, 633, 632, 1, 0, 0, 0, 634, 635, 1, 0, 0, 0, 635, 633, 1, 0, 0, 0, 635, 636, 1, 0, 0, 0, 636, 74, 1, 0, 0, 0, 637, 638, 5, 64, 0, 0, 638, 76, 1, 0, 0, 0, 639, 640, 5, 96, 0, 0, 640, 78, 1, 0, 0, 0, 641, 645, 8, 29, 0, 0, 642, 643, 5, 96, 0, 0, 643, 645, 5, 96, 0, 0, 644, 641, 1, 0, 0, 0, 644, 642, 1, 0, 0, 0, 645, 80, 1, 0, 0, 0, 646, 647, 5, 95, 0, 0, 647, 82, 1, 0, 0, 0, 648, 652, 3, 67, 26, 0, 649, 652, 3, 65, 25, 0, 650, 652, 3, 81, 33, 0, 651, 648, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 651, 650, 1, 0, 0, 0, 652, 84, 1, 0, 0, 0, 653, 658, 5, 34, 0, 0, 654, 657, 3, 69, 27, 0, 655, 657, 3, 71, 28, 0, 656, 654, 1, 0, 0, 0, 656, 655, 1, 0, 0, 0, 657, 660, 1, 0, 0, 0, 658, 656, 1, 0, 0, 0, 658, 659, 1, 0, 0, 0, 659, 661, 1, 0, 0, 0, 660, 658, 1, 0, 0, 0, 661, 683, 5, 34, 0, 0, 662, 663, 5, 34, 0, 0, 663, 664, 5, 34, 0, 0, 664, 665, 5, 34, 0, 0, 665, 669, 1, 0, 0, 0, 666, 668, 8, 22, 0, 0, 667, 666, 1, 0, 0, 0, 668, 671, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 669, 667, 1, 0, 0, 0, 670, 672, 1, 0, 0, 0, 671, 669, 1, 0, 0, 0, 672, 673, 5, 34, 0, 0, 673, 674, 5, 34, 0, 0, 674, 675, 5, 34, 0, 0, 675, 677, 1, 0, 0, 0, 676, 678, 5, 34, 0, 0, 677, 676, 1, 0, 0, 0, 677, 678, 1, 0, 0, 0, 678, 680, 1, 0, 0, 0, 679, 681, 5, 34, 0, 0, 680, 679, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 653, 1, 0, 0, 0, 682, 662, 1, 0, 0, 0, 683, 86, 1, 0, 0, 0, 684, 686, 3, 65, 25, 0, 685, 684, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 685, 1, 0, 0, 0, 687, 688, 1, 0, 0, 0, 688, 88, 1, 0, 0, 0, 689, 691, 3, 65, 25, 0, 690, 689, 1, 0, 0, 0, 691, 692, 1, 0, 0, 0, 692, 690, 1, 0, 0, 0, 692, 693, 1, 0, 0, 0, 693, 694, 1, 0, 0, 0, 694, 698, 3, 105, 45, 0, 695, 697, 3, 65, 25, 0, 696, 695, 1, 0, 0, 0, 697, 700, 1, 0, 0, 0, 698, 696, 1, 0, 0, 0, 698, 699, 1, 0, 0, 0, 699, 732, 1, 0, 0, 0, 700, 698, 1, 0, 0, 0, 701, 703, 3, 105, 45, 0, 702, 704, 3, 65, 25, 0, 703, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 703, 1, 0, 0, 0, 705, 706, 1, 0, 0, 0, 706, 732, 1, 0, 0, 0, 707, 709, 3, 65, 25, 0, 708, 707, 1, 0, 0, 0, 709, 710, 1, 0, 0, 0, 710, 708, 1, 0, 0, 0, 710, 711, 1, 0, 0, 0, 711, 719, 1, 0, 0, 0, 712, 716, 3, 105, 45, 0, 713, 715, 3, 65, 25, 0, 714, 713, 1, 0, 0, 0, 715, 718, 1, 0, 0, 0, 716, 714, 1, 0, 0, 0, 716, 717, 1, 0, 0, 0, 717, 720, 1, 0, 0, 0, 718, 716, 1, 0, 0, 0, 719, 712, 1, 0, 0, 0, 719, 720, 1, 0, 0, 0, 720, 721, 1, 0, 0, 0, 721, 722, 3, 73, 29, 0, 722, 732, 1, 0, 0, 0, 723, 725, 3, 105, 45, 0, 724, 726, 3, 65, 25, 0, 725, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 725, 1, 0, 0, 0, 727, 728, 1, 0, 0, 0, 728, 729, 1, 0, 0, 0, 729, 730, 3, 73, 29, 0, 730, 732, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 701, 1, 0, 0, 0, 731, 708, 1, 0, 0, 0, 731, 723, 1, 0, 0, 0, 732, 90, 1, 0, 0, 0, 733, 734, 7, 30, 0, 0, 734, 735, 7, 31, 0, 0, 735, 92, 1, 0, 0, 0, 736, 737, 7, 12, 0, 0, 737, 738, 7, 9, 0, 0, 738, 739, 7, 0, 0, 0, 739, 94, 1, 0, 0, 0, 740, 741, 7, 12, 0, 0, 741, 742, 7, 2, 0, 0, 742, 743, 7, 4, 0, 0, 743, 96, 1, 0, 0, 0, 744, 745, 5, 61, 0, 0, 745, 98, 1, 0, 0, 0, 746, 747, 5, 58, 0, 0, 747, 748, 5, 58, 0, 0, 748, 100, 1, 0, 0, 0, 749, 750, 5, 44, 0, 0, 750, 102, 1, 0, 0, 0, 751, 752, 7, 0, 0, 0, 752, 753, 7, 3, 0, 0, 753, 754, 7, 2, 0, 0, 754, 755, 7, 4, 0, 0, 755, 104, 1, 0, 0, 0, 756, 757, 5, 46, 0, 0, 757, 106, 1, 0, 0, 0, 758, 759, 7, 15, 0, 0, 759, 760, 7, 12, 0, 0, 760, 761, 7, 13, 0, 0, 761, 762, 7, 2, 0, 0, 762, 763, 7, 3, 0, 0, 763, 108, 1, 0, 0, 0, 764, 765, 7, 15, 0, 0, 765, 766, 7, 1, 0, 0, 766, 767, 7, 6, 0, 0, 767, 768, 7, 2, 0, 0, 768, 769, 7, 5, 0, 0, 769, 110, 1, 0, 0, 0, 770, 771, 7, 1, 0, 0, 771, 772, 7, 9, 0, 0, 772, 112, 1, 0, 0, 0, 773, 774, 7, 1, 0, 0, 774, 775, 7, 2, 0, 0, 775, 114, 1, 0, 0, 0, 776, 777, 7, 13, 0, 0, 777, 778, 7, 12, 0, 0, 778, 779, 7, 2, 0, 0, 779, 780, 7, 5, 0, 0, 780, 116, 1, 0, 0, 0, 781, 782, 7, 13, 0, 0, 782, 783, 7, 1, 0, 0, 783, 784, 7, 18, 0, 0, 784, 785, 7, 3, 0, 0, 785, 118, 1, 0, 0, 0, 786, 787, 5, 40, 0, 0, 787, 120, 1, 0, 0, 0, 788, 789, 7, 9, 0, 0, 789, 790, 7, 7, 0, 0, 790, 791, 7, 5, 0, 0, 791, 122, 1, 0, 0, 0, 792, 793, 7, 9, 0, 0, 793, 794, 7, 20, 0, 0, 794, 795, 7, 13, 0, 0, 795, 796, 7, 13, 0, 0, 796, 124, 1, 0, 0, 0, 797, 798, 7, 9, 0, 0, 798, 799, 7, 20, 0, 0, 799, 800, 7, 13, 0, 0, 800, 801, 7, 13, 0, 0, 801, 802, 7, 2, 0, 0, 802, 126, 1, 0, 0, 0, 803, 804, 7, 7, 0, 0, 804, 805, 7, 6, 0, 0, 805, 128, 1, 0, 0, 0, 806, 807, 5, 63, 0, 0, 807, 130, 1, 0, 0, 0, 808, 809, 7, 6, 0, 0, 809, 810, 7, 13, 0, 0, 810, 811, 7, 1, 0, 0, 811, 812, 7, 18, 0, 0, 812, 813, 7, 3, 0, 0, 813, 132, 1, 0, 0, 0, 814, 815, 5, 41, 0, 0, 815, 134, 1, 0, 0, 0, 816, 817, 7, 5, 0, 0, 817, 818, 7, 6, 0, 0, 818, 819, 7, 20, 0, 0, 819, 820, 7, 3, 0, 0, 820, 136, 1, 0, 0, 0, 821, 822, 5, 61, 0, 0, 822, 823, 5, 61, 0, 0, 823, 138, 1, 0, 0, 0, 824, 825, 5, 61, 0, 0, 825, 826, 5, 126, 0, 0, 826, 140, 1, 0, 0, 0, 827, 828, 5, 33, 0, 0, 828, 829, 5, 61, 0, 0, 829, 142, 1, 0, 0, 0, 830, 831, 5, 60, 0, 0, 831, 144, 1, 0, 0, 0, 832, 833, 5, 60, 0, 0, 833, 834, 5, 61, 0, 0, 834, 146, 1, 0, 0, 0, 835, 836, 5, 62, 0, 0, 836, 148, 1, 0, 0, 0, 837, 838, 5, 62, 0, 0, 838, 839, 5, 61, 0, 0, 839, 150, 1, 0, 0, 0, 840, 841, 5, 43, 0, 0, 841, 152, 1, 0, 0, 0, 842, 843, 5, 45, 0, 0, 843, 154, 1, 0, 0, 0, 844, 845, 5, 42, 0, 0, 845, 156, 1, 0, 0, 0, 846, 847, 5, 47, 0, 0, 847, 158, 1, 0, 0, 0, 848, 849, 5, 37, 0, 0, 849, 160, 1, 0, 0, 0, 850, 851, 4, 73, 4, 0, 851, 852, 3, 51, 18, 0, 852, 853, 1, 0, 0, 0, 853, 854, 6, 73, 12, 0, 854, 162, 1, 0, 0, 0, 855, 858, 3, 129, 57, 0, 856, 859, 3, 67, 26, 0, 857, 859, 3, 81, 33, 0, 858, 856, 1, 0, 0, 0, 858, 857, 1, 0, 0, 0, 859, 863, 1, 0, 0, 0, 860, 862, 3, 83, 34, 0, 861, 860, 1, 0, 0, 0, 862, 865, 1, 0, 0, 0, 863, 861, 1, 0, 0, 0, 863, 864, 1, 0, 0, 0, 864, 873, 1, 0, 0, 0, 865, 863, 1, 0, 0, 0, 866, 868, 3, 129, 57, 0, 867, 869, 3, 65, 25, 0, 868, 867, 1, 0, 0, 0, 869, 870, 1, 0, 0, 0, 870, 868, 1, 0, 0, 0, 870, 871, 1, 0, 0, 0, 871, 873, 1, 0, 0, 0, 872, 855, 1, 0, 0, 0, 872, 866, 1, 0, 0, 0, 873, 164, 1, 0, 0, 0, 874, 875, 5, 91, 0, 0, 875, 876, 1, 0, 0, 0, 876, 877, 6, 75, 0, 0, 877, 878, 6, 75, 0, 0, 878, 166, 1, 0, 0, 0, 879, 880, 5, 93, 0, 0, 880, 881, 1, 0, 0, 0, 881, 882, 6, 76, 11, 0, 882, 883, 6, 76, 11, 0, 883, 168, 1, 0, 0, 0, 884, 888, 3, 67, 26, 0, 885, 887, 3, 83, 34, 0, 886, 885, 1, 0, 0, 0, 887, 890, 1, 0, 0, 0, 888, 886, 1, 0, 0, 0, 888, 889, 1, 0, 0, 0, 889, 901, 1, 0, 0, 0, 890, 888, 1, 0, 0, 0, 891, 894, 3, 81, 33, 0, 892, 894, 3, 75, 30, 0, 893, 891, 1, 0, 0, 0, 893, 892, 1, 0, 0, 0, 894, 896, 1, 0, 0, 0, 895, 897, 3, 83, 34, 0, 896, 895, 1, 0, 0, 0, 897, 898, 1, 0, 0, 0, 898, 896, 1, 0, 0, 0, 898, 899, 1, 0, 0, 0, 899, 901, 1, 0, 0, 0, 900, 884, 1, 0, 0, 0, 900, 893, 1, 0, 0, 0, 901, 170, 1, 0, 0, 0, 902, 904, 3, 77, 31, 0, 903, 905, 3, 79, 32, 0, 904, 903, 1, 0, 0, 0, 905, 906, 1, 0, 0, 0, 906, 904, 1, 0, 0, 0, 906, 907, 1, 0, 0, 0, 907, 908, 1, 0, 0, 0, 908, 909, 3, 77, 31, 0, 909, 172, 1, 0, 0, 0, 910, 911, 3, 171, 78, 0, 911, 174, 1, 0, 0, 0, 912, 913, 3, 57, 21, 0, 913, 914, 1, 0, 0, 0, 914, 915, 6, 80, 10, 0, 915, 176, 1, 0, 0, 0, 916, 917, 3, 59, 22, 0, 917, 918, 1, 0, 0, 0, 918, 919, 6, 81, 10, 0, 919, 178, 1, 0, 0, 0, 920, 921, 3, 61, 23, 0, 921, 922, 1, 0, 0, 0, 922, 923, 6, 82, 10, 0, 923, 180, 1, 0, 0, 0, 924, 925, 3, 165, 75, 0, 925, 926, 1, 0, 0, 0, 926, 927, 6, 83, 13, 0, 927, 928, 6, 83, 14, 0, 928, 182, 1, 0, 0, 0, 929, 930, 3, 63, 24, 0, 930, 931, 1, 0, 0, 0, 931, 932, 6, 84, 15, 0, 932, 933, 6, 84, 11, 0, 933, 184, 1, 0, 0, 0, 934, 935, 3, 61, 23, 0, 935, 936, 1, 0, 0, 0, 936, 937, 6, 85, 10, 0, 937, 186, 1, 0, 0, 0, 938, 939, 3, 57, 21, 0, 939, 940, 1, 0, 0, 0, 940, 941, 6, 86, 10, 0, 941, 188, 1, 0, 0, 0, 942, 943, 3, 59, 22, 0, 943, 944, 1, 0, 0, 0, 944, 945, 6, 87, 10, 0, 945, 190, 1, 0, 0, 0, 946, 947, 3, 63, 24, 0, 947, 948, 1, 0, 0, 0, 948, 949, 6, 88, 15, 0, 949, 950, 6, 88, 11, 0, 950, 192, 1, 0, 0, 0, 951, 952, 3, 165, 75, 0, 952, 953, 1, 0, 0, 0, 953, 954, 6, 89, 13, 0, 954, 194, 1, 0, 0, 0, 955, 956, 3, 167, 76, 0, 956, 957, 1, 0, 0, 0, 957, 958, 6, 90, 16, 0, 958, 196, 1, 0, 0, 0, 959, 960, 3, 321, 153, 0, 960, 961, 1, 0, 0, 0, 961, 962, 6, 91, 17, 0, 962, 198, 1, 0, 0, 0, 963, 964, 3, 101, 43, 0, 964, 965, 1, 0, 0, 0, 965, 966, 6, 92, 18, 0, 966, 200, 1, 0, 0, 0, 967, 968, 3, 97, 41, 0, 968, 969, 1, 0, 0, 0, 969, 970, 6, 93, 19, 0, 970, 202, 1, 0, 0, 0, 971, 972, 7, 16, 0, 0, 972, 973, 7, 3, 0, 0, 973, 974, 7, 5, 0, 0, 974, 975, 7, 12, 0, 0, 975, 976, 7, 0, 0, 0, 976, 977, 7, 12, 0, 0, 977, 978, 7, 5, 0, 0, 978, 979, 7, 12, 0, 0, 979, 204, 1, 0, 0, 0, 980, 984, 8, 32, 0, 0, 981, 982, 5, 47, 0, 0, 982, 984, 8, 33, 0, 0, 983, 980, 1, 0, 0, 0, 983, 981, 1, 0, 0, 0, 984, 206, 1, 0, 0, 0, 985, 987, 3, 205, 95, 0, 986, 985, 1, 0, 0, 0, 987, 988, 1, 0, 0, 0, 988, 986, 1, 0, 0, 0, 988, 989, 1, 0, 0, 0, 989, 208, 1, 0, 0, 0, 990, 991, 3, 207, 96, 0, 991, 992, 1, 0, 0, 0, 992, 993, 6, 97, 20, 0, 993, 210, 1, 0, 0, 0, 994, 995, 3, 85, 35, 0, 995, 996, 1, 0, 0, 0, 996, 997, 6, 98, 21, 0, 997, 212, 1, 0, 0, 0, 998, 999, 3, 57, 21, 0, 999, 1000, 1, 0, 0, 0, 1000, 1001, 6, 99, 10, 0, 1001, 214, 1, 0, 0, 0, 1002, 1003, 3, 59, 22, 0, 1003, 1004, 1, 0, 0, 0, 1004, 1005, 6, 100, 10, 0, 1005, 216, 1, 0, 0, 0, 1006, 1007, 3, 61, 23, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1009, 6, 101, 10, 0, 1009, 218, 1, 0, 0, 0, 1010, 1011, 3, 63, 24, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1013, 6, 102, 15, 0, 1013, 1014, 6, 102, 11, 0, 1014, 220, 1, 0, 0, 0, 1015, 1016, 3, 105, 45, 0, 1016, 1017, 1, 0, 0, 0, 1017, 1018, 6, 103, 22, 0, 1018, 222, 1, 0, 0, 0, 1019, 1020, 3, 101, 43, 0, 1020, 1021, 1, 0, 0, 0, 1021, 1022, 6, 104, 18, 0, 1022, 224, 1, 0, 0, 0, 1023, 1028, 3, 67, 26, 0, 1024, 1028, 3, 65, 25, 0, 1025, 1028, 3, 81, 33, 0, 1026, 1028, 3, 155, 70, 0, 1027, 1023, 1, 0, 0, 0, 1027, 1024, 1, 0, 0, 0, 1027, 1025, 1, 0, 0, 0, 1027, 1026, 1, 0, 0, 0, 1028, 226, 1, 0, 0, 0, 1029, 1032, 3, 67, 26, 0, 1030, 1032, 3, 155, 70, 0, 1031, 1029, 1, 0, 0, 0, 1031, 1030, 1, 0, 0, 0, 1032, 1036, 1, 0, 0, 0, 1033, 1035, 3, 225, 105, 0, 1034, 1033, 1, 0, 0, 0, 1035, 1038, 1, 0, 0, 0, 1036, 1034, 1, 0, 0, 0, 1036, 1037, 1, 0, 0, 0, 1037, 1049, 1, 0, 0, 0, 1038, 1036, 1, 0, 0, 0, 1039, 1042, 3, 81, 33, 0, 1040, 1042, 3, 75, 30, 0, 1041, 1039, 1, 0, 0, 0, 1041, 1040, 1, 0, 0, 0, 1042, 1044, 1, 0, 0, 0, 1043, 1045, 3, 225, 105, 0, 1044, 1043, 1, 0, 0, 0, 1045, 1046, 1, 0, 0, 0, 1046, 1044, 1, 0, 0, 0, 1046, 1047, 1, 0, 0, 0, 1047, 1049, 1, 0, 0, 0, 1048, 1031, 1, 0, 0, 0, 1048, 1041, 1, 0, 0, 0, 1049, 228, 1, 0, 0, 0, 1050, 1053, 3, 227, 106, 0, 1051, 1053, 3, 171, 78, 0, 1052, 1050, 1, 0, 0, 0, 1052, 1051, 1, 0, 0, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1052, 1, 0, 0, 0, 1054, 1055, 1, 0, 0, 0, 1055, 230, 1, 0, 0, 0, 1056, 1057, 3, 57, 21, 0, 1057, 1058, 1, 0, 0, 0, 1058, 1059, 6, 108, 10, 0, 1059, 232, 1, 0, 0, 0, 1060, 1061, 3, 59, 22, 0, 1061, 1062, 1, 0, 0, 0, 1062, 1063, 6, 109, 10, 0, 1063, 234, 1, 0, 0, 0, 1064, 1065, 3, 61, 23, 0, 1065, 1066, 1, 0, 0, 0, 1066, 1067, 6, 110, 10, 0, 1067, 236, 1, 0, 0, 0, 1068, 1069, 3, 63, 24, 0, 1069, 1070, 1, 0, 0, 0, 1070, 1071, 6, 111, 15, 0, 1071, 1072, 6, 111, 11, 0, 1072, 238, 1, 0, 0, 0, 1073, 1074, 3, 97, 41, 0, 1074, 1075, 1, 0, 0, 0, 1075, 1076, 6, 112, 19, 0, 1076, 240, 1, 0, 0, 0, 1077, 1078, 3, 101, 43, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1080, 6, 113, 18, 0, 1080, 242, 1, 0, 0, 0, 1081, 1082, 3, 105, 45, 0, 1082, 1083, 1, 0, 0, 0, 1083, 1084, 6, 114, 22, 0, 1084, 244, 1, 0, 0, 0, 1085, 1086, 7, 12, 0, 0, 1086, 1087, 7, 2, 0, 0, 1087, 246, 1, 0, 0, 0, 1088, 1089, 3, 229, 107, 0, 1089, 1090, 1, 0, 0, 0, 1090, 1091, 6, 116, 23, 0, 1091, 248, 1, 0, 0, 0, 1092, 1093, 3, 57, 21, 0, 1093, 1094, 1, 0, 0, 0, 1094, 1095, 6, 117, 10, 0, 1095, 250, 1, 0, 0, 0, 1096, 1097, 3, 59, 22, 0, 1097, 1098, 1, 0, 0, 0, 1098, 1099, 6, 118, 10, 0, 1099, 252, 1, 0, 0, 0, 1100, 1101, 3, 61, 23, 0, 1101, 1102, 1, 0, 0, 0, 1102, 1103, 6, 119, 10, 0, 1103, 254, 1, 0, 0, 0, 1104, 1105, 3, 63, 24, 0, 1105, 1106, 1, 0, 0, 0, 1106, 1107, 6, 120, 15, 0, 1107, 1108, 6, 120, 11, 0, 1108, 256, 1, 0, 0, 0, 1109, 1110, 3, 165, 75, 0, 1110, 1111, 1, 0, 0, 0, 1111, 1112, 6, 121, 13, 0, 1112, 1113, 6, 121, 24, 0, 1113, 258, 1, 0, 0, 0, 1114, 1115, 7, 7, 0, 0, 1115, 1116, 7, 9, 0, 0, 1116, 1117, 1, 0, 0, 0, 1117, 1118, 6, 122, 25, 0, 1118, 260, 1, 0, 0, 0, 1119, 1120, 7, 19, 0, 0, 1120, 1121, 7, 1, 0, 0, 1121, 1122, 7, 5, 0, 0, 1122, 1123, 7, 10, 0, 0, 1123, 1124, 1, 0, 0, 0, 1124, 1125, 6, 123, 25, 0, 1125, 262, 1, 0, 0, 0, 1126, 1127, 8, 34, 0, 0, 1127, 264, 1, 0, 0, 0, 1128, 1130, 3, 263, 124, 0, 1129, 1128, 1, 0, 0, 0, 1130, 1131, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1131, 1132, 1, 0, 0, 0, 1132, 1133, 1, 0, 0, 0, 1133, 1134, 3, 321, 153, 0, 1134, 1136, 1, 0, 0, 0, 1135, 1129, 1, 0, 0, 0, 1135, 1136, 1, 0, 0, 0, 1136, 1138, 1, 0, 0, 0, 1137, 1139, 3, 263, 124, 0, 1138, 1137, 1, 0, 0, 0, 1139, 1140, 1, 0, 0, 0, 1140, 1138, 1, 0, 0, 0, 1140, 1141, 1, 0, 0, 0, 1141, 266, 1, 0, 0, 0, 1142, 1143, 3, 265, 125, 0, 1143, 1144, 1, 0, 0, 0, 1144, 1145, 6, 126, 26, 0, 1145, 268, 1, 0, 0, 0, 1146, 1147, 3, 57, 21, 0, 1147, 1148, 1, 0, 0, 0, 1148, 1149, 6, 127, 10, 0, 1149, 270, 1, 0, 0, 0, 1150, 1151, 3, 59, 22, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 6, 128, 10, 0, 1153, 272, 1, 0, 0, 0, 1154, 1155, 3, 61, 23, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1157, 6, 129, 10, 0, 1157, 274, 1, 0, 0, 0, 1158, 1159, 3, 63, 24, 0, 1159, 1160, 1, 0, 0, 0, 1160, 1161, 6, 130, 15, 0, 1161, 1162, 6, 130, 11, 0, 1162, 1163, 6, 130, 11, 0, 1163, 276, 1, 0, 0, 0, 1164, 1165, 3, 97, 41, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1167, 6, 131, 19, 0, 1167, 278, 1, 0, 0, 0, 1168, 1169, 3, 101, 43, 0, 1169, 1170, 1, 0, 0, 0, 1170, 1171, 6, 132, 18, 0, 1171, 280, 1, 0, 0, 0, 1172, 1173, 3, 105, 45, 0, 1173, 1174, 1, 0, 0, 0, 1174, 1175, 6, 133, 22, 0, 1175, 282, 1, 0, 0, 0, 1176, 1177, 3, 261, 123, 0, 1177, 1178, 1, 0, 0, 0, 1178, 1179, 6, 134, 27, 0, 1179, 284, 1, 0, 0, 0, 1180, 1181, 3, 229, 107, 0, 1181, 1182, 1, 0, 0, 0, 1182, 1183, 6, 135, 23, 0, 1183, 286, 1, 0, 0, 0, 1184, 1185, 3, 173, 79, 0, 1185, 1186, 1, 0, 0, 0, 1186, 1187, 6, 136, 28, 0, 1187, 288, 1, 0, 0, 0, 1188, 1189, 3, 57, 21, 0, 1189, 1190, 1, 0, 0, 0, 1190, 1191, 6, 137, 10, 0, 1191, 290, 1, 0, 0, 0, 1192, 1193, 3, 59, 22, 0, 1193, 1194, 1, 0, 0, 0, 1194, 1195, 6, 138, 10, 0, 1195, 292, 1, 0, 0, 0, 1196, 1197, 3, 61, 23, 0, 1197, 1198, 1, 0, 0, 0, 1198, 1199, 6, 139, 10, 0, 1199, 294, 1, 0, 0, 0, 1200, 1201, 3, 63, 24, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 6, 140, 15, 0, 1203, 1204, 6, 140, 11, 0, 1204, 296, 1, 0, 0, 0, 1205, 1206, 3, 105, 45, 0, 1206, 1207, 1, 0, 0, 0, 1207, 1208, 6, 141, 22, 0, 1208, 298, 1, 0, 0, 0, 1209, 1210, 3, 173, 79, 0, 1210, 1211, 1, 0, 0, 0, 1211, 1212, 6, 142, 28, 0, 1212, 300, 1, 0, 0, 0, 1213, 1214, 3, 169, 77, 0, 1214, 1215, 1, 0, 0, 0, 1215, 1216, 6, 143, 29, 0, 1216, 302, 1, 0, 0, 0, 1217, 1218, 3, 57, 21, 0, 1218, 1219, 1, 0, 0, 0, 1219, 1220, 6, 144, 10, 0, 1220, 304, 1, 0, 0, 0, 1221, 1222, 3, 59, 22, 0, 1222, 1223, 1, 0, 0, 0, 1223, 1224, 6, 145, 10, 0, 1224, 306, 1, 0, 0, 0, 1225, 1226, 3, 61, 23, 0, 1226, 1227, 1, 0, 0, 0, 1227, 1228, 6, 146, 10, 0, 1228, 308, 1, 0, 0, 0, 1229, 1230, 3, 63, 24, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1232, 6, 147, 15, 0, 1232, 1233, 6, 147, 11, 0, 1233, 310, 1, 0, 0, 0, 1234, 1235, 7, 1, 0, 0, 1235, 1236, 7, 9, 0, 0, 1236, 1237, 7, 15, 0, 0, 1237, 1238, 7, 7, 0, 0, 1238, 312, 1, 0, 0, 0, 1239, 1240, 3, 57, 21, 0, 1240, 1241, 1, 0, 0, 0, 1241, 1242, 6, 149, 10, 0, 1242, 314, 1, 0, 0, 0, 1243, 1244, 3, 59, 22, 0, 1244, 1245, 1, 0, 0, 0, 1245, 1246, 6, 150, 10, 0, 1246, 316, 1, 0, 0, 0, 1247, 1248, 3, 61, 23, 0, 1248, 1249, 1, 0, 0, 0, 1249, 1250, 6, 151, 10, 0, 1250, 318, 1, 0, 0, 0, 1251, 1252, 3, 167, 76, 0, 1252, 1253, 1, 0, 0, 0, 1253, 1254, 6, 152, 16, 0, 1254, 1255, 6, 152, 11, 0, 1255, 320, 1, 0, 0, 0, 1256, 1257, 5, 58, 0, 0, 1257, 322, 1, 0, 0, 0, 1258, 1264, 3, 75, 30, 0, 1259, 1264, 3, 65, 25, 0, 1260, 1264, 3, 105, 45, 0, 1261, 1264, 3, 67, 26, 0, 1262, 1264, 3, 81, 33, 0, 1263, 1258, 1, 0, 0, 0, 1263, 1259, 1, 0, 0, 0, 1263, 1260, 1, 0, 0, 0, 1263, 1261, 1, 0, 0, 0, 1263, 1262, 1, 0, 0, 0, 1264, 1265, 1, 0, 0, 0, 1265, 1263, 1, 0, 0, 0, 1265, 1266, 1, 0, 0, 0, 1266, 324, 1, 0, 0, 0, 1267, 1268, 3, 57, 21, 0, 1268, 1269, 1, 0, 0, 0, 1269, 1270, 6, 155, 10, 0, 1270, 326, 1, 0, 0, 0, 1271, 1272, 3, 59, 22, 0, 1272, 1273, 1, 0, 0, 0, 1273, 1274, 6, 156, 10, 0, 1274, 328, 1, 0, 0, 0, 1275, 1276, 3, 61, 23, 0, 1276, 1277, 1, 0, 0, 0, 1277, 1278, 6, 157, 10, 0, 1278, 330, 1, 0, 0, 0, 1279, 1280, 3, 63, 24, 0, 1280, 1281, 1, 0, 0, 0, 1281, 1282, 6, 158, 15, 0, 1282, 1283, 6, 158, 11, 0, 1283, 332, 1, 0, 0, 0, 1284, 1285, 3, 321, 153, 0, 1285, 1286, 1, 0, 0, 0, 1286, 1287, 6, 159, 17, 0, 1287, 334, 1, 0, 0, 0, 1288, 1289, 3, 101, 43, 0, 1289, 1290, 1, 0, 0, 0, 1290, 1291, 6, 160, 18, 0, 1291, 336, 1, 0, 0, 0, 1292, 1293, 3, 105, 45, 0, 1293, 1294, 1, 0, 0, 0, 1294, 1295, 6, 161, 22, 0, 1295, 338, 1, 0, 0, 0, 1296, 1297, 3, 259, 122, 0, 1297, 1298, 1, 0, 0, 0, 1298, 1299, 6, 162, 30, 0, 1299, 1300, 6, 162, 31, 0, 1300, 340, 1, 0, 0, 0, 1301, 1302, 3, 207, 96, 0, 1302, 1303, 1, 0, 0, 0, 1303, 1304, 6, 163, 20, 0, 1304, 342, 1, 0, 0, 0, 1305, 1306, 3, 85, 35, 0, 1306, 1307, 1, 0, 0, 0, 1307, 1308, 6, 164, 21, 0, 1308, 344, 1, 0, 0, 0, 1309, 1310, 3, 57, 21, 0, 1310, 1311, 1, 0, 0, 0, 1311, 1312, 6, 165, 10, 0, 1312, 346, 1, 0, 0, 0, 1313, 1314, 3, 59, 22, 0, 1314, 1315, 1, 0, 0, 0, 1315, 1316, 6, 166, 10, 0, 1316, 348, 1, 0, 0, 0, 1317, 1318, 3, 61, 23, 0, 1318, 1319, 1, 0, 0, 0, 1319, 1320, 6, 167, 10, 0, 1320, 350, 1, 0, 0, 0, 1321, 1322, 3, 63, 24, 0, 1322, 1323, 1, 0, 0, 0, 1323, 1324, 6, 168, 15, 0, 1324, 1325, 6, 168, 11, 0, 1325, 1326, 6, 168, 11, 0, 1326, 352, 1, 0, 0, 0, 1327, 1328, 3, 101, 43, 0, 1328, 1329, 1, 0, 0, 0, 1329, 1330, 6, 169, 18, 0, 1330, 354, 1, 0, 0, 0, 1331, 1332, 3, 105, 45, 0, 1332, 1333, 1, 0, 0, 0, 1333, 1334, 6, 170, 22, 0, 1334, 356, 1, 0, 0, 0, 1335, 1336, 3, 229, 107, 0, 1336, 1337, 1, 0, 0, 0, 1337, 1338, 6, 171, 23, 0, 1338, 358, 1, 0, 0, 0, 1339, 1340, 3, 57, 21, 0, 1340, 1341, 1, 0, 0, 0, 1341, 1342, 6, 172, 10, 0, 1342, 360, 1, 0, 0, 0, 1343, 1344, 3, 59, 22, 0, 1344, 1345, 1, 0, 0, 0, 1345, 1346, 6, 173, 10, 0, 1346, 362, 1, 0, 0, 0, 1347, 1348, 3, 61, 23, 0, 1348, 1349, 1, 0, 0, 0, 1349, 1350, 6, 174, 10, 0, 1350, 364, 1, 0, 0, 0, 1351, 1352, 3, 63, 24, 0, 1352, 1353, 1, 0, 0, 0, 1353, 1354, 6, 175, 15, 0, 1354, 1355, 6, 175, 11, 0, 1355, 366, 1, 0, 0, 0, 1356, 1357, 3, 207, 96, 0, 1357, 1358, 1, 0, 0, 0, 1358, 1359, 6, 176, 20, 0, 1359, 1360, 6, 176, 11, 0, 1360, 1361, 6, 176, 32, 0, 1361, 368, 1, 0, 0, 0, 1362, 1363, 3, 85, 35, 0, 1363, 1364, 1, 0, 0, 0, 1364, 1365, 6, 177, 21, 0, 1365, 1366, 6, 177, 11, 0, 1366, 1367, 6, 177, 32, 0, 1367, 370, 1, 0, 0, 0, 1368, 1369, 3, 57, 21, 0, 1369, 1370, 1, 0, 0, 0, 1370, 1371, 6, 178, 10, 0, 1371, 372, 1, 0, 0, 0, 1372, 1373, 3, 59, 22, 0, 1373, 1374, 1, 0, 0, 0, 1374, 1375, 6, 179, 10, 0, 1375, 374, 1, 0, 0, 0, 1376, 1377, 3, 61, 23, 0, 1377, 1378, 1, 0, 0, 0, 1378, 1379, 6, 180, 10, 0, 1379, 376, 1, 0, 0, 0, 1380, 1381, 3, 321, 153, 0, 1381, 1382, 1, 0, 0, 0, 1382, 1383, 6, 181, 17, 0, 1383, 1384, 6, 181, 11, 0, 1384, 1385, 6, 181, 9, 0, 1385, 378, 1, 0, 0, 0, 1386, 1387, 3, 101, 43, 0, 1387, 1388, 1, 0, 0, 0, 1388, 1389, 6, 182, 18, 0, 1389, 1390, 6, 182, 11, 0, 1390, 1391, 6, 182, 9, 0, 1391, 380, 1, 0, 0, 0, 1392, 1393, 3, 57, 21, 0, 1393, 1394, 1, 0, 0, 0, 1394, 1395, 6, 183, 10, 0, 1395, 382, 1, 0, 0, 0, 1396, 1397, 3, 59, 22, 0, 1397, 1398, 1, 0, 0, 0, 1398, 1399, 6, 184, 10, 0, 1399, 384, 1, 0, 0, 0, 1400, 1401, 3, 61, 23, 0, 1401, 1402, 1, 0, 0, 0, 1402, 1403, 6, 185, 10, 0, 1403, 386, 1, 0, 0, 0, 1404, 1405, 3, 173, 79, 0, 1405, 1406, 1, 0, 0, 0, 1406, 1407, 6, 186, 11, 0, 1407, 1408, 6, 186, 0, 0, 1408, 1409, 6, 186, 28, 0, 1409, 388, 1, 0, 0, 0, 1410, 1411, 3, 169, 77, 0, 1411, 1412, 1, 0, 0, 0, 1412, 1413, 6, 187, 11, 0, 1413, 1414, 6, 187, 0, 0, 1414, 1415, 6, 187, 29, 0, 1415, 390, 1, 0, 0, 0, 1416, 1417, 3, 91, 38, 0, 1417, 1418, 1, 0, 0, 0, 1418, 1419, 6, 188, 11, 0, 1419, 1420, 6, 188, 0, 0, 1420, 1421, 6, 188, 33, 0, 1421, 392, 1, 0, 0, 0, 1422, 1423, 3, 63, 24, 0, 1423, 1424, 1, 0, 0, 0, 1424, 1425, 6, 189, 15, 0, 1425, 1426, 6, 189, 11, 0, 1426, 394, 1, 0, 0, 0, 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 572, 582, 586, 589, 598, 600, 611, 630, 635, 644, 651, 656, 658, 669, 677, 680, 682, 687, 692, 698, 705, 710, 716, 719, 727, 731, 858, 863, 870, 872, 888, 893, 898, 900, 906, 983, 988, 1027, 1031, 1036, 1041, 1046, 1048, 1052, 1054, 1131, 1135, 1140, 1263, 1265, 34, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 11, 0, 5, 13, 0, 0, 1, 0, 4, 0, 0, 7, 19, 0, 7, 65, 0, 5, 0, 0, 7, 25, 0, 7, 66, 0, 7, 104, 0, 7, 34, 0, 7, 32, 0, 7, 76, 0, 7, 26, 0, 7, 36, 0, 7, 80, 0, 5, 10, 0, 5, 7, 0, 7, 90, 0, 7, 89, 0, 7, 68, 0, 7, 67, 0, 7, 88, 0, 5, 12, 0, 5, 14, 0, 7, 29, 0]
\ No newline at end of file
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java
index a746a0d49004f..d3ad1d00d749e 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer.java
@@ -26,36 +26,34 @@ public class EsqlBaseLexer extends LexerConfig {
     new PredictionContextCache();
   public static final int
     DISSECT=1, DROP=2, ENRICH=3, EVAL=4, EXPLAIN=5, FROM=6, GROK=7, KEEP=8, 
-    LIMIT=9, META=10, MV_EXPAND=11, RENAME=12, ROW=13, SHOW=14, SORT=15, STATS=16, 
-    WHERE=17, DEV_INLINESTATS=18, DEV_LOOKUP=19, DEV_MATCH=20, DEV_METRICS=21, 
-    UNKNOWN_CMD=22, LINE_COMMENT=23, MULTILINE_COMMENT=24, WS=25, PIPE=26, 
-    QUOTED_STRING=27, INTEGER_LITERAL=28, DECIMAL_LITERAL=29, BY=30, AND=31, 
-    ASC=32, ASSIGN=33, CAST_OP=34, COMMA=35, DESC=36, DOT=37, FALSE=38, FIRST=39, 
-    IN=40, IS=41, LAST=42, LIKE=43, LP=44, NOT=45, NULL=46, NULLS=47, OR=48, 
-    PARAM=49, RLIKE=50, RP=51, TRUE=52, EQ=53, CIEQ=54, NEQ=55, LT=56, LTE=57, 
-    GT=58, GTE=59, PLUS=60, MINUS=61, ASTERISK=62, SLASH=63, PERCENT=64, NAMED_OR_POSITIONAL_PARAM=65, 
-    OPENING_BRACKET=66, CLOSING_BRACKET=67, UNQUOTED_IDENTIFIER=68, QUOTED_IDENTIFIER=69, 
-    EXPR_LINE_COMMENT=70, EXPR_MULTILINE_COMMENT=71, EXPR_WS=72, EXPLAIN_WS=73, 
-    EXPLAIN_LINE_COMMENT=74, EXPLAIN_MULTILINE_COMMENT=75, METADATA=76, UNQUOTED_SOURCE=77, 
-    FROM_LINE_COMMENT=78, FROM_MULTILINE_COMMENT=79, FROM_WS=80, ID_PATTERN=81, 
-    PROJECT_LINE_COMMENT=82, PROJECT_MULTILINE_COMMENT=83, PROJECT_WS=84, 
-    AS=85, RENAME_LINE_COMMENT=86, RENAME_MULTILINE_COMMENT=87, RENAME_WS=88, 
-    ON=89, WITH=90, ENRICH_POLICY_NAME=91, ENRICH_LINE_COMMENT=92, ENRICH_MULTILINE_COMMENT=93, 
-    ENRICH_WS=94, ENRICH_FIELD_LINE_COMMENT=95, ENRICH_FIELD_MULTILINE_COMMENT=96, 
-    ENRICH_FIELD_WS=97, MVEXPAND_LINE_COMMENT=98, MVEXPAND_MULTILINE_COMMENT=99, 
-    MVEXPAND_WS=100, INFO=101, SHOW_LINE_COMMENT=102, SHOW_MULTILINE_COMMENT=103, 
-    SHOW_WS=104, FUNCTIONS=105, META_LINE_COMMENT=106, META_MULTILINE_COMMENT=107, 
-    META_WS=108, COLON=109, SETTING=110, SETTING_LINE_COMMENT=111, SETTTING_MULTILINE_COMMENT=112, 
-    SETTING_WS=113, LOOKUP_LINE_COMMENT=114, LOOKUP_MULTILINE_COMMENT=115, 
-    LOOKUP_WS=116, LOOKUP_FIELD_LINE_COMMENT=117, LOOKUP_FIELD_MULTILINE_COMMENT=118, 
-    LOOKUP_FIELD_WS=119, METRICS_LINE_COMMENT=120, METRICS_MULTILINE_COMMENT=121, 
-    METRICS_WS=122, CLOSING_METRICS_LINE_COMMENT=123, CLOSING_METRICS_MULTILINE_COMMENT=124, 
-    CLOSING_METRICS_WS=125;
+    LIMIT=9, MV_EXPAND=10, RENAME=11, ROW=12, SHOW=13, SORT=14, STATS=15, 
+    WHERE=16, DEV_INLINESTATS=17, DEV_LOOKUP=18, DEV_MATCH=19, DEV_METRICS=20, 
+    UNKNOWN_CMD=21, LINE_COMMENT=22, MULTILINE_COMMENT=23, WS=24, PIPE=25, 
+    QUOTED_STRING=26, INTEGER_LITERAL=27, DECIMAL_LITERAL=28, BY=29, AND=30, 
+    ASC=31, ASSIGN=32, CAST_OP=33, COMMA=34, DESC=35, DOT=36, FALSE=37, FIRST=38, 
+    IN=39, IS=40, LAST=41, LIKE=42, LP=43, NOT=44, NULL=45, NULLS=46, OR=47, 
+    PARAM=48, RLIKE=49, RP=50, TRUE=51, EQ=52, CIEQ=53, NEQ=54, LT=55, LTE=56, 
+    GT=57, GTE=58, PLUS=59, MINUS=60, ASTERISK=61, SLASH=62, PERCENT=63, NAMED_OR_POSITIONAL_PARAM=64, 
+    OPENING_BRACKET=65, CLOSING_BRACKET=66, UNQUOTED_IDENTIFIER=67, QUOTED_IDENTIFIER=68, 
+    EXPR_LINE_COMMENT=69, EXPR_MULTILINE_COMMENT=70, EXPR_WS=71, EXPLAIN_WS=72, 
+    EXPLAIN_LINE_COMMENT=73, EXPLAIN_MULTILINE_COMMENT=74, METADATA=75, UNQUOTED_SOURCE=76, 
+    FROM_LINE_COMMENT=77, FROM_MULTILINE_COMMENT=78, FROM_WS=79, ID_PATTERN=80, 
+    PROJECT_LINE_COMMENT=81, PROJECT_MULTILINE_COMMENT=82, PROJECT_WS=83, 
+    AS=84, RENAME_LINE_COMMENT=85, RENAME_MULTILINE_COMMENT=86, RENAME_WS=87, 
+    ON=88, WITH=89, ENRICH_POLICY_NAME=90, ENRICH_LINE_COMMENT=91, ENRICH_MULTILINE_COMMENT=92, 
+    ENRICH_WS=93, ENRICH_FIELD_LINE_COMMENT=94, ENRICH_FIELD_MULTILINE_COMMENT=95, 
+    ENRICH_FIELD_WS=96, MVEXPAND_LINE_COMMENT=97, MVEXPAND_MULTILINE_COMMENT=98, 
+    MVEXPAND_WS=99, INFO=100, SHOW_LINE_COMMENT=101, SHOW_MULTILINE_COMMENT=102, 
+    SHOW_WS=103, COLON=104, SETTING=105, SETTING_LINE_COMMENT=106, SETTTING_MULTILINE_COMMENT=107, 
+    SETTING_WS=108, LOOKUP_LINE_COMMENT=109, LOOKUP_MULTILINE_COMMENT=110, 
+    LOOKUP_WS=111, LOOKUP_FIELD_LINE_COMMENT=112, LOOKUP_FIELD_MULTILINE_COMMENT=113, 
+    LOOKUP_FIELD_WS=114, METRICS_LINE_COMMENT=115, METRICS_MULTILINE_COMMENT=116, 
+    METRICS_WS=117, CLOSING_METRICS_LINE_COMMENT=118, CLOSING_METRICS_MULTILINE_COMMENT=119, 
+    CLOSING_METRICS_WS=120;
   public static final int
     EXPRESSION_MODE=1, EXPLAIN_MODE=2, FROM_MODE=3, PROJECT_MODE=4, RENAME_MODE=5, 
-    ENRICH_MODE=6, ENRICH_FIELD_MODE=7, MVEXPAND_MODE=8, SHOW_MODE=9, META_MODE=10, 
-    SETTING_MODE=11, LOOKUP_MODE=12, LOOKUP_FIELD_MODE=13, METRICS_MODE=14, 
-    CLOSING_METRICS_MODE=15;
+    ENRICH_MODE=6, ENRICH_FIELD_MODE=7, MVEXPAND_MODE=8, SHOW_MODE=9, SETTING_MODE=10, 
+    LOOKUP_MODE=11, LOOKUP_FIELD_MODE=12, METRICS_MODE=13, CLOSING_METRICS_MODE=14;
   public static String[] channelNames = {
     "DEFAULT_TOKEN_CHANNEL", "HIDDEN"
   };
@@ -63,18 +61,17 @@ public class EsqlBaseLexer extends LexerConfig {
   public static String[] modeNames = {
     "DEFAULT_MODE", "EXPRESSION_MODE", "EXPLAIN_MODE", "FROM_MODE", "PROJECT_MODE", 
     "RENAME_MODE", "ENRICH_MODE", "ENRICH_FIELD_MODE", "MVEXPAND_MODE", "SHOW_MODE", 
-    "META_MODE", "SETTING_MODE", "LOOKUP_MODE", "LOOKUP_FIELD_MODE", "METRICS_MODE", 
-    "CLOSING_METRICS_MODE"
+    "SETTING_MODE", "LOOKUP_MODE", "LOOKUP_FIELD_MODE", "METRICS_MODE", "CLOSING_METRICS_MODE"
   };
 
   private static String[] makeRuleNames() {
     return new String[] {
       "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", "KEEP", 
-      "LIMIT", "META", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", 
-      "WHERE", "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", 
-      "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", 
-      "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "ASPERAND", 
-      "BACKQUOTE", "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", "QUOTED_STRING", 
+      "LIMIT", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", "WHERE", 
+      "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", "UNKNOWN_CMD", 
+      "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", "LETTER", 
+      "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "ASPERAND", "BACKQUOTE", 
+      "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", "QUOTED_STRING", 
       "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", 
       "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", 
       "LP", "NOT", "NULL", "NULLS", "OR", "PARAM", "RLIKE", "RP", "TRUE", "EQ", 
@@ -98,8 +95,7 @@ private static String[] makeRuleNames() {
       "ENRICH_FIELD_WS", "MVEXPAND_PIPE", "MVEXPAND_DOT", "MVEXPAND_QUOTED_IDENTIFIER", 
       "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", 
       "MVEXPAND_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", 
-      "SHOW_WS", "META_PIPE", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", 
-      "META_WS", "SETTING_CLOSING_BRACKET", "COLON", "SETTING", "SETTING_LINE_COMMENT", 
+      "SHOW_WS", "SETTING_CLOSING_BRACKET", "COLON", "SETTING", "SETTING_LINE_COMMENT", 
       "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "LOOKUP_PIPE", "LOOKUP_COLON", 
       "LOOKUP_COMMA", "LOOKUP_DOT", "LOOKUP_ON", "LOOKUP_UNQUOTED_SOURCE", 
       "LOOKUP_QUOTED_SOURCE", "LOOKUP_LINE_COMMENT", "LOOKUP_MULTILINE_COMMENT", 
@@ -117,25 +113,25 @@ private static String[] makeRuleNames() {
   private static String[] makeLiteralNames() {
     return new String[] {
       null, "'dissect'", "'drop'", "'enrich'", "'eval'", "'explain'", "'from'", 
-      "'grok'", "'keep'", "'limit'", "'meta'", "'mv_expand'", "'rename'", "'row'", 
-      "'show'", "'sort'", "'stats'", "'where'", null, null, null, null, null, 
-      null, null, null, "'|'", null, null, null, "'by'", "'and'", "'asc'", 
-      "'='", "'::'", "','", "'desc'", "'.'", "'false'", "'first'", "'in'", 
-      "'is'", "'last'", "'like'", "'('", "'not'", "'null'", "'nulls'", "'or'", 
-      "'?'", "'rlike'", "')'", "'true'", "'=='", "'=~'", "'!='", "'<'", "'<='", 
-      "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", null, null, "']'", 
-      null, null, null, null, null, null, null, null, "'metadata'", null, null, 
-      null, null, null, null, null, null, "'as'", null, null, null, "'on'", 
-      "'with'", null, null, null, null, null, null, null, null, null, null, 
-      "'info'", null, null, null, "'functions'", null, null, null, "':'"
+      "'grok'", "'keep'", "'limit'", "'mv_expand'", "'rename'", "'row'", "'show'", 
+      "'sort'", "'stats'", "'where'", null, null, null, null, null, null, null, 
+      null, "'|'", null, null, null, "'by'", "'and'", "'asc'", "'='", "'::'", 
+      "','", "'desc'", "'.'", "'false'", "'first'", "'in'", "'is'", "'last'", 
+      "'like'", "'('", "'not'", "'null'", "'nulls'", "'or'", "'?'", "'rlike'", 
+      "')'", "'true'", "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", 
+      "'+'", "'-'", "'*'", "'/'", "'%'", null, null, "']'", null, null, null, 
+      null, null, null, null, null, "'metadata'", null, null, null, null, null, 
+      null, null, null, "'as'", null, null, null, "'on'", "'with'", null, null, 
+      null, null, null, null, null, null, null, null, "'info'", null, null, 
+      null, "':'"
     };
   }
   private static final String[] _LITERAL_NAMES = makeLiteralNames();
   private static String[] makeSymbolicNames() {
     return new String[] {
       null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", 
-      "KEEP", "LIMIT", "META", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", 
-      "STATS", "WHERE", "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", 
+      "KEEP", "LIMIT", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", 
+      "WHERE", "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", 
       "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "QUOTED_STRING", 
       "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", 
       "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", 
@@ -151,8 +147,7 @@ private static String[] makeSymbolicNames() {
       "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", 
       "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "MVEXPAND_LINE_COMMENT", 
       "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "INFO", "SHOW_LINE_COMMENT", 
-      "SHOW_MULTILINE_COMMENT", "SHOW_WS", "FUNCTIONS", "META_LINE_COMMENT", 
-      "META_MULTILINE_COMMENT", "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", 
+      "SHOW_MULTILINE_COMMENT", "SHOW_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", 
       "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "LOOKUP_LINE_COMMENT", "LOOKUP_MULTILINE_COMMENT", 
       "LOOKUP_WS", "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", 
       "LOOKUP_FIELD_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", 
@@ -222,15 +217,15 @@ public EsqlBaseLexer(CharStream input) {
   @Override
   public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) {
     switch (ruleIndex) {
-    case 17:
+    case 16:
       return DEV_INLINESTATS_sempred((RuleContext)_localctx, predIndex);
-    case 18:
+    case 17:
       return DEV_LOOKUP_sempred((RuleContext)_localctx, predIndex);
-    case 19:
+    case 18:
       return DEV_MATCH_sempred((RuleContext)_localctx, predIndex);
-    case 20:
+    case 19:
       return DEV_METRICS_sempred((RuleContext)_localctx, predIndex);
-    case 74:
+    case 73:
       return DEV_MATCH_OP_sempred((RuleContext)_localctx, predIndex);
     }
     return true;
@@ -272,963 +267,931 @@ private boolean DEV_MATCH_OP_sempred(RuleContext _localctx, int predIndex) {
   }
 
   public static final String _serializedATN =
-    "\u0004\u0000}\u05c2\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+
+    "\u0004\u0000x\u0593\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+
     "\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+
     "\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+
     "\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+
-    "\u0006\uffff\uffff\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\u0002"+
-    "\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007\u000f"+
-    "\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011\u0002\u0012\u0007\u0012"+
-    "\u0002\u0013\u0007\u0013\u0002\u0014\u0007\u0014\u0002\u0015\u0007\u0015"+
-    "\u0002\u0016\u0007\u0016\u0002\u0017\u0007\u0017\u0002\u0018\u0007\u0018"+
-    "\u0002\u0019\u0007\u0019\u0002\u001a\u0007\u001a\u0002\u001b\u0007\u001b"+
-    "\u0002\u001c\u0007\u001c\u0002\u001d\u0007\u001d\u0002\u001e\u0007\u001e"+
-    "\u0002\u001f\u0007\u001f\u0002 \u0007 \u0002!\u0007!\u0002\"\u0007\"\u0002"+
-    "#\u0007#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0002"+
-    "(\u0007(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007+\u0002,\u0007,\u0002"+
-    "-\u0007-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u0002"+
-    "2\u00072\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u0002"+
-    "7\u00077\u00028\u00078\u00029\u00079\u0002:\u0007:\u0002;\u0007;\u0002"+
-    "<\u0007<\u0002=\u0007=\u0002>\u0007>\u0002?\u0007?\u0002@\u0007@\u0002"+
-    "A\u0007A\u0002B\u0007B\u0002C\u0007C\u0002D\u0007D\u0002E\u0007E\u0002"+
-    "F\u0007F\u0002G\u0007G\u0002H\u0007H\u0002I\u0007I\u0002J\u0007J\u0002"+
-    "K\u0007K\u0002L\u0007L\u0002M\u0007M\u0002N\u0007N\u0002O\u0007O\u0002"+
-    "P\u0007P\u0002Q\u0007Q\u0002R\u0007R\u0002S\u0007S\u0002T\u0007T\u0002"+
-    "U\u0007U\u0002V\u0007V\u0002W\u0007W\u0002X\u0007X\u0002Y\u0007Y\u0002"+
-    "Z\u0007Z\u0002[\u0007[\u0002\\\u0007\\\u0002]\u0007]\u0002^\u0007^\u0002"+
-    "_\u0007_\u0002`\u0007`\u0002a\u0007a\u0002b\u0007b\u0002c\u0007c\u0002"+
-    "d\u0007d\u0002e\u0007e\u0002f\u0007f\u0002g\u0007g\u0002h\u0007h\u0002"+
-    "i\u0007i\u0002j\u0007j\u0002k\u0007k\u0002l\u0007l\u0002m\u0007m\u0002"+
-    "n\u0007n\u0002o\u0007o\u0002p\u0007p\u0002q\u0007q\u0002r\u0007r\u0002"+
-    "s\u0007s\u0002t\u0007t\u0002u\u0007u\u0002v\u0007v\u0002w\u0007w\u0002"+
-    "x\u0007x\u0002y\u0007y\u0002z\u0007z\u0002{\u0007{\u0002|\u0007|\u0002"+
-    "}\u0007}\u0002~\u0007~\u0002\u007f\u0007\u007f\u0002\u0080\u0007\u0080"+
-    "\u0002\u0081\u0007\u0081\u0002\u0082\u0007\u0082\u0002\u0083\u0007\u0083"+
-    "\u0002\u0084\u0007\u0084\u0002\u0085\u0007\u0085\u0002\u0086\u0007\u0086"+
-    "\u0002\u0087\u0007\u0087\u0002\u0088\u0007\u0088\u0002\u0089\u0007\u0089"+
-    "\u0002\u008a\u0007\u008a\u0002\u008b\u0007\u008b\u0002\u008c\u0007\u008c"+
-    "\u0002\u008d\u0007\u008d\u0002\u008e\u0007\u008e\u0002\u008f\u0007\u008f"+
-    "\u0002\u0090\u0007\u0090\u0002\u0091\u0007\u0091\u0002\u0092\u0007\u0092"+
-    "\u0002\u0093\u0007\u0093\u0002\u0094\u0007\u0094\u0002\u0095\u0007\u0095"+
-    "\u0002\u0096\u0007\u0096\u0002\u0097\u0007\u0097\u0002\u0098\u0007\u0098"+
-    "\u0002\u0099\u0007\u0099\u0002\u009a\u0007\u009a\u0002\u009b\u0007\u009b"+
-    "\u0002\u009c\u0007\u009c\u0002\u009d\u0007\u009d\u0002\u009e\u0007\u009e"+
-    "\u0002\u009f\u0007\u009f\u0002\u00a0\u0007\u00a0\u0002\u00a1\u0007\u00a1"+
-    "\u0002\u00a2\u0007\u00a2\u0002\u00a3\u0007\u00a3\u0002\u00a4\u0007\u00a4"+
-    "\u0002\u00a5\u0007\u00a5\u0002\u00a6\u0007\u00a6\u0002\u00a7\u0007\u00a7"+
-    "\u0002\u00a8\u0007\u00a8\u0002\u00a9\u0007\u00a9\u0002\u00aa\u0007\u00aa"+
-    "\u0002\u00ab\u0007\u00ab\u0002\u00ac\u0007\u00ac\u0002\u00ad\u0007\u00ad"+
-    "\u0002\u00ae\u0007\u00ae\u0002\u00af\u0007\u00af\u0002\u00b0\u0007\u00b0"+
-    "\u0002\u00b1\u0007\u00b1\u0002\u00b2\u0007\u00b2\u0002\u00b3\u0007\u00b3"+
-    "\u0002\u00b4\u0007\u00b4\u0002\u00b5\u0007\u00b5\u0002\u00b6\u0007\u00b6"+
-    "\u0002\u00b7\u0007\u00b7\u0002\u00b8\u0007\u00b8\u0002\u00b9\u0007\u00b9"+
-    "\u0002\u00ba\u0007\u00ba\u0002\u00bb\u0007\u00bb\u0002\u00bc\u0007\u00bc"+
-    "\u0002\u00bd\u0007\u00bd\u0002\u00be\u0007\u00be\u0002\u00bf\u0007\u00bf"+
-    "\u0002\u00c0\u0007\u00c0\u0002\u00c1\u0007\u00c1\u0002\u00c2\u0007\u00c2"+
-    "\u0002\u00c3\u0007\u00c3\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000"+
-    "\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000"+
+    "\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\u0002\f\u0007\f\u0002"+
+    "\r\u0007\r\u0002\u000e\u0007\u000e\u0002\u000f\u0007\u000f\u0002\u0010"+
+    "\u0007\u0010\u0002\u0011\u0007\u0011\u0002\u0012\u0007\u0012\u0002\u0013"+
+    "\u0007\u0013\u0002\u0014\u0007\u0014\u0002\u0015\u0007\u0015\u0002\u0016"+
+    "\u0007\u0016\u0002\u0017\u0007\u0017\u0002\u0018\u0007\u0018\u0002\u0019"+
+    "\u0007\u0019\u0002\u001a\u0007\u001a\u0002\u001b\u0007\u001b\u0002\u001c"+
+    "\u0007\u001c\u0002\u001d\u0007\u001d\u0002\u001e\u0007\u001e\u0002\u001f"+
+    "\u0007\u001f\u0002 \u0007 \u0002!\u0007!\u0002\"\u0007\"\u0002#\u0007"+
+    "#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0002(\u0007"+
+    "(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007+\u0002,\u0007,\u0002-\u0007"+
+    "-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u00022\u0007"+
+    "2\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u00027\u0007"+
+    "7\u00028\u00078\u00029\u00079\u0002:\u0007:\u0002;\u0007;\u0002<\u0007"+
+    "<\u0002=\u0007=\u0002>\u0007>\u0002?\u0007?\u0002@\u0007@\u0002A\u0007"+
+    "A\u0002B\u0007B\u0002C\u0007C\u0002D\u0007D\u0002E\u0007E\u0002F\u0007"+
+    "F\u0002G\u0007G\u0002H\u0007H\u0002I\u0007I\u0002J\u0007J\u0002K\u0007"+
+    "K\u0002L\u0007L\u0002M\u0007M\u0002N\u0007N\u0002O\u0007O\u0002P\u0007"+
+    "P\u0002Q\u0007Q\u0002R\u0007R\u0002S\u0007S\u0002T\u0007T\u0002U\u0007"+
+    "U\u0002V\u0007V\u0002W\u0007W\u0002X\u0007X\u0002Y\u0007Y\u0002Z\u0007"+
+    "Z\u0002[\u0007[\u0002\\\u0007\\\u0002]\u0007]\u0002^\u0007^\u0002_\u0007"+
+    "_\u0002`\u0007`\u0002a\u0007a\u0002b\u0007b\u0002c\u0007c\u0002d\u0007"+
+    "d\u0002e\u0007e\u0002f\u0007f\u0002g\u0007g\u0002h\u0007h\u0002i\u0007"+
+    "i\u0002j\u0007j\u0002k\u0007k\u0002l\u0007l\u0002m\u0007m\u0002n\u0007"+
+    "n\u0002o\u0007o\u0002p\u0007p\u0002q\u0007q\u0002r\u0007r\u0002s\u0007"+
+    "s\u0002t\u0007t\u0002u\u0007u\u0002v\u0007v\u0002w\u0007w\u0002x\u0007"+
+    "x\u0002y\u0007y\u0002z\u0007z\u0002{\u0007{\u0002|\u0007|\u0002}\u0007"+
+    "}\u0002~\u0007~\u0002\u007f\u0007\u007f\u0002\u0080\u0007\u0080\u0002"+
+    "\u0081\u0007\u0081\u0002\u0082\u0007\u0082\u0002\u0083\u0007\u0083\u0002"+
+    "\u0084\u0007\u0084\u0002\u0085\u0007\u0085\u0002\u0086\u0007\u0086\u0002"+
+    "\u0087\u0007\u0087\u0002\u0088\u0007\u0088\u0002\u0089\u0007\u0089\u0002"+
+    "\u008a\u0007\u008a\u0002\u008b\u0007\u008b\u0002\u008c\u0007\u008c\u0002"+
+    "\u008d\u0007\u008d\u0002\u008e\u0007\u008e\u0002\u008f\u0007\u008f\u0002"+
+    "\u0090\u0007\u0090\u0002\u0091\u0007\u0091\u0002\u0092\u0007\u0092\u0002"+
+    "\u0093\u0007\u0093\u0002\u0094\u0007\u0094\u0002\u0095\u0007\u0095\u0002"+
+    "\u0096\u0007\u0096\u0002\u0097\u0007\u0097\u0002\u0098\u0007\u0098\u0002"+
+    "\u0099\u0007\u0099\u0002\u009a\u0007\u009a\u0002\u009b\u0007\u009b\u0002"+
+    "\u009c\u0007\u009c\u0002\u009d\u0007\u009d\u0002\u009e\u0007\u009e\u0002"+
+    "\u009f\u0007\u009f\u0002\u00a0\u0007\u00a0\u0002\u00a1\u0007\u00a1\u0002"+
+    "\u00a2\u0007\u00a2\u0002\u00a3\u0007\u00a3\u0002\u00a4\u0007\u00a4\u0002"+
+    "\u00a5\u0007\u00a5\u0002\u00a6\u0007\u00a6\u0002\u00a7\u0007\u00a7\u0002"+
+    "\u00a8\u0007\u00a8\u0002\u00a9\u0007\u00a9\u0002\u00aa\u0007\u00aa\u0002"+
+    "\u00ab\u0007\u00ab\u0002\u00ac\u0007\u00ac\u0002\u00ad\u0007\u00ad\u0002"+
+    "\u00ae\u0007\u00ae\u0002\u00af\u0007\u00af\u0002\u00b0\u0007\u00b0\u0002"+
+    "\u00b1\u0007\u00b1\u0002\u00b2\u0007\u00b2\u0002\u00b3\u0007\u00b3\u0002"+
+    "\u00b4\u0007\u00b4\u0002\u00b5\u0007\u00b5\u0002\u00b6\u0007\u00b6\u0002"+
+    "\u00b7\u0007\u00b7\u0002\u00b8\u0007\u00b8\u0002\u00b9\u0007\u00b9\u0002"+
+    "\u00ba\u0007\u00ba\u0002\u00bb\u0007\u00bb\u0002\u00bc\u0007\u00bc\u0002"+
+    "\u00bd\u0007\u00bd\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001"+
+    "\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001"+
     "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
-    "\u0001\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002"+
-    "\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0003\u0001\u0003"+
-    "\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004"+
-    "\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004"+
-    "\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005"+
-    "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0006\u0001\u0006"+
-    "\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0007"+
-    "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+
-    "\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001"+
-    "\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\n\u0001\n\u0001"+
-    "\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001"+
-    "\n\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b"+
-    "\u0001\u000b\u0001\u000b\u0001\u000b\u0001\f\u0001\f\u0001\f\u0001\f\u0001"+
-    "\f\u0001\f\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001"+
-    "\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001"+
-    "\u000e\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001"+
-    "\u000f\u0001\u000f\u0001\u000f\u0001\u0010\u0001\u0010\u0001\u0010\u0001"+
-    "\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0011\u0001"+
-    "\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001"+
-    "\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001"+
-    "\u0011\u0001\u0011\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+
-    "\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+
-    "\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001"+
-    "\u0013\u0001\u0013\u0001\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0001"+
-    "\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0001"+
-    "\u0014\u0001\u0014\u0001\u0015\u0004\u0015\u024f\b\u0015\u000b\u0015\f"+
-    "\u0015\u0250\u0001\u0015\u0001\u0015\u0001\u0016\u0001\u0016\u0001\u0016"+
-    "\u0001\u0016\u0005\u0016\u0259\b\u0016\n\u0016\f\u0016\u025c\t\u0016\u0001"+
-    "\u0016\u0003\u0016\u025f\b\u0016\u0001\u0016\u0003\u0016\u0262\b\u0016"+
-    "\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017"+
-    "\u0001\u0017\u0005\u0017\u026b\b\u0017\n\u0017\f\u0017\u026e\t\u0017\u0001"+
-    "\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0018\u0004"+
-    "\u0018\u0276\b\u0018\u000b\u0018\f\u0018\u0277\u0001\u0018\u0001\u0018"+
-    "\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u001a\u0001\u001a"+
-    "\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001d"+
-    "\u0001\u001d\u0001\u001e\u0001\u001e\u0003\u001e\u028b\b\u001e\u0001\u001e"+
-    "\u0004\u001e\u028e\b\u001e\u000b\u001e\f\u001e\u028f\u0001\u001f\u0001"+
-    "\u001f\u0001 \u0001 \u0001!\u0001!\u0001!\u0003!\u0299\b!\u0001\"\u0001"+
-    "\"\u0001#\u0001#\u0001#\u0003#\u02a0\b#\u0001$\u0001$\u0001$\u0005$\u02a5"+
-    "\b$\n$\f$\u02a8\t$\u0001$\u0001$\u0001$\u0001$\u0001$\u0001$\u0005$\u02b0"+
-    "\b$\n$\f$\u02b3\t$\u0001$\u0001$\u0001$\u0001$\u0001$\u0003$\u02ba\b$"+
-    "\u0001$\u0003$\u02bd\b$\u0003$\u02bf\b$\u0001%\u0004%\u02c2\b%\u000b%"+
-    "\f%\u02c3\u0001&\u0004&\u02c7\b&\u000b&\f&\u02c8\u0001&\u0001&\u0005&"+
-    "\u02cd\b&\n&\f&\u02d0\t&\u0001&\u0001&\u0004&\u02d4\b&\u000b&\f&\u02d5"+
-    "\u0001&\u0004&\u02d9\b&\u000b&\f&\u02da\u0001&\u0001&\u0005&\u02df\b&"+
-    "\n&\f&\u02e2\t&\u0003&\u02e4\b&\u0001&\u0001&\u0001&\u0001&\u0004&\u02ea"+
-    "\b&\u000b&\f&\u02eb\u0001&\u0001&\u0003&\u02f0\b&\u0001\'\u0001\'\u0001"+
-    "\'\u0001(\u0001(\u0001(\u0001(\u0001)\u0001)\u0001)\u0001)\u0001*\u0001"+
-    "*\u0001+\u0001+\u0001+\u0001,\u0001,\u0001-\u0001-\u0001-\u0001-\u0001"+
-    "-\u0001.\u0001.\u0001/\u0001/\u0001/\u0001/\u0001/\u0001/\u00010\u0001"+
-    "0\u00010\u00010\u00010\u00010\u00011\u00011\u00011\u00012\u00012\u0001"+
-    "2\u00013\u00013\u00013\u00013\u00013\u00014\u00014\u00014\u00014\u0001"+
-    "4\u00015\u00015\u00016\u00016\u00016\u00016\u00017\u00017\u00017\u0001"+
-    "7\u00017\u00018\u00018\u00018\u00018\u00018\u00018\u00019\u00019\u0001"+
-    "9\u0001:\u0001:\u0001;\u0001;\u0001;\u0001;\u0001;\u0001;\u0001<\u0001"+
-    "<\u0001=\u0001=\u0001=\u0001=\u0001=\u0001>\u0001>\u0001>\u0001?\u0001"+
-    "?\u0001?\u0001@\u0001@\u0001@\u0001A\u0001A\u0001B\u0001B\u0001B\u0001"+
-    "C\u0001C\u0001D\u0001D\u0001D\u0001E\u0001E\u0001F\u0001F\u0001G\u0001"+
-    "G\u0001H\u0001H\u0001I\u0001I\u0001J\u0001J\u0001J\u0001J\u0001J\u0001"+
-    "K\u0001K\u0001K\u0003K\u036f\bK\u0001K\u0005K\u0372\bK\nK\fK\u0375\tK"+
-    "\u0001K\u0001K\u0004K\u0379\bK\u000bK\fK\u037a\u0003K\u037d\bK\u0001L"+
-    "\u0001L\u0001L\u0001L\u0001L\u0001M\u0001M\u0001M\u0001M\u0001M\u0001"+
-    "N\u0001N\u0005N\u038b\bN\nN\fN\u038e\tN\u0001N\u0001N\u0003N\u0392\bN"+
-    "\u0001N\u0004N\u0395\bN\u000bN\fN\u0396\u0003N\u0399\bN\u0001O\u0001O"+
-    "\u0004O\u039d\bO\u000bO\fO\u039e\u0001O\u0001O\u0001P\u0001P\u0001Q\u0001"+
-    "Q\u0001Q\u0001Q\u0001R\u0001R\u0001R\u0001R\u0001S\u0001S\u0001S\u0001"+
-    "S\u0001T\u0001T\u0001T\u0001T\u0001T\u0001U\u0001U\u0001U\u0001U\u0001"+
-    "U\u0001V\u0001V\u0001V\u0001V\u0001W\u0001W\u0001W\u0001W\u0001X\u0001"+
-    "X\u0001X\u0001X\u0001Y\u0001Y\u0001Y\u0001Y\u0001Y\u0001Z\u0001Z\u0001"+
-    "Z\u0001Z\u0001[\u0001[\u0001[\u0001[\u0001\\\u0001\\\u0001\\\u0001\\\u0001"+
-    "]\u0001]\u0001]\u0001]\u0001^\u0001^\u0001^\u0001^\u0001_\u0001_\u0001"+
-    "_\u0001_\u0001_\u0001_\u0001_\u0001_\u0001_\u0001`\u0001`\u0001`\u0003"+
-    "`\u03ec\b`\u0001a\u0004a\u03ef\ba\u000ba\fa\u03f0\u0001b\u0001b\u0001"+
+    "\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+
+    "\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0003\u0001\u0003\u0001"+
+    "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001"+
+    "\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001"+
+    "\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005\u0001"+
+    "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0006\u0001\u0006\u0001"+
+    "\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0007\u0001"+
+    "\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001"+
+    "\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\t\u0001"+
+    "\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001"+
+    "\t\u0001\t\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001"+
+    "\n\u0001\n\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b"+
+    "\u0001\u000b\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\u0001"+
+    "\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\r\u0001\u000e\u0001\u000e"+
+    "\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e"+
+    "\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f"+
+    "\u0001\u000f\u0001\u000f\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010"+
+    "\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010"+
+    "\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0011"+
+    "\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011"+
+    "\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0012\u0001\u0012\u0001\u0012"+
+    "\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012"+
+    "\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013"+
+    "\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0014"+
+    "\u0004\u0014\u023b\b\u0014\u000b\u0014\f\u0014\u023c\u0001\u0014\u0001"+
+    "\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0005\u0015\u0245"+
+    "\b\u0015\n\u0015\f\u0015\u0248\t\u0015\u0001\u0015\u0003\u0015\u024b\b"+
+    "\u0015\u0001\u0015\u0003\u0015\u024e\b\u0015\u0001\u0015\u0001\u0015\u0001"+
+    "\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0005\u0016\u0257"+
+    "\b\u0016\n\u0016\f\u0016\u025a\t\u0016\u0001\u0016\u0001\u0016\u0001\u0016"+
+    "\u0001\u0016\u0001\u0016\u0001\u0017\u0004\u0017\u0262\b\u0017\u000b\u0017"+
+    "\f\u0017\u0263\u0001\u0017\u0001\u0017\u0001\u0018\u0001\u0018\u0001\u0018"+
+    "\u0001\u0018\u0001\u0019\u0001\u0019\u0001\u001a\u0001\u001a\u0001\u001b"+
+    "\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c\u0001\u001d\u0001\u001d"+
+    "\u0003\u001d\u0277\b\u001d\u0001\u001d\u0004\u001d\u027a\b\u001d\u000b"+
+    "\u001d\f\u001d\u027b\u0001\u001e\u0001\u001e\u0001\u001f\u0001\u001f\u0001"+
+    " \u0001 \u0001 \u0003 \u0285\b \u0001!\u0001!\u0001\"\u0001\"\u0001\""+
+    "\u0003\"\u028c\b\"\u0001#\u0001#\u0001#\u0005#\u0291\b#\n#\f#\u0294\t"+
+    "#\u0001#\u0001#\u0001#\u0001#\u0001#\u0001#\u0005#\u029c\b#\n#\f#\u029f"+
+    "\t#\u0001#\u0001#\u0001#\u0001#\u0001#\u0003#\u02a6\b#\u0001#\u0003#\u02a9"+
+    "\b#\u0003#\u02ab\b#\u0001$\u0004$\u02ae\b$\u000b$\f$\u02af\u0001%\u0004"+
+    "%\u02b3\b%\u000b%\f%\u02b4\u0001%\u0001%\u0005%\u02b9\b%\n%\f%\u02bc\t"+
+    "%\u0001%\u0001%\u0004%\u02c0\b%\u000b%\f%\u02c1\u0001%\u0004%\u02c5\b"+
+    "%\u000b%\f%\u02c6\u0001%\u0001%\u0005%\u02cb\b%\n%\f%\u02ce\t%\u0003%"+
+    "\u02d0\b%\u0001%\u0001%\u0001%\u0001%\u0004%\u02d6\b%\u000b%\f%\u02d7"+
+    "\u0001%\u0001%\u0003%\u02dc\b%\u0001&\u0001&\u0001&\u0001\'\u0001\'\u0001"+
+    "\'\u0001\'\u0001(\u0001(\u0001(\u0001(\u0001)\u0001)\u0001*\u0001*\u0001"+
+    "*\u0001+\u0001+\u0001,\u0001,\u0001,\u0001,\u0001,\u0001-\u0001-\u0001"+
+    ".\u0001.\u0001.\u0001.\u0001.\u0001.\u0001/\u0001/\u0001/\u0001/\u0001"+
+    "/\u0001/\u00010\u00010\u00010\u00011\u00011\u00011\u00012\u00012\u0001"+
+    "2\u00012\u00012\u00013\u00013\u00013\u00013\u00013\u00014\u00014\u0001"+
+    "5\u00015\u00015\u00015\u00016\u00016\u00016\u00016\u00016\u00017\u0001"+
+    "7\u00017\u00017\u00017\u00017\u00018\u00018\u00018\u00019\u00019\u0001"+
+    ":\u0001:\u0001:\u0001:\u0001:\u0001:\u0001;\u0001;\u0001<\u0001<\u0001"+
+    "<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001>\u0001>\u0001>\u0001?\u0001"+
+    "?\u0001?\u0001@\u0001@\u0001A\u0001A\u0001A\u0001B\u0001B\u0001C\u0001"+
+    "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001F\u0001F\u0001G\u0001G\u0001"+
+    "H\u0001H\u0001I\u0001I\u0001I\u0001I\u0001I\u0001J\u0001J\u0001J\u0003"+
+    "J\u035b\bJ\u0001J\u0005J\u035e\bJ\nJ\fJ\u0361\tJ\u0001J\u0001J\u0004J"+
+    "\u0365\bJ\u000bJ\fJ\u0366\u0003J\u0369\bJ\u0001K\u0001K\u0001K\u0001K"+
+    "\u0001K\u0001L\u0001L\u0001L\u0001L\u0001L\u0001M\u0001M\u0005M\u0377"+
+    "\bM\nM\fM\u037a\tM\u0001M\u0001M\u0003M\u037e\bM\u0001M\u0004M\u0381\b"+
+    "M\u000bM\fM\u0382\u0003M\u0385\bM\u0001N\u0001N\u0004N\u0389\bN\u000b"+
+    "N\fN\u038a\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0001"+
+    "Q\u0001Q\u0001Q\u0001Q\u0001R\u0001R\u0001R\u0001R\u0001S\u0001S\u0001"+
+    "S\u0001S\u0001S\u0001T\u0001T\u0001T\u0001T\u0001T\u0001U\u0001U\u0001"+
+    "U\u0001U\u0001V\u0001V\u0001V\u0001V\u0001W\u0001W\u0001W\u0001W\u0001"+
+    "X\u0001X\u0001X\u0001X\u0001X\u0001Y\u0001Y\u0001Y\u0001Y\u0001Z\u0001"+
+    "Z\u0001Z\u0001Z\u0001[\u0001[\u0001[\u0001[\u0001\\\u0001\\\u0001\\\u0001"+
+    "\\\u0001]\u0001]\u0001]\u0001]\u0001^\u0001^\u0001^\u0001^\u0001^\u0001"+
+    "^\u0001^\u0001^\u0001^\u0001_\u0001_\u0001_\u0003_\u03d8\b_\u0001`\u0004"+
+    "`\u03db\b`\u000b`\f`\u03dc\u0001a\u0001a\u0001a\u0001a\u0001b\u0001b\u0001"+
     "b\u0001b\u0001c\u0001c\u0001c\u0001c\u0001d\u0001d\u0001d\u0001d\u0001"+
-    "e\u0001e\u0001e\u0001e\u0001f\u0001f\u0001f\u0001f\u0001g\u0001g\u0001"+
+    "e\u0001e\u0001e\u0001e\u0001f\u0001f\u0001f\u0001f\u0001f\u0001g\u0001"+
     "g\u0001g\u0001g\u0001h\u0001h\u0001h\u0001h\u0001i\u0001i\u0001i\u0001"+
-    "i\u0001j\u0001j\u0001j\u0001j\u0003j\u0418\bj\u0001k\u0001k\u0003k\u041c"+
-    "\bk\u0001k\u0005k\u041f\bk\nk\fk\u0422\tk\u0001k\u0001k\u0003k\u0426\b"+
-    "k\u0001k\u0004k\u0429\bk\u000bk\fk\u042a\u0003k\u042d\bk\u0001l\u0001"+
-    "l\u0004l\u0431\bl\u000bl\fl\u0432\u0001m\u0001m\u0001m\u0001m\u0001n\u0001"+
-    "n\u0001n\u0001n\u0001o\u0001o\u0001o\u0001o\u0001p\u0001p\u0001p\u0001"+
-    "p\u0001p\u0001q\u0001q\u0001q\u0001q\u0001r\u0001r\u0001r\u0001r\u0001"+
-    "s\u0001s\u0001s\u0001s\u0001t\u0001t\u0001t\u0001u\u0001u\u0001u\u0001"+
-    "u\u0001v\u0001v\u0001v\u0001v\u0001w\u0001w\u0001w\u0001w\u0001x\u0001"+
-    "x\u0001x\u0001x\u0001y\u0001y\u0001y\u0001y\u0001y\u0001z\u0001z\u0001"+
-    "z\u0001z\u0001z\u0001{\u0001{\u0001{\u0001{\u0001{\u0001|\u0001|\u0001"+
-    "|\u0001|\u0001|\u0001|\u0001|\u0001}\u0001}\u0001~\u0004~\u047e\b~\u000b"+
-    "~\f~\u047f\u0001~\u0001~\u0003~\u0484\b~\u0001~\u0004~\u0487\b~\u000b"+
-    "~\f~\u0488\u0001\u007f\u0001\u007f\u0001\u007f\u0001\u007f\u0001\u0080"+
-    "\u0001\u0080\u0001\u0080\u0001\u0080\u0001\u0081\u0001\u0081\u0001\u0081"+
-    "\u0001\u0081\u0001\u0082\u0001\u0082\u0001\u0082\u0001\u0082\u0001\u0083"+
-    "\u0001\u0083\u0001\u0083\u0001\u0083\u0001\u0083\u0001\u0083\u0001\u0084"+
-    "\u0001\u0084\u0001\u0084\u0001\u0084\u0001\u0085\u0001\u0085\u0001\u0085"+
-    "\u0001\u0085\u0001\u0086\u0001\u0086\u0001\u0086\u0001\u0086\u0001\u0087"+
-    "\u0001\u0087\u0001\u0087\u0001\u0087\u0001\u0088\u0001\u0088\u0001\u0088"+
-    "\u0001\u0088\u0001\u0089\u0001\u0089\u0001\u0089\u0001\u0089\u0001\u008a"+
-    "\u0001\u008a\u0001\u008a\u0001\u008a\u0001\u008b\u0001\u008b\u0001\u008b"+
-    "\u0001\u008b\u0001\u008c\u0001\u008c\u0001\u008c\u0001\u008c\u0001\u008d"+
-    "\u0001\u008d\u0001\u008d\u0001\u008d\u0001\u008d\u0001\u008e\u0001\u008e"+
-    "\u0001\u008e\u0001\u008e\u0001\u008f\u0001\u008f\u0001\u008f\u0001\u008f"+
-    "\u0001\u0090\u0001\u0090\u0001\u0090\u0001\u0090\u0001\u0091\u0001\u0091"+
-    "\u0001\u0091\u0001\u0091\u0001\u0092\u0001\u0092\u0001\u0092\u0001\u0092"+
-    "\u0001\u0093\u0001\u0093\u0001\u0093\u0001\u0093\u0001\u0094\u0001\u0094"+
-    "\u0001\u0094\u0001\u0094\u0001\u0094\u0001\u0095\u0001\u0095\u0001\u0095"+
-    "\u0001\u0095\u0001\u0095\u0001\u0096\u0001\u0096\u0001\u0096\u0001\u0096"+
-    "\u0001\u0097\u0001\u0097\u0001\u0097\u0001\u0097\u0001\u0098\u0001\u0098"+
-    "\u0001\u0098\u0001\u0098\u0001\u0099\u0001\u0099\u0001\u0099\u0001\u0099"+
-    "\u0001\u0099\u0001\u009a\u0001\u009a\u0001\u009a\u0001\u009a\u0001\u009a"+
-    "\u0001\u009a\u0001\u009a\u0001\u009a\u0001\u009a\u0001\u009a\u0001\u009b"+
-    "\u0001\u009b\u0001\u009b\u0001\u009b\u0001\u009c\u0001\u009c\u0001\u009c"+
-    "\u0001\u009c\u0001\u009d\u0001\u009d\u0001\u009d\u0001\u009d\u0001\u009e"+
-    "\u0001\u009e\u0001\u009e\u0001\u009e\u0001\u009e\u0001\u009f\u0001\u009f"+
-    "\u0001\u00a0\u0001\u00a0\u0001\u00a0\u0001\u00a0\u0001\u00a0\u0004\u00a0"+
-    "\u051f\b\u00a0\u000b\u00a0\f\u00a0\u0520\u0001\u00a1\u0001\u00a1\u0001"+
-    "\u00a1\u0001\u00a1\u0001\u00a2\u0001\u00a2\u0001\u00a2\u0001\u00a2\u0001"+
-    "\u00a3\u0001\u00a3\u0001\u00a3\u0001\u00a3\u0001\u00a4\u0001\u00a4\u0001"+
-    "\u00a4\u0001\u00a4\u0001\u00a4\u0001\u00a5\u0001\u00a5\u0001\u00a5\u0001"+
-    "\u00a5\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a7\u0001"+
-    "\u00a7\u0001\u00a7\u0001\u00a7\u0001\u00a8\u0001\u00a8\u0001\u00a8\u0001"+
-    "\u00a8\u0001\u00a8\u0001\u00a9\u0001\u00a9\u0001\u00a9\u0001\u00a9\u0001"+
-    "\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00ab\u0001\u00ab\u0001"+
-    "\u00ab\u0001\u00ab\u0001\u00ac\u0001\u00ac\u0001\u00ac\u0001\u00ac\u0001"+
-    "\u00ad\u0001\u00ad\u0001\u00ad\u0001\u00ad\u0001\u00ae\u0001\u00ae\u0001"+
-    "\u00ae\u0001\u00ae\u0001\u00ae\u0001\u00ae\u0001\u00af\u0001\u00af\u0001"+
-    "\u00af\u0001\u00af\u0001\u00b0\u0001\u00b0\u0001\u00b0\u0001\u00b0\u0001"+
-    "\u00b1\u0001\u00b1\u0001\u00b1\u0001\u00b1\u0001\u00b2\u0001\u00b2\u0001"+
-    "\u00b2\u0001\u00b2\u0001\u00b3\u0001\u00b3\u0001\u00b3\u0001\u00b3\u0001"+
-    "\u00b4\u0001\u00b4\u0001\u00b4\u0001\u00b4\u0001\u00b5\u0001\u00b5\u0001"+
-    "\u00b5\u0001\u00b5\u0001\u00b5\u0001\u00b6\u0001\u00b6\u0001\u00b6\u0001"+
-    "\u00b6\u0001\u00b6\u0001\u00b6\u0001\u00b7\u0001\u00b7\u0001\u00b7\u0001"+
-    "\u00b7\u0001\u00b7\u0001\u00b7\u0001\u00b8\u0001\u00b8\u0001\u00b8\u0001"+
-    "\u00b8\u0001\u00b9\u0001\u00b9\u0001\u00b9\u0001\u00b9\u0001\u00ba\u0001"+
-    "\u00ba\u0001\u00ba\u0001\u00ba\u0001\u00bb\u0001\u00bb\u0001\u00bb\u0001"+
-    "\u00bb\u0001\u00bb\u0001\u00bb\u0001\u00bc\u0001\u00bc\u0001\u00bc\u0001"+
-    "\u00bc\u0001\u00bc\u0001\u00bc\u0001\u00bd\u0001\u00bd\u0001\u00bd\u0001"+
-    "\u00bd\u0001\u00be\u0001\u00be\u0001\u00be\u0001\u00be\u0001\u00bf\u0001"+
-    "\u00bf\u0001\u00bf\u0001\u00bf\u0001\u00c0\u0001\u00c0\u0001\u00c0\u0001"+
-    "\u00c0\u0001\u00c0\u0001\u00c0\u0001\u00c1\u0001\u00c1\u0001\u00c1\u0001"+
-    "\u00c1\u0001\u00c1\u0001\u00c1\u0001\u00c2\u0001\u00c2\u0001\u00c2\u0001"+
-    "\u00c2\u0001\u00c2\u0001\u00c2\u0001\u00c3\u0001\u00c3\u0001\u00c3\u0001"+
-    "\u00c3\u0001\u00c3\u0002\u026c\u02b1\u0000\u00c4\u0010\u0001\u0012\u0002"+
-    "\u0014\u0003\u0016\u0004\u0018\u0005\u001a\u0006\u001c\u0007\u001e\b "+
-    "\t\"\n$\u000b&\f(\r*\u000e,\u000f.\u00100\u00112\u00124\u00136\u00148"+
-    "\u0015:\u0016<\u0017>\u0018@\u0019B\u001aD\u0000F\u0000H\u0000J\u0000"+
-    "L\u0000N\u0000P\u0000R\u0000T\u0000V\u0000X\u001bZ\u001c\\\u001d^\u001e"+
-    "`\u001fb d!f\"h#j$l%n&p\'r(t)v*x+z,|-~.\u0080/\u00820\u00841\u00862\u0088"+
-    "3\u008a4\u008c5\u008e6\u00907\u00928\u00949\u0096:\u0098;\u009a<\u009c"+
-    "=\u009e>\u00a0?\u00a2@\u00a4\u0000\u00a6A\u00a8B\u00aaC\u00acD\u00ae\u0000"+
-    "\u00b0E\u00b2F\u00b4G\u00b6H\u00b8\u0000\u00ba\u0000\u00bcI\u00beJ\u00c0"+
-    "K\u00c2\u0000\u00c4\u0000\u00c6\u0000\u00c8\u0000\u00ca\u0000\u00cc\u0000"+
-    "\u00ceL\u00d0\u0000\u00d2M\u00d4\u0000\u00d6\u0000\u00d8N\u00daO\u00dc"+
-    "P\u00de\u0000\u00e0\u0000\u00e2\u0000\u00e4\u0000\u00e6\u0000\u00e8Q\u00ea"+
-    "R\u00ecS\u00eeT\u00f0\u0000\u00f2\u0000\u00f4\u0000\u00f6\u0000\u00f8"+
-    "U\u00fa\u0000\u00fcV\u00feW\u0100X\u0102\u0000\u0104\u0000\u0106Y\u0108"+
-    "Z\u010a\u0000\u010c[\u010e\u0000\u0110\\\u0112]\u0114^\u0116\u0000\u0118"+
-    "\u0000\u011a\u0000\u011c\u0000\u011e\u0000\u0120\u0000\u0122\u0000\u0124"+
-    "_\u0126`\u0128a\u012a\u0000\u012c\u0000\u012e\u0000\u0130\u0000\u0132"+
-    "b\u0134c\u0136d\u0138\u0000\u013ae\u013cf\u013eg\u0140h\u0142\u0000\u0144"+
-    "i\u0146j\u0148k\u014al\u014c\u0000\u014em\u0150n\u0152o\u0154p\u0156q"+
-    "\u0158\u0000\u015a\u0000\u015c\u0000\u015e\u0000\u0160\u0000\u0162\u0000"+
-    "\u0164\u0000\u0166r\u0168s\u016at\u016c\u0000\u016e\u0000\u0170\u0000"+
-    "\u0172\u0000\u0174u\u0176v\u0178w\u017a\u0000\u017c\u0000\u017e\u0000"+
-    "\u0180x\u0182y\u0184z\u0186\u0000\u0188\u0000\u018a{\u018c|\u018e}\u0190"+
-    "\u0000\u0192\u0000\u0194\u0000\u0196\u0000\u0010\u0000\u0001\u0002\u0003"+
-    "\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f#\u0002\u0000DDdd"+
-    "\u0002\u0000IIii\u0002\u0000SSss\u0002\u0000EEee\u0002\u0000CCcc\u0002"+
-    "\u0000TTtt\u0002\u0000RRrr\u0002\u0000OOoo\u0002\u0000PPpp\u0002\u0000"+
-    "NNnn\u0002\u0000HHhh\u0002\u0000VVvv\u0002\u0000AAaa\u0002\u0000LLll\u0002"+
-    "\u0000XXxx\u0002\u0000FFff\u0002\u0000MMmm\u0002\u0000GGgg\u0002\u0000"+
-    "KKkk\u0002\u0000WWww\u0002\u0000UUuu\u0006\u0000\t\n\r\r  //[[]]\u0002"+
-    "\u0000\n\n\r\r\u0003\u0000\t\n\r\r  \u0001\u000009\u0002\u0000AZaz\b\u0000"+
-    "\"\"NNRRTT\\\\nnrrtt\u0004\u0000\n\n\r\r\"\"\\\\\u0002\u0000++--\u0001"+
-    "\u0000``\u0002\u0000BBbb\u0002\u0000YYyy\u000b\u0000\t\n\r\r  \"\",,/"+
-    "/::==[[]]||\u0002\u0000**//\u000b\u0000\t\n\r\r  \"#,,//::<<>?\\\\||\u05dd"+
-    "\u0000\u0010\u0001\u0000\u0000\u0000\u0000\u0012\u0001\u0000\u0000\u0000"+
-    "\u0000\u0014\u0001\u0000\u0000\u0000\u0000\u0016\u0001\u0000\u0000\u0000"+
-    "\u0000\u0018\u0001\u0000\u0000\u0000\u0000\u001a\u0001\u0000\u0000\u0000"+
-    "\u0000\u001c\u0001\u0000\u0000\u0000\u0000\u001e\u0001\u0000\u0000\u0000"+
-    "\u0000 \u0001\u0000\u0000\u0000\u0000\"\u0001\u0000\u0000\u0000\u0000"+
-    "$\u0001\u0000\u0000\u0000\u0000&\u0001\u0000\u0000\u0000\u0000(\u0001"+
-    "\u0000\u0000\u0000\u0000*\u0001\u0000\u0000\u0000\u0000,\u0001\u0000\u0000"+
-    "\u0000\u0000.\u0001\u0000\u0000\u0000\u00000\u0001\u0000\u0000\u0000\u0000"+
-    "2\u0001\u0000\u0000\u0000\u00004\u0001\u0000\u0000\u0000\u00006\u0001"+
-    "\u0000\u0000\u0000\u00008\u0001\u0000\u0000\u0000\u0000:\u0001\u0000\u0000"+
-    "\u0000\u0000<\u0001\u0000\u0000\u0000\u0000>\u0001\u0000\u0000\u0000\u0000"+
-    "@\u0001\u0000\u0000\u0000\u0001B\u0001\u0000\u0000\u0000\u0001X\u0001"+
-    "\u0000\u0000\u0000\u0001Z\u0001\u0000\u0000\u0000\u0001\\\u0001\u0000"+
-    "\u0000\u0000\u0001^\u0001\u0000\u0000\u0000\u0001`\u0001\u0000\u0000\u0000"+
-    "\u0001b\u0001\u0000\u0000\u0000\u0001d\u0001\u0000\u0000\u0000\u0001f"+
-    "\u0001\u0000\u0000\u0000\u0001h\u0001\u0000\u0000\u0000\u0001j\u0001\u0000"+
-    "\u0000\u0000\u0001l\u0001\u0000\u0000\u0000\u0001n\u0001\u0000\u0000\u0000"+
-    "\u0001p\u0001\u0000\u0000\u0000\u0001r\u0001\u0000\u0000\u0000\u0001t"+
-    "\u0001\u0000\u0000\u0000\u0001v\u0001\u0000\u0000\u0000\u0001x\u0001\u0000"+
-    "\u0000\u0000\u0001z\u0001\u0000\u0000\u0000\u0001|\u0001\u0000\u0000\u0000"+
-    "\u0001~\u0001\u0000\u0000\u0000\u0001\u0080\u0001\u0000\u0000\u0000\u0001"+
-    "\u0082\u0001\u0000\u0000\u0000\u0001\u0084\u0001\u0000\u0000\u0000\u0001"+
-    "\u0086\u0001\u0000\u0000\u0000\u0001\u0088\u0001\u0000\u0000\u0000\u0001"+
-    "\u008a\u0001\u0000\u0000\u0000\u0001\u008c\u0001\u0000\u0000\u0000\u0001"+
-    "\u008e\u0001\u0000\u0000\u0000\u0001\u0090\u0001\u0000\u0000\u0000\u0001"+
-    "\u0092\u0001\u0000\u0000\u0000\u0001\u0094\u0001\u0000\u0000\u0000\u0001"+
-    "\u0096\u0001\u0000\u0000\u0000\u0001\u0098\u0001\u0000\u0000\u0000\u0001"+
-    "\u009a\u0001\u0000\u0000\u0000\u0001\u009c\u0001\u0000\u0000\u0000\u0001"+
-    "\u009e\u0001\u0000\u0000\u0000\u0001\u00a0\u0001\u0000\u0000\u0000\u0001"+
-    "\u00a2\u0001\u0000\u0000\u0000\u0001\u00a4\u0001\u0000\u0000\u0000\u0001"+
-    "\u00a6\u0001\u0000\u0000\u0000\u0001\u00a8\u0001\u0000\u0000\u0000\u0001"+
-    "\u00aa\u0001\u0000\u0000\u0000\u0001\u00ac\u0001\u0000\u0000\u0000\u0001"+
-    "\u00b0\u0001\u0000\u0000\u0000\u0001\u00b2\u0001\u0000\u0000\u0000\u0001"+
-    "\u00b4\u0001\u0000\u0000\u0000\u0001\u00b6\u0001\u0000\u0000\u0000\u0002"+
-    "\u00b8\u0001\u0000\u0000\u0000\u0002\u00ba\u0001\u0000\u0000\u0000\u0002"+
-    "\u00bc\u0001\u0000\u0000\u0000\u0002\u00be\u0001\u0000\u0000\u0000\u0002"+
-    "\u00c0\u0001\u0000\u0000\u0000\u0003\u00c2\u0001\u0000\u0000\u0000\u0003"+
-    "\u00c4\u0001\u0000\u0000\u0000\u0003\u00c6\u0001\u0000\u0000\u0000\u0003"+
-    "\u00c8\u0001\u0000\u0000\u0000\u0003\u00ca\u0001\u0000\u0000\u0000\u0003"+
-    "\u00cc\u0001\u0000\u0000\u0000\u0003\u00ce\u0001\u0000\u0000\u0000\u0003"+
-    "\u00d2\u0001\u0000\u0000\u0000\u0003\u00d4\u0001\u0000\u0000\u0000\u0003"+
-    "\u00d6\u0001\u0000\u0000\u0000\u0003\u00d8\u0001\u0000\u0000\u0000\u0003"+
-    "\u00da\u0001\u0000\u0000\u0000\u0003\u00dc\u0001\u0000\u0000\u0000\u0004"+
-    "\u00de\u0001\u0000\u0000\u0000\u0004\u00e0\u0001\u0000\u0000\u0000\u0004"+
-    "\u00e2\u0001\u0000\u0000\u0000\u0004\u00e8\u0001\u0000\u0000\u0000\u0004"+
-    "\u00ea\u0001\u0000\u0000\u0000\u0004\u00ec\u0001\u0000\u0000\u0000\u0004"+
-    "\u00ee\u0001\u0000\u0000\u0000\u0005\u00f0\u0001\u0000\u0000\u0000\u0005"+
-    "\u00f2\u0001\u0000\u0000\u0000\u0005\u00f4\u0001\u0000\u0000\u0000\u0005"+
-    "\u00f6\u0001\u0000\u0000\u0000\u0005\u00f8\u0001\u0000\u0000\u0000\u0005"+
-    "\u00fa\u0001\u0000\u0000\u0000\u0005\u00fc\u0001\u0000\u0000\u0000\u0005"+
-    "\u00fe\u0001\u0000\u0000\u0000\u0005\u0100\u0001\u0000\u0000\u0000\u0006"+
-    "\u0102\u0001\u0000\u0000\u0000\u0006\u0104\u0001\u0000\u0000\u0000\u0006"+
-    "\u0106\u0001\u0000\u0000\u0000\u0006\u0108\u0001\u0000\u0000\u0000\u0006"+
-    "\u010c\u0001\u0000\u0000\u0000\u0006\u010e\u0001\u0000\u0000\u0000\u0006"+
-    "\u0110\u0001\u0000\u0000\u0000\u0006\u0112\u0001\u0000\u0000\u0000\u0006"+
-    "\u0114\u0001\u0000\u0000\u0000\u0007\u0116\u0001\u0000\u0000\u0000\u0007"+
-    "\u0118\u0001\u0000\u0000\u0000\u0007\u011a\u0001\u0000\u0000\u0000\u0007"+
-    "\u011c\u0001\u0000\u0000\u0000\u0007\u011e\u0001\u0000\u0000\u0000\u0007"+
-    "\u0120\u0001\u0000\u0000\u0000\u0007\u0122\u0001\u0000\u0000\u0000\u0007"+
-    "\u0124\u0001\u0000\u0000\u0000\u0007\u0126\u0001\u0000\u0000\u0000\u0007"+
-    "\u0128\u0001\u0000\u0000\u0000\b\u012a\u0001\u0000\u0000\u0000\b\u012c"+
-    "\u0001\u0000\u0000\u0000\b\u012e\u0001\u0000\u0000\u0000\b\u0130\u0001"+
-    "\u0000\u0000\u0000\b\u0132\u0001\u0000\u0000\u0000\b\u0134\u0001\u0000"+
-    "\u0000\u0000\b\u0136\u0001\u0000\u0000\u0000\t\u0138\u0001\u0000\u0000"+
-    "\u0000\t\u013a\u0001\u0000\u0000\u0000\t\u013c\u0001\u0000\u0000\u0000"+
-    "\t\u013e\u0001\u0000\u0000\u0000\t\u0140\u0001\u0000\u0000\u0000\n\u0142"+
-    "\u0001\u0000\u0000\u0000\n\u0144\u0001\u0000\u0000\u0000\n\u0146\u0001"+
-    "\u0000\u0000\u0000\n\u0148\u0001\u0000\u0000\u0000\n\u014a\u0001\u0000"+
-    "\u0000\u0000\u000b\u014c\u0001\u0000\u0000\u0000\u000b\u014e\u0001\u0000"+
-    "\u0000\u0000\u000b\u0150\u0001\u0000\u0000\u0000\u000b\u0152\u0001\u0000"+
-    "\u0000\u0000\u000b\u0154\u0001\u0000\u0000\u0000\u000b\u0156\u0001\u0000"+
-    "\u0000\u0000\f\u0158\u0001\u0000\u0000\u0000\f\u015a\u0001\u0000\u0000"+
-    "\u0000\f\u015c\u0001\u0000\u0000\u0000\f\u015e\u0001\u0000\u0000\u0000"+
-    "\f\u0160\u0001\u0000\u0000\u0000\f\u0162\u0001\u0000\u0000\u0000\f\u0164"+
-    "\u0001\u0000\u0000\u0000\f\u0166\u0001\u0000\u0000\u0000\f\u0168\u0001"+
-    "\u0000\u0000\u0000\f\u016a\u0001\u0000\u0000\u0000\r\u016c\u0001\u0000"+
-    "\u0000\u0000\r\u016e\u0001\u0000\u0000\u0000\r\u0170\u0001\u0000\u0000"+
-    "\u0000\r\u0172\u0001\u0000\u0000\u0000\r\u0174\u0001\u0000\u0000\u0000"+
-    "\r\u0176\u0001\u0000\u0000\u0000\r\u0178\u0001\u0000\u0000\u0000\u000e"+
-    "\u017a\u0001\u0000\u0000\u0000\u000e\u017c\u0001\u0000\u0000\u0000\u000e"+
-    "\u017e\u0001\u0000\u0000\u0000\u000e\u0180\u0001\u0000\u0000\u0000\u000e"+
-    "\u0182\u0001\u0000\u0000\u0000\u000e\u0184\u0001\u0000\u0000\u0000\u000f"+
-    "\u0186\u0001\u0000\u0000\u0000\u000f\u0188\u0001\u0000\u0000\u0000\u000f"+
-    "\u018a\u0001\u0000\u0000\u0000\u000f\u018c\u0001\u0000\u0000\u0000\u000f"+
-    "\u018e\u0001\u0000\u0000\u0000\u000f\u0190\u0001\u0000\u0000\u0000\u000f"+
-    "\u0192\u0001\u0000\u0000\u0000\u000f\u0194\u0001\u0000\u0000\u0000\u000f"+
-    "\u0196\u0001\u0000\u0000\u0000\u0010\u0198\u0001\u0000\u0000\u0000\u0012"+
-    "\u01a2\u0001\u0000\u0000\u0000\u0014\u01a9\u0001\u0000\u0000\u0000\u0016"+
-    "\u01b2\u0001\u0000\u0000\u0000\u0018\u01b9\u0001\u0000\u0000\u0000\u001a"+
-    "\u01c3\u0001\u0000\u0000\u0000\u001c\u01ca\u0001\u0000\u0000\u0000\u001e"+
-    "\u01d1\u0001\u0000\u0000\u0000 \u01d8\u0001\u0000\u0000\u0000\"\u01e0"+
-    "\u0001\u0000\u0000\u0000$\u01e7\u0001\u0000\u0000\u0000&\u01f3\u0001\u0000"+
-    "\u0000\u0000(\u01fc\u0001\u0000\u0000\u0000*\u0202\u0001\u0000\u0000\u0000"+
-    ",\u0209\u0001\u0000\u0000\u0000.\u0210\u0001\u0000\u0000\u00000\u0218"+
-    "\u0001\u0000\u0000\u00002\u0220\u0001\u0000\u0000\u00004\u022f\u0001\u0000"+
-    "\u0000\u00006\u0239\u0001\u0000\u0000\u00008\u0242\u0001\u0000\u0000\u0000"+
-    ":\u024e\u0001\u0000\u0000\u0000<\u0254\u0001\u0000\u0000\u0000>\u0265"+
-    "\u0001\u0000\u0000\u0000@\u0275\u0001\u0000\u0000\u0000B\u027b\u0001\u0000"+
-    "\u0000\u0000D\u027f\u0001\u0000\u0000\u0000F\u0281\u0001\u0000\u0000\u0000"+
-    "H\u0283\u0001\u0000\u0000\u0000J\u0286\u0001\u0000\u0000\u0000L\u0288"+
-    "\u0001\u0000\u0000\u0000N\u0291\u0001\u0000\u0000\u0000P\u0293\u0001\u0000"+
-    "\u0000\u0000R\u0298\u0001\u0000\u0000\u0000T\u029a\u0001\u0000\u0000\u0000"+
-    "V\u029f\u0001\u0000\u0000\u0000X\u02be\u0001\u0000\u0000\u0000Z\u02c1"+
-    "\u0001\u0000\u0000\u0000\\\u02ef\u0001\u0000\u0000\u0000^\u02f1\u0001"+
-    "\u0000\u0000\u0000`\u02f4\u0001\u0000\u0000\u0000b\u02f8\u0001\u0000\u0000"+
-    "\u0000d\u02fc\u0001\u0000\u0000\u0000f\u02fe\u0001\u0000\u0000\u0000h"+
-    "\u0301\u0001\u0000\u0000\u0000j\u0303\u0001\u0000\u0000\u0000l\u0308\u0001"+
-    "\u0000\u0000\u0000n\u030a\u0001\u0000\u0000\u0000p\u0310\u0001\u0000\u0000"+
-    "\u0000r\u0316\u0001\u0000\u0000\u0000t\u0319\u0001\u0000\u0000\u0000v"+
-    "\u031c\u0001\u0000\u0000\u0000x\u0321\u0001\u0000\u0000\u0000z\u0326\u0001"+
-    "\u0000\u0000\u0000|\u0328\u0001\u0000\u0000\u0000~\u032c\u0001\u0000\u0000"+
-    "\u0000\u0080\u0331\u0001\u0000\u0000\u0000\u0082\u0337\u0001\u0000\u0000"+
-    "\u0000\u0084\u033a\u0001\u0000\u0000\u0000\u0086\u033c\u0001\u0000\u0000"+
-    "\u0000\u0088\u0342\u0001\u0000\u0000\u0000\u008a\u0344\u0001\u0000\u0000"+
-    "\u0000\u008c\u0349\u0001\u0000\u0000\u0000\u008e\u034c\u0001\u0000\u0000"+
-    "\u0000\u0090\u034f\u0001\u0000\u0000\u0000\u0092\u0352\u0001\u0000\u0000"+
-    "\u0000\u0094\u0354\u0001\u0000\u0000\u0000\u0096\u0357\u0001\u0000\u0000"+
-    "\u0000\u0098\u0359\u0001\u0000\u0000\u0000\u009a\u035c\u0001\u0000\u0000"+
-    "\u0000\u009c\u035e\u0001\u0000\u0000\u0000\u009e\u0360\u0001\u0000\u0000"+
-    "\u0000\u00a0\u0362\u0001\u0000\u0000\u0000\u00a2\u0364\u0001\u0000\u0000"+
-    "\u0000\u00a4\u0366\u0001\u0000\u0000\u0000\u00a6\u037c\u0001\u0000\u0000"+
-    "\u0000\u00a8\u037e\u0001\u0000\u0000\u0000\u00aa\u0383\u0001\u0000\u0000"+
-    "\u0000\u00ac\u0398\u0001\u0000\u0000\u0000\u00ae\u039a\u0001\u0000\u0000"+
-    "\u0000\u00b0\u03a2\u0001\u0000\u0000\u0000\u00b2\u03a4\u0001\u0000\u0000"+
-    "\u0000\u00b4\u03a8\u0001\u0000\u0000\u0000\u00b6\u03ac\u0001\u0000\u0000"+
-    "\u0000\u00b8\u03b0\u0001\u0000\u0000\u0000\u00ba\u03b5\u0001\u0000\u0000"+
-    "\u0000\u00bc\u03ba\u0001\u0000\u0000\u0000\u00be\u03be\u0001\u0000\u0000"+
-    "\u0000\u00c0\u03c2\u0001\u0000\u0000\u0000\u00c2\u03c6\u0001\u0000\u0000"+
-    "\u0000\u00c4\u03cb\u0001\u0000\u0000\u0000\u00c6\u03cf\u0001\u0000\u0000"+
-    "\u0000\u00c8\u03d3\u0001\u0000\u0000\u0000\u00ca\u03d7\u0001\u0000\u0000"+
-    "\u0000\u00cc\u03db\u0001\u0000\u0000\u0000\u00ce\u03df\u0001\u0000\u0000"+
-    "\u0000\u00d0\u03eb\u0001\u0000\u0000\u0000\u00d2\u03ee\u0001\u0000\u0000"+
-    "\u0000\u00d4\u03f2\u0001\u0000\u0000\u0000\u00d6\u03f6\u0001\u0000\u0000"+
-    "\u0000\u00d8\u03fa\u0001\u0000\u0000\u0000\u00da\u03fe\u0001\u0000\u0000"+
-    "\u0000\u00dc\u0402\u0001\u0000\u0000\u0000\u00de\u0406\u0001\u0000\u0000"+
-    "\u0000\u00e0\u040b\u0001\u0000\u0000\u0000\u00e2\u040f\u0001\u0000\u0000"+
-    "\u0000\u00e4\u0417\u0001\u0000\u0000\u0000\u00e6\u042c\u0001\u0000\u0000"+
-    "\u0000\u00e8\u0430\u0001\u0000\u0000\u0000\u00ea\u0434\u0001\u0000\u0000"+
-    "\u0000\u00ec\u0438\u0001\u0000\u0000\u0000\u00ee\u043c\u0001\u0000\u0000"+
-    "\u0000\u00f0\u0440\u0001\u0000\u0000\u0000\u00f2\u0445\u0001\u0000\u0000"+
-    "\u0000\u00f4\u0449\u0001\u0000\u0000\u0000\u00f6\u044d\u0001\u0000\u0000"+
-    "\u0000\u00f8\u0451\u0001\u0000\u0000\u0000\u00fa\u0454\u0001\u0000\u0000"+
-    "\u0000\u00fc\u0458\u0001\u0000\u0000\u0000\u00fe\u045c\u0001\u0000\u0000"+
-    "\u0000\u0100\u0460\u0001\u0000\u0000\u0000\u0102\u0464\u0001\u0000\u0000"+
-    "\u0000\u0104\u0469\u0001\u0000\u0000\u0000\u0106\u046e\u0001\u0000\u0000"+
-    "\u0000\u0108\u0473\u0001\u0000\u0000\u0000\u010a\u047a\u0001\u0000\u0000"+
-    "\u0000\u010c\u0483\u0001\u0000\u0000\u0000\u010e\u048a\u0001\u0000\u0000"+
-    "\u0000\u0110\u048e\u0001\u0000\u0000\u0000\u0112\u0492\u0001\u0000\u0000"+
-    "\u0000\u0114\u0496\u0001\u0000\u0000\u0000\u0116\u049a\u0001\u0000\u0000"+
-    "\u0000\u0118\u04a0\u0001\u0000\u0000\u0000\u011a\u04a4\u0001\u0000\u0000"+
-    "\u0000\u011c\u04a8\u0001\u0000\u0000\u0000\u011e\u04ac\u0001\u0000\u0000"+
-    "\u0000\u0120\u04b0\u0001\u0000\u0000\u0000\u0122\u04b4\u0001\u0000\u0000"+
-    "\u0000\u0124\u04b8\u0001\u0000\u0000\u0000\u0126\u04bc\u0001\u0000\u0000"+
-    "\u0000\u0128\u04c0\u0001\u0000\u0000\u0000\u012a\u04c4\u0001\u0000\u0000"+
-    "\u0000\u012c\u04c9\u0001\u0000\u0000\u0000\u012e\u04cd\u0001\u0000\u0000"+
-    "\u0000\u0130\u04d1\u0001\u0000\u0000\u0000\u0132\u04d5\u0001\u0000\u0000"+
-    "\u0000\u0134\u04d9\u0001\u0000\u0000\u0000\u0136\u04dd\u0001\u0000\u0000"+
-    "\u0000\u0138\u04e1\u0001\u0000\u0000\u0000\u013a\u04e6\u0001\u0000\u0000"+
-    "\u0000\u013c\u04eb\u0001\u0000\u0000\u0000\u013e\u04ef\u0001\u0000\u0000"+
-    "\u0000\u0140\u04f3\u0001\u0000\u0000\u0000\u0142\u04f7\u0001\u0000\u0000"+
-    "\u0000\u0144\u04fc\u0001\u0000\u0000\u0000\u0146\u0506\u0001\u0000\u0000"+
-    "\u0000\u0148\u050a\u0001\u0000\u0000\u0000\u014a\u050e\u0001\u0000\u0000"+
-    "\u0000\u014c\u0512\u0001\u0000\u0000\u0000\u014e\u0517\u0001\u0000\u0000"+
-    "\u0000\u0150\u051e\u0001\u0000\u0000\u0000\u0152\u0522\u0001\u0000\u0000"+
-    "\u0000\u0154\u0526\u0001\u0000\u0000\u0000\u0156\u052a\u0001\u0000\u0000"+
-    "\u0000\u0158\u052e\u0001\u0000\u0000\u0000\u015a\u0533\u0001\u0000\u0000"+
-    "\u0000\u015c\u0537\u0001\u0000\u0000\u0000\u015e\u053b\u0001\u0000\u0000"+
-    "\u0000\u0160\u053f\u0001\u0000\u0000\u0000\u0162\u0544\u0001\u0000\u0000"+
-    "\u0000\u0164\u0548\u0001\u0000\u0000\u0000\u0166\u054c\u0001\u0000\u0000"+
-    "\u0000\u0168\u0550\u0001\u0000\u0000\u0000\u016a\u0554\u0001\u0000\u0000"+
-    "\u0000\u016c\u0558\u0001\u0000\u0000\u0000\u016e\u055e\u0001\u0000\u0000"+
-    "\u0000\u0170\u0562\u0001\u0000\u0000\u0000\u0172\u0566\u0001\u0000\u0000"+
-    "\u0000\u0174\u056a\u0001\u0000\u0000\u0000\u0176\u056e\u0001\u0000\u0000"+
-    "\u0000\u0178\u0572\u0001\u0000\u0000\u0000\u017a\u0576\u0001\u0000\u0000"+
-    "\u0000\u017c\u057b\u0001\u0000\u0000\u0000\u017e\u0581\u0001\u0000\u0000"+
-    "\u0000\u0180\u0587\u0001\u0000\u0000\u0000\u0182\u058b\u0001\u0000\u0000"+
-    "\u0000\u0184\u058f\u0001\u0000\u0000\u0000\u0186\u0593\u0001\u0000\u0000"+
-    "\u0000\u0188\u0599\u0001\u0000\u0000\u0000\u018a\u059f\u0001\u0000\u0000"+
-    "\u0000\u018c\u05a3\u0001\u0000\u0000\u0000\u018e\u05a7\u0001\u0000\u0000"+
-    "\u0000\u0190\u05ab\u0001\u0000\u0000\u0000\u0192\u05b1\u0001\u0000\u0000"+
-    "\u0000\u0194\u05b7\u0001\u0000\u0000\u0000\u0196\u05bd\u0001\u0000\u0000"+
-    "\u0000\u0198\u0199\u0007\u0000\u0000\u0000\u0199\u019a\u0007\u0001\u0000"+
-    "\u0000\u019a\u019b\u0007\u0002\u0000\u0000\u019b\u019c\u0007\u0002\u0000"+
-    "\u0000\u019c\u019d\u0007\u0003\u0000\u0000\u019d\u019e\u0007\u0004\u0000"+
-    "\u0000\u019e\u019f\u0007\u0005\u0000\u0000\u019f\u01a0\u0001\u0000\u0000"+
-    "\u0000\u01a0\u01a1\u0006\u0000\u0000\u0000\u01a1\u0011\u0001\u0000\u0000"+
-    "\u0000\u01a2\u01a3\u0007\u0000\u0000\u0000\u01a3\u01a4\u0007\u0006\u0000"+
-    "\u0000\u01a4\u01a5\u0007\u0007\u0000\u0000\u01a5\u01a6\u0007\b\u0000\u0000"+
-    "\u01a6\u01a7\u0001\u0000\u0000\u0000\u01a7\u01a8\u0006\u0001\u0001\u0000"+
-    "\u01a8\u0013\u0001\u0000\u0000\u0000\u01a9\u01aa\u0007\u0003\u0000\u0000"+
-    "\u01aa\u01ab\u0007\t\u0000\u0000\u01ab\u01ac\u0007\u0006\u0000\u0000\u01ac"+
-    "\u01ad\u0007\u0001\u0000\u0000\u01ad\u01ae\u0007\u0004\u0000\u0000\u01ae"+
-    "\u01af\u0007\n\u0000\u0000\u01af\u01b0\u0001\u0000\u0000\u0000\u01b0\u01b1"+
-    "\u0006\u0002\u0002\u0000\u01b1\u0015\u0001\u0000\u0000\u0000\u01b2\u01b3"+
-    "\u0007\u0003\u0000\u0000\u01b3\u01b4\u0007\u000b\u0000\u0000\u01b4\u01b5"+
-    "\u0007\f\u0000\u0000\u01b5\u01b6\u0007\r\u0000\u0000\u01b6\u01b7\u0001"+
-    "\u0000\u0000\u0000\u01b7\u01b8\u0006\u0003\u0000\u0000\u01b8\u0017\u0001"+
-    "\u0000\u0000\u0000\u01b9\u01ba\u0007\u0003\u0000\u0000\u01ba\u01bb\u0007"+
-    "\u000e\u0000\u0000\u01bb\u01bc\u0007\b\u0000\u0000\u01bc\u01bd\u0007\r"+
-    "\u0000\u0000\u01bd\u01be\u0007\f\u0000\u0000\u01be\u01bf\u0007\u0001\u0000"+
-    "\u0000\u01bf\u01c0\u0007\t\u0000\u0000\u01c0\u01c1\u0001\u0000\u0000\u0000"+
-    "\u01c1\u01c2\u0006\u0004\u0003\u0000\u01c2\u0019\u0001\u0000\u0000\u0000"+
-    "\u01c3\u01c4\u0007\u000f\u0000\u0000\u01c4\u01c5\u0007\u0006\u0000\u0000"+
-    "\u01c5\u01c6\u0007\u0007\u0000\u0000\u01c6\u01c7\u0007\u0010\u0000\u0000"+
-    "\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8\u01c9\u0006\u0005\u0004\u0000"+
-    "\u01c9\u001b\u0001\u0000\u0000\u0000\u01ca\u01cb\u0007\u0011\u0000\u0000"+
-    "\u01cb\u01cc\u0007\u0006\u0000\u0000\u01cc\u01cd\u0007\u0007\u0000\u0000"+
-    "\u01cd\u01ce\u0007\u0012\u0000\u0000\u01ce\u01cf\u0001\u0000\u0000\u0000"+
-    "\u01cf\u01d0\u0006\u0006\u0000\u0000\u01d0\u001d\u0001\u0000\u0000\u0000"+
-    "\u01d1\u01d2\u0007\u0012\u0000\u0000\u01d2\u01d3\u0007\u0003\u0000\u0000"+
-    "\u01d3\u01d4\u0007\u0003\u0000\u0000\u01d4\u01d5\u0007\b\u0000\u0000\u01d5"+
-    "\u01d6\u0001\u0000\u0000\u0000\u01d6\u01d7\u0006\u0007\u0001\u0000\u01d7"+
-    "\u001f\u0001\u0000\u0000\u0000\u01d8\u01d9\u0007\r\u0000\u0000\u01d9\u01da"+
-    "\u0007\u0001\u0000\u0000\u01da\u01db\u0007\u0010\u0000\u0000\u01db\u01dc"+
-    "\u0007\u0001\u0000\u0000\u01dc\u01dd\u0007\u0005\u0000\u0000\u01dd\u01de"+
-    "\u0001\u0000\u0000\u0000\u01de\u01df\u0006\b\u0000\u0000\u01df!\u0001"+
-    "\u0000\u0000\u0000\u01e0\u01e1\u0007\u0010\u0000\u0000\u01e1\u01e2\u0007"+
-    "\u0003\u0000\u0000\u01e2\u01e3\u0007\u0005\u0000\u0000\u01e3\u01e4\u0007"+
-    "\f\u0000\u0000\u01e4\u01e5\u0001\u0000\u0000\u0000\u01e5\u01e6\u0006\t"+
-    "\u0005\u0000\u01e6#\u0001\u0000\u0000\u0000\u01e7\u01e8\u0007\u0010\u0000"+
-    "\u0000\u01e8\u01e9\u0007\u000b\u0000\u0000\u01e9\u01ea\u0005_\u0000\u0000"+
-    "\u01ea\u01eb\u0007\u0003\u0000\u0000\u01eb\u01ec\u0007\u000e\u0000\u0000"+
-    "\u01ec\u01ed\u0007\b\u0000\u0000\u01ed\u01ee\u0007\f\u0000\u0000\u01ee"+
-    "\u01ef\u0007\t\u0000\u0000\u01ef\u01f0\u0007\u0000\u0000\u0000\u01f0\u01f1"+
-    "\u0001\u0000\u0000\u0000\u01f1\u01f2\u0006\n\u0006\u0000\u01f2%\u0001"+
-    "\u0000\u0000\u0000\u01f3\u01f4\u0007\u0006\u0000\u0000\u01f4\u01f5\u0007"+
-    "\u0003\u0000\u0000\u01f5\u01f6\u0007\t\u0000\u0000\u01f6\u01f7\u0007\f"+
-    "\u0000\u0000\u01f7\u01f8\u0007\u0010\u0000\u0000\u01f8\u01f9\u0007\u0003"+
-    "\u0000\u0000\u01f9\u01fa\u0001\u0000\u0000\u0000\u01fa\u01fb\u0006\u000b"+
-    "\u0007\u0000\u01fb\'\u0001\u0000\u0000\u0000\u01fc\u01fd\u0007\u0006\u0000"+
-    "\u0000\u01fd\u01fe\u0007\u0007\u0000\u0000\u01fe\u01ff\u0007\u0013\u0000"+
-    "\u0000\u01ff\u0200\u0001\u0000\u0000\u0000\u0200\u0201\u0006\f\u0000\u0000"+
-    "\u0201)\u0001\u0000\u0000\u0000\u0202\u0203\u0007\u0002\u0000\u0000\u0203"+
-    "\u0204\u0007\n\u0000\u0000\u0204\u0205\u0007\u0007\u0000\u0000\u0205\u0206"+
-    "\u0007\u0013\u0000\u0000\u0206\u0207\u0001\u0000\u0000\u0000\u0207\u0208"+
-    "\u0006\r\b\u0000\u0208+\u0001\u0000\u0000\u0000\u0209\u020a\u0007\u0002"+
-    "\u0000\u0000\u020a\u020b\u0007\u0007\u0000\u0000\u020b\u020c\u0007\u0006"+
-    "\u0000\u0000\u020c\u020d\u0007\u0005\u0000\u0000\u020d\u020e\u0001\u0000"+
-    "\u0000\u0000\u020e\u020f\u0006\u000e\u0000\u0000\u020f-\u0001\u0000\u0000"+
-    "\u0000\u0210\u0211\u0007\u0002\u0000\u0000\u0211\u0212\u0007\u0005\u0000"+
-    "\u0000\u0212\u0213\u0007\f\u0000\u0000\u0213\u0214\u0007\u0005\u0000\u0000"+
-    "\u0214\u0215\u0007\u0002\u0000\u0000\u0215\u0216\u0001\u0000\u0000\u0000"+
-    "\u0216\u0217\u0006\u000f\u0000\u0000\u0217/\u0001\u0000\u0000\u0000\u0218"+
-    "\u0219\u0007\u0013\u0000\u0000\u0219\u021a\u0007\n\u0000\u0000\u021a\u021b"+
-    "\u0007\u0003\u0000\u0000\u021b\u021c\u0007\u0006\u0000\u0000\u021c\u021d"+
-    "\u0007\u0003\u0000\u0000\u021d\u021e\u0001\u0000\u0000\u0000\u021e\u021f"+
-    "\u0006\u0010\u0000\u0000\u021f1\u0001\u0000\u0000\u0000\u0220\u0221\u0004"+
-    "\u0011\u0000\u0000\u0221\u0222\u0007\u0001\u0000\u0000\u0222\u0223\u0007"+
-    "\t\u0000\u0000\u0223\u0224\u0007\r\u0000\u0000\u0224\u0225\u0007\u0001"+
-    "\u0000\u0000\u0225\u0226\u0007\t\u0000\u0000\u0226\u0227\u0007\u0003\u0000"+
-    "\u0000\u0227\u0228\u0007\u0002\u0000\u0000\u0228\u0229\u0007\u0005\u0000"+
-    "\u0000\u0229\u022a\u0007\f\u0000\u0000\u022a\u022b\u0007\u0005\u0000\u0000"+
-    "\u022b\u022c\u0007\u0002\u0000\u0000\u022c\u022d\u0001\u0000\u0000\u0000"+
-    "\u022d\u022e\u0006\u0011\u0000\u0000\u022e3\u0001\u0000\u0000\u0000\u022f"+
-    "\u0230\u0004\u0012\u0001\u0000\u0230\u0231\u0007\r\u0000\u0000\u0231\u0232"+
-    "\u0007\u0007\u0000\u0000\u0232\u0233\u0007\u0007\u0000\u0000\u0233\u0234"+
-    "\u0007\u0012\u0000\u0000\u0234\u0235\u0007\u0014\u0000\u0000\u0235\u0236"+
-    "\u0007\b\u0000\u0000\u0236\u0237\u0001\u0000\u0000\u0000\u0237\u0238\u0006"+
-    "\u0012\t\u0000\u02385\u0001\u0000\u0000\u0000\u0239\u023a\u0004\u0013"+
-    "\u0002\u0000\u023a\u023b\u0007\u0010\u0000\u0000\u023b\u023c\u0007\f\u0000"+
-    "\u0000\u023c\u023d\u0007\u0005\u0000\u0000\u023d\u023e\u0007\u0004\u0000"+
-    "\u0000\u023e\u023f\u0007\n\u0000\u0000\u023f\u0240\u0001\u0000\u0000\u0000"+
-    "\u0240\u0241\u0006\u0013\u0000\u0000\u02417\u0001\u0000\u0000\u0000\u0242"+
-    "\u0243\u0004\u0014\u0003\u0000\u0243\u0244\u0007\u0010\u0000\u0000\u0244"+
-    "\u0245\u0007\u0003\u0000\u0000\u0245\u0246\u0007\u0005\u0000\u0000\u0246"+
-    "\u0247\u0007\u0006\u0000\u0000\u0247\u0248\u0007\u0001\u0000\u0000\u0248"+
-    "\u0249\u0007\u0004\u0000\u0000\u0249\u024a\u0007\u0002\u0000\u0000\u024a"+
-    "\u024b\u0001\u0000\u0000\u0000\u024b\u024c\u0006\u0014\n\u0000\u024c9"+
-    "\u0001\u0000\u0000\u0000\u024d\u024f\b\u0015\u0000\u0000\u024e\u024d\u0001"+
-    "\u0000\u0000\u0000\u024f\u0250\u0001\u0000\u0000\u0000\u0250\u024e\u0001"+
-    "\u0000\u0000\u0000\u0250\u0251\u0001\u0000\u0000\u0000\u0251\u0252\u0001"+
-    "\u0000\u0000\u0000\u0252\u0253\u0006\u0015\u0000\u0000\u0253;\u0001\u0000"+
-    "\u0000\u0000\u0254\u0255\u0005/\u0000\u0000\u0255\u0256\u0005/\u0000\u0000"+
-    "\u0256\u025a\u0001\u0000\u0000\u0000\u0257\u0259\b\u0016\u0000\u0000\u0258"+
-    "\u0257\u0001\u0000\u0000\u0000\u0259\u025c\u0001\u0000\u0000\u0000\u025a"+
-    "\u0258\u0001\u0000\u0000\u0000\u025a\u025b\u0001\u0000\u0000\u0000\u025b"+
-    "\u025e\u0001\u0000\u0000\u0000\u025c\u025a\u0001\u0000\u0000\u0000\u025d"+
-    "\u025f\u0005\r\u0000\u0000\u025e\u025d\u0001\u0000\u0000\u0000\u025e\u025f"+
-    "\u0001\u0000\u0000\u0000\u025f\u0261\u0001\u0000\u0000\u0000\u0260\u0262"+
-    "\u0005\n\u0000\u0000\u0261\u0260\u0001\u0000\u0000\u0000\u0261\u0262\u0001"+
-    "\u0000\u0000\u0000\u0262\u0263\u0001\u0000\u0000\u0000\u0263\u0264\u0006"+
-    "\u0016\u000b\u0000\u0264=\u0001\u0000\u0000\u0000\u0265\u0266\u0005/\u0000"+
-    "\u0000\u0266\u0267\u0005*\u0000\u0000\u0267\u026c\u0001\u0000\u0000\u0000"+
-    "\u0268\u026b\u0003>\u0017\u0000\u0269\u026b\t\u0000\u0000\u0000\u026a"+
-    "\u0268\u0001\u0000\u0000\u0000\u026a\u0269\u0001\u0000\u0000\u0000\u026b"+
-    "\u026e\u0001\u0000\u0000\u0000\u026c\u026d\u0001\u0000\u0000\u0000\u026c"+
-    "\u026a\u0001\u0000\u0000\u0000\u026d\u026f\u0001\u0000\u0000\u0000\u026e"+
-    "\u026c\u0001\u0000\u0000\u0000\u026f\u0270\u0005*\u0000\u0000\u0270\u0271"+
-    "\u0005/\u0000\u0000\u0271\u0272\u0001\u0000\u0000\u0000\u0272\u0273\u0006"+
-    "\u0017\u000b\u0000\u0273?\u0001\u0000\u0000\u0000\u0274\u0276\u0007\u0017"+
-    "\u0000\u0000\u0275\u0274\u0001\u0000\u0000\u0000\u0276\u0277\u0001\u0000"+
-    "\u0000\u0000\u0277\u0275\u0001\u0000\u0000\u0000\u0277\u0278\u0001\u0000"+
-    "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u027a\u0006\u0018"+
-    "\u000b\u0000\u027aA\u0001\u0000\u0000\u0000\u027b\u027c\u0005|\u0000\u0000"+
-    "\u027c\u027d\u0001\u0000\u0000\u0000\u027d\u027e\u0006\u0019\f\u0000\u027e"+
-    "C\u0001\u0000\u0000\u0000\u027f\u0280\u0007\u0018\u0000\u0000\u0280E\u0001"+
-    "\u0000\u0000\u0000\u0281\u0282\u0007\u0019\u0000\u0000\u0282G\u0001\u0000"+
-    "\u0000\u0000\u0283\u0284\u0005\\\u0000\u0000\u0284\u0285\u0007\u001a\u0000"+
-    "\u0000\u0285I\u0001\u0000\u0000\u0000\u0286\u0287\b\u001b\u0000\u0000"+
-    "\u0287K\u0001\u0000\u0000\u0000\u0288\u028a\u0007\u0003\u0000\u0000\u0289"+
-    "\u028b\u0007\u001c\u0000\u0000\u028a\u0289\u0001\u0000\u0000\u0000\u028a"+
-    "\u028b\u0001\u0000\u0000\u0000\u028b\u028d\u0001\u0000\u0000\u0000\u028c"+
-    "\u028e\u0003D\u001a\u0000\u028d\u028c\u0001\u0000\u0000\u0000\u028e\u028f"+
-    "\u0001\u0000\u0000\u0000\u028f\u028d\u0001\u0000\u0000\u0000\u028f\u0290"+
-    "\u0001\u0000\u0000\u0000\u0290M\u0001\u0000\u0000\u0000\u0291\u0292\u0005"+
-    "@\u0000\u0000\u0292O\u0001\u0000\u0000\u0000\u0293\u0294\u0005`\u0000"+
-    "\u0000\u0294Q\u0001\u0000\u0000\u0000\u0295\u0299\b\u001d\u0000\u0000"+
-    "\u0296\u0297\u0005`\u0000\u0000\u0297\u0299\u0005`\u0000\u0000\u0298\u0295"+
-    "\u0001\u0000\u0000\u0000\u0298\u0296\u0001\u0000\u0000\u0000\u0299S\u0001"+
-    "\u0000\u0000\u0000\u029a\u029b\u0005_\u0000\u0000\u029bU\u0001\u0000\u0000"+
-    "\u0000\u029c\u02a0\u0003F\u001b\u0000\u029d\u02a0\u0003D\u001a\u0000\u029e"+
-    "\u02a0\u0003T\"\u0000\u029f\u029c\u0001\u0000\u0000\u0000\u029f\u029d"+
-    "\u0001\u0000\u0000\u0000\u029f\u029e\u0001\u0000\u0000\u0000\u02a0W\u0001"+
-    "\u0000\u0000\u0000\u02a1\u02a6\u0005\"\u0000\u0000\u02a2\u02a5\u0003H"+
-    "\u001c\u0000\u02a3\u02a5\u0003J\u001d\u0000\u02a4\u02a2\u0001\u0000\u0000"+
-    "\u0000\u02a4\u02a3\u0001\u0000\u0000\u0000\u02a5\u02a8\u0001\u0000\u0000"+
-    "\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a6\u02a7\u0001\u0000\u0000"+
-    "\u0000\u02a7\u02a9\u0001\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000"+
-    "\u0000\u02a9\u02bf\u0005\"\u0000\u0000\u02aa\u02ab\u0005\"\u0000\u0000"+
-    "\u02ab\u02ac\u0005\"\u0000\u0000\u02ac\u02ad\u0005\"\u0000\u0000\u02ad"+
-    "\u02b1\u0001\u0000\u0000\u0000\u02ae\u02b0\b\u0016\u0000\u0000\u02af\u02ae"+
-    "\u0001\u0000\u0000\u0000\u02b0\u02b3\u0001\u0000\u0000\u0000\u02b1\u02b2"+
-    "\u0001\u0000\u0000\u0000\u02b1\u02af\u0001\u0000\u0000\u0000\u02b2\u02b4"+
-    "\u0001\u0000\u0000\u0000\u02b3\u02b1\u0001\u0000\u0000\u0000\u02b4\u02b5"+
-    "\u0005\"\u0000\u0000\u02b5\u02b6\u0005\"\u0000\u0000\u02b6\u02b7\u0005"+
-    "\"\u0000\u0000\u02b7\u02b9\u0001\u0000\u0000\u0000\u02b8\u02ba\u0005\""+
-    "\u0000\u0000\u02b9\u02b8\u0001\u0000\u0000\u0000\u02b9\u02ba\u0001\u0000"+
-    "\u0000\u0000\u02ba\u02bc\u0001\u0000\u0000\u0000\u02bb\u02bd\u0005\"\u0000"+
-    "\u0000\u02bc\u02bb\u0001\u0000\u0000\u0000\u02bc\u02bd\u0001\u0000\u0000"+
-    "\u0000\u02bd\u02bf\u0001\u0000\u0000\u0000\u02be\u02a1\u0001\u0000\u0000"+
-    "\u0000\u02be\u02aa\u0001\u0000\u0000\u0000\u02bfY\u0001\u0000\u0000\u0000"+
-    "\u02c0\u02c2\u0003D\u001a\u0000\u02c1\u02c0\u0001\u0000\u0000\u0000\u02c2"+
-    "\u02c3\u0001\u0000\u0000\u0000\u02c3\u02c1\u0001\u0000\u0000\u0000\u02c3"+
-    "\u02c4\u0001\u0000\u0000\u0000\u02c4[\u0001\u0000\u0000\u0000\u02c5\u02c7"+
-    "\u0003D\u001a\u0000\u02c6\u02c5\u0001\u0000\u0000\u0000\u02c7\u02c8\u0001"+
-    "\u0000\u0000\u0000\u02c8\u02c6\u0001\u0000\u0000\u0000\u02c8\u02c9\u0001"+
-    "\u0000\u0000\u0000\u02c9\u02ca\u0001\u0000\u0000\u0000\u02ca\u02ce\u0003"+
-    "l.\u0000\u02cb\u02cd\u0003D\u001a\u0000\u02cc\u02cb\u0001\u0000\u0000"+
-    "\u0000\u02cd\u02d0\u0001\u0000\u0000\u0000\u02ce\u02cc\u0001\u0000\u0000"+
-    "\u0000\u02ce\u02cf\u0001\u0000\u0000\u0000\u02cf\u02f0\u0001\u0000\u0000"+
-    "\u0000\u02d0\u02ce\u0001\u0000\u0000\u0000\u02d1\u02d3\u0003l.\u0000\u02d2"+
-    "\u02d4\u0003D\u001a\u0000\u02d3\u02d2\u0001\u0000\u0000\u0000\u02d4\u02d5"+
-    "\u0001\u0000\u0000\u0000\u02d5\u02d3\u0001\u0000\u0000\u0000\u02d5\u02d6"+
-    "\u0001\u0000\u0000\u0000\u02d6\u02f0\u0001\u0000\u0000\u0000\u02d7\u02d9"+
-    "\u0003D\u001a\u0000\u02d8\u02d7\u0001\u0000\u0000\u0000\u02d9\u02da\u0001"+
-    "\u0000\u0000\u0000\u02da\u02d8\u0001\u0000\u0000\u0000\u02da\u02db\u0001"+
-    "\u0000\u0000\u0000\u02db\u02e3\u0001\u0000\u0000\u0000\u02dc\u02e0\u0003"+
-    "l.\u0000\u02dd\u02df\u0003D\u001a\u0000\u02de\u02dd\u0001\u0000\u0000"+
-    "\u0000\u02df\u02e2\u0001\u0000\u0000\u0000\u02e0\u02de\u0001\u0000\u0000"+
-    "\u0000\u02e0\u02e1\u0001\u0000\u0000\u0000\u02e1\u02e4\u0001\u0000\u0000"+
-    "\u0000\u02e2\u02e0\u0001\u0000\u0000\u0000\u02e3\u02dc\u0001\u0000\u0000"+
-    "\u0000\u02e3\u02e4\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000"+
-    "\u0000\u02e5\u02e6\u0003L\u001e\u0000\u02e6\u02f0\u0001\u0000\u0000\u0000"+
-    "\u02e7\u02e9\u0003l.\u0000\u02e8\u02ea\u0003D\u001a\u0000\u02e9\u02e8"+
-    "\u0001\u0000\u0000\u0000\u02ea\u02eb\u0001\u0000\u0000\u0000\u02eb\u02e9"+
-    "\u0001\u0000\u0000\u0000\u02eb\u02ec\u0001\u0000\u0000\u0000\u02ec\u02ed"+
-    "\u0001\u0000\u0000\u0000\u02ed\u02ee\u0003L\u001e\u0000\u02ee\u02f0\u0001"+
-    "\u0000\u0000\u0000\u02ef\u02c6\u0001\u0000\u0000\u0000\u02ef\u02d1\u0001"+
-    "\u0000\u0000\u0000\u02ef\u02d8\u0001\u0000\u0000\u0000\u02ef\u02e7\u0001"+
-    "\u0000\u0000\u0000\u02f0]\u0001\u0000\u0000\u0000\u02f1\u02f2\u0007\u001e"+
-    "\u0000\u0000\u02f2\u02f3\u0007\u001f\u0000\u0000\u02f3_\u0001\u0000\u0000"+
-    "\u0000\u02f4\u02f5\u0007\f\u0000\u0000\u02f5\u02f6\u0007\t\u0000\u0000"+
-    "\u02f6\u02f7\u0007\u0000\u0000\u0000\u02f7a\u0001\u0000\u0000\u0000\u02f8"+
-    "\u02f9\u0007\f\u0000\u0000\u02f9\u02fa\u0007\u0002\u0000\u0000\u02fa\u02fb"+
-    "\u0007\u0004\u0000\u0000\u02fbc\u0001\u0000\u0000\u0000\u02fc\u02fd\u0005"+
-    "=\u0000\u0000\u02fde\u0001\u0000\u0000\u0000\u02fe\u02ff\u0005:\u0000"+
-    "\u0000\u02ff\u0300\u0005:\u0000\u0000\u0300g\u0001\u0000\u0000\u0000\u0301"+
-    "\u0302\u0005,\u0000\u0000\u0302i\u0001\u0000\u0000\u0000\u0303\u0304\u0007"+
-    "\u0000\u0000\u0000\u0304\u0305\u0007\u0003\u0000\u0000\u0305\u0306\u0007"+
-    "\u0002\u0000\u0000\u0306\u0307\u0007\u0004\u0000\u0000\u0307k\u0001\u0000"+
-    "\u0000\u0000\u0308\u0309\u0005.\u0000\u0000\u0309m\u0001\u0000\u0000\u0000"+
-    "\u030a\u030b\u0007\u000f\u0000\u0000\u030b\u030c\u0007\f\u0000\u0000\u030c"+
-    "\u030d\u0007\r\u0000\u0000\u030d\u030e\u0007\u0002\u0000\u0000\u030e\u030f"+
-    "\u0007\u0003\u0000\u0000\u030fo\u0001\u0000\u0000\u0000\u0310\u0311\u0007"+
-    "\u000f\u0000\u0000\u0311\u0312\u0007\u0001\u0000\u0000\u0312\u0313\u0007"+
-    "\u0006\u0000\u0000\u0313\u0314\u0007\u0002\u0000\u0000\u0314\u0315\u0007"+
-    "\u0005\u0000\u0000\u0315q\u0001\u0000\u0000\u0000\u0316\u0317\u0007\u0001"+
-    "\u0000\u0000\u0317\u0318\u0007\t\u0000\u0000\u0318s\u0001\u0000\u0000"+
-    "\u0000\u0319\u031a\u0007\u0001\u0000\u0000\u031a\u031b\u0007\u0002\u0000"+
-    "\u0000\u031bu\u0001\u0000\u0000\u0000\u031c\u031d\u0007\r\u0000\u0000"+
-    "\u031d\u031e\u0007\f\u0000\u0000\u031e\u031f\u0007\u0002\u0000\u0000\u031f"+
-    "\u0320\u0007\u0005\u0000\u0000\u0320w\u0001\u0000\u0000\u0000\u0321\u0322"+
-    "\u0007\r\u0000\u0000\u0322\u0323\u0007\u0001\u0000\u0000\u0323\u0324\u0007"+
-    "\u0012\u0000\u0000\u0324\u0325\u0007\u0003\u0000\u0000\u0325y\u0001\u0000"+
-    "\u0000\u0000\u0326\u0327\u0005(\u0000\u0000\u0327{\u0001\u0000\u0000\u0000"+
-    "\u0328\u0329\u0007\t\u0000\u0000\u0329\u032a\u0007\u0007\u0000\u0000\u032a"+
-    "\u032b\u0007\u0005\u0000\u0000\u032b}\u0001\u0000\u0000\u0000\u032c\u032d"+
-    "\u0007\t\u0000\u0000\u032d\u032e\u0007\u0014\u0000\u0000\u032e\u032f\u0007"+
-    "\r\u0000\u0000\u032f\u0330\u0007\r\u0000\u0000\u0330\u007f\u0001\u0000"+
-    "\u0000\u0000\u0331\u0332\u0007\t\u0000\u0000\u0332\u0333\u0007\u0014\u0000"+
-    "\u0000\u0333\u0334\u0007\r\u0000\u0000\u0334\u0335\u0007\r\u0000\u0000"+
-    "\u0335\u0336\u0007\u0002\u0000\u0000\u0336\u0081\u0001\u0000\u0000\u0000"+
-    "\u0337\u0338\u0007\u0007\u0000\u0000\u0338\u0339\u0007\u0006\u0000\u0000"+
-    "\u0339\u0083\u0001\u0000\u0000\u0000\u033a\u033b\u0005?\u0000\u0000\u033b"+
-    "\u0085\u0001\u0000\u0000\u0000\u033c\u033d\u0007\u0006\u0000\u0000\u033d"+
-    "\u033e\u0007\r\u0000\u0000\u033e\u033f\u0007\u0001\u0000\u0000\u033f\u0340"+
-    "\u0007\u0012\u0000\u0000\u0340\u0341\u0007\u0003\u0000\u0000\u0341\u0087"+
-    "\u0001\u0000\u0000\u0000\u0342\u0343\u0005)\u0000\u0000\u0343\u0089\u0001"+
-    "\u0000\u0000\u0000\u0344\u0345\u0007\u0005\u0000\u0000\u0345\u0346\u0007"+
-    "\u0006\u0000\u0000\u0346\u0347\u0007\u0014\u0000\u0000\u0347\u0348\u0007"+
-    "\u0003\u0000\u0000\u0348\u008b\u0001\u0000\u0000\u0000\u0349\u034a\u0005"+
-    "=\u0000\u0000\u034a\u034b\u0005=\u0000\u0000\u034b\u008d\u0001\u0000\u0000"+
-    "\u0000\u034c\u034d\u0005=\u0000\u0000\u034d\u034e\u0005~\u0000\u0000\u034e"+
-    "\u008f\u0001\u0000\u0000\u0000\u034f\u0350\u0005!\u0000\u0000\u0350\u0351"+
-    "\u0005=\u0000\u0000\u0351\u0091\u0001\u0000\u0000\u0000\u0352\u0353\u0005"+
-    "<\u0000\u0000\u0353\u0093\u0001\u0000\u0000\u0000\u0354\u0355\u0005<\u0000"+
-    "\u0000\u0355\u0356\u0005=\u0000\u0000\u0356\u0095\u0001\u0000\u0000\u0000"+
-    "\u0357\u0358\u0005>\u0000\u0000\u0358\u0097\u0001\u0000\u0000\u0000\u0359"+
-    "\u035a\u0005>\u0000\u0000\u035a\u035b\u0005=\u0000\u0000\u035b\u0099\u0001"+
-    "\u0000\u0000\u0000\u035c\u035d\u0005+\u0000\u0000\u035d\u009b\u0001\u0000"+
-    "\u0000\u0000\u035e\u035f\u0005-\u0000\u0000\u035f\u009d\u0001\u0000\u0000"+
-    "\u0000\u0360\u0361\u0005*\u0000\u0000\u0361\u009f\u0001\u0000\u0000\u0000"+
-    "\u0362\u0363\u0005/\u0000\u0000\u0363\u00a1\u0001\u0000\u0000\u0000\u0364"+
-    "\u0365\u0005%\u0000\u0000\u0365\u00a3\u0001\u0000\u0000\u0000\u0366\u0367"+
-    "\u0004J\u0004\u0000\u0367\u0368\u00036\u0013\u0000\u0368\u0369\u0001\u0000"+
-    "\u0000\u0000\u0369\u036a\u0006J\r\u0000\u036a\u00a5\u0001\u0000\u0000"+
-    "\u0000\u036b\u036e\u0003\u0084:\u0000\u036c\u036f\u0003F\u001b\u0000\u036d"+
-    "\u036f\u0003T\"\u0000\u036e\u036c\u0001\u0000\u0000\u0000\u036e\u036d"+
-    "\u0001\u0000\u0000\u0000\u036f\u0373\u0001\u0000\u0000\u0000\u0370\u0372"+
-    "\u0003V#\u0000\u0371\u0370\u0001\u0000\u0000\u0000\u0372\u0375\u0001\u0000"+
-    "\u0000\u0000\u0373\u0371\u0001\u0000\u0000\u0000\u0373\u0374\u0001\u0000"+
-    "\u0000\u0000\u0374\u037d\u0001\u0000\u0000\u0000\u0375\u0373\u0001\u0000"+
-    "\u0000\u0000\u0376\u0378\u0003\u0084:\u0000\u0377\u0379\u0003D\u001a\u0000"+
-    "\u0378\u0377\u0001\u0000\u0000\u0000\u0379\u037a\u0001\u0000\u0000\u0000"+
-    "\u037a\u0378\u0001\u0000\u0000\u0000\u037a\u037b\u0001\u0000\u0000\u0000"+
-    "\u037b\u037d\u0001\u0000\u0000\u0000\u037c\u036b\u0001\u0000\u0000\u0000"+
-    "\u037c\u0376\u0001\u0000\u0000\u0000\u037d\u00a7\u0001\u0000\u0000\u0000"+
-    "\u037e\u037f\u0005[\u0000\u0000\u037f\u0380\u0001\u0000\u0000\u0000\u0380"+
-    "\u0381\u0006L\u0000\u0000\u0381\u0382\u0006L\u0000\u0000\u0382\u00a9\u0001"+
-    "\u0000\u0000\u0000\u0383\u0384\u0005]\u0000\u0000\u0384\u0385\u0001\u0000"+
-    "\u0000\u0000\u0385\u0386\u0006M\f\u0000\u0386\u0387\u0006M\f\u0000\u0387"+
-    "\u00ab\u0001\u0000\u0000\u0000\u0388\u038c\u0003F\u001b\u0000\u0389\u038b"+
-    "\u0003V#\u0000\u038a\u0389\u0001\u0000\u0000\u0000\u038b\u038e\u0001\u0000"+
-    "\u0000\u0000\u038c\u038a\u0001\u0000\u0000\u0000\u038c\u038d\u0001\u0000"+
-    "\u0000\u0000\u038d\u0399\u0001\u0000\u0000\u0000\u038e\u038c\u0001\u0000"+
-    "\u0000\u0000\u038f\u0392\u0003T\"\u0000\u0390\u0392\u0003N\u001f\u0000"+
-    "\u0391\u038f\u0001\u0000\u0000\u0000\u0391\u0390\u0001\u0000\u0000\u0000"+
-    "\u0392\u0394\u0001\u0000\u0000\u0000\u0393\u0395\u0003V#\u0000\u0394\u0393"+
-    "\u0001\u0000\u0000\u0000\u0395\u0396\u0001\u0000\u0000\u0000\u0396\u0394"+
-    "\u0001\u0000\u0000\u0000\u0396\u0397\u0001\u0000\u0000\u0000\u0397\u0399"+
-    "\u0001\u0000\u0000\u0000\u0398\u0388\u0001\u0000\u0000\u0000\u0398\u0391"+
-    "\u0001\u0000\u0000\u0000\u0399\u00ad\u0001\u0000\u0000\u0000\u039a\u039c"+
-    "\u0003P \u0000\u039b\u039d\u0003R!\u0000\u039c\u039b\u0001\u0000\u0000"+
-    "\u0000\u039d\u039e\u0001\u0000\u0000\u0000\u039e\u039c\u0001\u0000\u0000"+
-    "\u0000\u039e\u039f\u0001\u0000\u0000\u0000\u039f\u03a0\u0001\u0000\u0000"+
-    "\u0000\u03a0\u03a1\u0003P \u0000\u03a1\u00af\u0001\u0000\u0000\u0000\u03a2"+
-    "\u03a3\u0003\u00aeO\u0000\u03a3\u00b1\u0001\u0000\u0000\u0000\u03a4\u03a5"+
-    "\u0003<\u0016\u0000\u03a5\u03a6\u0001\u0000\u0000\u0000\u03a6\u03a7\u0006"+
-    "Q\u000b\u0000\u03a7\u00b3\u0001\u0000\u0000\u0000\u03a8\u03a9\u0003>\u0017"+
-    "\u0000\u03a9\u03aa\u0001\u0000\u0000\u0000\u03aa\u03ab\u0006R\u000b\u0000"+
-    "\u03ab\u00b5\u0001\u0000\u0000\u0000\u03ac\u03ad\u0003@\u0018\u0000\u03ad"+
-    "\u03ae\u0001\u0000\u0000\u0000\u03ae\u03af\u0006S\u000b\u0000\u03af\u00b7"+
-    "\u0001\u0000\u0000\u0000\u03b0\u03b1\u0003\u00a8L\u0000\u03b1\u03b2\u0001"+
-    "\u0000\u0000\u0000\u03b2\u03b3\u0006T\u000e\u0000\u03b3\u03b4\u0006T\u000f"+
-    "\u0000\u03b4\u00b9\u0001\u0000\u0000\u0000\u03b5\u03b6\u0003B\u0019\u0000"+
-    "\u03b6\u03b7\u0001\u0000\u0000\u0000\u03b7\u03b8\u0006U\u0010\u0000\u03b8"+
-    "\u03b9\u0006U\f\u0000\u03b9\u00bb\u0001\u0000\u0000\u0000\u03ba\u03bb"+
-    "\u0003@\u0018\u0000\u03bb\u03bc\u0001\u0000\u0000\u0000\u03bc\u03bd\u0006"+
-    "V\u000b\u0000\u03bd\u00bd\u0001\u0000\u0000\u0000\u03be\u03bf\u0003<\u0016"+
-    "\u0000\u03bf\u03c0\u0001\u0000\u0000\u0000\u03c0\u03c1\u0006W\u000b\u0000"+
-    "\u03c1\u00bf\u0001\u0000\u0000\u0000\u03c2\u03c3\u0003>\u0017\u0000\u03c3"+
-    "\u03c4\u0001\u0000\u0000\u0000\u03c4\u03c5\u0006X\u000b\u0000\u03c5\u00c1"+
-    "\u0001\u0000\u0000\u0000\u03c6\u03c7\u0003B\u0019\u0000\u03c7\u03c8\u0001"+
-    "\u0000\u0000\u0000\u03c8\u03c9\u0006Y\u0010\u0000\u03c9\u03ca\u0006Y\f"+
-    "\u0000\u03ca\u00c3\u0001\u0000\u0000\u0000\u03cb\u03cc\u0003\u00a8L\u0000"+
-    "\u03cc\u03cd\u0001\u0000\u0000\u0000\u03cd\u03ce\u0006Z\u000e\u0000\u03ce"+
-    "\u00c5\u0001\u0000\u0000\u0000\u03cf\u03d0\u0003\u00aaM\u0000\u03d0\u03d1"+
-    "\u0001\u0000\u0000\u0000\u03d1\u03d2\u0006[\u0011\u0000\u03d2\u00c7\u0001"+
-    "\u0000\u0000\u0000\u03d3\u03d4\u0003\u014e\u009f\u0000\u03d4\u03d5\u0001"+
-    "\u0000\u0000\u0000\u03d5\u03d6\u0006\\\u0012\u0000\u03d6\u00c9\u0001\u0000"+
-    "\u0000\u0000\u03d7\u03d8\u0003h,\u0000\u03d8\u03d9\u0001\u0000\u0000\u0000"+
-    "\u03d9\u03da\u0006]\u0013\u0000\u03da\u00cb\u0001\u0000\u0000\u0000\u03db"+
-    "\u03dc\u0003d*\u0000\u03dc\u03dd\u0001\u0000\u0000\u0000\u03dd\u03de\u0006"+
-    "^\u0014\u0000\u03de\u00cd\u0001\u0000\u0000\u0000\u03df\u03e0\u0007\u0010"+
-    "\u0000\u0000\u03e0\u03e1\u0007\u0003\u0000\u0000\u03e1\u03e2\u0007\u0005"+
-    "\u0000\u0000\u03e2\u03e3\u0007\f\u0000\u0000\u03e3\u03e4\u0007\u0000\u0000"+
-    "\u0000\u03e4\u03e5\u0007\f\u0000\u0000\u03e5\u03e6\u0007\u0005\u0000\u0000"+
-    "\u03e6\u03e7\u0007\f\u0000\u0000\u03e7\u00cf\u0001\u0000\u0000\u0000\u03e8"+
-    "\u03ec\b \u0000\u0000\u03e9\u03ea\u0005/\u0000\u0000\u03ea\u03ec\b!\u0000"+
-    "\u0000\u03eb\u03e8\u0001\u0000\u0000\u0000\u03eb\u03e9\u0001\u0000\u0000"+
-    "\u0000\u03ec\u00d1\u0001\u0000\u0000\u0000\u03ed\u03ef\u0003\u00d0`\u0000"+
-    "\u03ee\u03ed\u0001\u0000\u0000\u0000\u03ef\u03f0\u0001\u0000\u0000\u0000"+
-    "\u03f0\u03ee\u0001\u0000\u0000\u0000\u03f0\u03f1\u0001\u0000\u0000\u0000"+
-    "\u03f1\u00d3\u0001\u0000\u0000\u0000\u03f2\u03f3\u0003\u00d2a\u0000\u03f3"+
-    "\u03f4\u0001\u0000\u0000\u0000\u03f4\u03f5\u0006b\u0015\u0000\u03f5\u00d5"+
-    "\u0001\u0000\u0000\u0000\u03f6\u03f7\u0003X$\u0000\u03f7\u03f8\u0001\u0000"+
-    "\u0000\u0000\u03f8\u03f9\u0006c\u0016\u0000\u03f9\u00d7\u0001\u0000\u0000"+
-    "\u0000\u03fa\u03fb\u0003<\u0016\u0000\u03fb\u03fc\u0001\u0000\u0000\u0000"+
-    "\u03fc\u03fd\u0006d\u000b\u0000\u03fd\u00d9\u0001\u0000\u0000\u0000\u03fe"+
-    "\u03ff\u0003>\u0017\u0000\u03ff\u0400\u0001\u0000\u0000\u0000\u0400\u0401"+
-    "\u0006e\u000b\u0000\u0401\u00db\u0001\u0000\u0000\u0000\u0402\u0403\u0003"+
-    "@\u0018\u0000\u0403\u0404\u0001\u0000\u0000\u0000\u0404\u0405\u0006f\u000b"+
-    "\u0000\u0405\u00dd\u0001\u0000\u0000\u0000\u0406\u0407\u0003B\u0019\u0000"+
-    "\u0407\u0408\u0001\u0000\u0000\u0000\u0408\u0409\u0006g\u0010\u0000\u0409"+
-    "\u040a\u0006g\f\u0000\u040a\u00df\u0001\u0000\u0000\u0000\u040b\u040c"+
-    "\u0003l.\u0000\u040c\u040d\u0001\u0000\u0000\u0000\u040d\u040e\u0006h"+
-    "\u0017\u0000\u040e\u00e1\u0001\u0000\u0000\u0000\u040f\u0410\u0003h,\u0000"+
-    "\u0410\u0411\u0001\u0000\u0000\u0000\u0411\u0412\u0006i\u0013\u0000\u0412"+
-    "\u00e3\u0001\u0000\u0000\u0000\u0413\u0418\u0003F\u001b\u0000\u0414\u0418"+
-    "\u0003D\u001a\u0000\u0415\u0418\u0003T\"\u0000\u0416\u0418\u0003\u009e"+
-    "G\u0000\u0417\u0413\u0001\u0000\u0000\u0000\u0417\u0414\u0001\u0000\u0000"+
-    "\u0000\u0417\u0415\u0001\u0000\u0000\u0000\u0417\u0416\u0001\u0000\u0000"+
-    "\u0000\u0418\u00e5\u0001\u0000\u0000\u0000\u0419\u041c\u0003F\u001b\u0000"+
-    "\u041a\u041c\u0003\u009eG\u0000\u041b\u0419\u0001\u0000\u0000\u0000\u041b"+
-    "\u041a\u0001\u0000\u0000\u0000\u041c\u0420\u0001\u0000\u0000\u0000\u041d"+
-    "\u041f\u0003\u00e4j\u0000\u041e\u041d\u0001\u0000\u0000\u0000\u041f\u0422"+
-    "\u0001\u0000\u0000\u0000\u0420\u041e\u0001\u0000\u0000\u0000\u0420\u0421"+
-    "\u0001\u0000\u0000\u0000\u0421\u042d\u0001\u0000\u0000\u0000\u0422\u0420"+
-    "\u0001\u0000\u0000\u0000\u0423\u0426\u0003T\"\u0000\u0424\u0426\u0003"+
-    "N\u001f\u0000\u0425\u0423\u0001\u0000\u0000\u0000\u0425\u0424\u0001\u0000"+
-    "\u0000\u0000\u0426\u0428\u0001\u0000\u0000\u0000\u0427\u0429\u0003\u00e4"+
-    "j\u0000\u0428\u0427\u0001\u0000\u0000\u0000\u0429\u042a\u0001\u0000\u0000"+
-    "\u0000\u042a\u0428\u0001\u0000\u0000\u0000\u042a\u042b\u0001\u0000\u0000"+
-    "\u0000\u042b\u042d\u0001\u0000\u0000\u0000\u042c\u041b\u0001\u0000\u0000"+
-    "\u0000\u042c\u0425\u0001\u0000\u0000\u0000\u042d\u00e7\u0001\u0000\u0000"+
-    "\u0000\u042e\u0431\u0003\u00e6k\u0000\u042f\u0431\u0003\u00aeO\u0000\u0430"+
-    "\u042e\u0001\u0000\u0000\u0000\u0430\u042f\u0001\u0000\u0000\u0000\u0431"+
-    "\u0432\u0001\u0000\u0000\u0000\u0432\u0430\u0001\u0000\u0000\u0000\u0432"+
-    "\u0433\u0001\u0000\u0000\u0000\u0433\u00e9\u0001\u0000\u0000\u0000\u0434"+
-    "\u0435\u0003<\u0016\u0000\u0435\u0436\u0001\u0000\u0000\u0000\u0436\u0437"+
-    "\u0006m\u000b\u0000\u0437\u00eb\u0001\u0000\u0000\u0000\u0438\u0439\u0003"+
-    ">\u0017\u0000\u0439\u043a\u0001\u0000\u0000\u0000\u043a\u043b\u0006n\u000b"+
-    "\u0000\u043b\u00ed\u0001\u0000\u0000\u0000\u043c\u043d\u0003@\u0018\u0000"+
-    "\u043d\u043e\u0001\u0000\u0000\u0000\u043e\u043f\u0006o\u000b\u0000\u043f"+
-    "\u00ef\u0001\u0000\u0000\u0000\u0440\u0441\u0003B\u0019\u0000\u0441\u0442"+
-    "\u0001\u0000\u0000\u0000\u0442\u0443\u0006p\u0010\u0000\u0443\u0444\u0006"+
-    "p\f\u0000\u0444\u00f1\u0001\u0000\u0000\u0000\u0445\u0446\u0003d*\u0000"+
-    "\u0446\u0447\u0001\u0000\u0000\u0000\u0447\u0448\u0006q\u0014\u0000\u0448"+
-    "\u00f3\u0001\u0000\u0000\u0000\u0449\u044a\u0003h,\u0000\u044a\u044b\u0001"+
-    "\u0000\u0000\u0000\u044b\u044c\u0006r\u0013\u0000\u044c\u00f5\u0001\u0000"+
-    "\u0000\u0000\u044d\u044e\u0003l.\u0000\u044e\u044f\u0001\u0000\u0000\u0000"+
-    "\u044f\u0450\u0006s\u0017\u0000\u0450\u00f7\u0001\u0000\u0000\u0000\u0451"+
-    "\u0452\u0007\f\u0000\u0000\u0452\u0453\u0007\u0002\u0000\u0000\u0453\u00f9"+
-    "\u0001\u0000\u0000\u0000\u0454\u0455\u0003\u00e8l\u0000\u0455\u0456\u0001"+
-    "\u0000\u0000\u0000\u0456\u0457\u0006u\u0018\u0000\u0457\u00fb\u0001\u0000"+
-    "\u0000\u0000\u0458\u0459\u0003<\u0016\u0000\u0459\u045a\u0001\u0000\u0000"+
-    "\u0000\u045a\u045b\u0006v\u000b\u0000\u045b\u00fd\u0001\u0000\u0000\u0000"+
-    "\u045c\u045d\u0003>\u0017\u0000\u045d\u045e\u0001\u0000\u0000\u0000\u045e"+
-    "\u045f\u0006w\u000b\u0000\u045f\u00ff\u0001\u0000\u0000\u0000\u0460\u0461"+
-    "\u0003@\u0018\u0000\u0461\u0462\u0001\u0000\u0000\u0000\u0462\u0463\u0006"+
-    "x\u000b\u0000\u0463\u0101\u0001\u0000\u0000\u0000\u0464\u0465\u0003B\u0019"+
-    "\u0000\u0465\u0466\u0001\u0000\u0000\u0000\u0466\u0467\u0006y\u0010\u0000"+
-    "\u0467\u0468\u0006y\f\u0000\u0468\u0103\u0001\u0000\u0000\u0000\u0469"+
-    "\u046a\u0003\u00a8L\u0000\u046a\u046b\u0001\u0000\u0000\u0000\u046b\u046c"+
-    "\u0006z\u000e\u0000\u046c\u046d\u0006z\u0019\u0000\u046d\u0105\u0001\u0000"+
-    "\u0000\u0000\u046e\u046f\u0007\u0007\u0000\u0000\u046f\u0470\u0007\t\u0000"+
-    "\u0000\u0470\u0471\u0001\u0000\u0000\u0000\u0471\u0472\u0006{\u001a\u0000"+
-    "\u0472\u0107\u0001\u0000\u0000\u0000\u0473\u0474\u0007\u0013\u0000\u0000"+
-    "\u0474\u0475\u0007\u0001\u0000\u0000\u0475\u0476\u0007\u0005\u0000\u0000"+
-    "\u0476\u0477\u0007\n\u0000\u0000\u0477\u0478\u0001\u0000\u0000\u0000\u0478"+
-    "\u0479\u0006|\u001a\u0000\u0479\u0109\u0001\u0000\u0000\u0000\u047a\u047b"+
-    "\b\"\u0000\u0000\u047b\u010b\u0001\u0000\u0000\u0000\u047c\u047e\u0003"+
-    "\u010a}\u0000\u047d\u047c\u0001\u0000\u0000\u0000\u047e\u047f\u0001\u0000"+
-    "\u0000\u0000\u047f\u047d\u0001\u0000\u0000\u0000\u047f\u0480\u0001\u0000"+
-    "\u0000\u0000\u0480\u0481\u0001\u0000\u0000\u0000\u0481\u0482\u0003\u014e"+
-    "\u009f\u0000\u0482\u0484\u0001\u0000\u0000\u0000\u0483\u047d\u0001\u0000"+
-    "\u0000\u0000\u0483\u0484\u0001\u0000\u0000\u0000\u0484\u0486\u0001\u0000"+
-    "\u0000\u0000\u0485\u0487\u0003\u010a}\u0000\u0486\u0485\u0001\u0000\u0000"+
-    "\u0000\u0487\u0488\u0001\u0000\u0000\u0000\u0488\u0486\u0001\u0000\u0000"+
-    "\u0000\u0488\u0489\u0001\u0000\u0000\u0000\u0489\u010d\u0001\u0000\u0000"+
-    "\u0000\u048a\u048b\u0003\u010c~\u0000\u048b\u048c\u0001\u0000\u0000\u0000"+
-    "\u048c\u048d\u0006\u007f\u001b\u0000\u048d\u010f\u0001\u0000\u0000\u0000"+
-    "\u048e\u048f\u0003<\u0016\u0000\u048f\u0490\u0001\u0000\u0000\u0000\u0490"+
-    "\u0491\u0006\u0080\u000b\u0000\u0491\u0111\u0001\u0000\u0000\u0000\u0492"+
-    "\u0493\u0003>\u0017\u0000\u0493\u0494\u0001\u0000\u0000\u0000\u0494\u0495"+
-    "\u0006\u0081\u000b\u0000\u0495\u0113\u0001\u0000\u0000\u0000\u0496\u0497"+
-    "\u0003@\u0018\u0000\u0497\u0498\u0001\u0000\u0000\u0000\u0498\u0499\u0006"+
-    "\u0082\u000b\u0000\u0499\u0115\u0001\u0000\u0000\u0000\u049a\u049b\u0003"+
-    "B\u0019\u0000\u049b\u049c\u0001\u0000\u0000\u0000\u049c\u049d\u0006\u0083"+
-    "\u0010\u0000\u049d\u049e\u0006\u0083\f\u0000\u049e\u049f\u0006\u0083\f"+
-    "\u0000\u049f\u0117\u0001\u0000\u0000\u0000\u04a0\u04a1\u0003d*\u0000\u04a1"+
-    "\u04a2\u0001\u0000\u0000\u0000\u04a2\u04a3\u0006\u0084\u0014\u0000\u04a3"+
-    "\u0119\u0001\u0000\u0000\u0000\u04a4\u04a5\u0003h,\u0000\u04a5\u04a6\u0001"+
-    "\u0000\u0000\u0000\u04a6\u04a7\u0006\u0085\u0013\u0000\u04a7\u011b\u0001"+
-    "\u0000\u0000\u0000\u04a8\u04a9\u0003l.\u0000\u04a9\u04aa\u0001\u0000\u0000"+
-    "\u0000\u04aa\u04ab\u0006\u0086\u0017\u0000\u04ab\u011d\u0001\u0000\u0000"+
-    "\u0000\u04ac\u04ad\u0003\u0108|\u0000\u04ad\u04ae\u0001\u0000\u0000\u0000"+
-    "\u04ae\u04af\u0006\u0087\u001c\u0000\u04af\u011f\u0001\u0000\u0000\u0000"+
-    "\u04b0\u04b1\u0003\u00e8l\u0000\u04b1\u04b2\u0001\u0000\u0000\u0000\u04b2"+
-    "\u04b3\u0006\u0088\u0018\u0000\u04b3\u0121\u0001\u0000\u0000\u0000\u04b4"+
-    "\u04b5\u0003\u00b0P\u0000\u04b5\u04b6\u0001\u0000\u0000\u0000\u04b6\u04b7"+
-    "\u0006\u0089\u001d\u0000\u04b7\u0123\u0001\u0000\u0000\u0000\u04b8\u04b9"+
-    "\u0003<\u0016\u0000\u04b9\u04ba\u0001\u0000\u0000\u0000\u04ba\u04bb\u0006"+
-    "\u008a\u000b\u0000\u04bb\u0125\u0001\u0000\u0000\u0000\u04bc\u04bd\u0003"+
-    ">\u0017\u0000\u04bd\u04be\u0001\u0000\u0000\u0000\u04be\u04bf\u0006\u008b"+
-    "\u000b\u0000\u04bf\u0127\u0001\u0000\u0000\u0000\u04c0\u04c1\u0003@\u0018"+
-    "\u0000\u04c1\u04c2\u0001\u0000\u0000\u0000\u04c2\u04c3\u0006\u008c\u000b"+
-    "\u0000\u04c3\u0129\u0001\u0000\u0000\u0000\u04c4\u04c5\u0003B\u0019\u0000"+
-    "\u04c5\u04c6\u0001\u0000\u0000\u0000\u04c6\u04c7\u0006\u008d\u0010\u0000"+
-    "\u04c7\u04c8\u0006\u008d\f\u0000\u04c8\u012b\u0001\u0000\u0000\u0000\u04c9"+
-    "\u04ca\u0003l.\u0000\u04ca\u04cb\u0001\u0000\u0000\u0000\u04cb\u04cc\u0006"+
-    "\u008e\u0017\u0000\u04cc\u012d\u0001\u0000\u0000\u0000\u04cd\u04ce\u0003"+
-    "\u00b0P\u0000\u04ce\u04cf\u0001\u0000\u0000\u0000\u04cf\u04d0\u0006\u008f"+
-    "\u001d\u0000\u04d0\u012f\u0001\u0000\u0000\u0000\u04d1\u04d2\u0003\u00ac"+
-    "N\u0000\u04d2\u04d3\u0001\u0000\u0000\u0000\u04d3\u04d4\u0006\u0090\u001e"+
-    "\u0000\u04d4\u0131\u0001\u0000\u0000\u0000\u04d5\u04d6\u0003<\u0016\u0000"+
-    "\u04d6\u04d7\u0001\u0000\u0000\u0000\u04d7\u04d8\u0006\u0091\u000b\u0000"+
-    "\u04d8\u0133\u0001\u0000\u0000\u0000\u04d9\u04da\u0003>\u0017\u0000\u04da"+
-    "\u04db\u0001\u0000\u0000\u0000\u04db\u04dc\u0006\u0092\u000b\u0000\u04dc"+
-    "\u0135\u0001\u0000\u0000\u0000\u04dd\u04de\u0003@\u0018\u0000\u04de\u04df"+
-    "\u0001\u0000\u0000\u0000\u04df\u04e0\u0006\u0093\u000b\u0000\u04e0\u0137"+
-    "\u0001\u0000\u0000\u0000\u04e1\u04e2\u0003B\u0019\u0000\u04e2\u04e3\u0001"+
-    "\u0000\u0000\u0000\u04e3\u04e4\u0006\u0094\u0010\u0000\u04e4\u04e5\u0006"+
-    "\u0094\f\u0000\u04e5\u0139\u0001\u0000\u0000\u0000\u04e6\u04e7\u0007\u0001"+
-    "\u0000\u0000\u04e7\u04e8\u0007\t\u0000\u0000\u04e8\u04e9\u0007\u000f\u0000"+
-    "\u0000\u04e9\u04ea\u0007\u0007\u0000\u0000\u04ea\u013b\u0001\u0000\u0000"+
-    "\u0000\u04eb\u04ec\u0003<\u0016\u0000\u04ec\u04ed\u0001\u0000\u0000\u0000"+
-    "\u04ed\u04ee\u0006\u0096\u000b\u0000\u04ee\u013d\u0001\u0000\u0000\u0000"+
-    "\u04ef\u04f0\u0003>\u0017\u0000\u04f0\u04f1\u0001\u0000\u0000\u0000\u04f1"+
-    "\u04f2\u0006\u0097\u000b\u0000\u04f2\u013f\u0001\u0000\u0000\u0000\u04f3"+
-    "\u04f4\u0003@\u0018\u0000\u04f4\u04f5\u0001\u0000\u0000\u0000\u04f5\u04f6"+
-    "\u0006\u0098\u000b\u0000\u04f6\u0141\u0001\u0000\u0000\u0000\u04f7\u04f8"+
-    "\u0003B\u0019\u0000\u04f8\u04f9\u0001\u0000\u0000\u0000\u04f9\u04fa\u0006"+
-    "\u0099\u0010\u0000\u04fa\u04fb\u0006\u0099\f\u0000\u04fb\u0143\u0001\u0000"+
-    "\u0000\u0000\u04fc\u04fd\u0007\u000f\u0000\u0000\u04fd\u04fe\u0007\u0014"+
-    "\u0000\u0000\u04fe\u04ff\u0007\t\u0000\u0000\u04ff\u0500\u0007\u0004\u0000"+
-    "\u0000\u0500\u0501\u0007\u0005\u0000\u0000\u0501\u0502\u0007\u0001\u0000"+
-    "\u0000\u0502\u0503\u0007\u0007\u0000\u0000\u0503\u0504\u0007\t\u0000\u0000"+
-    "\u0504\u0505\u0007\u0002\u0000\u0000\u0505\u0145\u0001\u0000\u0000\u0000"+
-    "\u0506\u0507\u0003<\u0016\u0000\u0507\u0508\u0001\u0000\u0000\u0000\u0508"+
-    "\u0509\u0006\u009b\u000b\u0000\u0509\u0147\u0001\u0000\u0000\u0000\u050a"+
-    "\u050b\u0003>\u0017\u0000\u050b\u050c\u0001\u0000\u0000\u0000\u050c\u050d"+
-    "\u0006\u009c\u000b\u0000\u050d\u0149\u0001\u0000\u0000\u0000\u050e\u050f"+
-    "\u0003@\u0018\u0000\u050f\u0510\u0001\u0000\u0000\u0000\u0510\u0511\u0006"+
-    "\u009d\u000b\u0000\u0511\u014b\u0001\u0000\u0000\u0000\u0512\u0513\u0003"+
-    "\u00aaM\u0000\u0513\u0514\u0001\u0000\u0000\u0000\u0514\u0515\u0006\u009e"+
-    "\u0011\u0000\u0515\u0516\u0006\u009e\f\u0000\u0516\u014d\u0001\u0000\u0000"+
-    "\u0000\u0517\u0518\u0005:\u0000\u0000\u0518\u014f\u0001\u0000\u0000\u0000"+
-    "\u0519\u051f\u0003N\u001f\u0000\u051a\u051f\u0003D\u001a\u0000\u051b\u051f"+
-    "\u0003l.\u0000\u051c\u051f\u0003F\u001b\u0000\u051d\u051f\u0003T\"\u0000"+
-    "\u051e\u0519\u0001\u0000\u0000\u0000\u051e\u051a\u0001\u0000\u0000\u0000"+
-    "\u051e\u051b\u0001\u0000\u0000\u0000\u051e\u051c\u0001\u0000\u0000\u0000"+
-    "\u051e\u051d\u0001\u0000\u0000\u0000\u051f\u0520\u0001\u0000\u0000\u0000"+
-    "\u0520\u051e\u0001\u0000\u0000\u0000\u0520\u0521\u0001\u0000\u0000\u0000"+
-    "\u0521\u0151\u0001\u0000\u0000\u0000\u0522\u0523\u0003<\u0016\u0000\u0523"+
-    "\u0524\u0001\u0000\u0000\u0000\u0524\u0525\u0006\u00a1\u000b\u0000\u0525"+
-    "\u0153\u0001\u0000\u0000\u0000\u0526\u0527\u0003>\u0017\u0000\u0527\u0528"+
-    "\u0001\u0000\u0000\u0000\u0528\u0529\u0006\u00a2\u000b\u0000\u0529\u0155"+
-    "\u0001\u0000\u0000\u0000\u052a\u052b\u0003@\u0018\u0000\u052b\u052c\u0001"+
-    "\u0000\u0000\u0000\u052c\u052d\u0006\u00a3\u000b\u0000\u052d\u0157\u0001"+
-    "\u0000\u0000\u0000\u052e\u052f\u0003B\u0019\u0000\u052f\u0530\u0001\u0000"+
-    "\u0000\u0000\u0530\u0531\u0006\u00a4\u0010\u0000\u0531\u0532\u0006\u00a4"+
-    "\f\u0000\u0532\u0159\u0001\u0000\u0000\u0000\u0533\u0534\u0003\u014e\u009f"+
-    "\u0000\u0534\u0535\u0001\u0000\u0000\u0000\u0535\u0536\u0006\u00a5\u0012"+
-    "\u0000\u0536\u015b\u0001\u0000\u0000\u0000\u0537\u0538\u0003h,\u0000\u0538"+
-    "\u0539\u0001\u0000\u0000\u0000\u0539\u053a\u0006\u00a6\u0013\u0000\u053a"+
-    "\u015d\u0001\u0000\u0000\u0000\u053b\u053c\u0003l.\u0000\u053c\u053d\u0001"+
-    "\u0000\u0000\u0000\u053d\u053e\u0006\u00a7\u0017\u0000\u053e\u015f\u0001"+
-    "\u0000\u0000\u0000\u053f\u0540\u0003\u0106{\u0000\u0540\u0541\u0001\u0000"+
-    "\u0000\u0000\u0541\u0542\u0006\u00a8\u001f\u0000\u0542\u0543\u0006\u00a8"+
-    " \u0000\u0543\u0161\u0001\u0000\u0000\u0000\u0544\u0545\u0003\u00d2a\u0000"+
-    "\u0545\u0546\u0001\u0000\u0000\u0000\u0546\u0547\u0006\u00a9\u0015\u0000"+
-    "\u0547\u0163\u0001\u0000\u0000\u0000\u0548\u0549\u0003X$\u0000\u0549\u054a"+
-    "\u0001\u0000\u0000\u0000\u054a\u054b\u0006\u00aa\u0016\u0000\u054b\u0165"+
-    "\u0001\u0000\u0000\u0000\u054c\u054d\u0003<\u0016\u0000\u054d\u054e\u0001"+
-    "\u0000\u0000\u0000\u054e\u054f\u0006\u00ab\u000b\u0000\u054f\u0167\u0001"+
-    "\u0000\u0000\u0000\u0550\u0551\u0003>\u0017\u0000\u0551\u0552\u0001\u0000"+
-    "\u0000\u0000\u0552\u0553\u0006\u00ac\u000b\u0000\u0553\u0169\u0001\u0000"+
-    "\u0000\u0000\u0554\u0555\u0003@\u0018\u0000\u0555\u0556\u0001\u0000\u0000"+
-    "\u0000\u0556\u0557\u0006\u00ad\u000b\u0000\u0557\u016b\u0001\u0000\u0000"+
-    "\u0000\u0558\u0559\u0003B\u0019\u0000\u0559\u055a\u0001\u0000\u0000\u0000"+
-    "\u055a\u055b\u0006\u00ae\u0010\u0000\u055b\u055c\u0006\u00ae\f\u0000\u055c"+
-    "\u055d\u0006\u00ae\f\u0000\u055d\u016d\u0001\u0000\u0000\u0000\u055e\u055f"+
-    "\u0003h,\u0000\u055f\u0560\u0001\u0000\u0000\u0000\u0560\u0561\u0006\u00af"+
-    "\u0013\u0000\u0561\u016f\u0001\u0000\u0000\u0000\u0562\u0563\u0003l.\u0000"+
-    "\u0563\u0564\u0001\u0000\u0000\u0000\u0564\u0565\u0006\u00b0\u0017\u0000"+
-    "\u0565\u0171\u0001\u0000\u0000\u0000\u0566\u0567\u0003\u00e8l\u0000\u0567"+
-    "\u0568\u0001\u0000\u0000\u0000\u0568\u0569\u0006\u00b1\u0018\u0000\u0569"+
-    "\u0173\u0001\u0000\u0000\u0000\u056a\u056b\u0003<\u0016\u0000\u056b\u056c"+
-    "\u0001\u0000\u0000\u0000\u056c\u056d\u0006\u00b2\u000b\u0000\u056d\u0175"+
-    "\u0001\u0000\u0000\u0000\u056e\u056f\u0003>\u0017\u0000\u056f\u0570\u0001"+
-    "\u0000\u0000\u0000\u0570\u0571\u0006\u00b3\u000b\u0000\u0571\u0177\u0001"+
-    "\u0000\u0000\u0000\u0572\u0573\u0003@\u0018\u0000\u0573\u0574\u0001\u0000"+
-    "\u0000\u0000\u0574\u0575\u0006\u00b4\u000b\u0000\u0575\u0179\u0001\u0000"+
-    "\u0000\u0000\u0576\u0577\u0003B\u0019\u0000\u0577\u0578\u0001\u0000\u0000"+
-    "\u0000\u0578\u0579\u0006\u00b5\u0010\u0000\u0579\u057a\u0006\u00b5\f\u0000"+
-    "\u057a\u017b\u0001\u0000\u0000\u0000\u057b\u057c\u0003\u00d2a\u0000\u057c"+
-    "\u057d\u0001\u0000\u0000\u0000\u057d\u057e\u0006\u00b6\u0015\u0000\u057e"+
-    "\u057f\u0006\u00b6\f\u0000\u057f\u0580\u0006\u00b6!\u0000\u0580\u017d"+
-    "\u0001\u0000\u0000\u0000\u0581\u0582\u0003X$\u0000\u0582\u0583\u0001\u0000"+
-    "\u0000\u0000\u0583\u0584\u0006\u00b7\u0016\u0000\u0584\u0585\u0006\u00b7"+
-    "\f\u0000\u0585\u0586\u0006\u00b7!\u0000\u0586\u017f\u0001\u0000\u0000"+
-    "\u0000\u0587\u0588\u0003<\u0016\u0000\u0588\u0589\u0001\u0000\u0000\u0000"+
-    "\u0589\u058a\u0006\u00b8\u000b\u0000\u058a\u0181\u0001\u0000\u0000\u0000"+
-    "\u058b\u058c\u0003>\u0017\u0000\u058c\u058d\u0001\u0000\u0000\u0000\u058d"+
-    "\u058e\u0006\u00b9\u000b\u0000\u058e\u0183\u0001\u0000\u0000\u0000\u058f"+
-    "\u0590\u0003@\u0018\u0000\u0590\u0591\u0001\u0000\u0000\u0000\u0591\u0592"+
-    "\u0006\u00ba\u000b\u0000\u0592\u0185\u0001\u0000\u0000\u0000\u0593\u0594"+
-    "\u0003\u014e\u009f\u0000\u0594\u0595\u0001\u0000\u0000\u0000\u0595\u0596"+
-    "\u0006\u00bb\u0012\u0000\u0596\u0597\u0006\u00bb\f\u0000\u0597\u0598\u0006"+
-    "\u00bb\n\u0000\u0598\u0187\u0001\u0000\u0000\u0000\u0599\u059a\u0003h"+
-    ",\u0000\u059a\u059b\u0001\u0000\u0000\u0000\u059b\u059c\u0006\u00bc\u0013"+
-    "\u0000\u059c\u059d\u0006\u00bc\f\u0000\u059d\u059e\u0006\u00bc\n\u0000"+
-    "\u059e\u0189\u0001\u0000\u0000\u0000\u059f\u05a0\u0003<\u0016\u0000\u05a0"+
-    "\u05a1\u0001\u0000\u0000\u0000\u05a1\u05a2\u0006\u00bd\u000b\u0000\u05a2"+
-    "\u018b\u0001\u0000\u0000\u0000\u05a3\u05a4\u0003>\u0017\u0000\u05a4\u05a5"+
-    "\u0001\u0000\u0000\u0000\u05a5\u05a6\u0006\u00be\u000b\u0000\u05a6\u018d"+
-    "\u0001\u0000\u0000\u0000\u05a7\u05a8\u0003@\u0018\u0000\u05a8\u05a9\u0001"+
-    "\u0000\u0000\u0000\u05a9\u05aa\u0006\u00bf\u000b\u0000\u05aa\u018f\u0001"+
-    "\u0000\u0000\u0000\u05ab\u05ac\u0003\u00b0P\u0000\u05ac\u05ad\u0001\u0000"+
-    "\u0000\u0000\u05ad\u05ae\u0006\u00c0\f\u0000\u05ae\u05af\u0006\u00c0\u0000"+
-    "\u0000\u05af\u05b0\u0006\u00c0\u001d\u0000\u05b0\u0191\u0001\u0000\u0000"+
-    "\u0000\u05b1\u05b2\u0003\u00acN\u0000\u05b2\u05b3\u0001\u0000\u0000\u0000"+
-    "\u05b3\u05b4\u0006\u00c1\f\u0000\u05b4\u05b5\u0006\u00c1\u0000\u0000\u05b5"+
-    "\u05b6\u0006\u00c1\u001e\u0000\u05b6\u0193\u0001\u0000\u0000\u0000\u05b7"+
-    "\u05b8\u0003^\'\u0000\u05b8\u05b9\u0001\u0000\u0000\u0000\u05b9\u05ba"+
-    "\u0006\u00c2\f\u0000\u05ba\u05bb\u0006\u00c2\u0000\u0000\u05bb\u05bc\u0006"+
-    "\u00c2\"\u0000\u05bc\u0195\u0001\u0000\u0000\u0000\u05bd\u05be\u0003B"+
-    "\u0019\u0000\u05be\u05bf\u0001\u0000\u0000\u0000\u05bf\u05c0\u0006\u00c3"+
-    "\u0010\u0000\u05c0\u05c1\u0006\u00c3\f\u0000\u05c1\u0197\u0001\u0000\u0000"+
-    "\u0000B\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f"+
-    "\r\u000e\u000f\u0250\u025a\u025e\u0261\u026a\u026c\u0277\u028a\u028f\u0298"+
-    "\u029f\u02a4\u02a6\u02b1\u02b9\u02bc\u02be\u02c3\u02c8\u02ce\u02d5\u02da"+
-    "\u02e0\u02e3\u02eb\u02ef\u036e\u0373\u037a\u037c\u038c\u0391\u0396\u0398"+
-    "\u039e\u03eb\u03f0\u0417\u041b\u0420\u0425\u042a\u042c\u0430\u0432\u047f"+
-    "\u0483\u0488\u051e\u0520#\u0005\u0001\u0000\u0005\u0004\u0000\u0005\u0006"+
-    "\u0000\u0005\u0002\u0000\u0005\u0003\u0000\u0005\n\u0000\u0005\b\u0000"+
-    "\u0005\u0005\u0000\u0005\t\u0000\u0005\f\u0000\u0005\u000e\u0000\u0000"+
-    "\u0001\u0000\u0004\u0000\u0000\u0007\u0014\u0000\u0007B\u0000\u0005\u0000"+
-    "\u0000\u0007\u001a\u0000\u0007C\u0000\u0007m\u0000\u0007#\u0000\u0007"+
-    "!\u0000\u0007M\u0000\u0007\u001b\u0000\u0007%\u0000\u0007Q\u0000\u0005"+
-    "\u000b\u0000\u0005\u0007\u0000\u0007[\u0000\u0007Z\u0000\u0007E\u0000"+
-    "\u0007D\u0000\u0007Y\u0000\u0005\r\u0000\u0005\u000f\u0000\u0007\u001e"+
-    "\u0000";
+    "i\u0003i\u0404\bi\u0001j\u0001j\u0003j\u0408\bj\u0001j\u0005j\u040b\b"+
+    "j\nj\fj\u040e\tj\u0001j\u0001j\u0003j\u0412\bj\u0001j\u0004j\u0415\bj"+
+    "\u000bj\fj\u0416\u0003j\u0419\bj\u0001k\u0001k\u0004k\u041d\bk\u000bk"+
+    "\fk\u041e\u0001l\u0001l\u0001l\u0001l\u0001m\u0001m\u0001m\u0001m\u0001"+
+    "n\u0001n\u0001n\u0001n\u0001o\u0001o\u0001o\u0001o\u0001o\u0001p\u0001"+
+    "p\u0001p\u0001p\u0001q\u0001q\u0001q\u0001q\u0001r\u0001r\u0001r\u0001"+
+    "r\u0001s\u0001s\u0001s\u0001t\u0001t\u0001t\u0001t\u0001u\u0001u\u0001"+
+    "u\u0001u\u0001v\u0001v\u0001v\u0001v\u0001w\u0001w\u0001w\u0001w\u0001"+
+    "x\u0001x\u0001x\u0001x\u0001x\u0001y\u0001y\u0001y\u0001y\u0001y\u0001"+
+    "z\u0001z\u0001z\u0001z\u0001z\u0001{\u0001{\u0001{\u0001{\u0001{\u0001"+
+    "{\u0001{\u0001|\u0001|\u0001}\u0004}\u046a\b}\u000b}\f}\u046b\u0001}\u0001"+
+    "}\u0003}\u0470\b}\u0001}\u0004}\u0473\b}\u000b}\f}\u0474\u0001~\u0001"+
+    "~\u0001~\u0001~\u0001\u007f\u0001\u007f\u0001\u007f\u0001\u007f\u0001"+
+    "\u0080\u0001\u0080\u0001\u0080\u0001\u0080\u0001\u0081\u0001\u0081\u0001"+
+    "\u0081\u0001\u0081\u0001\u0082\u0001\u0082\u0001\u0082\u0001\u0082\u0001"+
+    "\u0082\u0001\u0082\u0001\u0083\u0001\u0083\u0001\u0083\u0001\u0083\u0001"+
+    "\u0084\u0001\u0084\u0001\u0084\u0001\u0084\u0001\u0085\u0001\u0085\u0001"+
+    "\u0085\u0001\u0085\u0001\u0086\u0001\u0086\u0001\u0086\u0001\u0086\u0001"+
+    "\u0087\u0001\u0087\u0001\u0087\u0001\u0087\u0001\u0088\u0001\u0088\u0001"+
+    "\u0088\u0001\u0088\u0001\u0089\u0001\u0089\u0001\u0089\u0001\u0089\u0001"+
+    "\u008a\u0001\u008a\u0001\u008a\u0001\u008a\u0001\u008b\u0001\u008b\u0001"+
+    "\u008b\u0001\u008b\u0001\u008c\u0001\u008c\u0001\u008c\u0001\u008c\u0001"+
+    "\u008c\u0001\u008d\u0001\u008d\u0001\u008d\u0001\u008d\u0001\u008e\u0001"+
+    "\u008e\u0001\u008e\u0001\u008e\u0001\u008f\u0001\u008f\u0001\u008f\u0001"+
+    "\u008f\u0001\u0090\u0001\u0090\u0001\u0090\u0001\u0090\u0001\u0091\u0001"+
+    "\u0091\u0001\u0091\u0001\u0091\u0001\u0092\u0001\u0092\u0001\u0092\u0001"+
+    "\u0092\u0001\u0093\u0001\u0093\u0001\u0093\u0001\u0093\u0001\u0093\u0001"+
+    "\u0094\u0001\u0094\u0001\u0094\u0001\u0094\u0001\u0094\u0001\u0095\u0001"+
+    "\u0095\u0001\u0095\u0001\u0095\u0001\u0096\u0001\u0096\u0001\u0096\u0001"+
+    "\u0096\u0001\u0097\u0001\u0097\u0001\u0097\u0001\u0097\u0001\u0098\u0001"+
+    "\u0098\u0001\u0098\u0001\u0098\u0001\u0098\u0001\u0099\u0001\u0099\u0001"+
+    "\u009a\u0001\u009a\u0001\u009a\u0001\u009a\u0001\u009a\u0004\u009a\u04f0"+
+    "\b\u009a\u000b\u009a\f\u009a\u04f1\u0001\u009b\u0001\u009b\u0001\u009b"+
+    "\u0001\u009b\u0001\u009c\u0001\u009c\u0001\u009c\u0001\u009c\u0001\u009d"+
+    "\u0001\u009d\u0001\u009d\u0001\u009d\u0001\u009e\u0001\u009e\u0001\u009e"+
+    "\u0001\u009e\u0001\u009e\u0001\u009f\u0001\u009f\u0001\u009f\u0001\u009f"+
+    "\u0001\u00a0\u0001\u00a0\u0001\u00a0\u0001\u00a0\u0001\u00a1\u0001\u00a1"+
+    "\u0001\u00a1\u0001\u00a1\u0001\u00a2\u0001\u00a2\u0001\u00a2\u0001\u00a2"+
+    "\u0001\u00a2\u0001\u00a3\u0001\u00a3\u0001\u00a3\u0001\u00a3\u0001\u00a4"+
+    "\u0001\u00a4\u0001\u00a4\u0001\u00a4\u0001\u00a5\u0001\u00a5\u0001\u00a5"+
+    "\u0001\u00a5\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a7"+
+    "\u0001\u00a7\u0001\u00a7\u0001\u00a7\u0001\u00a8\u0001\u00a8\u0001\u00a8"+
+    "\u0001\u00a8\u0001\u00a8\u0001\u00a8\u0001\u00a9\u0001\u00a9\u0001\u00a9"+
+    "\u0001\u00a9\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00ab"+
+    "\u0001\u00ab\u0001\u00ab\u0001\u00ab\u0001\u00ac\u0001\u00ac\u0001\u00ac"+
+    "\u0001\u00ac\u0001\u00ad\u0001\u00ad\u0001\u00ad\u0001\u00ad\u0001\u00ae"+
+    "\u0001\u00ae\u0001\u00ae\u0001\u00ae\u0001\u00af\u0001\u00af\u0001\u00af"+
+    "\u0001\u00af\u0001\u00af\u0001\u00b0\u0001\u00b0\u0001\u00b0\u0001\u00b0"+
+    "\u0001\u00b0\u0001\u00b0\u0001\u00b1\u0001\u00b1\u0001\u00b1\u0001\u00b1"+
+    "\u0001\u00b1\u0001\u00b1\u0001\u00b2\u0001\u00b2\u0001\u00b2\u0001\u00b2"+
+    "\u0001\u00b3\u0001\u00b3\u0001\u00b3\u0001\u00b3\u0001\u00b4\u0001\u00b4"+
+    "\u0001\u00b4\u0001\u00b4\u0001\u00b5\u0001\u00b5\u0001\u00b5\u0001\u00b5"+
+    "\u0001\u00b5\u0001\u00b5\u0001\u00b6\u0001\u00b6\u0001\u00b6\u0001\u00b6"+
+    "\u0001\u00b6\u0001\u00b6\u0001\u00b7\u0001\u00b7\u0001\u00b7\u0001\u00b7"+
+    "\u0001\u00b8\u0001\u00b8\u0001\u00b8\u0001\u00b8\u0001\u00b9\u0001\u00b9"+
+    "\u0001\u00b9\u0001\u00b9\u0001\u00ba\u0001\u00ba\u0001\u00ba\u0001\u00ba"+
+    "\u0001\u00ba\u0001\u00ba\u0001\u00bb\u0001\u00bb\u0001\u00bb\u0001\u00bb"+
+    "\u0001\u00bb\u0001\u00bb\u0001\u00bc\u0001\u00bc\u0001\u00bc\u0001\u00bc"+
+    "\u0001\u00bc\u0001\u00bc\u0001\u00bd\u0001\u00bd\u0001\u00bd\u0001\u00bd"+
+    "\u0001\u00bd\u0002\u0258\u029d\u0000\u00be\u000f\u0001\u0011\u0002\u0013"+
+    "\u0003\u0015\u0004\u0017\u0005\u0019\u0006\u001b\u0007\u001d\b\u001f\t"+
+    "!\n#\u000b%\f\'\r)\u000e+\u000f-\u0010/\u00111\u00123\u00135\u00147\u0015"+
+    "9\u0016;\u0017=\u0018?\u0019A\u0000C\u0000E\u0000G\u0000I\u0000K\u0000"+
+    "M\u0000O\u0000Q\u0000S\u0000U\u001aW\u001bY\u001c[\u001d]\u001e_\u001f"+
+    "a c!e\"g#i$k%m&o\'q(s)u*w+y,{-}.\u007f/\u00810\u00831\u00852\u00873\u0089"+
+    "4\u008b5\u008d6\u008f7\u00918\u00939\u0095:\u0097;\u0099<\u009b=\u009d"+
+    ">\u009f?\u00a1\u0000\u00a3@\u00a5A\u00a7B\u00a9C\u00ab\u0000\u00adD\u00af"+
+    "E\u00b1F\u00b3G\u00b5\u0000\u00b7\u0000\u00b9H\u00bbI\u00bdJ\u00bf\u0000"+
+    "\u00c1\u0000\u00c3\u0000\u00c5\u0000\u00c7\u0000\u00c9\u0000\u00cbK\u00cd"+
+    "\u0000\u00cfL\u00d1\u0000\u00d3\u0000\u00d5M\u00d7N\u00d9O\u00db\u0000"+
+    "\u00dd\u0000\u00df\u0000\u00e1\u0000\u00e3\u0000\u00e5P\u00e7Q\u00e9R"+
+    "\u00ebS\u00ed\u0000\u00ef\u0000\u00f1\u0000\u00f3\u0000\u00f5T\u00f7\u0000"+
+    "\u00f9U\u00fbV\u00fdW\u00ff\u0000\u0101\u0000\u0103X\u0105Y\u0107\u0000"+
+    "\u0109Z\u010b\u0000\u010d[\u010f\\\u0111]\u0113\u0000\u0115\u0000\u0117"+
+    "\u0000\u0119\u0000\u011b\u0000\u011d\u0000\u011f\u0000\u0121^\u0123_\u0125"+
+    "`\u0127\u0000\u0129\u0000\u012b\u0000\u012d\u0000\u012fa\u0131b\u0133"+
+    "c\u0135\u0000\u0137d\u0139e\u013bf\u013dg\u013f\u0000\u0141h\u0143i\u0145"+
+    "j\u0147k\u0149l\u014b\u0000\u014d\u0000\u014f\u0000\u0151\u0000\u0153"+
+    "\u0000\u0155\u0000\u0157\u0000\u0159m\u015bn\u015do\u015f\u0000\u0161"+
+    "\u0000\u0163\u0000\u0165\u0000\u0167p\u0169q\u016br\u016d\u0000\u016f"+
+    "\u0000\u0171\u0000\u0173s\u0175t\u0177u\u0179\u0000\u017b\u0000\u017d"+
+    "v\u017fw\u0181x\u0183\u0000\u0185\u0000\u0187\u0000\u0189\u0000\u000f"+
+    "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e"+
+    "#\u0002\u0000DDdd\u0002\u0000IIii\u0002\u0000SSss\u0002\u0000EEee\u0002"+
+    "\u0000CCcc\u0002\u0000TTtt\u0002\u0000RRrr\u0002\u0000OOoo\u0002\u0000"+
+    "PPpp\u0002\u0000NNnn\u0002\u0000HHhh\u0002\u0000VVvv\u0002\u0000AAaa\u0002"+
+    "\u0000LLll\u0002\u0000XXxx\u0002\u0000FFff\u0002\u0000MMmm\u0002\u0000"+
+    "GGgg\u0002\u0000KKkk\u0002\u0000WWww\u0002\u0000UUuu\u0006\u0000\t\n\r"+
+    "\r  //[[]]\u0002\u0000\n\n\r\r\u0003\u0000\t\n\r\r  \u0001\u000009\u0002"+
+    "\u0000AZaz\b\u0000\"\"NNRRTT\\\\nnrrtt\u0004\u0000\n\n\r\r\"\"\\\\\u0002"+
+    "\u0000++--\u0001\u0000``\u0002\u0000BBbb\u0002\u0000YYyy\u000b\u0000\t"+
+    "\n\r\r  \"\",,//::==[[]]||\u0002\u0000**//\u000b\u0000\t\n\r\r  \"#,,"+
+    "//::<<>?\\\\||\u05af\u0000\u000f\u0001\u0000\u0000\u0000\u0000\u0011\u0001"+
+    "\u0000\u0000\u0000\u0000\u0013\u0001\u0000\u0000\u0000\u0000\u0015\u0001"+
+    "\u0000\u0000\u0000\u0000\u0017\u0001\u0000\u0000\u0000\u0000\u0019\u0001"+
+    "\u0000\u0000\u0000\u0000\u001b\u0001\u0000\u0000\u0000\u0000\u001d\u0001"+
+    "\u0000\u0000\u0000\u0000\u001f\u0001\u0000\u0000\u0000\u0000!\u0001\u0000"+
+    "\u0000\u0000\u0000#\u0001\u0000\u0000\u0000\u0000%\u0001\u0000\u0000\u0000"+
+    "\u0000\'\u0001\u0000\u0000\u0000\u0000)\u0001\u0000\u0000\u0000\u0000"+
+    "+\u0001\u0000\u0000\u0000\u0000-\u0001\u0000\u0000\u0000\u0000/\u0001"+
+    "\u0000\u0000\u0000\u00001\u0001\u0000\u0000\u0000\u00003\u0001\u0000\u0000"+
+    "\u0000\u00005\u0001\u0000\u0000\u0000\u00007\u0001\u0000\u0000\u0000\u0000"+
+    "9\u0001\u0000\u0000\u0000\u0000;\u0001\u0000\u0000\u0000\u0000=\u0001"+
+    "\u0000\u0000\u0000\u0001?\u0001\u0000\u0000\u0000\u0001U\u0001\u0000\u0000"+
+    "\u0000\u0001W\u0001\u0000\u0000\u0000\u0001Y\u0001\u0000\u0000\u0000\u0001"+
+    "[\u0001\u0000\u0000\u0000\u0001]\u0001\u0000\u0000\u0000\u0001_\u0001"+
+    "\u0000\u0000\u0000\u0001a\u0001\u0000\u0000\u0000\u0001c\u0001\u0000\u0000"+
+    "\u0000\u0001e\u0001\u0000\u0000\u0000\u0001g\u0001\u0000\u0000\u0000\u0001"+
+    "i\u0001\u0000\u0000\u0000\u0001k\u0001\u0000\u0000\u0000\u0001m\u0001"+
+    "\u0000\u0000\u0000\u0001o\u0001\u0000\u0000\u0000\u0001q\u0001\u0000\u0000"+
+    "\u0000\u0001s\u0001\u0000\u0000\u0000\u0001u\u0001\u0000\u0000\u0000\u0001"+
+    "w\u0001\u0000\u0000\u0000\u0001y\u0001\u0000\u0000\u0000\u0001{\u0001"+
+    "\u0000\u0000\u0000\u0001}\u0001\u0000\u0000\u0000\u0001\u007f\u0001\u0000"+
+    "\u0000\u0000\u0001\u0081\u0001\u0000\u0000\u0000\u0001\u0083\u0001\u0000"+
+    "\u0000\u0000\u0001\u0085\u0001\u0000\u0000\u0000\u0001\u0087\u0001\u0000"+
+    "\u0000\u0000\u0001\u0089\u0001\u0000\u0000\u0000\u0001\u008b\u0001\u0000"+
+    "\u0000\u0000\u0001\u008d\u0001\u0000\u0000\u0000\u0001\u008f\u0001\u0000"+
+    "\u0000\u0000\u0001\u0091\u0001\u0000\u0000\u0000\u0001\u0093\u0001\u0000"+
+    "\u0000\u0000\u0001\u0095\u0001\u0000\u0000\u0000\u0001\u0097\u0001\u0000"+
+    "\u0000\u0000\u0001\u0099\u0001\u0000\u0000\u0000\u0001\u009b\u0001\u0000"+
+    "\u0000\u0000\u0001\u009d\u0001\u0000\u0000\u0000\u0001\u009f\u0001\u0000"+
+    "\u0000\u0000\u0001\u00a1\u0001\u0000\u0000\u0000\u0001\u00a3\u0001\u0000"+
+    "\u0000\u0000\u0001\u00a5\u0001\u0000\u0000\u0000\u0001\u00a7\u0001\u0000"+
+    "\u0000\u0000\u0001\u00a9\u0001\u0000\u0000\u0000\u0001\u00ad\u0001\u0000"+
+    "\u0000\u0000\u0001\u00af\u0001\u0000\u0000\u0000\u0001\u00b1\u0001\u0000"+
+    "\u0000\u0000\u0001\u00b3\u0001\u0000\u0000\u0000\u0002\u00b5\u0001\u0000"+
+    "\u0000\u0000\u0002\u00b7\u0001\u0000\u0000\u0000\u0002\u00b9\u0001\u0000"+
+    "\u0000\u0000\u0002\u00bb\u0001\u0000\u0000\u0000\u0002\u00bd\u0001\u0000"+
+    "\u0000\u0000\u0003\u00bf\u0001\u0000\u0000\u0000\u0003\u00c1\u0001\u0000"+
+    "\u0000\u0000\u0003\u00c3\u0001\u0000\u0000\u0000\u0003\u00c5\u0001\u0000"+
+    "\u0000\u0000\u0003\u00c7\u0001\u0000\u0000\u0000\u0003\u00c9\u0001\u0000"+
+    "\u0000\u0000\u0003\u00cb\u0001\u0000\u0000\u0000\u0003\u00cf\u0001\u0000"+
+    "\u0000\u0000\u0003\u00d1\u0001\u0000\u0000\u0000\u0003\u00d3\u0001\u0000"+
+    "\u0000\u0000\u0003\u00d5\u0001\u0000\u0000\u0000\u0003\u00d7\u0001\u0000"+
+    "\u0000\u0000\u0003\u00d9\u0001\u0000\u0000\u0000\u0004\u00db\u0001\u0000"+
+    "\u0000\u0000\u0004\u00dd\u0001\u0000\u0000\u0000\u0004\u00df\u0001\u0000"+
+    "\u0000\u0000\u0004\u00e5\u0001\u0000\u0000\u0000\u0004\u00e7\u0001\u0000"+
+    "\u0000\u0000\u0004\u00e9\u0001\u0000\u0000\u0000\u0004\u00eb\u0001\u0000"+
+    "\u0000\u0000\u0005\u00ed\u0001\u0000\u0000\u0000\u0005\u00ef\u0001\u0000"+
+    "\u0000\u0000\u0005\u00f1\u0001\u0000\u0000\u0000\u0005\u00f3\u0001\u0000"+
+    "\u0000\u0000\u0005\u00f5\u0001\u0000\u0000\u0000\u0005\u00f7\u0001\u0000"+
+    "\u0000\u0000\u0005\u00f9\u0001\u0000\u0000\u0000\u0005\u00fb\u0001\u0000"+
+    "\u0000\u0000\u0005\u00fd\u0001\u0000\u0000\u0000\u0006\u00ff\u0001\u0000"+
+    "\u0000\u0000\u0006\u0101\u0001\u0000\u0000\u0000\u0006\u0103\u0001\u0000"+
+    "\u0000\u0000\u0006\u0105\u0001\u0000\u0000\u0000\u0006\u0109\u0001\u0000"+
+    "\u0000\u0000\u0006\u010b\u0001\u0000\u0000\u0000\u0006\u010d\u0001\u0000"+
+    "\u0000\u0000\u0006\u010f\u0001\u0000\u0000\u0000\u0006\u0111\u0001\u0000"+
+    "\u0000\u0000\u0007\u0113\u0001\u0000\u0000\u0000\u0007\u0115\u0001\u0000"+
+    "\u0000\u0000\u0007\u0117\u0001\u0000\u0000\u0000\u0007\u0119\u0001\u0000"+
+    "\u0000\u0000\u0007\u011b\u0001\u0000\u0000\u0000\u0007\u011d\u0001\u0000"+
+    "\u0000\u0000\u0007\u011f\u0001\u0000\u0000\u0000\u0007\u0121\u0001\u0000"+
+    "\u0000\u0000\u0007\u0123\u0001\u0000\u0000\u0000\u0007\u0125\u0001\u0000"+
+    "\u0000\u0000\b\u0127\u0001\u0000\u0000\u0000\b\u0129\u0001\u0000\u0000"+
+    "\u0000\b\u012b\u0001\u0000\u0000\u0000\b\u012d\u0001\u0000\u0000\u0000"+
+    "\b\u012f\u0001\u0000\u0000\u0000\b\u0131\u0001\u0000\u0000\u0000\b\u0133"+
+    "\u0001\u0000\u0000\u0000\t\u0135\u0001\u0000\u0000\u0000\t\u0137\u0001"+
+    "\u0000\u0000\u0000\t\u0139\u0001\u0000\u0000\u0000\t\u013b\u0001\u0000"+
+    "\u0000\u0000\t\u013d\u0001\u0000\u0000\u0000\n\u013f\u0001\u0000\u0000"+
+    "\u0000\n\u0141\u0001\u0000\u0000\u0000\n\u0143\u0001\u0000\u0000\u0000"+
+    "\n\u0145\u0001\u0000\u0000\u0000\n\u0147\u0001\u0000\u0000\u0000\n\u0149"+
+    "\u0001\u0000\u0000\u0000\u000b\u014b\u0001\u0000\u0000\u0000\u000b\u014d"+
+    "\u0001\u0000\u0000\u0000\u000b\u014f\u0001\u0000\u0000\u0000\u000b\u0151"+
+    "\u0001\u0000\u0000\u0000\u000b\u0153\u0001\u0000\u0000\u0000\u000b\u0155"+
+    "\u0001\u0000\u0000\u0000\u000b\u0157\u0001\u0000\u0000\u0000\u000b\u0159"+
+    "\u0001\u0000\u0000\u0000\u000b\u015b\u0001\u0000\u0000\u0000\u000b\u015d"+
+    "\u0001\u0000\u0000\u0000\f\u015f\u0001\u0000\u0000\u0000\f\u0161\u0001"+
+    "\u0000\u0000\u0000\f\u0163\u0001\u0000\u0000\u0000\f\u0165\u0001\u0000"+
+    "\u0000\u0000\f\u0167\u0001\u0000\u0000\u0000\f\u0169\u0001\u0000\u0000"+
+    "\u0000\f\u016b\u0001\u0000\u0000\u0000\r\u016d\u0001\u0000\u0000\u0000"+
+    "\r\u016f\u0001\u0000\u0000\u0000\r\u0171\u0001\u0000\u0000\u0000\r\u0173"+
+    "\u0001\u0000\u0000\u0000\r\u0175\u0001\u0000\u0000\u0000\r\u0177\u0001"+
+    "\u0000\u0000\u0000\u000e\u0179\u0001\u0000\u0000\u0000\u000e\u017b\u0001"+
+    "\u0000\u0000\u0000\u000e\u017d\u0001\u0000\u0000\u0000\u000e\u017f\u0001"+
+    "\u0000\u0000\u0000\u000e\u0181\u0001\u0000\u0000\u0000\u000e\u0183\u0001"+
+    "\u0000\u0000\u0000\u000e\u0185\u0001\u0000\u0000\u0000\u000e\u0187\u0001"+
+    "\u0000\u0000\u0000\u000e\u0189\u0001\u0000\u0000\u0000\u000f\u018b\u0001"+
+    "\u0000\u0000\u0000\u0011\u0195\u0001\u0000\u0000\u0000\u0013\u019c\u0001"+
+    "\u0000\u0000\u0000\u0015\u01a5\u0001\u0000\u0000\u0000\u0017\u01ac\u0001"+
+    "\u0000\u0000\u0000\u0019\u01b6\u0001\u0000\u0000\u0000\u001b\u01bd\u0001"+
+    "\u0000\u0000\u0000\u001d\u01c4\u0001\u0000\u0000\u0000\u001f\u01cb\u0001"+
+    "\u0000\u0000\u0000!\u01d3\u0001\u0000\u0000\u0000#\u01df\u0001\u0000\u0000"+
+    "\u0000%\u01e8\u0001\u0000\u0000\u0000\'\u01ee\u0001\u0000\u0000\u0000"+
+    ")\u01f5\u0001\u0000\u0000\u0000+\u01fc\u0001\u0000\u0000\u0000-\u0204"+
+    "\u0001\u0000\u0000\u0000/\u020c\u0001\u0000\u0000\u00001\u021b\u0001\u0000"+
+    "\u0000\u00003\u0225\u0001\u0000\u0000\u00005\u022e\u0001\u0000\u0000\u0000"+
+    "7\u023a\u0001\u0000\u0000\u00009\u0240\u0001\u0000\u0000\u0000;\u0251"+
+    "\u0001\u0000\u0000\u0000=\u0261\u0001\u0000\u0000\u0000?\u0267\u0001\u0000"+
+    "\u0000\u0000A\u026b\u0001\u0000\u0000\u0000C\u026d\u0001\u0000\u0000\u0000"+
+    "E\u026f\u0001\u0000\u0000\u0000G\u0272\u0001\u0000\u0000\u0000I\u0274"+
+    "\u0001\u0000\u0000\u0000K\u027d\u0001\u0000\u0000\u0000M\u027f\u0001\u0000"+
+    "\u0000\u0000O\u0284\u0001\u0000\u0000\u0000Q\u0286\u0001\u0000\u0000\u0000"+
+    "S\u028b\u0001\u0000\u0000\u0000U\u02aa\u0001\u0000\u0000\u0000W\u02ad"+
+    "\u0001\u0000\u0000\u0000Y\u02db\u0001\u0000\u0000\u0000[\u02dd\u0001\u0000"+
+    "\u0000\u0000]\u02e0\u0001\u0000\u0000\u0000_\u02e4\u0001\u0000\u0000\u0000"+
+    "a\u02e8\u0001\u0000\u0000\u0000c\u02ea\u0001\u0000\u0000\u0000e\u02ed"+
+    "\u0001\u0000\u0000\u0000g\u02ef\u0001\u0000\u0000\u0000i\u02f4\u0001\u0000"+
+    "\u0000\u0000k\u02f6\u0001\u0000\u0000\u0000m\u02fc\u0001\u0000\u0000\u0000"+
+    "o\u0302\u0001\u0000\u0000\u0000q\u0305\u0001\u0000\u0000\u0000s\u0308"+
+    "\u0001\u0000\u0000\u0000u\u030d\u0001\u0000\u0000\u0000w\u0312\u0001\u0000"+
+    "\u0000\u0000y\u0314\u0001\u0000\u0000\u0000{\u0318\u0001\u0000\u0000\u0000"+
+    "}\u031d\u0001\u0000\u0000\u0000\u007f\u0323\u0001\u0000\u0000\u0000\u0081"+
+    "\u0326\u0001\u0000\u0000\u0000\u0083\u0328\u0001\u0000\u0000\u0000\u0085"+
+    "\u032e\u0001\u0000\u0000\u0000\u0087\u0330\u0001\u0000\u0000\u0000\u0089"+
+    "\u0335\u0001\u0000\u0000\u0000\u008b\u0338\u0001\u0000\u0000\u0000\u008d"+
+    "\u033b\u0001\u0000\u0000\u0000\u008f\u033e\u0001\u0000\u0000\u0000\u0091"+
+    "\u0340\u0001\u0000\u0000\u0000\u0093\u0343\u0001\u0000\u0000\u0000\u0095"+
+    "\u0345\u0001\u0000\u0000\u0000\u0097\u0348\u0001\u0000\u0000\u0000\u0099"+
+    "\u034a\u0001\u0000\u0000\u0000\u009b\u034c\u0001\u0000\u0000\u0000\u009d"+
+    "\u034e\u0001\u0000\u0000\u0000\u009f\u0350\u0001\u0000\u0000\u0000\u00a1"+
+    "\u0352\u0001\u0000\u0000\u0000\u00a3\u0368\u0001\u0000\u0000\u0000\u00a5"+
+    "\u036a\u0001\u0000\u0000\u0000\u00a7\u036f\u0001\u0000\u0000\u0000\u00a9"+
+    "\u0384\u0001\u0000\u0000\u0000\u00ab\u0386\u0001\u0000\u0000\u0000\u00ad"+
+    "\u038e\u0001\u0000\u0000\u0000\u00af\u0390\u0001\u0000\u0000\u0000\u00b1"+
+    "\u0394\u0001\u0000\u0000\u0000\u00b3\u0398\u0001\u0000\u0000\u0000\u00b5"+
+    "\u039c\u0001\u0000\u0000\u0000\u00b7\u03a1\u0001\u0000\u0000\u0000\u00b9"+
+    "\u03a6\u0001\u0000\u0000\u0000\u00bb\u03aa\u0001\u0000\u0000\u0000\u00bd"+
+    "\u03ae\u0001\u0000\u0000\u0000\u00bf\u03b2\u0001\u0000\u0000\u0000\u00c1"+
+    "\u03b7\u0001\u0000\u0000\u0000\u00c3\u03bb\u0001\u0000\u0000\u0000\u00c5"+
+    "\u03bf\u0001\u0000\u0000\u0000\u00c7\u03c3\u0001\u0000\u0000\u0000\u00c9"+
+    "\u03c7\u0001\u0000\u0000\u0000\u00cb\u03cb\u0001\u0000\u0000\u0000\u00cd"+
+    "\u03d7\u0001\u0000\u0000\u0000\u00cf\u03da\u0001\u0000\u0000\u0000\u00d1"+
+    "\u03de\u0001\u0000\u0000\u0000\u00d3\u03e2\u0001\u0000\u0000\u0000\u00d5"+
+    "\u03e6\u0001\u0000\u0000\u0000\u00d7\u03ea\u0001\u0000\u0000\u0000\u00d9"+
+    "\u03ee\u0001\u0000\u0000\u0000\u00db\u03f2\u0001\u0000\u0000\u0000\u00dd"+
+    "\u03f7\u0001\u0000\u0000\u0000\u00df\u03fb\u0001\u0000\u0000\u0000\u00e1"+
+    "\u0403\u0001\u0000\u0000\u0000\u00e3\u0418\u0001\u0000\u0000\u0000\u00e5"+
+    "\u041c\u0001\u0000\u0000\u0000\u00e7\u0420\u0001\u0000\u0000\u0000\u00e9"+
+    "\u0424\u0001\u0000\u0000\u0000\u00eb\u0428\u0001\u0000\u0000\u0000\u00ed"+
+    "\u042c\u0001\u0000\u0000\u0000\u00ef\u0431\u0001\u0000\u0000\u0000\u00f1"+
+    "\u0435\u0001\u0000\u0000\u0000\u00f3\u0439\u0001\u0000\u0000\u0000\u00f5"+
+    "\u043d\u0001\u0000\u0000\u0000\u00f7\u0440\u0001\u0000\u0000\u0000\u00f9"+
+    "\u0444\u0001\u0000\u0000\u0000\u00fb\u0448\u0001\u0000\u0000\u0000\u00fd"+
+    "\u044c\u0001\u0000\u0000\u0000\u00ff\u0450\u0001\u0000\u0000\u0000\u0101"+
+    "\u0455\u0001\u0000\u0000\u0000\u0103\u045a\u0001\u0000\u0000\u0000\u0105"+
+    "\u045f\u0001\u0000\u0000\u0000\u0107\u0466\u0001\u0000\u0000\u0000\u0109"+
+    "\u046f\u0001\u0000\u0000\u0000\u010b\u0476\u0001\u0000\u0000\u0000\u010d"+
+    "\u047a\u0001\u0000\u0000\u0000\u010f\u047e\u0001\u0000\u0000\u0000\u0111"+
+    "\u0482\u0001\u0000\u0000\u0000\u0113\u0486\u0001\u0000\u0000\u0000\u0115"+
+    "\u048c\u0001\u0000\u0000\u0000\u0117\u0490\u0001\u0000\u0000\u0000\u0119"+
+    "\u0494\u0001\u0000\u0000\u0000\u011b\u0498\u0001\u0000\u0000\u0000\u011d"+
+    "\u049c\u0001\u0000\u0000\u0000\u011f\u04a0\u0001\u0000\u0000\u0000\u0121"+
+    "\u04a4\u0001\u0000\u0000\u0000\u0123\u04a8\u0001\u0000\u0000\u0000\u0125"+
+    "\u04ac\u0001\u0000\u0000\u0000\u0127\u04b0\u0001\u0000\u0000\u0000\u0129"+
+    "\u04b5\u0001\u0000\u0000\u0000\u012b\u04b9\u0001\u0000\u0000\u0000\u012d"+
+    "\u04bd\u0001\u0000\u0000\u0000\u012f\u04c1\u0001\u0000\u0000\u0000\u0131"+
+    "\u04c5\u0001\u0000\u0000\u0000\u0133\u04c9\u0001\u0000\u0000\u0000\u0135"+
+    "\u04cd\u0001\u0000\u0000\u0000\u0137\u04d2\u0001\u0000\u0000\u0000\u0139"+
+    "\u04d7\u0001\u0000\u0000\u0000\u013b\u04db\u0001\u0000\u0000\u0000\u013d"+
+    "\u04df\u0001\u0000\u0000\u0000\u013f\u04e3\u0001\u0000\u0000\u0000\u0141"+
+    "\u04e8\u0001\u0000\u0000\u0000\u0143\u04ef\u0001\u0000\u0000\u0000\u0145"+
+    "\u04f3\u0001\u0000\u0000\u0000\u0147\u04f7\u0001\u0000\u0000\u0000\u0149"+
+    "\u04fb\u0001\u0000\u0000\u0000\u014b\u04ff\u0001\u0000\u0000\u0000\u014d"+
+    "\u0504\u0001\u0000\u0000\u0000\u014f\u0508\u0001\u0000\u0000\u0000\u0151"+
+    "\u050c\u0001\u0000\u0000\u0000\u0153\u0510\u0001\u0000\u0000\u0000\u0155"+
+    "\u0515\u0001\u0000\u0000\u0000\u0157\u0519\u0001\u0000\u0000\u0000\u0159"+
+    "\u051d\u0001\u0000\u0000\u0000\u015b\u0521\u0001\u0000\u0000\u0000\u015d"+
+    "\u0525\u0001\u0000\u0000\u0000\u015f\u0529\u0001\u0000\u0000\u0000\u0161"+
+    "\u052f\u0001\u0000\u0000\u0000\u0163\u0533\u0001\u0000\u0000\u0000\u0165"+
+    "\u0537\u0001\u0000\u0000\u0000\u0167\u053b\u0001\u0000\u0000\u0000\u0169"+
+    "\u053f\u0001\u0000\u0000\u0000\u016b\u0543\u0001\u0000\u0000\u0000\u016d"+
+    "\u0547\u0001\u0000\u0000\u0000\u016f\u054c\u0001\u0000\u0000\u0000\u0171"+
+    "\u0552\u0001\u0000\u0000\u0000\u0173\u0558\u0001\u0000\u0000\u0000\u0175"+
+    "\u055c\u0001\u0000\u0000\u0000\u0177\u0560\u0001\u0000\u0000\u0000\u0179"+
+    "\u0564\u0001\u0000\u0000\u0000\u017b\u056a\u0001\u0000\u0000\u0000\u017d"+
+    "\u0570\u0001\u0000\u0000\u0000\u017f\u0574\u0001\u0000\u0000\u0000\u0181"+
+    "\u0578\u0001\u0000\u0000\u0000\u0183\u057c\u0001\u0000\u0000\u0000\u0185"+
+    "\u0582\u0001\u0000\u0000\u0000\u0187\u0588\u0001\u0000\u0000\u0000\u0189"+
+    "\u058e\u0001\u0000\u0000\u0000\u018b\u018c\u0007\u0000\u0000\u0000\u018c"+
+    "\u018d\u0007\u0001\u0000\u0000\u018d\u018e\u0007\u0002\u0000\u0000\u018e"+
+    "\u018f\u0007\u0002\u0000\u0000\u018f\u0190\u0007\u0003\u0000\u0000\u0190"+
+    "\u0191\u0007\u0004\u0000\u0000\u0191\u0192\u0007\u0005\u0000\u0000\u0192"+
+    "\u0193\u0001\u0000\u0000\u0000\u0193\u0194\u0006\u0000\u0000\u0000\u0194"+
+    "\u0010\u0001\u0000\u0000\u0000\u0195\u0196\u0007\u0000\u0000\u0000\u0196"+
+    "\u0197\u0007\u0006\u0000\u0000\u0197\u0198\u0007\u0007\u0000\u0000\u0198"+
+    "\u0199\u0007\b\u0000\u0000\u0199\u019a\u0001\u0000\u0000\u0000\u019a\u019b"+
+    "\u0006\u0001\u0001\u0000\u019b\u0012\u0001\u0000\u0000\u0000\u019c\u019d"+
+    "\u0007\u0003\u0000\u0000\u019d\u019e\u0007\t\u0000\u0000\u019e\u019f\u0007"+
+    "\u0006\u0000\u0000\u019f\u01a0\u0007\u0001\u0000\u0000\u01a0\u01a1\u0007"+
+    "\u0004\u0000\u0000\u01a1\u01a2\u0007\n\u0000\u0000\u01a2\u01a3\u0001\u0000"+
+    "\u0000\u0000\u01a3\u01a4\u0006\u0002\u0002\u0000\u01a4\u0014\u0001\u0000"+
+    "\u0000\u0000\u01a5\u01a6\u0007\u0003\u0000\u0000\u01a6\u01a7\u0007\u000b"+
+    "\u0000\u0000\u01a7\u01a8\u0007\f\u0000\u0000\u01a8\u01a9\u0007\r\u0000"+
+    "\u0000\u01a9\u01aa\u0001\u0000\u0000\u0000\u01aa\u01ab\u0006\u0003\u0000"+
+    "\u0000\u01ab\u0016\u0001\u0000\u0000\u0000\u01ac\u01ad\u0007\u0003\u0000"+
+    "\u0000\u01ad\u01ae\u0007\u000e\u0000\u0000\u01ae\u01af\u0007\b\u0000\u0000"+
+    "\u01af\u01b0\u0007\r\u0000\u0000\u01b0\u01b1\u0007\f\u0000\u0000\u01b1"+
+    "\u01b2\u0007\u0001\u0000\u0000\u01b2\u01b3\u0007\t\u0000\u0000\u01b3\u01b4"+
+    "\u0001\u0000\u0000\u0000\u01b4\u01b5\u0006\u0004\u0003\u0000\u01b5\u0018"+
+    "\u0001\u0000\u0000\u0000\u01b6\u01b7\u0007\u000f\u0000\u0000\u01b7\u01b8"+
+    "\u0007\u0006\u0000\u0000\u01b8\u01b9\u0007\u0007\u0000\u0000\u01b9\u01ba"+
+    "\u0007\u0010\u0000\u0000\u01ba\u01bb\u0001\u0000\u0000\u0000\u01bb\u01bc"+
+    "\u0006\u0005\u0004\u0000\u01bc\u001a\u0001\u0000\u0000\u0000\u01bd\u01be"+
+    "\u0007\u0011\u0000\u0000\u01be\u01bf\u0007\u0006\u0000\u0000\u01bf\u01c0"+
+    "\u0007\u0007\u0000\u0000\u01c0\u01c1\u0007\u0012\u0000\u0000\u01c1\u01c2"+
+    "\u0001\u0000\u0000\u0000\u01c2\u01c3\u0006\u0006\u0000\u0000\u01c3\u001c"+
+    "\u0001\u0000\u0000\u0000\u01c4\u01c5\u0007\u0012\u0000\u0000\u01c5\u01c6"+
+    "\u0007\u0003\u0000\u0000\u01c6\u01c7\u0007\u0003\u0000\u0000\u01c7\u01c8"+
+    "\u0007\b\u0000\u0000\u01c8\u01c9\u0001\u0000\u0000\u0000\u01c9\u01ca\u0006"+
+    "\u0007\u0001\u0000\u01ca\u001e\u0001\u0000\u0000\u0000\u01cb\u01cc\u0007"+
+    "\r\u0000\u0000\u01cc\u01cd\u0007\u0001\u0000\u0000\u01cd\u01ce\u0007\u0010"+
+    "\u0000\u0000\u01ce\u01cf\u0007\u0001\u0000\u0000\u01cf\u01d0\u0007\u0005"+
+    "\u0000\u0000\u01d0\u01d1\u0001\u0000\u0000\u0000\u01d1\u01d2\u0006\b\u0000"+
+    "\u0000\u01d2 \u0001\u0000\u0000\u0000\u01d3\u01d4\u0007\u0010\u0000\u0000"+
+    "\u01d4\u01d5\u0007\u000b\u0000\u0000\u01d5\u01d6\u0005_\u0000\u0000\u01d6"+
+    "\u01d7\u0007\u0003\u0000\u0000\u01d7\u01d8\u0007\u000e\u0000\u0000\u01d8"+
+    "\u01d9\u0007\b\u0000\u0000\u01d9\u01da\u0007\f\u0000\u0000\u01da\u01db"+
+    "\u0007\t\u0000\u0000\u01db\u01dc\u0007\u0000\u0000\u0000\u01dc\u01dd\u0001"+
+    "\u0000\u0000\u0000\u01dd\u01de\u0006\t\u0005\u0000\u01de\"\u0001\u0000"+
+    "\u0000\u0000\u01df\u01e0\u0007\u0006\u0000\u0000\u01e0\u01e1\u0007\u0003"+
+    "\u0000\u0000\u01e1\u01e2\u0007\t\u0000\u0000\u01e2\u01e3\u0007\f\u0000"+
+    "\u0000\u01e3\u01e4\u0007\u0010\u0000\u0000\u01e4\u01e5\u0007\u0003\u0000"+
+    "\u0000\u01e5\u01e6\u0001\u0000\u0000\u0000\u01e6\u01e7\u0006\n\u0006\u0000"+
+    "\u01e7$\u0001\u0000\u0000\u0000\u01e8\u01e9\u0007\u0006\u0000\u0000\u01e9"+
+    "\u01ea\u0007\u0007\u0000\u0000\u01ea\u01eb\u0007\u0013\u0000\u0000\u01eb"+
+    "\u01ec\u0001\u0000\u0000\u0000\u01ec\u01ed\u0006\u000b\u0000\u0000\u01ed"+
+    "&\u0001\u0000\u0000\u0000\u01ee\u01ef\u0007\u0002\u0000\u0000\u01ef\u01f0"+
+    "\u0007\n\u0000\u0000\u01f0\u01f1\u0007\u0007\u0000\u0000\u01f1\u01f2\u0007"+
+    "\u0013\u0000\u0000\u01f2\u01f3\u0001\u0000\u0000\u0000\u01f3\u01f4\u0006"+
+    "\f\u0007\u0000\u01f4(\u0001\u0000\u0000\u0000\u01f5\u01f6\u0007\u0002"+
+    "\u0000\u0000\u01f6\u01f7\u0007\u0007\u0000\u0000\u01f7\u01f8\u0007\u0006"+
+    "\u0000\u0000\u01f8\u01f9\u0007\u0005\u0000\u0000\u01f9\u01fa\u0001\u0000"+
+    "\u0000\u0000\u01fa\u01fb\u0006\r\u0000\u0000\u01fb*\u0001\u0000\u0000"+
+    "\u0000\u01fc\u01fd\u0007\u0002\u0000\u0000\u01fd\u01fe\u0007\u0005\u0000"+
+    "\u0000\u01fe\u01ff\u0007\f\u0000\u0000\u01ff\u0200\u0007\u0005\u0000\u0000"+
+    "\u0200\u0201\u0007\u0002\u0000\u0000\u0201\u0202\u0001\u0000\u0000\u0000"+
+    "\u0202\u0203\u0006\u000e\u0000\u0000\u0203,\u0001\u0000\u0000\u0000\u0204"+
+    "\u0205\u0007\u0013\u0000\u0000\u0205\u0206\u0007\n\u0000\u0000\u0206\u0207"+
+    "\u0007\u0003\u0000\u0000\u0207\u0208\u0007\u0006\u0000\u0000\u0208\u0209"+
+    "\u0007\u0003\u0000\u0000\u0209\u020a\u0001\u0000\u0000\u0000\u020a\u020b"+
+    "\u0006\u000f\u0000\u0000\u020b.\u0001\u0000\u0000\u0000\u020c\u020d\u0004"+
+    "\u0010\u0000\u0000\u020d\u020e\u0007\u0001\u0000\u0000\u020e\u020f\u0007"+
+    "\t\u0000\u0000\u020f\u0210\u0007\r\u0000\u0000\u0210\u0211\u0007\u0001"+
+    "\u0000\u0000\u0211\u0212\u0007\t\u0000\u0000\u0212\u0213\u0007\u0003\u0000"+
+    "\u0000\u0213\u0214\u0007\u0002\u0000\u0000\u0214\u0215\u0007\u0005\u0000"+
+    "\u0000\u0215\u0216\u0007\f\u0000\u0000\u0216\u0217\u0007\u0005\u0000\u0000"+
+    "\u0217\u0218\u0007\u0002\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000"+
+    "\u0219\u021a\u0006\u0010\u0000\u0000\u021a0\u0001\u0000\u0000\u0000\u021b"+
+    "\u021c\u0004\u0011\u0001\u0000\u021c\u021d\u0007\r\u0000\u0000\u021d\u021e"+
+    "\u0007\u0007\u0000\u0000\u021e\u021f\u0007\u0007\u0000\u0000\u021f\u0220"+
+    "\u0007\u0012\u0000\u0000\u0220\u0221\u0007\u0014\u0000\u0000\u0221\u0222"+
+    "\u0007\b\u0000\u0000\u0222\u0223\u0001\u0000\u0000\u0000\u0223\u0224\u0006"+
+    "\u0011\b\u0000\u02242\u0001\u0000\u0000\u0000\u0225\u0226\u0004\u0012"+
+    "\u0002\u0000\u0226\u0227\u0007\u0010\u0000\u0000\u0227\u0228\u0007\f\u0000"+
+    "\u0000\u0228\u0229\u0007\u0005\u0000\u0000\u0229\u022a\u0007\u0004\u0000"+
+    "\u0000\u022a\u022b\u0007\n\u0000\u0000\u022b\u022c\u0001\u0000\u0000\u0000"+
+    "\u022c\u022d\u0006\u0012\u0000\u0000\u022d4\u0001\u0000\u0000\u0000\u022e"+
+    "\u022f\u0004\u0013\u0003\u0000\u022f\u0230\u0007\u0010\u0000\u0000\u0230"+
+    "\u0231\u0007\u0003\u0000\u0000\u0231\u0232\u0007\u0005\u0000\u0000\u0232"+
+    "\u0233\u0007\u0006\u0000\u0000\u0233\u0234\u0007\u0001\u0000\u0000\u0234"+
+    "\u0235\u0007\u0004\u0000\u0000\u0235\u0236\u0007\u0002\u0000\u0000\u0236"+
+    "\u0237\u0001\u0000\u0000\u0000\u0237\u0238\u0006\u0013\t\u0000\u02386"+
+    "\u0001\u0000\u0000\u0000\u0239\u023b\b\u0015\u0000\u0000\u023a\u0239\u0001"+
+    "\u0000\u0000\u0000\u023b\u023c\u0001\u0000\u0000\u0000\u023c\u023a\u0001"+
+    "\u0000\u0000\u0000\u023c\u023d\u0001\u0000\u0000\u0000\u023d\u023e\u0001"+
+    "\u0000\u0000\u0000\u023e\u023f\u0006\u0014\u0000\u0000\u023f8\u0001\u0000"+
+    "\u0000\u0000\u0240\u0241\u0005/\u0000\u0000\u0241\u0242\u0005/\u0000\u0000"+
+    "\u0242\u0246\u0001\u0000\u0000\u0000\u0243\u0245\b\u0016\u0000\u0000\u0244"+
+    "\u0243\u0001\u0000\u0000\u0000\u0245\u0248\u0001\u0000\u0000\u0000\u0246"+
+    "\u0244\u0001\u0000\u0000\u0000\u0246\u0247\u0001\u0000\u0000\u0000\u0247"+
+    "\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000\u0000\u0000\u0249"+
+    "\u024b\u0005\r\u0000\u0000\u024a\u0249\u0001\u0000\u0000\u0000\u024a\u024b"+
+    "\u0001\u0000\u0000\u0000\u024b\u024d\u0001\u0000\u0000\u0000\u024c\u024e"+
+    "\u0005\n\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d\u024e\u0001"+
+    "\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250\u0006"+
+    "\u0015\n\u0000\u0250:\u0001\u0000\u0000\u0000\u0251\u0252\u0005/\u0000"+
+    "\u0000\u0252\u0253\u0005*\u0000\u0000\u0253\u0258\u0001\u0000\u0000\u0000"+
+    "\u0254\u0257\u0003;\u0016\u0000\u0255\u0257\t\u0000\u0000\u0000\u0256"+
+    "\u0254\u0001\u0000\u0000\u0000\u0256\u0255\u0001\u0000\u0000\u0000\u0257"+
+    "\u025a\u0001\u0000\u0000\u0000\u0258\u0259\u0001\u0000\u0000\u0000\u0258"+
+    "\u0256\u0001\u0000\u0000\u0000\u0259\u025b\u0001\u0000\u0000\u0000\u025a"+
+    "\u0258\u0001\u0000\u0000\u0000\u025b\u025c\u0005*\u0000\u0000\u025c\u025d"+
+    "\u0005/\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f\u0006"+
+    "\u0016\n\u0000\u025f<\u0001\u0000\u0000\u0000\u0260\u0262\u0007\u0017"+
+    "\u0000\u0000\u0261\u0260\u0001\u0000\u0000\u0000\u0262\u0263\u0001\u0000"+
+    "\u0000\u0000\u0263\u0261\u0001\u0000\u0000\u0000\u0263\u0264\u0001\u0000"+
+    "\u0000\u0000\u0264\u0265\u0001\u0000\u0000\u0000\u0265\u0266\u0006\u0017"+
+    "\n\u0000\u0266>\u0001\u0000\u0000\u0000\u0267\u0268\u0005|\u0000\u0000"+
+    "\u0268\u0269\u0001\u0000\u0000\u0000\u0269\u026a\u0006\u0018\u000b\u0000"+
+    "\u026a@\u0001\u0000\u0000\u0000\u026b\u026c\u0007\u0018\u0000\u0000\u026c"+
+    "B\u0001\u0000\u0000\u0000\u026d\u026e\u0007\u0019\u0000\u0000\u026eD\u0001"+
+    "\u0000\u0000\u0000\u026f\u0270\u0005\\\u0000\u0000\u0270\u0271\u0007\u001a"+
+    "\u0000\u0000\u0271F\u0001\u0000\u0000\u0000\u0272\u0273\b\u001b\u0000"+
+    "\u0000\u0273H\u0001\u0000\u0000\u0000\u0274\u0276\u0007\u0003\u0000\u0000"+
+    "\u0275\u0277\u0007\u001c\u0000\u0000\u0276\u0275\u0001\u0000\u0000\u0000"+
+    "\u0276\u0277\u0001\u0000\u0000\u0000\u0277\u0279\u0001\u0000\u0000\u0000"+
+    "\u0278\u027a\u0003A\u0019\u0000\u0279\u0278\u0001\u0000\u0000\u0000\u027a"+
+    "\u027b\u0001\u0000\u0000\u0000\u027b\u0279\u0001\u0000\u0000\u0000\u027b"+
+    "\u027c\u0001\u0000\u0000\u0000\u027cJ\u0001\u0000\u0000\u0000\u027d\u027e"+
+    "\u0005@\u0000\u0000\u027eL\u0001\u0000\u0000\u0000\u027f\u0280\u0005`"+
+    "\u0000\u0000\u0280N\u0001\u0000\u0000\u0000\u0281\u0285\b\u001d\u0000"+
+    "\u0000\u0282\u0283\u0005`\u0000\u0000\u0283\u0285\u0005`\u0000\u0000\u0284"+
+    "\u0281\u0001\u0000\u0000\u0000\u0284\u0282\u0001\u0000\u0000\u0000\u0285"+
+    "P\u0001\u0000\u0000\u0000\u0286\u0287\u0005_\u0000\u0000\u0287R\u0001"+
+    "\u0000\u0000\u0000\u0288\u028c\u0003C\u001a\u0000\u0289\u028c\u0003A\u0019"+
+    "\u0000\u028a\u028c\u0003Q!\u0000\u028b\u0288\u0001\u0000\u0000\u0000\u028b"+
+    "\u0289\u0001\u0000\u0000\u0000\u028b\u028a\u0001\u0000\u0000\u0000\u028c"+
+    "T\u0001\u0000\u0000\u0000\u028d\u0292\u0005\"\u0000\u0000\u028e\u0291"+
+    "\u0003E\u001b\u0000\u028f\u0291\u0003G\u001c\u0000\u0290\u028e\u0001\u0000"+
+    "\u0000\u0000\u0290\u028f\u0001\u0000\u0000\u0000\u0291\u0294\u0001\u0000"+
+    "\u0000\u0000\u0292\u0290\u0001\u0000\u0000\u0000\u0292\u0293\u0001\u0000"+
+    "\u0000\u0000\u0293\u0295\u0001\u0000\u0000\u0000\u0294\u0292\u0001\u0000"+
+    "\u0000\u0000\u0295\u02ab\u0005\"\u0000\u0000\u0296\u0297\u0005\"\u0000"+
+    "\u0000\u0297\u0298\u0005\"\u0000\u0000\u0298\u0299\u0005\"\u0000\u0000"+
+    "\u0299\u029d\u0001\u0000\u0000\u0000\u029a\u029c\b\u0016\u0000\u0000\u029b"+
+    "\u029a\u0001\u0000\u0000\u0000\u029c\u029f\u0001\u0000\u0000\u0000\u029d"+
+    "\u029e\u0001\u0000\u0000\u0000\u029d\u029b\u0001\u0000\u0000\u0000\u029e"+
+    "\u02a0\u0001\u0000\u0000\u0000\u029f\u029d\u0001\u0000\u0000\u0000\u02a0"+
+    "\u02a1\u0005\"\u0000\u0000\u02a1\u02a2\u0005\"\u0000\u0000\u02a2\u02a3"+
+    "\u0005\"\u0000\u0000\u02a3\u02a5\u0001\u0000\u0000\u0000\u02a4\u02a6\u0005"+
+    "\"\u0000\u0000\u02a5\u02a4\u0001\u0000\u0000\u0000\u02a5\u02a6\u0001\u0000"+
+    "\u0000\u0000\u02a6\u02a8\u0001\u0000\u0000\u0000\u02a7\u02a9\u0005\"\u0000"+
+    "\u0000\u02a8\u02a7\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001\u0000\u0000"+
+    "\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u028d\u0001\u0000\u0000"+
+    "\u0000\u02aa\u0296\u0001\u0000\u0000\u0000\u02abV\u0001\u0000\u0000\u0000"+
+    "\u02ac\u02ae\u0003A\u0019\u0000\u02ad\u02ac\u0001\u0000\u0000\u0000\u02ae"+
+    "\u02af\u0001\u0000\u0000\u0000\u02af\u02ad\u0001\u0000\u0000\u0000\u02af"+
+    "\u02b0\u0001\u0000\u0000\u0000\u02b0X\u0001\u0000\u0000\u0000\u02b1\u02b3"+
+    "\u0003A\u0019\u0000\u02b2\u02b1\u0001\u0000\u0000\u0000\u02b3\u02b4\u0001"+
+    "\u0000\u0000\u0000\u02b4\u02b2\u0001\u0000\u0000\u0000\u02b4\u02b5\u0001"+
+    "\u0000\u0000\u0000\u02b5\u02b6\u0001\u0000\u0000\u0000\u02b6\u02ba\u0003"+
+    "i-\u0000\u02b7\u02b9\u0003A\u0019\u0000\u02b8\u02b7\u0001\u0000\u0000"+
+    "\u0000\u02b9\u02bc\u0001\u0000\u0000\u0000\u02ba\u02b8\u0001\u0000\u0000"+
+    "\u0000\u02ba\u02bb\u0001\u0000\u0000\u0000\u02bb\u02dc\u0001\u0000\u0000"+
+    "\u0000\u02bc\u02ba\u0001\u0000\u0000\u0000\u02bd\u02bf\u0003i-\u0000\u02be"+
+    "\u02c0\u0003A\u0019\u0000\u02bf\u02be\u0001\u0000\u0000\u0000\u02c0\u02c1"+
+    "\u0001\u0000\u0000\u0000\u02c1\u02bf\u0001\u0000\u0000\u0000\u02c1\u02c2"+
+    "\u0001\u0000\u0000\u0000\u02c2\u02dc\u0001\u0000\u0000\u0000\u02c3\u02c5"+
+    "\u0003A\u0019\u0000\u02c4\u02c3\u0001\u0000\u0000\u0000\u02c5\u02c6\u0001"+
+    "\u0000\u0000\u0000\u02c6\u02c4\u0001\u0000\u0000\u0000\u02c6\u02c7\u0001"+
+    "\u0000\u0000\u0000\u02c7\u02cf\u0001\u0000\u0000\u0000\u02c8\u02cc\u0003"+
+    "i-\u0000\u02c9\u02cb\u0003A\u0019\u0000\u02ca\u02c9\u0001\u0000\u0000"+
+    "\u0000\u02cb\u02ce\u0001\u0000\u0000\u0000\u02cc\u02ca\u0001\u0000\u0000"+
+    "\u0000\u02cc\u02cd\u0001\u0000\u0000\u0000\u02cd\u02d0\u0001\u0000\u0000"+
+    "\u0000\u02ce\u02cc\u0001\u0000\u0000\u0000\u02cf\u02c8\u0001\u0000\u0000"+
+    "\u0000\u02cf\u02d0\u0001\u0000\u0000\u0000\u02d0\u02d1\u0001\u0000\u0000"+
+    "\u0000\u02d1\u02d2\u0003I\u001d\u0000\u02d2\u02dc\u0001\u0000\u0000\u0000"+
+    "\u02d3\u02d5\u0003i-\u0000\u02d4\u02d6\u0003A\u0019\u0000\u02d5\u02d4"+
+    "\u0001\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d5"+
+    "\u0001\u0000\u0000\u0000\u02d7\u02d8\u0001\u0000\u0000\u0000\u02d8\u02d9"+
+    "\u0001\u0000\u0000\u0000\u02d9\u02da\u0003I\u001d\u0000\u02da\u02dc\u0001"+
+    "\u0000\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02bd\u0001"+
+    "\u0000\u0000\u0000\u02db\u02c4\u0001\u0000\u0000\u0000\u02db\u02d3\u0001"+
+    "\u0000\u0000\u0000\u02dcZ\u0001\u0000\u0000\u0000\u02dd\u02de\u0007\u001e"+
+    "\u0000\u0000\u02de\u02df\u0007\u001f\u0000\u0000\u02df\\\u0001\u0000\u0000"+
+    "\u0000\u02e0\u02e1\u0007\f\u0000\u0000\u02e1\u02e2\u0007\t\u0000\u0000"+
+    "\u02e2\u02e3\u0007\u0000\u0000\u0000\u02e3^\u0001\u0000\u0000\u0000\u02e4"+
+    "\u02e5\u0007\f\u0000\u0000\u02e5\u02e6\u0007\u0002\u0000\u0000\u02e6\u02e7"+
+    "\u0007\u0004\u0000\u0000\u02e7`\u0001\u0000\u0000\u0000\u02e8\u02e9\u0005"+
+    "=\u0000\u0000\u02e9b\u0001\u0000\u0000\u0000\u02ea\u02eb\u0005:\u0000"+
+    "\u0000\u02eb\u02ec\u0005:\u0000\u0000\u02ecd\u0001\u0000\u0000\u0000\u02ed"+
+    "\u02ee\u0005,\u0000\u0000\u02eef\u0001\u0000\u0000\u0000\u02ef\u02f0\u0007"+
+    "\u0000\u0000\u0000\u02f0\u02f1\u0007\u0003\u0000\u0000\u02f1\u02f2\u0007"+
+    "\u0002\u0000\u0000\u02f2\u02f3\u0007\u0004\u0000\u0000\u02f3h\u0001\u0000"+
+    "\u0000\u0000\u02f4\u02f5\u0005.\u0000\u0000\u02f5j\u0001\u0000\u0000\u0000"+
+    "\u02f6\u02f7\u0007\u000f\u0000\u0000\u02f7\u02f8\u0007\f\u0000\u0000\u02f8"+
+    "\u02f9\u0007\r\u0000\u0000\u02f9\u02fa\u0007\u0002\u0000\u0000\u02fa\u02fb"+
+    "\u0007\u0003\u0000\u0000\u02fbl\u0001\u0000\u0000\u0000\u02fc\u02fd\u0007"+
+    "\u000f\u0000\u0000\u02fd\u02fe\u0007\u0001\u0000\u0000\u02fe\u02ff\u0007"+
+    "\u0006\u0000\u0000\u02ff\u0300\u0007\u0002\u0000\u0000\u0300\u0301\u0007"+
+    "\u0005\u0000\u0000\u0301n\u0001\u0000\u0000\u0000\u0302\u0303\u0007\u0001"+
+    "\u0000\u0000\u0303\u0304\u0007\t\u0000\u0000\u0304p\u0001\u0000\u0000"+
+    "\u0000\u0305\u0306\u0007\u0001\u0000\u0000\u0306\u0307\u0007\u0002\u0000"+
+    "\u0000\u0307r\u0001\u0000\u0000\u0000\u0308\u0309\u0007\r\u0000\u0000"+
+    "\u0309\u030a\u0007\f\u0000\u0000\u030a\u030b\u0007\u0002\u0000\u0000\u030b"+
+    "\u030c\u0007\u0005\u0000\u0000\u030ct\u0001\u0000\u0000\u0000\u030d\u030e"+
+    "\u0007\r\u0000\u0000\u030e\u030f\u0007\u0001\u0000\u0000\u030f\u0310\u0007"+
+    "\u0012\u0000\u0000\u0310\u0311\u0007\u0003\u0000\u0000\u0311v\u0001\u0000"+
+    "\u0000\u0000\u0312\u0313\u0005(\u0000\u0000\u0313x\u0001\u0000\u0000\u0000"+
+    "\u0314\u0315\u0007\t\u0000\u0000\u0315\u0316\u0007\u0007\u0000\u0000\u0316"+
+    "\u0317\u0007\u0005\u0000\u0000\u0317z\u0001\u0000\u0000\u0000\u0318\u0319"+
+    "\u0007\t\u0000\u0000\u0319\u031a\u0007\u0014\u0000\u0000\u031a\u031b\u0007"+
+    "\r\u0000\u0000\u031b\u031c\u0007\r\u0000\u0000\u031c|\u0001\u0000\u0000"+
+    "\u0000\u031d\u031e\u0007\t\u0000\u0000\u031e\u031f\u0007\u0014\u0000\u0000"+
+    "\u031f\u0320\u0007\r\u0000\u0000\u0320\u0321\u0007\r\u0000\u0000\u0321"+
+    "\u0322\u0007\u0002\u0000\u0000\u0322~\u0001\u0000\u0000\u0000\u0323\u0324"+
+    "\u0007\u0007\u0000\u0000\u0324\u0325\u0007\u0006\u0000\u0000\u0325\u0080"+
+    "\u0001\u0000\u0000\u0000\u0326\u0327\u0005?\u0000\u0000\u0327\u0082\u0001"+
+    "\u0000\u0000\u0000\u0328\u0329\u0007\u0006\u0000\u0000\u0329\u032a\u0007"+
+    "\r\u0000\u0000\u032a\u032b\u0007\u0001\u0000\u0000\u032b\u032c\u0007\u0012"+
+    "\u0000\u0000\u032c\u032d\u0007\u0003\u0000\u0000\u032d\u0084\u0001\u0000"+
+    "\u0000\u0000\u032e\u032f\u0005)\u0000\u0000\u032f\u0086\u0001\u0000\u0000"+
+    "\u0000\u0330\u0331\u0007\u0005\u0000\u0000\u0331\u0332\u0007\u0006\u0000"+
+    "\u0000\u0332\u0333\u0007\u0014\u0000\u0000\u0333\u0334\u0007\u0003\u0000"+
+    "\u0000\u0334\u0088\u0001\u0000\u0000\u0000\u0335\u0336\u0005=\u0000\u0000"+
+    "\u0336\u0337\u0005=\u0000\u0000\u0337\u008a\u0001\u0000\u0000\u0000\u0338"+
+    "\u0339\u0005=\u0000\u0000\u0339\u033a\u0005~\u0000\u0000\u033a\u008c\u0001"+
+    "\u0000\u0000\u0000\u033b\u033c\u0005!\u0000\u0000\u033c\u033d\u0005=\u0000"+
+    "\u0000\u033d\u008e\u0001\u0000\u0000\u0000\u033e\u033f\u0005<\u0000\u0000"+
+    "\u033f\u0090\u0001\u0000\u0000\u0000\u0340\u0341\u0005<\u0000\u0000\u0341"+
+    "\u0342\u0005=\u0000\u0000\u0342\u0092\u0001\u0000\u0000\u0000\u0343\u0344"+
+    "\u0005>\u0000\u0000\u0344\u0094\u0001\u0000\u0000\u0000\u0345\u0346\u0005"+
+    ">\u0000\u0000\u0346\u0347\u0005=\u0000\u0000\u0347\u0096\u0001\u0000\u0000"+
+    "\u0000\u0348\u0349\u0005+\u0000\u0000\u0349\u0098\u0001\u0000\u0000\u0000"+
+    "\u034a\u034b\u0005-\u0000\u0000\u034b\u009a\u0001\u0000\u0000\u0000\u034c"+
+    "\u034d\u0005*\u0000\u0000\u034d\u009c\u0001\u0000\u0000\u0000\u034e\u034f"+
+    "\u0005/\u0000\u0000\u034f\u009e\u0001\u0000\u0000\u0000\u0350\u0351\u0005"+
+    "%\u0000\u0000\u0351\u00a0\u0001\u0000\u0000\u0000\u0352\u0353\u0004I\u0004"+
+    "\u0000\u0353\u0354\u00033\u0012\u0000\u0354\u0355\u0001\u0000\u0000\u0000"+
+    "\u0355\u0356\u0006I\f\u0000\u0356\u00a2\u0001\u0000\u0000\u0000\u0357"+
+    "\u035a\u0003\u00819\u0000\u0358\u035b\u0003C\u001a\u0000\u0359\u035b\u0003"+
+    "Q!\u0000\u035a\u0358\u0001\u0000\u0000\u0000\u035a\u0359\u0001\u0000\u0000"+
+    "\u0000\u035b\u035f\u0001\u0000\u0000\u0000\u035c\u035e\u0003S\"\u0000"+
+    "\u035d\u035c\u0001\u0000\u0000\u0000\u035e\u0361\u0001\u0000\u0000\u0000"+
+    "\u035f\u035d\u0001\u0000\u0000\u0000\u035f\u0360\u0001\u0000\u0000\u0000"+
+    "\u0360\u0369\u0001\u0000\u0000\u0000\u0361\u035f\u0001\u0000\u0000\u0000"+
+    "\u0362\u0364\u0003\u00819\u0000\u0363\u0365\u0003A\u0019\u0000\u0364\u0363"+
+    "\u0001\u0000\u0000\u0000\u0365\u0366\u0001\u0000\u0000\u0000\u0366\u0364"+
+    "\u0001\u0000\u0000\u0000\u0366\u0367\u0001\u0000\u0000\u0000\u0367\u0369"+
+    "\u0001\u0000\u0000\u0000\u0368\u0357\u0001\u0000\u0000\u0000\u0368\u0362"+
+    "\u0001\u0000\u0000\u0000\u0369\u00a4\u0001\u0000\u0000\u0000\u036a\u036b"+
+    "\u0005[\u0000\u0000\u036b\u036c\u0001\u0000\u0000\u0000\u036c\u036d\u0006"+
+    "K\u0000\u0000\u036d\u036e\u0006K\u0000\u0000\u036e\u00a6\u0001\u0000\u0000"+
+    "\u0000\u036f\u0370\u0005]\u0000\u0000\u0370\u0371\u0001\u0000\u0000\u0000"+
+    "\u0371\u0372\u0006L\u000b\u0000\u0372\u0373\u0006L\u000b\u0000\u0373\u00a8"+
+    "\u0001\u0000\u0000\u0000\u0374\u0378\u0003C\u001a\u0000\u0375\u0377\u0003"+
+    "S\"\u0000\u0376\u0375\u0001\u0000\u0000\u0000\u0377\u037a\u0001\u0000"+
+    "\u0000\u0000\u0378\u0376\u0001\u0000\u0000\u0000\u0378\u0379\u0001\u0000"+
+    "\u0000\u0000\u0379\u0385\u0001\u0000\u0000\u0000\u037a\u0378\u0001\u0000"+
+    "\u0000\u0000\u037b\u037e\u0003Q!\u0000\u037c\u037e\u0003K\u001e\u0000"+
+    "\u037d\u037b\u0001\u0000\u0000\u0000\u037d\u037c\u0001\u0000\u0000\u0000"+
+    "\u037e\u0380\u0001\u0000\u0000\u0000\u037f\u0381\u0003S\"\u0000\u0380"+
+    "\u037f\u0001\u0000\u0000\u0000\u0381\u0382\u0001\u0000\u0000\u0000\u0382"+
+    "\u0380\u0001\u0000\u0000\u0000\u0382\u0383\u0001\u0000\u0000\u0000\u0383"+
+    "\u0385\u0001\u0000\u0000\u0000\u0384\u0374\u0001\u0000\u0000\u0000\u0384"+
+    "\u037d\u0001\u0000\u0000\u0000\u0385\u00aa\u0001\u0000\u0000\u0000\u0386"+
+    "\u0388\u0003M\u001f\u0000\u0387\u0389\u0003O \u0000\u0388\u0387\u0001"+
+    "\u0000\u0000\u0000\u0389\u038a\u0001\u0000\u0000\u0000\u038a\u0388\u0001"+
+    "\u0000\u0000\u0000\u038a\u038b\u0001\u0000\u0000\u0000\u038b\u038c\u0001"+
+    "\u0000\u0000\u0000\u038c\u038d\u0003M\u001f\u0000\u038d\u00ac\u0001\u0000"+
+    "\u0000\u0000\u038e\u038f\u0003\u00abN\u0000\u038f\u00ae\u0001\u0000\u0000"+
+    "\u0000\u0390\u0391\u00039\u0015\u0000\u0391\u0392\u0001\u0000\u0000\u0000"+
+    "\u0392\u0393\u0006P\n\u0000\u0393\u00b0\u0001\u0000\u0000\u0000\u0394"+
+    "\u0395\u0003;\u0016\u0000\u0395\u0396\u0001\u0000\u0000\u0000\u0396\u0397"+
+    "\u0006Q\n\u0000\u0397\u00b2\u0001\u0000\u0000\u0000\u0398\u0399\u0003"+
+    "=\u0017\u0000\u0399\u039a\u0001\u0000\u0000\u0000\u039a\u039b\u0006R\n"+
+    "\u0000\u039b\u00b4\u0001\u0000\u0000\u0000\u039c\u039d\u0003\u00a5K\u0000"+
+    "\u039d\u039e\u0001\u0000\u0000\u0000\u039e\u039f\u0006S\r\u0000\u039f"+
+    "\u03a0\u0006S\u000e\u0000\u03a0\u00b6\u0001\u0000\u0000\u0000\u03a1\u03a2"+
+    "\u0003?\u0018\u0000\u03a2\u03a3\u0001\u0000\u0000\u0000\u03a3\u03a4\u0006"+
+    "T\u000f\u0000\u03a4\u03a5\u0006T\u000b\u0000\u03a5\u00b8\u0001\u0000\u0000"+
+    "\u0000\u03a6\u03a7\u0003=\u0017\u0000\u03a7\u03a8\u0001\u0000\u0000\u0000"+
+    "\u03a8\u03a9\u0006U\n\u0000\u03a9\u00ba\u0001\u0000\u0000\u0000\u03aa"+
+    "\u03ab\u00039\u0015\u0000\u03ab\u03ac\u0001\u0000\u0000\u0000\u03ac\u03ad"+
+    "\u0006V\n\u0000\u03ad\u00bc\u0001\u0000\u0000\u0000\u03ae\u03af\u0003"+
+    ";\u0016\u0000\u03af\u03b0\u0001\u0000\u0000\u0000\u03b0\u03b1\u0006W\n"+
+    "\u0000\u03b1\u00be\u0001\u0000\u0000\u0000\u03b2\u03b3\u0003?\u0018\u0000"+
+    "\u03b3\u03b4\u0001\u0000\u0000\u0000\u03b4\u03b5\u0006X\u000f\u0000\u03b5"+
+    "\u03b6\u0006X\u000b\u0000\u03b6\u00c0\u0001\u0000\u0000\u0000\u03b7\u03b8"+
+    "\u0003\u00a5K\u0000\u03b8\u03b9\u0001\u0000\u0000\u0000\u03b9\u03ba\u0006"+
+    "Y\r\u0000\u03ba\u00c2\u0001\u0000\u0000\u0000\u03bb\u03bc\u0003\u00a7"+
+    "L\u0000\u03bc\u03bd\u0001\u0000\u0000\u0000\u03bd\u03be\u0006Z\u0010\u0000"+
+    "\u03be\u00c4\u0001\u0000\u0000\u0000\u03bf\u03c0\u0003\u0141\u0099\u0000"+
+    "\u03c0\u03c1\u0001\u0000\u0000\u0000\u03c1\u03c2\u0006[\u0011\u0000\u03c2"+
+    "\u00c6\u0001\u0000\u0000\u0000\u03c3\u03c4\u0003e+\u0000\u03c4\u03c5\u0001"+
+    "\u0000\u0000\u0000\u03c5\u03c6\u0006\\\u0012\u0000\u03c6\u00c8\u0001\u0000"+
+    "\u0000\u0000\u03c7\u03c8\u0003a)\u0000\u03c8\u03c9\u0001\u0000\u0000\u0000"+
+    "\u03c9\u03ca\u0006]\u0013\u0000\u03ca\u00ca\u0001\u0000\u0000\u0000\u03cb"+
+    "\u03cc\u0007\u0010\u0000\u0000\u03cc\u03cd\u0007\u0003\u0000\u0000\u03cd"+
+    "\u03ce\u0007\u0005\u0000\u0000\u03ce\u03cf\u0007\f\u0000\u0000\u03cf\u03d0"+
+    "\u0007\u0000\u0000\u0000\u03d0\u03d1\u0007\f\u0000\u0000\u03d1\u03d2\u0007"+
+    "\u0005\u0000\u0000\u03d2\u03d3\u0007\f\u0000\u0000\u03d3\u00cc\u0001\u0000"+
+    "\u0000\u0000\u03d4\u03d8\b \u0000\u0000\u03d5\u03d6\u0005/\u0000\u0000"+
+    "\u03d6\u03d8\b!\u0000\u0000\u03d7\u03d4\u0001\u0000\u0000\u0000\u03d7"+
+    "\u03d5\u0001\u0000\u0000\u0000\u03d8\u00ce\u0001\u0000\u0000\u0000\u03d9"+
+    "\u03db\u0003\u00cd_\u0000\u03da\u03d9\u0001\u0000\u0000\u0000\u03db\u03dc"+
+    "\u0001\u0000\u0000\u0000\u03dc\u03da\u0001\u0000\u0000\u0000\u03dc\u03dd"+
+    "\u0001\u0000\u0000\u0000\u03dd\u00d0\u0001\u0000\u0000\u0000\u03de\u03df"+
+    "\u0003\u00cf`\u0000\u03df\u03e0\u0001\u0000\u0000\u0000\u03e0\u03e1\u0006"+
+    "a\u0014\u0000\u03e1\u00d2\u0001\u0000\u0000\u0000\u03e2\u03e3\u0003U#"+
+    "\u0000\u03e3\u03e4\u0001\u0000\u0000\u0000\u03e4\u03e5\u0006b\u0015\u0000"+
+    "\u03e5\u00d4\u0001\u0000\u0000\u0000\u03e6\u03e7\u00039\u0015\u0000\u03e7"+
+    "\u03e8\u0001\u0000\u0000\u0000\u03e8\u03e9\u0006c\n\u0000\u03e9\u00d6"+
+    "\u0001\u0000\u0000\u0000\u03ea\u03eb\u0003;\u0016\u0000\u03eb\u03ec\u0001"+
+    "\u0000\u0000\u0000\u03ec\u03ed\u0006d\n\u0000\u03ed\u00d8\u0001\u0000"+
+    "\u0000\u0000\u03ee\u03ef\u0003=\u0017\u0000\u03ef\u03f0\u0001\u0000\u0000"+
+    "\u0000\u03f0\u03f1\u0006e\n\u0000\u03f1\u00da\u0001\u0000\u0000\u0000"+
+    "\u03f2\u03f3\u0003?\u0018\u0000\u03f3\u03f4\u0001\u0000\u0000\u0000\u03f4"+
+    "\u03f5\u0006f\u000f\u0000\u03f5\u03f6\u0006f\u000b\u0000\u03f6\u00dc\u0001"+
+    "\u0000\u0000\u0000\u03f7\u03f8\u0003i-\u0000\u03f8\u03f9\u0001\u0000\u0000"+
+    "\u0000\u03f9\u03fa\u0006g\u0016\u0000\u03fa\u00de\u0001\u0000\u0000\u0000"+
+    "\u03fb\u03fc\u0003e+\u0000\u03fc\u03fd\u0001\u0000\u0000\u0000\u03fd\u03fe"+
+    "\u0006h\u0012\u0000\u03fe\u00e0\u0001\u0000\u0000\u0000\u03ff\u0404\u0003"+
+    "C\u001a\u0000\u0400\u0404\u0003A\u0019\u0000\u0401\u0404\u0003Q!\u0000"+
+    "\u0402\u0404\u0003\u009bF\u0000\u0403\u03ff\u0001\u0000\u0000\u0000\u0403"+
+    "\u0400\u0001\u0000\u0000\u0000\u0403\u0401\u0001\u0000\u0000\u0000\u0403"+
+    "\u0402\u0001\u0000\u0000\u0000\u0404\u00e2\u0001\u0000\u0000\u0000\u0405"+
+    "\u0408\u0003C\u001a\u0000\u0406\u0408\u0003\u009bF\u0000\u0407\u0405\u0001"+
+    "\u0000\u0000\u0000\u0407\u0406\u0001\u0000\u0000\u0000\u0408\u040c\u0001"+
+    "\u0000\u0000\u0000\u0409\u040b\u0003\u00e1i\u0000\u040a\u0409\u0001\u0000"+
+    "\u0000\u0000\u040b\u040e\u0001\u0000\u0000\u0000\u040c\u040a\u0001\u0000"+
+    "\u0000\u0000\u040c\u040d\u0001\u0000\u0000\u0000\u040d\u0419\u0001\u0000"+
+    "\u0000\u0000\u040e\u040c\u0001\u0000\u0000\u0000\u040f\u0412\u0003Q!\u0000"+
+    "\u0410\u0412\u0003K\u001e\u0000\u0411\u040f\u0001\u0000\u0000\u0000\u0411"+
+    "\u0410\u0001\u0000\u0000\u0000\u0412\u0414\u0001\u0000\u0000\u0000\u0413"+
+    "\u0415\u0003\u00e1i\u0000\u0414\u0413\u0001\u0000\u0000\u0000\u0415\u0416"+
+    "\u0001\u0000\u0000\u0000\u0416\u0414\u0001\u0000\u0000\u0000\u0416\u0417"+
+    "\u0001\u0000\u0000\u0000\u0417\u0419\u0001\u0000\u0000\u0000\u0418\u0407"+
+    "\u0001\u0000\u0000\u0000\u0418\u0411\u0001\u0000\u0000\u0000\u0419\u00e4"+
+    "\u0001\u0000\u0000\u0000\u041a\u041d\u0003\u00e3j\u0000\u041b\u041d\u0003"+
+    "\u00abN\u0000\u041c\u041a\u0001\u0000\u0000\u0000\u041c\u041b\u0001\u0000"+
+    "\u0000\u0000\u041d\u041e\u0001\u0000\u0000\u0000\u041e\u041c\u0001\u0000"+
+    "\u0000\u0000\u041e\u041f\u0001\u0000\u0000\u0000\u041f\u00e6\u0001\u0000"+
+    "\u0000\u0000\u0420\u0421\u00039\u0015\u0000\u0421\u0422\u0001\u0000\u0000"+
+    "\u0000\u0422\u0423\u0006l\n\u0000\u0423\u00e8\u0001\u0000\u0000\u0000"+
+    "\u0424\u0425\u0003;\u0016\u0000\u0425\u0426\u0001\u0000\u0000\u0000\u0426"+
+    "\u0427\u0006m\n\u0000\u0427\u00ea\u0001\u0000\u0000\u0000\u0428\u0429"+
+    "\u0003=\u0017\u0000\u0429\u042a\u0001\u0000\u0000\u0000\u042a\u042b\u0006"+
+    "n\n\u0000\u042b\u00ec\u0001\u0000\u0000\u0000\u042c\u042d\u0003?\u0018"+
+    "\u0000\u042d\u042e\u0001\u0000\u0000\u0000\u042e\u042f\u0006o\u000f\u0000"+
+    "\u042f\u0430\u0006o\u000b\u0000\u0430\u00ee\u0001\u0000\u0000\u0000\u0431"+
+    "\u0432\u0003a)\u0000\u0432\u0433\u0001\u0000\u0000\u0000\u0433\u0434\u0006"+
+    "p\u0013\u0000\u0434\u00f0\u0001\u0000\u0000\u0000\u0435\u0436\u0003e+"+
+    "\u0000\u0436\u0437\u0001\u0000\u0000\u0000\u0437\u0438\u0006q\u0012\u0000"+
+    "\u0438\u00f2\u0001\u0000\u0000\u0000\u0439\u043a\u0003i-\u0000\u043a\u043b"+
+    "\u0001\u0000\u0000\u0000\u043b\u043c\u0006r\u0016\u0000\u043c\u00f4\u0001"+
+    "\u0000\u0000\u0000\u043d\u043e\u0007\f\u0000\u0000\u043e\u043f\u0007\u0002"+
+    "\u0000\u0000\u043f\u00f6\u0001\u0000\u0000\u0000\u0440\u0441\u0003\u00e5"+
+    "k\u0000\u0441\u0442\u0001\u0000\u0000\u0000\u0442\u0443\u0006t\u0017\u0000"+
+    "\u0443\u00f8\u0001\u0000\u0000\u0000\u0444\u0445\u00039\u0015\u0000\u0445"+
+    "\u0446\u0001\u0000\u0000\u0000\u0446\u0447\u0006u\n\u0000\u0447\u00fa"+
+    "\u0001\u0000\u0000\u0000\u0448\u0449\u0003;\u0016\u0000\u0449\u044a\u0001"+
+    "\u0000\u0000\u0000\u044a\u044b\u0006v\n\u0000\u044b\u00fc\u0001\u0000"+
+    "\u0000\u0000\u044c\u044d\u0003=\u0017\u0000\u044d\u044e\u0001\u0000\u0000"+
+    "\u0000\u044e\u044f\u0006w\n\u0000\u044f\u00fe\u0001\u0000\u0000\u0000"+
+    "\u0450\u0451\u0003?\u0018\u0000\u0451\u0452\u0001\u0000\u0000\u0000\u0452"+
+    "\u0453\u0006x\u000f\u0000\u0453\u0454\u0006x\u000b\u0000\u0454\u0100\u0001"+
+    "\u0000\u0000\u0000\u0455\u0456\u0003\u00a5K\u0000\u0456\u0457\u0001\u0000"+
+    "\u0000\u0000\u0457\u0458\u0006y\r\u0000\u0458\u0459\u0006y\u0018\u0000"+
+    "\u0459\u0102\u0001\u0000\u0000\u0000\u045a\u045b\u0007\u0007\u0000\u0000"+
+    "\u045b\u045c\u0007\t\u0000\u0000\u045c\u045d\u0001\u0000\u0000\u0000\u045d"+
+    "\u045e\u0006z\u0019\u0000\u045e\u0104\u0001\u0000\u0000\u0000\u045f\u0460"+
+    "\u0007\u0013\u0000\u0000\u0460\u0461\u0007\u0001\u0000\u0000\u0461\u0462"+
+    "\u0007\u0005\u0000\u0000\u0462\u0463\u0007\n\u0000\u0000\u0463\u0464\u0001"+
+    "\u0000\u0000\u0000\u0464\u0465\u0006{\u0019\u0000\u0465\u0106\u0001\u0000"+
+    "\u0000\u0000\u0466\u0467\b\"\u0000\u0000\u0467\u0108\u0001\u0000\u0000"+
+    "\u0000\u0468\u046a\u0003\u0107|\u0000\u0469\u0468\u0001\u0000\u0000\u0000"+
+    "\u046a\u046b\u0001\u0000\u0000\u0000\u046b\u0469\u0001\u0000\u0000\u0000"+
+    "\u046b\u046c\u0001\u0000\u0000\u0000\u046c\u046d\u0001\u0000\u0000\u0000"+
+    "\u046d\u046e\u0003\u0141\u0099\u0000\u046e\u0470\u0001\u0000\u0000\u0000"+
+    "\u046f\u0469\u0001\u0000\u0000\u0000\u046f\u0470\u0001\u0000\u0000\u0000"+
+    "\u0470\u0472\u0001\u0000\u0000\u0000\u0471\u0473\u0003\u0107|\u0000\u0472"+
+    "\u0471\u0001\u0000\u0000\u0000\u0473\u0474\u0001\u0000\u0000\u0000\u0474"+
+    "\u0472\u0001\u0000\u0000\u0000\u0474\u0475\u0001\u0000\u0000\u0000\u0475"+
+    "\u010a\u0001\u0000\u0000\u0000\u0476\u0477\u0003\u0109}\u0000\u0477\u0478"+
+    "\u0001\u0000\u0000\u0000\u0478\u0479\u0006~\u001a\u0000\u0479\u010c\u0001"+
+    "\u0000\u0000\u0000\u047a\u047b\u00039\u0015\u0000\u047b\u047c\u0001\u0000"+
+    "\u0000\u0000\u047c\u047d\u0006\u007f\n\u0000\u047d\u010e\u0001\u0000\u0000"+
+    "\u0000\u047e\u047f\u0003;\u0016\u0000\u047f\u0480\u0001\u0000\u0000\u0000"+
+    "\u0480\u0481\u0006\u0080\n\u0000\u0481\u0110\u0001\u0000\u0000\u0000\u0482"+
+    "\u0483\u0003=\u0017\u0000\u0483\u0484\u0001\u0000\u0000\u0000\u0484\u0485"+
+    "\u0006\u0081\n\u0000\u0485\u0112\u0001\u0000\u0000\u0000\u0486\u0487\u0003"+
+    "?\u0018\u0000\u0487\u0488\u0001\u0000\u0000\u0000\u0488\u0489\u0006\u0082"+
+    "\u000f\u0000\u0489\u048a\u0006\u0082\u000b\u0000\u048a\u048b\u0006\u0082"+
+    "\u000b\u0000\u048b\u0114\u0001\u0000\u0000\u0000\u048c\u048d\u0003a)\u0000"+
+    "\u048d\u048e\u0001\u0000\u0000\u0000\u048e\u048f\u0006\u0083\u0013\u0000"+
+    "\u048f\u0116\u0001\u0000\u0000\u0000\u0490\u0491\u0003e+\u0000\u0491\u0492"+
+    "\u0001\u0000\u0000\u0000\u0492\u0493\u0006\u0084\u0012\u0000\u0493\u0118"+
+    "\u0001\u0000\u0000\u0000\u0494\u0495\u0003i-\u0000\u0495\u0496\u0001\u0000"+
+    "\u0000\u0000\u0496\u0497\u0006\u0085\u0016\u0000\u0497\u011a\u0001\u0000"+
+    "\u0000\u0000\u0498\u0499\u0003\u0105{\u0000\u0499\u049a\u0001\u0000\u0000"+
+    "\u0000\u049a\u049b\u0006\u0086\u001b\u0000\u049b\u011c\u0001\u0000\u0000"+
+    "\u0000\u049c\u049d\u0003\u00e5k\u0000\u049d\u049e\u0001\u0000\u0000\u0000"+
+    "\u049e\u049f\u0006\u0087\u0017\u0000\u049f\u011e\u0001\u0000\u0000\u0000"+
+    "\u04a0\u04a1\u0003\u00adO\u0000\u04a1\u04a2\u0001\u0000\u0000\u0000\u04a2"+
+    "\u04a3\u0006\u0088\u001c\u0000\u04a3\u0120\u0001\u0000\u0000\u0000\u04a4"+
+    "\u04a5\u00039\u0015\u0000\u04a5\u04a6\u0001\u0000\u0000\u0000\u04a6\u04a7"+
+    "\u0006\u0089\n\u0000\u04a7\u0122\u0001\u0000\u0000\u0000\u04a8\u04a9\u0003"+
+    ";\u0016\u0000\u04a9\u04aa\u0001\u0000\u0000\u0000\u04aa\u04ab\u0006\u008a"+
+    "\n\u0000\u04ab\u0124\u0001\u0000\u0000\u0000\u04ac\u04ad\u0003=\u0017"+
+    "\u0000\u04ad\u04ae\u0001\u0000\u0000\u0000\u04ae\u04af\u0006\u008b\n\u0000"+
+    "\u04af\u0126\u0001\u0000\u0000\u0000\u04b0\u04b1\u0003?\u0018\u0000\u04b1"+
+    "\u04b2\u0001\u0000\u0000\u0000\u04b2\u04b3\u0006\u008c\u000f\u0000\u04b3"+
+    "\u04b4\u0006\u008c\u000b\u0000\u04b4\u0128\u0001\u0000\u0000\u0000\u04b5"+
+    "\u04b6\u0003i-\u0000\u04b6\u04b7\u0001\u0000\u0000\u0000\u04b7\u04b8\u0006"+
+    "\u008d\u0016\u0000\u04b8\u012a\u0001\u0000\u0000\u0000\u04b9\u04ba\u0003"+
+    "\u00adO\u0000\u04ba\u04bb\u0001\u0000\u0000\u0000\u04bb\u04bc\u0006\u008e"+
+    "\u001c\u0000\u04bc\u012c\u0001\u0000\u0000\u0000\u04bd\u04be\u0003\u00a9"+
+    "M\u0000\u04be\u04bf\u0001\u0000\u0000\u0000\u04bf\u04c0\u0006\u008f\u001d"+
+    "\u0000\u04c0\u012e\u0001\u0000\u0000\u0000\u04c1\u04c2\u00039\u0015\u0000"+
+    "\u04c2\u04c3\u0001\u0000\u0000\u0000\u04c3\u04c4\u0006\u0090\n\u0000\u04c4"+
+    "\u0130\u0001\u0000\u0000\u0000\u04c5\u04c6\u0003;\u0016\u0000\u04c6\u04c7"+
+    "\u0001\u0000\u0000\u0000\u04c7\u04c8\u0006\u0091\n\u0000\u04c8\u0132\u0001"+
+    "\u0000\u0000\u0000\u04c9\u04ca\u0003=\u0017\u0000\u04ca\u04cb\u0001\u0000"+
+    "\u0000\u0000\u04cb\u04cc\u0006\u0092\n\u0000\u04cc\u0134\u0001\u0000\u0000"+
+    "\u0000\u04cd\u04ce\u0003?\u0018\u0000\u04ce\u04cf\u0001\u0000\u0000\u0000"+
+    "\u04cf\u04d0\u0006\u0093\u000f\u0000\u04d0\u04d1\u0006\u0093\u000b\u0000"+
+    "\u04d1\u0136\u0001\u0000\u0000\u0000\u04d2\u04d3\u0007\u0001\u0000\u0000"+
+    "\u04d3\u04d4\u0007\t\u0000\u0000\u04d4\u04d5\u0007\u000f\u0000\u0000\u04d5"+
+    "\u04d6\u0007\u0007\u0000\u0000\u04d6\u0138\u0001\u0000\u0000\u0000\u04d7"+
+    "\u04d8\u00039\u0015\u0000\u04d8\u04d9\u0001\u0000\u0000\u0000\u04d9\u04da"+
+    "\u0006\u0095\n\u0000\u04da\u013a\u0001\u0000\u0000\u0000\u04db\u04dc\u0003"+
+    ";\u0016\u0000\u04dc\u04dd\u0001\u0000\u0000\u0000\u04dd\u04de\u0006\u0096"+
+    "\n\u0000\u04de\u013c\u0001\u0000\u0000\u0000\u04df\u04e0\u0003=\u0017"+
+    "\u0000\u04e0\u04e1\u0001\u0000\u0000\u0000\u04e1\u04e2\u0006\u0097\n\u0000"+
+    "\u04e2\u013e\u0001\u0000\u0000\u0000\u04e3\u04e4\u0003\u00a7L\u0000\u04e4"+
+    "\u04e5\u0001\u0000\u0000\u0000\u04e5\u04e6\u0006\u0098\u0010\u0000\u04e6"+
+    "\u04e7\u0006\u0098\u000b\u0000\u04e7\u0140\u0001\u0000\u0000\u0000\u04e8"+
+    "\u04e9\u0005:\u0000\u0000\u04e9\u0142\u0001\u0000\u0000\u0000\u04ea\u04f0"+
+    "\u0003K\u001e\u0000\u04eb\u04f0\u0003A\u0019\u0000\u04ec\u04f0\u0003i"+
+    "-\u0000\u04ed\u04f0\u0003C\u001a\u0000\u04ee\u04f0\u0003Q!\u0000\u04ef"+
+    "\u04ea\u0001\u0000\u0000\u0000\u04ef\u04eb\u0001\u0000\u0000\u0000\u04ef"+
+    "\u04ec\u0001\u0000\u0000\u0000\u04ef\u04ed\u0001\u0000\u0000\u0000\u04ef"+
+    "\u04ee\u0001\u0000\u0000\u0000\u04f0\u04f1\u0001\u0000\u0000\u0000\u04f1"+
+    "\u04ef\u0001\u0000\u0000\u0000\u04f1\u04f2\u0001\u0000\u0000\u0000\u04f2"+
+    "\u0144\u0001\u0000\u0000\u0000\u04f3\u04f4\u00039\u0015\u0000\u04f4\u04f5"+
+    "\u0001\u0000\u0000\u0000\u04f5\u04f6\u0006\u009b\n\u0000\u04f6\u0146\u0001"+
+    "\u0000\u0000\u0000\u04f7\u04f8\u0003;\u0016\u0000\u04f8\u04f9\u0001\u0000"+
+    "\u0000\u0000\u04f9\u04fa\u0006\u009c\n\u0000\u04fa\u0148\u0001\u0000\u0000"+
+    "\u0000\u04fb\u04fc\u0003=\u0017\u0000\u04fc\u04fd\u0001\u0000\u0000\u0000"+
+    "\u04fd\u04fe\u0006\u009d\n\u0000\u04fe\u014a\u0001\u0000\u0000\u0000\u04ff"+
+    "\u0500\u0003?\u0018\u0000\u0500\u0501\u0001\u0000\u0000\u0000\u0501\u0502"+
+    "\u0006\u009e\u000f\u0000\u0502\u0503\u0006\u009e\u000b\u0000\u0503\u014c"+
+    "\u0001\u0000\u0000\u0000\u0504\u0505\u0003\u0141\u0099\u0000\u0505\u0506"+
+    "\u0001\u0000\u0000\u0000\u0506\u0507\u0006\u009f\u0011\u0000\u0507\u014e"+
+    "\u0001\u0000\u0000\u0000\u0508\u0509\u0003e+\u0000\u0509\u050a\u0001\u0000"+
+    "\u0000\u0000\u050a\u050b\u0006\u00a0\u0012\u0000\u050b\u0150\u0001\u0000"+
+    "\u0000\u0000\u050c\u050d\u0003i-\u0000\u050d\u050e\u0001\u0000\u0000\u0000"+
+    "\u050e\u050f\u0006\u00a1\u0016\u0000\u050f\u0152\u0001\u0000\u0000\u0000"+
+    "\u0510\u0511\u0003\u0103z\u0000\u0511\u0512\u0001\u0000\u0000\u0000\u0512"+
+    "\u0513\u0006\u00a2\u001e\u0000\u0513\u0514\u0006\u00a2\u001f\u0000\u0514"+
+    "\u0154\u0001\u0000\u0000\u0000\u0515\u0516\u0003\u00cf`\u0000\u0516\u0517"+
+    "\u0001\u0000\u0000\u0000\u0517\u0518\u0006\u00a3\u0014\u0000\u0518\u0156"+
+    "\u0001\u0000\u0000\u0000\u0519\u051a\u0003U#\u0000\u051a\u051b\u0001\u0000"+
+    "\u0000\u0000\u051b\u051c\u0006\u00a4\u0015\u0000\u051c\u0158\u0001\u0000"+
+    "\u0000\u0000\u051d\u051e\u00039\u0015\u0000\u051e\u051f\u0001\u0000\u0000"+
+    "\u0000\u051f\u0520\u0006\u00a5\n\u0000\u0520\u015a\u0001\u0000\u0000\u0000"+
+    "\u0521\u0522\u0003;\u0016\u0000\u0522\u0523\u0001\u0000\u0000\u0000\u0523"+
+    "\u0524\u0006\u00a6\n\u0000\u0524\u015c\u0001\u0000\u0000\u0000\u0525\u0526"+
+    "\u0003=\u0017\u0000\u0526\u0527\u0001\u0000\u0000\u0000\u0527\u0528\u0006"+
+    "\u00a7\n\u0000\u0528\u015e\u0001\u0000\u0000\u0000\u0529\u052a\u0003?"+
+    "\u0018\u0000\u052a\u052b\u0001\u0000\u0000\u0000\u052b\u052c\u0006\u00a8"+
+    "\u000f\u0000\u052c\u052d\u0006\u00a8\u000b\u0000\u052d\u052e\u0006\u00a8"+
+    "\u000b\u0000\u052e\u0160\u0001\u0000\u0000\u0000\u052f\u0530\u0003e+\u0000"+
+    "\u0530\u0531\u0001\u0000\u0000\u0000\u0531\u0532\u0006\u00a9\u0012\u0000"+
+    "\u0532\u0162\u0001\u0000\u0000\u0000\u0533\u0534\u0003i-\u0000\u0534\u0535"+
+    "\u0001\u0000\u0000\u0000\u0535\u0536\u0006\u00aa\u0016\u0000\u0536\u0164"+
+    "\u0001\u0000\u0000\u0000\u0537\u0538\u0003\u00e5k\u0000\u0538\u0539\u0001"+
+    "\u0000\u0000\u0000\u0539\u053a\u0006\u00ab\u0017\u0000\u053a\u0166\u0001"+
+    "\u0000\u0000\u0000\u053b\u053c\u00039\u0015\u0000\u053c\u053d\u0001\u0000"+
+    "\u0000\u0000\u053d\u053e\u0006\u00ac\n\u0000\u053e\u0168\u0001\u0000\u0000"+
+    "\u0000\u053f\u0540\u0003;\u0016\u0000\u0540\u0541\u0001\u0000\u0000\u0000"+
+    "\u0541\u0542\u0006\u00ad\n\u0000\u0542\u016a\u0001\u0000\u0000\u0000\u0543"+
+    "\u0544\u0003=\u0017\u0000\u0544\u0545\u0001\u0000\u0000\u0000\u0545\u0546"+
+    "\u0006\u00ae\n\u0000\u0546\u016c\u0001\u0000\u0000\u0000\u0547\u0548\u0003"+
+    "?\u0018\u0000\u0548\u0549\u0001\u0000\u0000\u0000\u0549\u054a\u0006\u00af"+
+    "\u000f\u0000\u054a\u054b\u0006\u00af\u000b\u0000\u054b\u016e\u0001\u0000"+
+    "\u0000\u0000\u054c\u054d\u0003\u00cf`\u0000\u054d\u054e\u0001\u0000\u0000"+
+    "\u0000\u054e\u054f\u0006\u00b0\u0014\u0000\u054f\u0550\u0006\u00b0\u000b"+
+    "\u0000\u0550\u0551\u0006\u00b0 \u0000\u0551\u0170\u0001\u0000\u0000\u0000"+
+    "\u0552\u0553\u0003U#\u0000\u0553\u0554\u0001\u0000\u0000\u0000\u0554\u0555"+
+    "\u0006\u00b1\u0015\u0000\u0555\u0556\u0006\u00b1\u000b\u0000\u0556\u0557"+
+    "\u0006\u00b1 \u0000\u0557\u0172\u0001\u0000\u0000\u0000\u0558\u0559\u0003"+
+    "9\u0015\u0000\u0559\u055a\u0001\u0000\u0000\u0000\u055a\u055b\u0006\u00b2"+
+    "\n\u0000\u055b\u0174\u0001\u0000\u0000\u0000\u055c\u055d\u0003;\u0016"+
+    "\u0000\u055d\u055e\u0001\u0000\u0000\u0000\u055e\u055f\u0006\u00b3\n\u0000"+
+    "\u055f\u0176\u0001\u0000\u0000\u0000\u0560\u0561\u0003=\u0017\u0000\u0561"+
+    "\u0562\u0001\u0000\u0000\u0000\u0562\u0563\u0006\u00b4\n\u0000\u0563\u0178"+
+    "\u0001\u0000\u0000\u0000\u0564\u0565\u0003\u0141\u0099\u0000\u0565\u0566"+
+    "\u0001\u0000\u0000\u0000\u0566\u0567\u0006\u00b5\u0011\u0000\u0567\u0568"+
+    "\u0006\u00b5\u000b\u0000\u0568\u0569\u0006\u00b5\t\u0000\u0569\u017a\u0001"+
+    "\u0000\u0000\u0000\u056a\u056b\u0003e+\u0000\u056b\u056c\u0001\u0000\u0000"+
+    "\u0000\u056c\u056d\u0006\u00b6\u0012\u0000\u056d\u056e\u0006\u00b6\u000b"+
+    "\u0000\u056e\u056f\u0006\u00b6\t\u0000\u056f\u017c\u0001\u0000\u0000\u0000"+
+    "\u0570\u0571\u00039\u0015\u0000\u0571\u0572\u0001\u0000\u0000\u0000\u0572"+
+    "\u0573\u0006\u00b7\n\u0000\u0573\u017e\u0001\u0000\u0000\u0000\u0574\u0575"+
+    "\u0003;\u0016\u0000\u0575\u0576\u0001\u0000\u0000\u0000\u0576\u0577\u0006"+
+    "\u00b8\n\u0000\u0577\u0180\u0001\u0000\u0000\u0000\u0578\u0579\u0003="+
+    "\u0017\u0000\u0579\u057a\u0001\u0000\u0000\u0000\u057a\u057b\u0006\u00b9"+
+    "\n\u0000\u057b\u0182\u0001\u0000\u0000\u0000\u057c\u057d\u0003\u00adO"+
+    "\u0000\u057d\u057e\u0001\u0000\u0000\u0000\u057e\u057f\u0006\u00ba\u000b"+
+    "\u0000\u057f\u0580\u0006\u00ba\u0000\u0000\u0580\u0581\u0006\u00ba\u001c"+
+    "\u0000\u0581\u0184\u0001\u0000\u0000\u0000\u0582\u0583\u0003\u00a9M\u0000"+
+    "\u0583\u0584\u0001\u0000\u0000\u0000\u0584\u0585\u0006\u00bb\u000b\u0000"+
+    "\u0585\u0586\u0006\u00bb\u0000\u0000\u0586\u0587\u0006\u00bb\u001d\u0000"+
+    "\u0587\u0186\u0001\u0000\u0000\u0000\u0588\u0589\u0003[&\u0000\u0589\u058a"+
+    "\u0001\u0000\u0000\u0000\u058a\u058b\u0006\u00bc\u000b\u0000\u058b\u058c"+
+    "\u0006\u00bc\u0000\u0000\u058c\u058d\u0006\u00bc!\u0000\u058d\u0188\u0001"+
+    "\u0000\u0000\u0000\u058e\u058f\u0003?\u0018\u0000\u058f\u0590\u0001\u0000"+
+    "\u0000\u0000\u0590\u0591\u0006\u00bd\u000f\u0000\u0591\u0592\u0006\u00bd"+
+    "\u000b\u0000\u0592\u018a\u0001\u0000\u0000\u0000A\u0000\u0001\u0002\u0003"+
+    "\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u023c\u0246\u024a\u024d"+
+    "\u0256\u0258\u0263\u0276\u027b\u0284\u028b\u0290\u0292\u029d\u02a5\u02a8"+
+    "\u02aa\u02af\u02b4\u02ba\u02c1\u02c6\u02cc\u02cf\u02d7\u02db\u035a\u035f"+
+    "\u0366\u0368\u0378\u037d\u0382\u0384\u038a\u03d7\u03dc\u0403\u0407\u040c"+
+    "\u0411\u0416\u0418\u041c\u041e\u046b\u046f\u0474\u04ef\u04f1\"\u0005\u0001"+
+    "\u0000\u0005\u0004\u0000\u0005\u0006\u0000\u0005\u0002\u0000\u0005\u0003"+
+    "\u0000\u0005\b\u0000\u0005\u0005\u0000\u0005\t\u0000\u0005\u000b\u0000"+
+    "\u0005\r\u0000\u0000\u0001\u0000\u0004\u0000\u0000\u0007\u0013\u0000\u0007"+
+    "A\u0000\u0005\u0000\u0000\u0007\u0019\u0000\u0007B\u0000\u0007h\u0000"+
+    "\u0007\"\u0000\u0007 \u0000\u0007L\u0000\u0007\u001a\u0000\u0007$\u0000"+
+    "\u0007P\u0000\u0005\n\u0000\u0005\u0007\u0000\u0007Z\u0000\u0007Y\u0000"+
+    "\u0007D\u0000\u0007C\u0000\u0007X\u0000\u0005\f\u0000\u0005\u000e\u0000"+
+    "\u0007\u001d\u0000";
   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/EsqlBaseParser.interp b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.interp
index f7eed3e9be796..ae34d683403fb 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
@@ -9,7 +9,6 @@ null
 'grok'
 'keep'
 'limit'
-'meta'
 'mv_expand'
 'rename'
 'row'
@@ -104,10 +103,6 @@ null
 null
 null
 null
-'functions'
-null
-null
-null
 ':'
 null
 null
@@ -137,7 +132,6 @@ FROM
 GROK
 KEEP
 LIMIT
-META
 MV_EXPAND
 RENAME
 ROW
@@ -232,10 +226,6 @@ INFO
 SHOW_LINE_COMMENT
 SHOW_MULTILINE_COMMENT
 SHOW_WS
-FUNCTIONS
-META_LINE_COMMENT
-META_MULTILINE_COMMENT
-META_WS
 COLON
 SETTING
 SETTING_LINE_COMMENT
@@ -309,7 +299,6 @@ comparisonOperator
 explainCommand
 subqueryExpression
 showCommand
-metaCommand
 enrichCommand
 enrichWithClause
 lookupCommand
@@ -317,4 +306,4 @@ inlinestatsCommand
 
 
 atn:
-[4, 1, 125, 578, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 128, 8, 1, 10, 1, 12, 1, 131, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 140, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 158, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 170, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 177, 8, 5, 10, 5, 12, 5, 180, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 187, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 193, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 201, 8, 5, 10, 5, 12, 5, 204, 9, 5, 1, 6, 1, 6, 3, 6, 208, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 215, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 220, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 231, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 237, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 245, 8, 9, 10, 9, 12, 9, 248, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 258, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 263, 8, 10, 10, 10, 12, 10, 266, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 274, 8, 11, 10, 11, 12, 11, 277, 9, 11, 3, 11, 279, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 5, 14, 291, 8, 14, 10, 14, 12, 14, 294, 9, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 301, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 307, 8, 16, 10, 16, 12, 16, 310, 9, 16, 1, 16, 3, 16, 313, 8, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 320, 8, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 328, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 334, 8, 21, 10, 21, 12, 21, 337, 9, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 5, 23, 347, 8, 23, 10, 23, 12, 23, 350, 9, 23, 1, 23, 3, 23, 353, 8, 23, 1, 23, 1, 23, 3, 23, 357, 8, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 3, 25, 364, 8, 25, 1, 25, 1, 25, 3, 25, 368, 8, 25, 1, 26, 1, 26, 1, 26, 5, 26, 373, 8, 26, 10, 26, 12, 26, 376, 9, 26, 1, 27, 1, 27, 1, 27, 5, 27, 381, 8, 27, 10, 27, 12, 27, 384, 9, 27, 1, 28, 1, 28, 1, 28, 5, 28, 389, 8, 28, 10, 28, 12, 28, 392, 9, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 411, 8, 31, 10, 31, 12, 31, 414, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 422, 8, 31, 10, 31, 12, 31, 425, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 433, 8, 31, 10, 31, 12, 31, 436, 9, 31, 1, 31, 1, 31, 3, 31, 440, 8, 31, 1, 32, 1, 32, 3, 32, 444, 8, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 453, 8, 34, 10, 34, 12, 34, 456, 9, 34, 1, 35, 1, 35, 3, 35, 460, 8, 35, 1, 35, 1, 35, 3, 35, 464, 8, 35, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 476, 8, 38, 10, 38, 12, 38, 479, 9, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 3, 40, 489, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 5, 43, 501, 8, 43, 10, 43, 12, 43, 504, 9, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 3, 46, 514, 8, 46, 1, 47, 3, 47, 517, 8, 47, 1, 47, 1, 47, 1, 48, 3, 48, 522, 8, 48, 1, 48, 1, 48, 1, 49, 1, 49, 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, 55, 1, 55, 1, 55, 1, 55, 3, 55, 547, 8, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 553, 8, 55, 10, 55, 12, 55, 556, 9, 55, 3, 55, 558, 8, 55, 1, 56, 1, 56, 1, 56, 3, 56, 563, 8, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 576, 8, 58, 1, 58, 0, 4, 2, 10, 18, 20, 59, 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, 0, 8, 1, 0, 60, 61, 1, 0, 62, 64, 2, 0, 27, 27, 77, 77, 1, 0, 68, 69, 2, 0, 32, 32, 36, 36, 2, 0, 39, 39, 42, 42, 2, 0, 38, 38, 52, 52, 2, 0, 53, 53, 55, 59, 603, 0, 118, 1, 0, 0, 0, 2, 121, 1, 0, 0, 0, 4, 139, 1, 0, 0, 0, 6, 157, 1, 0, 0, 0, 8, 159, 1, 0, 0, 0, 10, 192, 1, 0, 0, 0, 12, 219, 1, 0, 0, 0, 14, 221, 1, 0, 0, 0, 16, 230, 1, 0, 0, 0, 18, 236, 1, 0, 0, 0, 20, 257, 1, 0, 0, 0, 22, 267, 1, 0, 0, 0, 24, 282, 1, 0, 0, 0, 26, 284, 1, 0, 0, 0, 28, 287, 1, 0, 0, 0, 30, 300, 1, 0, 0, 0, 32, 302, 1, 0, 0, 0, 34, 319, 1, 0, 0, 0, 36, 321, 1, 0, 0, 0, 38, 323, 1, 0, 0, 0, 40, 327, 1, 0, 0, 0, 42, 329, 1, 0, 0, 0, 44, 338, 1, 0, 0, 0, 46, 342, 1, 0, 0, 0, 48, 358, 1, 0, 0, 0, 50, 361, 1, 0, 0, 0, 52, 369, 1, 0, 0, 0, 54, 377, 1, 0, 0, 0, 56, 385, 1, 0, 0, 0, 58, 393, 1, 0, 0, 0, 60, 395, 1, 0, 0, 0, 62, 439, 1, 0, 0, 0, 64, 443, 1, 0, 0, 0, 66, 445, 1, 0, 0, 0, 68, 448, 1, 0, 0, 0, 70, 457, 1, 0, 0, 0, 72, 465, 1, 0, 0, 0, 74, 468, 1, 0, 0, 0, 76, 471, 1, 0, 0, 0, 78, 480, 1, 0, 0, 0, 80, 484, 1, 0, 0, 0, 82, 490, 1, 0, 0, 0, 84, 494, 1, 0, 0, 0, 86, 497, 1, 0, 0, 0, 88, 505, 1, 0, 0, 0, 90, 509, 1, 0, 0, 0, 92, 513, 1, 0, 0, 0, 94, 516, 1, 0, 0, 0, 96, 521, 1, 0, 0, 0, 98, 525, 1, 0, 0, 0, 100, 527, 1, 0, 0, 0, 102, 529, 1, 0, 0, 0, 104, 532, 1, 0, 0, 0, 106, 536, 1, 0, 0, 0, 108, 539, 1, 0, 0, 0, 110, 542, 1, 0, 0, 0, 112, 562, 1, 0, 0, 0, 114, 566, 1, 0, 0, 0, 116, 571, 1, 0, 0, 0, 118, 119, 3, 2, 1, 0, 119, 120, 5, 0, 0, 1, 120, 1, 1, 0, 0, 0, 121, 122, 6, 1, -1, 0, 122, 123, 3, 4, 2, 0, 123, 129, 1, 0, 0, 0, 124, 125, 10, 1, 0, 0, 125, 126, 5, 26, 0, 0, 126, 128, 3, 6, 3, 0, 127, 124, 1, 0, 0, 0, 128, 131, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 3, 1, 0, 0, 0, 131, 129, 1, 0, 0, 0, 132, 140, 3, 102, 51, 0, 133, 140, 3, 32, 16, 0, 134, 140, 3, 108, 54, 0, 135, 140, 3, 26, 13, 0, 136, 140, 3, 106, 53, 0, 137, 138, 4, 2, 1, 0, 138, 140, 3, 46, 23, 0, 139, 132, 1, 0, 0, 0, 139, 133, 1, 0, 0, 0, 139, 134, 1, 0, 0, 0, 139, 135, 1, 0, 0, 0, 139, 136, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 140, 5, 1, 0, 0, 0, 141, 158, 3, 48, 24, 0, 142, 158, 3, 8, 4, 0, 143, 158, 3, 72, 36, 0, 144, 158, 3, 66, 33, 0, 145, 158, 3, 50, 25, 0, 146, 158, 3, 68, 34, 0, 147, 158, 3, 74, 37, 0, 148, 158, 3, 76, 38, 0, 149, 158, 3, 80, 40, 0, 150, 158, 3, 82, 41, 0, 151, 158, 3, 110, 55, 0, 152, 158, 3, 84, 42, 0, 153, 154, 4, 3, 2, 0, 154, 158, 3, 116, 58, 0, 155, 156, 4, 3, 3, 0, 156, 158, 3, 114, 57, 0, 157, 141, 1, 0, 0, 0, 157, 142, 1, 0, 0, 0, 157, 143, 1, 0, 0, 0, 157, 144, 1, 0, 0, 0, 157, 145, 1, 0, 0, 0, 157, 146, 1, 0, 0, 0, 157, 147, 1, 0, 0, 0, 157, 148, 1, 0, 0, 0, 157, 149, 1, 0, 0, 0, 157, 150, 1, 0, 0, 0, 157, 151, 1, 0, 0, 0, 157, 152, 1, 0, 0, 0, 157, 153, 1, 0, 0, 0, 157, 155, 1, 0, 0, 0, 158, 7, 1, 0, 0, 0, 159, 160, 5, 17, 0, 0, 160, 161, 3, 10, 5, 0, 161, 9, 1, 0, 0, 0, 162, 163, 6, 5, -1, 0, 163, 164, 5, 45, 0, 0, 164, 193, 3, 10, 5, 8, 165, 193, 3, 16, 8, 0, 166, 193, 3, 12, 6, 0, 167, 169, 3, 16, 8, 0, 168, 170, 5, 45, 0, 0, 169, 168, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 171, 1, 0, 0, 0, 171, 172, 5, 40, 0, 0, 172, 173, 5, 44, 0, 0, 173, 178, 3, 16, 8, 0, 174, 175, 5, 35, 0, 0, 175, 177, 3, 16, 8, 0, 176, 174, 1, 0, 0, 0, 177, 180, 1, 0, 0, 0, 178, 176, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 181, 1, 0, 0, 0, 180, 178, 1, 0, 0, 0, 181, 182, 5, 51, 0, 0, 182, 193, 1, 0, 0, 0, 183, 184, 3, 16, 8, 0, 184, 186, 5, 41, 0, 0, 185, 187, 5, 45, 0, 0, 186, 185, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 1, 0, 0, 0, 188, 189, 5, 46, 0, 0, 189, 193, 1, 0, 0, 0, 190, 191, 4, 5, 4, 0, 191, 193, 3, 14, 7, 0, 192, 162, 1, 0, 0, 0, 192, 165, 1, 0, 0, 0, 192, 166, 1, 0, 0, 0, 192, 167, 1, 0, 0, 0, 192, 183, 1, 0, 0, 0, 192, 190, 1, 0, 0, 0, 193, 202, 1, 0, 0, 0, 194, 195, 10, 5, 0, 0, 195, 196, 5, 31, 0, 0, 196, 201, 3, 10, 5, 6, 197, 198, 10, 4, 0, 0, 198, 199, 5, 48, 0, 0, 199, 201, 3, 10, 5, 5, 200, 194, 1, 0, 0, 0, 200, 197, 1, 0, 0, 0, 201, 204, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 202, 203, 1, 0, 0, 0, 203, 11, 1, 0, 0, 0, 204, 202, 1, 0, 0, 0, 205, 207, 3, 16, 8, 0, 206, 208, 5, 45, 0, 0, 207, 206, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 209, 1, 0, 0, 0, 209, 210, 5, 43, 0, 0, 210, 211, 3, 98, 49, 0, 211, 220, 1, 0, 0, 0, 212, 214, 3, 16, 8, 0, 213, 215, 5, 45, 0, 0, 214, 213, 1, 0, 0, 0, 214, 215, 1, 0, 0, 0, 215, 216, 1, 0, 0, 0, 216, 217, 5, 50, 0, 0, 217, 218, 3, 98, 49, 0, 218, 220, 1, 0, 0, 0, 219, 205, 1, 0, 0, 0, 219, 212, 1, 0, 0, 0, 220, 13, 1, 0, 0, 0, 221, 222, 3, 16, 8, 0, 222, 223, 5, 20, 0, 0, 223, 224, 3, 98, 49, 0, 224, 15, 1, 0, 0, 0, 225, 231, 3, 18, 9, 0, 226, 227, 3, 18, 9, 0, 227, 228, 3, 100, 50, 0, 228, 229, 3, 18, 9, 0, 229, 231, 1, 0, 0, 0, 230, 225, 1, 0, 0, 0, 230, 226, 1, 0, 0, 0, 231, 17, 1, 0, 0, 0, 232, 233, 6, 9, -1, 0, 233, 237, 3, 20, 10, 0, 234, 235, 7, 0, 0, 0, 235, 237, 3, 18, 9, 3, 236, 232, 1, 0, 0, 0, 236, 234, 1, 0, 0, 0, 237, 246, 1, 0, 0, 0, 238, 239, 10, 2, 0, 0, 239, 240, 7, 1, 0, 0, 240, 245, 3, 18, 9, 3, 241, 242, 10, 1, 0, 0, 242, 243, 7, 0, 0, 0, 243, 245, 3, 18, 9, 2, 244, 238, 1, 0, 0, 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, 19, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 249, 250, 6, 10, -1, 0, 250, 258, 3, 62, 31, 0, 251, 258, 3, 52, 26, 0, 252, 258, 3, 22, 11, 0, 253, 254, 5, 44, 0, 0, 254, 255, 3, 10, 5, 0, 255, 256, 5, 51, 0, 0, 256, 258, 1, 0, 0, 0, 257, 249, 1, 0, 0, 0, 257, 251, 1, 0, 0, 0, 257, 252, 1, 0, 0, 0, 257, 253, 1, 0, 0, 0, 258, 264, 1, 0, 0, 0, 259, 260, 10, 1, 0, 0, 260, 261, 5, 34, 0, 0, 261, 263, 3, 24, 12, 0, 262, 259, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 21, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 268, 3, 58, 29, 0, 268, 278, 5, 44, 0, 0, 269, 279, 5, 62, 0, 0, 270, 275, 3, 10, 5, 0, 271, 272, 5, 35, 0, 0, 272, 274, 3, 10, 5, 0, 273, 271, 1, 0, 0, 0, 274, 277, 1, 0, 0, 0, 275, 273, 1, 0, 0, 0, 275, 276, 1, 0, 0, 0, 276, 279, 1, 0, 0, 0, 277, 275, 1, 0, 0, 0, 278, 269, 1, 0, 0, 0, 278, 270, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 281, 5, 51, 0, 0, 281, 23, 1, 0, 0, 0, 282, 283, 3, 58, 29, 0, 283, 25, 1, 0, 0, 0, 284, 285, 5, 13, 0, 0, 285, 286, 3, 28, 14, 0, 286, 27, 1, 0, 0, 0, 287, 292, 3, 30, 15, 0, 288, 289, 5, 35, 0, 0, 289, 291, 3, 30, 15, 0, 290, 288, 1, 0, 0, 0, 291, 294, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 29, 1, 0, 0, 0, 294, 292, 1, 0, 0, 0, 295, 301, 3, 10, 5, 0, 296, 297, 3, 52, 26, 0, 297, 298, 5, 33, 0, 0, 298, 299, 3, 10, 5, 0, 299, 301, 1, 0, 0, 0, 300, 295, 1, 0, 0, 0, 300, 296, 1, 0, 0, 0, 301, 31, 1, 0, 0, 0, 302, 303, 5, 6, 0, 0, 303, 308, 3, 34, 17, 0, 304, 305, 5, 35, 0, 0, 305, 307, 3, 34, 17, 0, 306, 304, 1, 0, 0, 0, 307, 310, 1, 0, 0, 0, 308, 306, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 312, 1, 0, 0, 0, 310, 308, 1, 0, 0, 0, 311, 313, 3, 40, 20, 0, 312, 311, 1, 0, 0, 0, 312, 313, 1, 0, 0, 0, 313, 33, 1, 0, 0, 0, 314, 315, 3, 36, 18, 0, 315, 316, 5, 109, 0, 0, 316, 317, 3, 38, 19, 0, 317, 320, 1, 0, 0, 0, 318, 320, 3, 38, 19, 0, 319, 314, 1, 0, 0, 0, 319, 318, 1, 0, 0, 0, 320, 35, 1, 0, 0, 0, 321, 322, 5, 77, 0, 0, 322, 37, 1, 0, 0, 0, 323, 324, 7, 2, 0, 0, 324, 39, 1, 0, 0, 0, 325, 328, 3, 42, 21, 0, 326, 328, 3, 44, 22, 0, 327, 325, 1, 0, 0, 0, 327, 326, 1, 0, 0, 0, 328, 41, 1, 0, 0, 0, 329, 330, 5, 76, 0, 0, 330, 335, 5, 77, 0, 0, 331, 332, 5, 35, 0, 0, 332, 334, 5, 77, 0, 0, 333, 331, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 333, 1, 0, 0, 0, 335, 336, 1, 0, 0, 0, 336, 43, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 339, 5, 66, 0, 0, 339, 340, 3, 42, 21, 0, 340, 341, 5, 67, 0, 0, 341, 45, 1, 0, 0, 0, 342, 343, 5, 21, 0, 0, 343, 348, 3, 34, 17, 0, 344, 345, 5, 35, 0, 0, 345, 347, 3, 34, 17, 0, 346, 344, 1, 0, 0, 0, 347, 350, 1, 0, 0, 0, 348, 346, 1, 0, 0, 0, 348, 349, 1, 0, 0, 0, 349, 352, 1, 0, 0, 0, 350, 348, 1, 0, 0, 0, 351, 353, 3, 28, 14, 0, 352, 351, 1, 0, 0, 0, 352, 353, 1, 0, 0, 0, 353, 356, 1, 0, 0, 0, 354, 355, 5, 30, 0, 0, 355, 357, 3, 28, 14, 0, 356, 354, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 47, 1, 0, 0, 0, 358, 359, 5, 4, 0, 0, 359, 360, 3, 28, 14, 0, 360, 49, 1, 0, 0, 0, 361, 363, 5, 16, 0, 0, 362, 364, 3, 28, 14, 0, 363, 362, 1, 0, 0, 0, 363, 364, 1, 0, 0, 0, 364, 367, 1, 0, 0, 0, 365, 366, 5, 30, 0, 0, 366, 368, 3, 28, 14, 0, 367, 365, 1, 0, 0, 0, 367, 368, 1, 0, 0, 0, 368, 51, 1, 0, 0, 0, 369, 374, 3, 58, 29, 0, 370, 371, 5, 37, 0, 0, 371, 373, 3, 58, 29, 0, 372, 370, 1, 0, 0, 0, 373, 376, 1, 0, 0, 0, 374, 372, 1, 0, 0, 0, 374, 375, 1, 0, 0, 0, 375, 53, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 377, 382, 3, 60, 30, 0, 378, 379, 5, 37, 0, 0, 379, 381, 3, 60, 30, 0, 380, 378, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 380, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 55, 1, 0, 0, 0, 384, 382, 1, 0, 0, 0, 385, 390, 3, 54, 27, 0, 386, 387, 5, 35, 0, 0, 387, 389, 3, 54, 27, 0, 388, 386, 1, 0, 0, 0, 389, 392, 1, 0, 0, 0, 390, 388, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 57, 1, 0, 0, 0, 392, 390, 1, 0, 0, 0, 393, 394, 7, 3, 0, 0, 394, 59, 1, 0, 0, 0, 395, 396, 5, 81, 0, 0, 396, 61, 1, 0, 0, 0, 397, 440, 5, 46, 0, 0, 398, 399, 3, 96, 48, 0, 399, 400, 5, 68, 0, 0, 400, 440, 1, 0, 0, 0, 401, 440, 3, 94, 47, 0, 402, 440, 3, 96, 48, 0, 403, 440, 3, 90, 45, 0, 404, 440, 3, 64, 32, 0, 405, 440, 3, 98, 49, 0, 406, 407, 5, 66, 0, 0, 407, 412, 3, 92, 46, 0, 408, 409, 5, 35, 0, 0, 409, 411, 3, 92, 46, 0, 410, 408, 1, 0, 0, 0, 411, 414, 1, 0, 0, 0, 412, 410, 1, 0, 0, 0, 412, 413, 1, 0, 0, 0, 413, 415, 1, 0, 0, 0, 414, 412, 1, 0, 0, 0, 415, 416, 5, 67, 0, 0, 416, 440, 1, 0, 0, 0, 417, 418, 5, 66, 0, 0, 418, 423, 3, 90, 45, 0, 419, 420, 5, 35, 0, 0, 420, 422, 3, 90, 45, 0, 421, 419, 1, 0, 0, 0, 422, 425, 1, 0, 0, 0, 423, 421, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 426, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 5, 67, 0, 0, 427, 440, 1, 0, 0, 0, 428, 429, 5, 66, 0, 0, 429, 434, 3, 98, 49, 0, 430, 431, 5, 35, 0, 0, 431, 433, 3, 98, 49, 0, 432, 430, 1, 0, 0, 0, 433, 436, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 437, 1, 0, 0, 0, 436, 434, 1, 0, 0, 0, 437, 438, 5, 67, 0, 0, 438, 440, 1, 0, 0, 0, 439, 397, 1, 0, 0, 0, 439, 398, 1, 0, 0, 0, 439, 401, 1, 0, 0, 0, 439, 402, 1, 0, 0, 0, 439, 403, 1, 0, 0, 0, 439, 404, 1, 0, 0, 0, 439, 405, 1, 0, 0, 0, 439, 406, 1, 0, 0, 0, 439, 417, 1, 0, 0, 0, 439, 428, 1, 0, 0, 0, 440, 63, 1, 0, 0, 0, 441, 444, 5, 49, 0, 0, 442, 444, 5, 65, 0, 0, 443, 441, 1, 0, 0, 0, 443, 442, 1, 0, 0, 0, 444, 65, 1, 0, 0, 0, 445, 446, 5, 9, 0, 0, 446, 447, 5, 28, 0, 0, 447, 67, 1, 0, 0, 0, 448, 449, 5, 15, 0, 0, 449, 454, 3, 70, 35, 0, 450, 451, 5, 35, 0, 0, 451, 453, 3, 70, 35, 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, 69, 1, 0, 0, 0, 456, 454, 1, 0, 0, 0, 457, 459, 3, 10, 5, 0, 458, 460, 7, 4, 0, 0, 459, 458, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 463, 1, 0, 0, 0, 461, 462, 5, 47, 0, 0, 462, 464, 7, 5, 0, 0, 463, 461, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 71, 1, 0, 0, 0, 465, 466, 5, 8, 0, 0, 466, 467, 3, 56, 28, 0, 467, 73, 1, 0, 0, 0, 468, 469, 5, 2, 0, 0, 469, 470, 3, 56, 28, 0, 470, 75, 1, 0, 0, 0, 471, 472, 5, 12, 0, 0, 472, 477, 3, 78, 39, 0, 473, 474, 5, 35, 0, 0, 474, 476, 3, 78, 39, 0, 475, 473, 1, 0, 0, 0, 476, 479, 1, 0, 0, 0, 477, 475, 1, 0, 0, 0, 477, 478, 1, 0, 0, 0, 478, 77, 1, 0, 0, 0, 479, 477, 1, 0, 0, 0, 480, 481, 3, 54, 27, 0, 481, 482, 5, 85, 0, 0, 482, 483, 3, 54, 27, 0, 483, 79, 1, 0, 0, 0, 484, 485, 5, 1, 0, 0, 485, 486, 3, 20, 10, 0, 486, 488, 3, 98, 49, 0, 487, 489, 3, 86, 43, 0, 488, 487, 1, 0, 0, 0, 488, 489, 1, 0, 0, 0, 489, 81, 1, 0, 0, 0, 490, 491, 5, 7, 0, 0, 491, 492, 3, 20, 10, 0, 492, 493, 3, 98, 49, 0, 493, 83, 1, 0, 0, 0, 494, 495, 5, 11, 0, 0, 495, 496, 3, 52, 26, 0, 496, 85, 1, 0, 0, 0, 497, 502, 3, 88, 44, 0, 498, 499, 5, 35, 0, 0, 499, 501, 3, 88, 44, 0, 500, 498, 1, 0, 0, 0, 501, 504, 1, 0, 0, 0, 502, 500, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 87, 1, 0, 0, 0, 504, 502, 1, 0, 0, 0, 505, 506, 3, 58, 29, 0, 506, 507, 5, 33, 0, 0, 507, 508, 3, 62, 31, 0, 508, 89, 1, 0, 0, 0, 509, 510, 7, 6, 0, 0, 510, 91, 1, 0, 0, 0, 511, 514, 3, 94, 47, 0, 512, 514, 3, 96, 48, 0, 513, 511, 1, 0, 0, 0, 513, 512, 1, 0, 0, 0, 514, 93, 1, 0, 0, 0, 515, 517, 7, 0, 0, 0, 516, 515, 1, 0, 0, 0, 516, 517, 1, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 519, 5, 29, 0, 0, 519, 95, 1, 0, 0, 0, 520, 522, 7, 0, 0, 0, 521, 520, 1, 0, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, 1, 0, 0, 0, 523, 524, 5, 28, 0, 0, 524, 97, 1, 0, 0, 0, 525, 526, 5, 27, 0, 0, 526, 99, 1, 0, 0, 0, 527, 528, 7, 7, 0, 0, 528, 101, 1, 0, 0, 0, 529, 530, 5, 5, 0, 0, 530, 531, 3, 104, 52, 0, 531, 103, 1, 0, 0, 0, 532, 533, 5, 66, 0, 0, 533, 534, 3, 2, 1, 0, 534, 535, 5, 67, 0, 0, 535, 105, 1, 0, 0, 0, 536, 537, 5, 14, 0, 0, 537, 538, 5, 101, 0, 0, 538, 107, 1, 0, 0, 0, 539, 540, 5, 10, 0, 0, 540, 541, 5, 105, 0, 0, 541, 109, 1, 0, 0, 0, 542, 543, 5, 3, 0, 0, 543, 546, 5, 91, 0, 0, 544, 545, 5, 89, 0, 0, 545, 547, 3, 54, 27, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 557, 1, 0, 0, 0, 548, 549, 5, 90, 0, 0, 549, 554, 3, 112, 56, 0, 550, 551, 5, 35, 0, 0, 551, 553, 3, 112, 56, 0, 552, 550, 1, 0, 0, 0, 553, 556, 1, 0, 0, 0, 554, 552, 1, 0, 0, 0, 554, 555, 1, 0, 0, 0, 555, 558, 1, 0, 0, 0, 556, 554, 1, 0, 0, 0, 557, 548, 1, 0, 0, 0, 557, 558, 1, 0, 0, 0, 558, 111, 1, 0, 0, 0, 559, 560, 3, 54, 27, 0, 560, 561, 5, 33, 0, 0, 561, 563, 1, 0, 0, 0, 562, 559, 1, 0, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 1, 0, 0, 0, 564, 565, 3, 54, 27, 0, 565, 113, 1, 0, 0, 0, 566, 567, 5, 19, 0, 0, 567, 568, 3, 34, 17, 0, 568, 569, 5, 89, 0, 0, 569, 570, 3, 56, 28, 0, 570, 115, 1, 0, 0, 0, 571, 572, 5, 18, 0, 0, 572, 575, 3, 28, 14, 0, 573, 574, 5, 30, 0, 0, 574, 576, 3, 28, 14, 0, 575, 573, 1, 0, 0, 0, 575, 576, 1, 0, 0, 0, 576, 117, 1, 0, 0, 0, 54, 129, 139, 157, 169, 178, 186, 192, 200, 202, 207, 214, 219, 230, 236, 244, 246, 257, 264, 275, 278, 292, 300, 308, 312, 319, 327, 335, 348, 352, 356, 363, 367, 374, 382, 390, 412, 423, 434, 439, 443, 454, 459, 463, 477, 488, 502, 513, 516, 521, 546, 554, 557, 562, 575]
\ No newline at end of file
+[4, 1, 120, 572, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 126, 8, 1, 10, 1, 12, 1, 129, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 137, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 155, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 167, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 174, 8, 5, 10, 5, 12, 5, 177, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 184, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 190, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 198, 8, 5, 10, 5, 12, 5, 201, 9, 5, 1, 6, 1, 6, 3, 6, 205, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 212, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 217, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 228, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 234, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 242, 8, 9, 10, 9, 12, 9, 245, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 255, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 260, 8, 10, 10, 10, 12, 10, 263, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 271, 8, 11, 10, 11, 12, 11, 274, 9, 11, 3, 11, 276, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 5, 14, 288, 8, 14, 10, 14, 12, 14, 291, 9, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 298, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 304, 8, 16, 10, 16, 12, 16, 307, 9, 16, 1, 16, 3, 16, 310, 8, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 317, 8, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 325, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 331, 8, 21, 10, 21, 12, 21, 334, 9, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 5, 23, 344, 8, 23, 10, 23, 12, 23, 347, 9, 23, 1, 23, 3, 23, 350, 8, 23, 1, 23, 1, 23, 3, 23, 354, 8, 23, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 3, 25, 361, 8, 25, 1, 25, 1, 25, 3, 25, 365, 8, 25, 1, 26, 1, 26, 1, 26, 5, 26, 370, 8, 26, 10, 26, 12, 26, 373, 9, 26, 1, 27, 1, 27, 1, 27, 5, 27, 378, 8, 27, 10, 27, 12, 27, 381, 9, 27, 1, 28, 1, 28, 1, 28, 5, 28, 386, 8, 28, 10, 28, 12, 28, 389, 9, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 408, 8, 31, 10, 31, 12, 31, 411, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 419, 8, 31, 10, 31, 12, 31, 422, 9, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 430, 8, 31, 10, 31, 12, 31, 433, 9, 31, 1, 31, 1, 31, 3, 31, 437, 8, 31, 1, 32, 1, 32, 3, 32, 441, 8, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 450, 8, 34, 10, 34, 12, 34, 453, 9, 34, 1, 35, 1, 35, 3, 35, 457, 8, 35, 1, 35, 1, 35, 3, 35, 461, 8, 35, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 473, 8, 38, 10, 38, 12, 38, 476, 9, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 3, 40, 486, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 5, 43, 498, 8, 43, 10, 43, 12, 43, 501, 9, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 3, 46, 511, 8, 46, 1, 47, 3, 47, 514, 8, 47, 1, 47, 1, 47, 1, 48, 3, 48, 519, 8, 48, 1, 48, 1, 48, 1, 49, 1, 49, 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, 541, 8, 54, 1, 54, 1, 54, 1, 54, 1, 54, 5, 54, 547, 8, 54, 10, 54, 12, 54, 550, 9, 54, 3, 54, 552, 8, 54, 1, 55, 1, 55, 1, 55, 3, 55, 557, 8, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 570, 8, 57, 1, 57, 0, 4, 2, 10, 18, 20, 58, 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, 0, 8, 1, 0, 59, 60, 1, 0, 61, 63, 2, 0, 26, 26, 76, 76, 1, 0, 67, 68, 2, 0, 31, 31, 35, 35, 2, 0, 38, 38, 41, 41, 2, 0, 37, 37, 51, 51, 2, 0, 52, 52, 54, 58, 597, 0, 116, 1, 0, 0, 0, 2, 119, 1, 0, 0, 0, 4, 136, 1, 0, 0, 0, 6, 154, 1, 0, 0, 0, 8, 156, 1, 0, 0, 0, 10, 189, 1, 0, 0, 0, 12, 216, 1, 0, 0, 0, 14, 218, 1, 0, 0, 0, 16, 227, 1, 0, 0, 0, 18, 233, 1, 0, 0, 0, 20, 254, 1, 0, 0, 0, 22, 264, 1, 0, 0, 0, 24, 279, 1, 0, 0, 0, 26, 281, 1, 0, 0, 0, 28, 284, 1, 0, 0, 0, 30, 297, 1, 0, 0, 0, 32, 299, 1, 0, 0, 0, 34, 316, 1, 0, 0, 0, 36, 318, 1, 0, 0, 0, 38, 320, 1, 0, 0, 0, 40, 324, 1, 0, 0, 0, 42, 326, 1, 0, 0, 0, 44, 335, 1, 0, 0, 0, 46, 339, 1, 0, 0, 0, 48, 355, 1, 0, 0, 0, 50, 358, 1, 0, 0, 0, 52, 366, 1, 0, 0, 0, 54, 374, 1, 0, 0, 0, 56, 382, 1, 0, 0, 0, 58, 390, 1, 0, 0, 0, 60, 392, 1, 0, 0, 0, 62, 436, 1, 0, 0, 0, 64, 440, 1, 0, 0, 0, 66, 442, 1, 0, 0, 0, 68, 445, 1, 0, 0, 0, 70, 454, 1, 0, 0, 0, 72, 462, 1, 0, 0, 0, 74, 465, 1, 0, 0, 0, 76, 468, 1, 0, 0, 0, 78, 477, 1, 0, 0, 0, 80, 481, 1, 0, 0, 0, 82, 487, 1, 0, 0, 0, 84, 491, 1, 0, 0, 0, 86, 494, 1, 0, 0, 0, 88, 502, 1, 0, 0, 0, 90, 506, 1, 0, 0, 0, 92, 510, 1, 0, 0, 0, 94, 513, 1, 0, 0, 0, 96, 518, 1, 0, 0, 0, 98, 522, 1, 0, 0, 0, 100, 524, 1, 0, 0, 0, 102, 526, 1, 0, 0, 0, 104, 529, 1, 0, 0, 0, 106, 533, 1, 0, 0, 0, 108, 536, 1, 0, 0, 0, 110, 556, 1, 0, 0, 0, 112, 560, 1, 0, 0, 0, 114, 565, 1, 0, 0, 0, 116, 117, 3, 2, 1, 0, 117, 118, 5, 0, 0, 1, 118, 1, 1, 0, 0, 0, 119, 120, 6, 1, -1, 0, 120, 121, 3, 4, 2, 0, 121, 127, 1, 0, 0, 0, 122, 123, 10, 1, 0, 0, 123, 124, 5, 25, 0, 0, 124, 126, 3, 6, 3, 0, 125, 122, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 3, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 130, 137, 3, 102, 51, 0, 131, 137, 3, 32, 16, 0, 132, 137, 3, 26, 13, 0, 133, 137, 3, 106, 53, 0, 134, 135, 4, 2, 1, 0, 135, 137, 3, 46, 23, 0, 136, 130, 1, 0, 0, 0, 136, 131, 1, 0, 0, 0, 136, 132, 1, 0, 0, 0, 136, 133, 1, 0, 0, 0, 136, 134, 1, 0, 0, 0, 137, 5, 1, 0, 0, 0, 138, 155, 3, 48, 24, 0, 139, 155, 3, 8, 4, 0, 140, 155, 3, 72, 36, 0, 141, 155, 3, 66, 33, 0, 142, 155, 3, 50, 25, 0, 143, 155, 3, 68, 34, 0, 144, 155, 3, 74, 37, 0, 145, 155, 3, 76, 38, 0, 146, 155, 3, 80, 40, 0, 147, 155, 3, 82, 41, 0, 148, 155, 3, 108, 54, 0, 149, 155, 3, 84, 42, 0, 150, 151, 4, 3, 2, 0, 151, 155, 3, 114, 57, 0, 152, 153, 4, 3, 3, 0, 153, 155, 3, 112, 56, 0, 154, 138, 1, 0, 0, 0, 154, 139, 1, 0, 0, 0, 154, 140, 1, 0, 0, 0, 154, 141, 1, 0, 0, 0, 154, 142, 1, 0, 0, 0, 154, 143, 1, 0, 0, 0, 154, 144, 1, 0, 0, 0, 154, 145, 1, 0, 0, 0, 154, 146, 1, 0, 0, 0, 154, 147, 1, 0, 0, 0, 154, 148, 1, 0, 0, 0, 154, 149, 1, 0, 0, 0, 154, 150, 1, 0, 0, 0, 154, 152, 1, 0, 0, 0, 155, 7, 1, 0, 0, 0, 156, 157, 5, 16, 0, 0, 157, 158, 3, 10, 5, 0, 158, 9, 1, 0, 0, 0, 159, 160, 6, 5, -1, 0, 160, 161, 5, 44, 0, 0, 161, 190, 3, 10, 5, 8, 162, 190, 3, 16, 8, 0, 163, 190, 3, 12, 6, 0, 164, 166, 3, 16, 8, 0, 165, 167, 5, 44, 0, 0, 166, 165, 1, 0, 0, 0, 166, 167, 1, 0, 0, 0, 167, 168, 1, 0, 0, 0, 168, 169, 5, 39, 0, 0, 169, 170, 5, 43, 0, 0, 170, 175, 3, 16, 8, 0, 171, 172, 5, 34, 0, 0, 172, 174, 3, 16, 8, 0, 173, 171, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 178, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 179, 5, 50, 0, 0, 179, 190, 1, 0, 0, 0, 180, 181, 3, 16, 8, 0, 181, 183, 5, 40, 0, 0, 182, 184, 5, 44, 0, 0, 183, 182, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 185, 1, 0, 0, 0, 185, 186, 5, 45, 0, 0, 186, 190, 1, 0, 0, 0, 187, 188, 4, 5, 4, 0, 188, 190, 3, 14, 7, 0, 189, 159, 1, 0, 0, 0, 189, 162, 1, 0, 0, 0, 189, 163, 1, 0, 0, 0, 189, 164, 1, 0, 0, 0, 189, 180, 1, 0, 0, 0, 189, 187, 1, 0, 0, 0, 190, 199, 1, 0, 0, 0, 191, 192, 10, 5, 0, 0, 192, 193, 5, 30, 0, 0, 193, 198, 3, 10, 5, 6, 194, 195, 10, 4, 0, 0, 195, 196, 5, 47, 0, 0, 196, 198, 3, 10, 5, 5, 197, 191, 1, 0, 0, 0, 197, 194, 1, 0, 0, 0, 198, 201, 1, 0, 0, 0, 199, 197, 1, 0, 0, 0, 199, 200, 1, 0, 0, 0, 200, 11, 1, 0, 0, 0, 201, 199, 1, 0, 0, 0, 202, 204, 3, 16, 8, 0, 203, 205, 5, 44, 0, 0, 204, 203, 1, 0, 0, 0, 204, 205, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, 207, 5, 42, 0, 0, 207, 208, 3, 98, 49, 0, 208, 217, 1, 0, 0, 0, 209, 211, 3, 16, 8, 0, 210, 212, 5, 44, 0, 0, 211, 210, 1, 0, 0, 0, 211, 212, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 214, 5, 49, 0, 0, 214, 215, 3, 98, 49, 0, 215, 217, 1, 0, 0, 0, 216, 202, 1, 0, 0, 0, 216, 209, 1, 0, 0, 0, 217, 13, 1, 0, 0, 0, 218, 219, 3, 16, 8, 0, 219, 220, 5, 19, 0, 0, 220, 221, 3, 98, 49, 0, 221, 15, 1, 0, 0, 0, 222, 228, 3, 18, 9, 0, 223, 224, 3, 18, 9, 0, 224, 225, 3, 100, 50, 0, 225, 226, 3, 18, 9, 0, 226, 228, 1, 0, 0, 0, 227, 222, 1, 0, 0, 0, 227, 223, 1, 0, 0, 0, 228, 17, 1, 0, 0, 0, 229, 230, 6, 9, -1, 0, 230, 234, 3, 20, 10, 0, 231, 232, 7, 0, 0, 0, 232, 234, 3, 18, 9, 3, 233, 229, 1, 0, 0, 0, 233, 231, 1, 0, 0, 0, 234, 243, 1, 0, 0, 0, 235, 236, 10, 2, 0, 0, 236, 237, 7, 1, 0, 0, 237, 242, 3, 18, 9, 3, 238, 239, 10, 1, 0, 0, 239, 240, 7, 0, 0, 0, 240, 242, 3, 18, 9, 2, 241, 235, 1, 0, 0, 0, 241, 238, 1, 0, 0, 0, 242, 245, 1, 0, 0, 0, 243, 241, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 19, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 246, 247, 6, 10, -1, 0, 247, 255, 3, 62, 31, 0, 248, 255, 3, 52, 26, 0, 249, 255, 3, 22, 11, 0, 250, 251, 5, 43, 0, 0, 251, 252, 3, 10, 5, 0, 252, 253, 5, 50, 0, 0, 253, 255, 1, 0, 0, 0, 254, 246, 1, 0, 0, 0, 254, 248, 1, 0, 0, 0, 254, 249, 1, 0, 0, 0, 254, 250, 1, 0, 0, 0, 255, 261, 1, 0, 0, 0, 256, 257, 10, 1, 0, 0, 257, 258, 5, 33, 0, 0, 258, 260, 3, 24, 12, 0, 259, 256, 1, 0, 0, 0, 260, 263, 1, 0, 0, 0, 261, 259, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 21, 1, 0, 0, 0, 263, 261, 1, 0, 0, 0, 264, 265, 3, 58, 29, 0, 265, 275, 5, 43, 0, 0, 266, 276, 5, 61, 0, 0, 267, 272, 3, 10, 5, 0, 268, 269, 5, 34, 0, 0, 269, 271, 3, 10, 5, 0, 270, 268, 1, 0, 0, 0, 271, 274, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 276, 1, 0, 0, 0, 274, 272, 1, 0, 0, 0, 275, 266, 1, 0, 0, 0, 275, 267, 1, 0, 0, 0, 275, 276, 1, 0, 0, 0, 276, 277, 1, 0, 0, 0, 277, 278, 5, 50, 0, 0, 278, 23, 1, 0, 0, 0, 279, 280, 3, 58, 29, 0, 280, 25, 1, 0, 0, 0, 281, 282, 5, 12, 0, 0, 282, 283, 3, 28, 14, 0, 283, 27, 1, 0, 0, 0, 284, 289, 3, 30, 15, 0, 285, 286, 5, 34, 0, 0, 286, 288, 3, 30, 15, 0, 287, 285, 1, 0, 0, 0, 288, 291, 1, 0, 0, 0, 289, 287, 1, 0, 0, 0, 289, 290, 1, 0, 0, 0, 290, 29, 1, 0, 0, 0, 291, 289, 1, 0, 0, 0, 292, 298, 3, 10, 5, 0, 293, 294, 3, 52, 26, 0, 294, 295, 5, 32, 0, 0, 295, 296, 3, 10, 5, 0, 296, 298, 1, 0, 0, 0, 297, 292, 1, 0, 0, 0, 297, 293, 1, 0, 0, 0, 298, 31, 1, 0, 0, 0, 299, 300, 5, 6, 0, 0, 300, 305, 3, 34, 17, 0, 301, 302, 5, 34, 0, 0, 302, 304, 3, 34, 17, 0, 303, 301, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 303, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 306, 309, 1, 0, 0, 0, 307, 305, 1, 0, 0, 0, 308, 310, 3, 40, 20, 0, 309, 308, 1, 0, 0, 0, 309, 310, 1, 0, 0, 0, 310, 33, 1, 0, 0, 0, 311, 312, 3, 36, 18, 0, 312, 313, 5, 104, 0, 0, 313, 314, 3, 38, 19, 0, 314, 317, 1, 0, 0, 0, 315, 317, 3, 38, 19, 0, 316, 311, 1, 0, 0, 0, 316, 315, 1, 0, 0, 0, 317, 35, 1, 0, 0, 0, 318, 319, 5, 76, 0, 0, 319, 37, 1, 0, 0, 0, 320, 321, 7, 2, 0, 0, 321, 39, 1, 0, 0, 0, 322, 325, 3, 42, 21, 0, 323, 325, 3, 44, 22, 0, 324, 322, 1, 0, 0, 0, 324, 323, 1, 0, 0, 0, 325, 41, 1, 0, 0, 0, 326, 327, 5, 75, 0, 0, 327, 332, 5, 76, 0, 0, 328, 329, 5, 34, 0, 0, 329, 331, 5, 76, 0, 0, 330, 328, 1, 0, 0, 0, 331, 334, 1, 0, 0, 0, 332, 330, 1, 0, 0, 0, 332, 333, 1, 0, 0, 0, 333, 43, 1, 0, 0, 0, 334, 332, 1, 0, 0, 0, 335, 336, 5, 65, 0, 0, 336, 337, 3, 42, 21, 0, 337, 338, 5, 66, 0, 0, 338, 45, 1, 0, 0, 0, 339, 340, 5, 20, 0, 0, 340, 345, 3, 34, 17, 0, 341, 342, 5, 34, 0, 0, 342, 344, 3, 34, 17, 0, 343, 341, 1, 0, 0, 0, 344, 347, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 345, 346, 1, 0, 0, 0, 346, 349, 1, 0, 0, 0, 347, 345, 1, 0, 0, 0, 348, 350, 3, 28, 14, 0, 349, 348, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 353, 1, 0, 0, 0, 351, 352, 5, 29, 0, 0, 352, 354, 3, 28, 14, 0, 353, 351, 1, 0, 0, 0, 353, 354, 1, 0, 0, 0, 354, 47, 1, 0, 0, 0, 355, 356, 5, 4, 0, 0, 356, 357, 3, 28, 14, 0, 357, 49, 1, 0, 0, 0, 358, 360, 5, 15, 0, 0, 359, 361, 3, 28, 14, 0, 360, 359, 1, 0, 0, 0, 360, 361, 1, 0, 0, 0, 361, 364, 1, 0, 0, 0, 362, 363, 5, 29, 0, 0, 363, 365, 3, 28, 14, 0, 364, 362, 1, 0, 0, 0, 364, 365, 1, 0, 0, 0, 365, 51, 1, 0, 0, 0, 366, 371, 3, 58, 29, 0, 367, 368, 5, 36, 0, 0, 368, 370, 3, 58, 29, 0, 369, 367, 1, 0, 0, 0, 370, 373, 1, 0, 0, 0, 371, 369, 1, 0, 0, 0, 371, 372, 1, 0, 0, 0, 372, 53, 1, 0, 0, 0, 373, 371, 1, 0, 0, 0, 374, 379, 3, 60, 30, 0, 375, 376, 5, 36, 0, 0, 376, 378, 3, 60, 30, 0, 377, 375, 1, 0, 0, 0, 378, 381, 1, 0, 0, 0, 379, 377, 1, 0, 0, 0, 379, 380, 1, 0, 0, 0, 380, 55, 1, 0, 0, 0, 381, 379, 1, 0, 0, 0, 382, 387, 3, 54, 27, 0, 383, 384, 5, 34, 0, 0, 384, 386, 3, 54, 27, 0, 385, 383, 1, 0, 0, 0, 386, 389, 1, 0, 0, 0, 387, 385, 1, 0, 0, 0, 387, 388, 1, 0, 0, 0, 388, 57, 1, 0, 0, 0, 389, 387, 1, 0, 0, 0, 390, 391, 7, 3, 0, 0, 391, 59, 1, 0, 0, 0, 392, 393, 5, 80, 0, 0, 393, 61, 1, 0, 0, 0, 394, 437, 5, 45, 0, 0, 395, 396, 3, 96, 48, 0, 396, 397, 5, 67, 0, 0, 397, 437, 1, 0, 0, 0, 398, 437, 3, 94, 47, 0, 399, 437, 3, 96, 48, 0, 400, 437, 3, 90, 45, 0, 401, 437, 3, 64, 32, 0, 402, 437, 3, 98, 49, 0, 403, 404, 5, 65, 0, 0, 404, 409, 3, 92, 46, 0, 405, 406, 5, 34, 0, 0, 406, 408, 3, 92, 46, 0, 407, 405, 1, 0, 0, 0, 408, 411, 1, 0, 0, 0, 409, 407, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 412, 1, 0, 0, 0, 411, 409, 1, 0, 0, 0, 412, 413, 5, 66, 0, 0, 413, 437, 1, 0, 0, 0, 414, 415, 5, 65, 0, 0, 415, 420, 3, 90, 45, 0, 416, 417, 5, 34, 0, 0, 417, 419, 3, 90, 45, 0, 418, 416, 1, 0, 0, 0, 419, 422, 1, 0, 0, 0, 420, 418, 1, 0, 0, 0, 420, 421, 1, 0, 0, 0, 421, 423, 1, 0, 0, 0, 422, 420, 1, 0, 0, 0, 423, 424, 5, 66, 0, 0, 424, 437, 1, 0, 0, 0, 425, 426, 5, 65, 0, 0, 426, 431, 3, 98, 49, 0, 427, 428, 5, 34, 0, 0, 428, 430, 3, 98, 49, 0, 429, 427, 1, 0, 0, 0, 430, 433, 1, 0, 0, 0, 431, 429, 1, 0, 0, 0, 431, 432, 1, 0, 0, 0, 432, 434, 1, 0, 0, 0, 433, 431, 1, 0, 0, 0, 434, 435, 5, 66, 0, 0, 435, 437, 1, 0, 0, 0, 436, 394, 1, 0, 0, 0, 436, 395, 1, 0, 0, 0, 436, 398, 1, 0, 0, 0, 436, 399, 1, 0, 0, 0, 436, 400, 1, 0, 0, 0, 436, 401, 1, 0, 0, 0, 436, 402, 1, 0, 0, 0, 436, 403, 1, 0, 0, 0, 436, 414, 1, 0, 0, 0, 436, 425, 1, 0, 0, 0, 437, 63, 1, 0, 0, 0, 438, 441, 5, 48, 0, 0, 439, 441, 5, 64, 0, 0, 440, 438, 1, 0, 0, 0, 440, 439, 1, 0, 0, 0, 441, 65, 1, 0, 0, 0, 442, 443, 5, 9, 0, 0, 443, 444, 5, 27, 0, 0, 444, 67, 1, 0, 0, 0, 445, 446, 5, 14, 0, 0, 446, 451, 3, 70, 35, 0, 447, 448, 5, 34, 0, 0, 448, 450, 3, 70, 35, 0, 449, 447, 1, 0, 0, 0, 450, 453, 1, 0, 0, 0, 451, 449, 1, 0, 0, 0, 451, 452, 1, 0, 0, 0, 452, 69, 1, 0, 0, 0, 453, 451, 1, 0, 0, 0, 454, 456, 3, 10, 5, 0, 455, 457, 7, 4, 0, 0, 456, 455, 1, 0, 0, 0, 456, 457, 1, 0, 0, 0, 457, 460, 1, 0, 0, 0, 458, 459, 5, 46, 0, 0, 459, 461, 7, 5, 0, 0, 460, 458, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 71, 1, 0, 0, 0, 462, 463, 5, 8, 0, 0, 463, 464, 3, 56, 28, 0, 464, 73, 1, 0, 0, 0, 465, 466, 5, 2, 0, 0, 466, 467, 3, 56, 28, 0, 467, 75, 1, 0, 0, 0, 468, 469, 5, 11, 0, 0, 469, 474, 3, 78, 39, 0, 470, 471, 5, 34, 0, 0, 471, 473, 3, 78, 39, 0, 472, 470, 1, 0, 0, 0, 473, 476, 1, 0, 0, 0, 474, 472, 1, 0, 0, 0, 474, 475, 1, 0, 0, 0, 475, 77, 1, 0, 0, 0, 476, 474, 1, 0, 0, 0, 477, 478, 3, 54, 27, 0, 478, 479, 5, 84, 0, 0, 479, 480, 3, 54, 27, 0, 480, 79, 1, 0, 0, 0, 481, 482, 5, 1, 0, 0, 482, 483, 3, 20, 10, 0, 483, 485, 3, 98, 49, 0, 484, 486, 3, 86, 43, 0, 485, 484, 1, 0, 0, 0, 485, 486, 1, 0, 0, 0, 486, 81, 1, 0, 0, 0, 487, 488, 5, 7, 0, 0, 488, 489, 3, 20, 10, 0, 489, 490, 3, 98, 49, 0, 490, 83, 1, 0, 0, 0, 491, 492, 5, 10, 0, 0, 492, 493, 3, 52, 26, 0, 493, 85, 1, 0, 0, 0, 494, 499, 3, 88, 44, 0, 495, 496, 5, 34, 0, 0, 496, 498, 3, 88, 44, 0, 497, 495, 1, 0, 0, 0, 498, 501, 1, 0, 0, 0, 499, 497, 1, 0, 0, 0, 499, 500, 1, 0, 0, 0, 500, 87, 1, 0, 0, 0, 501, 499, 1, 0, 0, 0, 502, 503, 3, 58, 29, 0, 503, 504, 5, 32, 0, 0, 504, 505, 3, 62, 31, 0, 505, 89, 1, 0, 0, 0, 506, 507, 7, 6, 0, 0, 507, 91, 1, 0, 0, 0, 508, 511, 3, 94, 47, 0, 509, 511, 3, 96, 48, 0, 510, 508, 1, 0, 0, 0, 510, 509, 1, 0, 0, 0, 511, 93, 1, 0, 0, 0, 512, 514, 7, 0, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 515, 1, 0, 0, 0, 515, 516, 5, 28, 0, 0, 516, 95, 1, 0, 0, 0, 517, 519, 7, 0, 0, 0, 518, 517, 1, 0, 0, 0, 518, 519, 1, 0, 0, 0, 519, 520, 1, 0, 0, 0, 520, 521, 5, 27, 0, 0, 521, 97, 1, 0, 0, 0, 522, 523, 5, 26, 0, 0, 523, 99, 1, 0, 0, 0, 524, 525, 7, 7, 0, 0, 525, 101, 1, 0, 0, 0, 526, 527, 5, 5, 0, 0, 527, 528, 3, 104, 52, 0, 528, 103, 1, 0, 0, 0, 529, 530, 5, 65, 0, 0, 530, 531, 3, 2, 1, 0, 531, 532, 5, 66, 0, 0, 532, 105, 1, 0, 0, 0, 533, 534, 5, 13, 0, 0, 534, 535, 5, 100, 0, 0, 535, 107, 1, 0, 0, 0, 536, 537, 5, 3, 0, 0, 537, 540, 5, 90, 0, 0, 538, 539, 5, 88, 0, 0, 539, 541, 3, 54, 27, 0, 540, 538, 1, 0, 0, 0, 540, 541, 1, 0, 0, 0, 541, 551, 1, 0, 0, 0, 542, 543, 5, 89, 0, 0, 543, 548, 3, 110, 55, 0, 544, 545, 5, 34, 0, 0, 545, 547, 3, 110, 55, 0, 546, 544, 1, 0, 0, 0, 547, 550, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 548, 549, 1, 0, 0, 0, 549, 552, 1, 0, 0, 0, 550, 548, 1, 0, 0, 0, 551, 542, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 109, 1, 0, 0, 0, 553, 554, 3, 54, 27, 0, 554, 555, 5, 32, 0, 0, 555, 557, 1, 0, 0, 0, 556, 553, 1, 0, 0, 0, 556, 557, 1, 0, 0, 0, 557, 558, 1, 0, 0, 0, 558, 559, 3, 54, 27, 0, 559, 111, 1, 0, 0, 0, 560, 561, 5, 18, 0, 0, 561, 562, 3, 34, 17, 0, 562, 563, 5, 88, 0, 0, 563, 564, 3, 56, 28, 0, 564, 113, 1, 0, 0, 0, 565, 566, 5, 17, 0, 0, 566, 569, 3, 28, 14, 0, 567, 568, 5, 29, 0, 0, 568, 570, 3, 28, 14, 0, 569, 567, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 115, 1, 0, 0, 0, 54, 127, 136, 154, 166, 175, 183, 189, 197, 199, 204, 211, 216, 227, 233, 241, 243, 254, 261, 272, 275, 289, 297, 305, 309, 316, 324, 332, 345, 349, 353, 360, 364, 371, 379, 387, 409, 420, 431, 436, 440, 451, 456, 460, 474, 485, 499, 510, 513, 518, 540, 548, 551, 556, 569]
\ 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 578da6fe786ac..cf1c5fa691d1e 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
@@ -26,31 +26,30 @@ public class EsqlBaseParser extends ParserConfig {
     new PredictionContextCache();
   public static final int
     DISSECT=1, DROP=2, ENRICH=3, EVAL=4, EXPLAIN=5, FROM=6, GROK=7, KEEP=8, 
-    LIMIT=9, META=10, MV_EXPAND=11, RENAME=12, ROW=13, SHOW=14, SORT=15, STATS=16, 
-    WHERE=17, DEV_INLINESTATS=18, DEV_LOOKUP=19, DEV_MATCH=20, DEV_METRICS=21, 
-    UNKNOWN_CMD=22, LINE_COMMENT=23, MULTILINE_COMMENT=24, WS=25, PIPE=26, 
-    QUOTED_STRING=27, INTEGER_LITERAL=28, DECIMAL_LITERAL=29, BY=30, AND=31, 
-    ASC=32, ASSIGN=33, CAST_OP=34, COMMA=35, DESC=36, DOT=37, FALSE=38, FIRST=39, 
-    IN=40, IS=41, LAST=42, LIKE=43, LP=44, NOT=45, NULL=46, NULLS=47, OR=48, 
-    PARAM=49, RLIKE=50, RP=51, TRUE=52, EQ=53, CIEQ=54, NEQ=55, LT=56, LTE=57, 
-    GT=58, GTE=59, PLUS=60, MINUS=61, ASTERISK=62, SLASH=63, PERCENT=64, NAMED_OR_POSITIONAL_PARAM=65, 
-    OPENING_BRACKET=66, CLOSING_BRACKET=67, UNQUOTED_IDENTIFIER=68, QUOTED_IDENTIFIER=69, 
-    EXPR_LINE_COMMENT=70, EXPR_MULTILINE_COMMENT=71, EXPR_WS=72, EXPLAIN_WS=73, 
-    EXPLAIN_LINE_COMMENT=74, EXPLAIN_MULTILINE_COMMENT=75, METADATA=76, UNQUOTED_SOURCE=77, 
-    FROM_LINE_COMMENT=78, FROM_MULTILINE_COMMENT=79, FROM_WS=80, ID_PATTERN=81, 
-    PROJECT_LINE_COMMENT=82, PROJECT_MULTILINE_COMMENT=83, PROJECT_WS=84, 
-    AS=85, RENAME_LINE_COMMENT=86, RENAME_MULTILINE_COMMENT=87, RENAME_WS=88, 
-    ON=89, WITH=90, ENRICH_POLICY_NAME=91, ENRICH_LINE_COMMENT=92, ENRICH_MULTILINE_COMMENT=93, 
-    ENRICH_WS=94, ENRICH_FIELD_LINE_COMMENT=95, ENRICH_FIELD_MULTILINE_COMMENT=96, 
-    ENRICH_FIELD_WS=97, MVEXPAND_LINE_COMMENT=98, MVEXPAND_MULTILINE_COMMENT=99, 
-    MVEXPAND_WS=100, INFO=101, SHOW_LINE_COMMENT=102, SHOW_MULTILINE_COMMENT=103, 
-    SHOW_WS=104, FUNCTIONS=105, META_LINE_COMMENT=106, META_MULTILINE_COMMENT=107, 
-    META_WS=108, COLON=109, SETTING=110, SETTING_LINE_COMMENT=111, SETTTING_MULTILINE_COMMENT=112, 
-    SETTING_WS=113, LOOKUP_LINE_COMMENT=114, LOOKUP_MULTILINE_COMMENT=115, 
-    LOOKUP_WS=116, LOOKUP_FIELD_LINE_COMMENT=117, LOOKUP_FIELD_MULTILINE_COMMENT=118, 
-    LOOKUP_FIELD_WS=119, METRICS_LINE_COMMENT=120, METRICS_MULTILINE_COMMENT=121, 
-    METRICS_WS=122, CLOSING_METRICS_LINE_COMMENT=123, CLOSING_METRICS_MULTILINE_COMMENT=124, 
-    CLOSING_METRICS_WS=125;
+    LIMIT=9, MV_EXPAND=10, RENAME=11, ROW=12, SHOW=13, SORT=14, STATS=15, 
+    WHERE=16, DEV_INLINESTATS=17, DEV_LOOKUP=18, DEV_MATCH=19, DEV_METRICS=20, 
+    UNKNOWN_CMD=21, LINE_COMMENT=22, MULTILINE_COMMENT=23, WS=24, PIPE=25, 
+    QUOTED_STRING=26, INTEGER_LITERAL=27, DECIMAL_LITERAL=28, BY=29, AND=30, 
+    ASC=31, ASSIGN=32, CAST_OP=33, COMMA=34, DESC=35, DOT=36, FALSE=37, FIRST=38, 
+    IN=39, IS=40, LAST=41, LIKE=42, LP=43, NOT=44, NULL=45, NULLS=46, OR=47, 
+    PARAM=48, RLIKE=49, RP=50, TRUE=51, EQ=52, CIEQ=53, NEQ=54, LT=55, LTE=56, 
+    GT=57, GTE=58, PLUS=59, MINUS=60, ASTERISK=61, SLASH=62, PERCENT=63, NAMED_OR_POSITIONAL_PARAM=64, 
+    OPENING_BRACKET=65, CLOSING_BRACKET=66, UNQUOTED_IDENTIFIER=67, QUOTED_IDENTIFIER=68, 
+    EXPR_LINE_COMMENT=69, EXPR_MULTILINE_COMMENT=70, EXPR_WS=71, EXPLAIN_WS=72, 
+    EXPLAIN_LINE_COMMENT=73, EXPLAIN_MULTILINE_COMMENT=74, METADATA=75, UNQUOTED_SOURCE=76, 
+    FROM_LINE_COMMENT=77, FROM_MULTILINE_COMMENT=78, FROM_WS=79, ID_PATTERN=80, 
+    PROJECT_LINE_COMMENT=81, PROJECT_MULTILINE_COMMENT=82, PROJECT_WS=83, 
+    AS=84, RENAME_LINE_COMMENT=85, RENAME_MULTILINE_COMMENT=86, RENAME_WS=87, 
+    ON=88, WITH=89, ENRICH_POLICY_NAME=90, ENRICH_LINE_COMMENT=91, ENRICH_MULTILINE_COMMENT=92, 
+    ENRICH_WS=93, ENRICH_FIELD_LINE_COMMENT=94, ENRICH_FIELD_MULTILINE_COMMENT=95, 
+    ENRICH_FIELD_WS=96, MVEXPAND_LINE_COMMENT=97, MVEXPAND_MULTILINE_COMMENT=98, 
+    MVEXPAND_WS=99, INFO=100, SHOW_LINE_COMMENT=101, SHOW_MULTILINE_COMMENT=102, 
+    SHOW_WS=103, COLON=104, SETTING=105, SETTING_LINE_COMMENT=106, SETTTING_MULTILINE_COMMENT=107, 
+    SETTING_WS=108, LOOKUP_LINE_COMMENT=109, LOOKUP_MULTILINE_COMMENT=110, 
+    LOOKUP_WS=111, LOOKUP_FIELD_LINE_COMMENT=112, LOOKUP_FIELD_MULTILINE_COMMENT=113, 
+    LOOKUP_FIELD_WS=114, METRICS_LINE_COMMENT=115, METRICS_MULTILINE_COMMENT=116, 
+    METRICS_WS=117, CLOSING_METRICS_LINE_COMMENT=118, CLOSING_METRICS_MULTILINE_COMMENT=119, 
+    CLOSING_METRICS_WS=120;
   public static final int
     RULE_singleStatement = 0, RULE_query = 1, RULE_sourceCommand = 2, RULE_processingCommand = 3, 
     RULE_whereCommand = 4, RULE_booleanExpression = 5, RULE_regexBooleanExpression = 6, 
@@ -69,8 +68,8 @@ public class EsqlBaseParser extends ParserConfig {
     RULE_booleanValue = 45, RULE_numericValue = 46, RULE_decimalValue = 47, 
     RULE_integerValue = 48, RULE_string = 49, RULE_comparisonOperator = 50, 
     RULE_explainCommand = 51, RULE_subqueryExpression = 52, RULE_showCommand = 53, 
-    RULE_metaCommand = 54, RULE_enrichCommand = 55, RULE_enrichWithClause = 56, 
-    RULE_lookupCommand = 57, RULE_inlinestatsCommand = 58;
+    RULE_enrichCommand = 54, RULE_enrichWithClause = 55, RULE_lookupCommand = 56, 
+    RULE_inlinestatsCommand = 57;
   private static String[] makeRuleNames() {
     return new String[] {
       "singleStatement", "query", "sourceCommand", "processingCommand", "whereCommand", 
@@ -84,8 +83,8 @@ private static String[] makeRuleNames() {
       "dropCommand", "renameCommand", "renameClause", "dissectCommand", "grokCommand", 
       "mvExpandCommand", "commandOptions", "commandOption", "booleanValue", 
       "numericValue", "decimalValue", "integerValue", "string", "comparisonOperator", 
-      "explainCommand", "subqueryExpression", "showCommand", "metaCommand", 
-      "enrichCommand", "enrichWithClause", "lookupCommand", "inlinestatsCommand"
+      "explainCommand", "subqueryExpression", "showCommand", "enrichCommand", 
+      "enrichWithClause", "lookupCommand", "inlinestatsCommand"
     };
   }
   public static final String[] ruleNames = makeRuleNames();
@@ -93,25 +92,25 @@ private static String[] makeRuleNames() {
   private static String[] makeLiteralNames() {
     return new String[] {
       null, "'dissect'", "'drop'", "'enrich'", "'eval'", "'explain'", "'from'", 
-      "'grok'", "'keep'", "'limit'", "'meta'", "'mv_expand'", "'rename'", "'row'", 
-      "'show'", "'sort'", "'stats'", "'where'", null, null, null, null, null, 
-      null, null, null, "'|'", null, null, null, "'by'", "'and'", "'asc'", 
-      "'='", "'::'", "','", "'desc'", "'.'", "'false'", "'first'", "'in'", 
-      "'is'", "'last'", "'like'", "'('", "'not'", "'null'", "'nulls'", "'or'", 
-      "'?'", "'rlike'", "')'", "'true'", "'=='", "'=~'", "'!='", "'<'", "'<='", 
-      "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", null, null, "']'", 
-      null, null, null, null, null, null, null, null, "'metadata'", null, null, 
-      null, null, null, null, null, null, "'as'", null, null, null, "'on'", 
-      "'with'", null, null, null, null, null, null, null, null, null, null, 
-      "'info'", null, null, null, "'functions'", null, null, null, "':'"
+      "'grok'", "'keep'", "'limit'", "'mv_expand'", "'rename'", "'row'", "'show'", 
+      "'sort'", "'stats'", "'where'", null, null, null, null, null, null, null, 
+      null, "'|'", null, null, null, "'by'", "'and'", "'asc'", "'='", "'::'", 
+      "','", "'desc'", "'.'", "'false'", "'first'", "'in'", "'is'", "'last'", 
+      "'like'", "'('", "'not'", "'null'", "'nulls'", "'or'", "'?'", "'rlike'", 
+      "')'", "'true'", "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", 
+      "'+'", "'-'", "'*'", "'/'", "'%'", null, null, "']'", null, null, null, 
+      null, null, null, null, null, "'metadata'", null, null, null, null, null, 
+      null, null, null, "'as'", null, null, null, "'on'", "'with'", null, null, 
+      null, null, null, null, null, null, null, null, "'info'", null, null, 
+      null, "':'"
     };
   }
   private static final String[] _LITERAL_NAMES = makeLiteralNames();
   private static String[] makeSymbolicNames() {
     return new String[] {
       null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", 
-      "KEEP", "LIMIT", "META", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", 
-      "STATS", "WHERE", "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", 
+      "KEEP", "LIMIT", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", 
+      "WHERE", "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_MATCH", "DEV_METRICS", 
       "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "QUOTED_STRING", 
       "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", 
       "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", 
@@ -127,8 +126,7 @@ private static String[] makeSymbolicNames() {
       "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", 
       "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "MVEXPAND_LINE_COMMENT", 
       "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "INFO", "SHOW_LINE_COMMENT", 
-      "SHOW_MULTILINE_COMMENT", "SHOW_WS", "FUNCTIONS", "META_LINE_COMMENT", 
-      "META_MULTILINE_COMMENT", "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", 
+      "SHOW_MULTILINE_COMMENT", "SHOW_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", 
       "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "LOOKUP_LINE_COMMENT", "LOOKUP_MULTILINE_COMMENT", 
       "LOOKUP_WS", "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", 
       "LOOKUP_FIELD_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", 
@@ -220,9 +218,9 @@ public final SingleStatementContext singleStatement() throws RecognitionExceptio
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(118);
+      setState(116);
       query(0);
-      setState(119);
+      setState(117);
       match(EOF);
       }
     }
@@ -318,11 +316,11 @@ private QueryContext query(int _p) throws RecognitionException {
       _ctx = _localctx;
       _prevctx = _localctx;
 
-      setState(122);
+      setState(120);
       sourceCommand();
       }
       _ctx.stop = _input.LT(-1);
-      setState(129);
+      setState(127);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,0,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
@@ -333,16 +331,16 @@ private QueryContext query(int _p) throws RecognitionException {
           {
           _localctx = new CompositeQueryContext(new QueryContext(_parentctx, _parentState));
           pushNewRecursionContext(_localctx, _startState, RULE_query);
-          setState(124);
+          setState(122);
           if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)");
-          setState(125);
+          setState(123);
           match(PIPE);
-          setState(126);
+          setState(124);
           processingCommand();
           }
           } 
         }
-        setState(131);
+        setState(129);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,0,_ctx);
       }
@@ -367,9 +365,6 @@ public ExplainCommandContext explainCommand() {
     public FromCommandContext fromCommand() {
       return getRuleContext(FromCommandContext.class,0);
     }
-    public MetaCommandContext metaCommand() {
-      return getRuleContext(MetaCommandContext.class,0);
-    }
     public RowCommandContext rowCommand() {
       return getRuleContext(RowCommandContext.class,0);
     }
@@ -403,50 +398,43 @@ public final SourceCommandContext sourceCommand() throws RecognitionException {
     SourceCommandContext _localctx = new SourceCommandContext(_ctx, getState());
     enterRule(_localctx, 4, RULE_sourceCommand);
     try {
-      setState(139);
+      setState(136);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) {
       case 1:
         enterOuterAlt(_localctx, 1);
         {
-        setState(132);
+        setState(130);
         explainCommand();
         }
         break;
       case 2:
         enterOuterAlt(_localctx, 2);
         {
-        setState(133);
+        setState(131);
         fromCommand();
         }
         break;
       case 3:
         enterOuterAlt(_localctx, 3);
         {
-        setState(134);
-        metaCommand();
+        setState(132);
+        rowCommand();
         }
         break;
       case 4:
         enterOuterAlt(_localctx, 4);
         {
-        setState(135);
-        rowCommand();
+        setState(133);
+        showCommand();
         }
         break;
       case 5:
         enterOuterAlt(_localctx, 5);
         {
-        setState(136);
-        showCommand();
-        }
-        break;
-      case 6:
-        enterOuterAlt(_localctx, 6);
-        {
-        setState(137);
+        setState(134);
         if (!(this.isDevVersion())) throw new FailedPredicateException(this, "this.isDevVersion()");
-        setState(138);
+        setState(135);
         metricsCommand();
         }
         break;
@@ -531,108 +519,108 @@ public final ProcessingCommandContext processingCommand() throws RecognitionExce
     ProcessingCommandContext _localctx = new ProcessingCommandContext(_ctx, getState());
     enterRule(_localctx, 6, RULE_processingCommand);
     try {
-      setState(157);
+      setState(154);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,2,_ctx) ) {
       case 1:
         enterOuterAlt(_localctx, 1);
         {
-        setState(141);
+        setState(138);
         evalCommand();
         }
         break;
       case 2:
         enterOuterAlt(_localctx, 2);
         {
-        setState(142);
+        setState(139);
         whereCommand();
         }
         break;
       case 3:
         enterOuterAlt(_localctx, 3);
         {
-        setState(143);
+        setState(140);
         keepCommand();
         }
         break;
       case 4:
         enterOuterAlt(_localctx, 4);
         {
-        setState(144);
+        setState(141);
         limitCommand();
         }
         break;
       case 5:
         enterOuterAlt(_localctx, 5);
         {
-        setState(145);
+        setState(142);
         statsCommand();
         }
         break;
       case 6:
         enterOuterAlt(_localctx, 6);
         {
-        setState(146);
+        setState(143);
         sortCommand();
         }
         break;
       case 7:
         enterOuterAlt(_localctx, 7);
         {
-        setState(147);
+        setState(144);
         dropCommand();
         }
         break;
       case 8:
         enterOuterAlt(_localctx, 8);
         {
-        setState(148);
+        setState(145);
         renameCommand();
         }
         break;
       case 9:
         enterOuterAlt(_localctx, 9);
         {
-        setState(149);
+        setState(146);
         dissectCommand();
         }
         break;
       case 10:
         enterOuterAlt(_localctx, 10);
         {
-        setState(150);
+        setState(147);
         grokCommand();
         }
         break;
       case 11:
         enterOuterAlt(_localctx, 11);
         {
-        setState(151);
+        setState(148);
         enrichCommand();
         }
         break;
       case 12:
         enterOuterAlt(_localctx, 12);
         {
-        setState(152);
+        setState(149);
         mvExpandCommand();
         }
         break;
       case 13:
         enterOuterAlt(_localctx, 13);
         {
-        setState(153);
+        setState(150);
         if (!(this.isDevVersion())) throw new FailedPredicateException(this, "this.isDevVersion()");
-        setState(154);
+        setState(151);
         inlinestatsCommand();
         }
         break;
       case 14:
         enterOuterAlt(_localctx, 14);
         {
-        setState(155);
+        setState(152);
         if (!(this.isDevVersion())) throw new FailedPredicateException(this, "this.isDevVersion()");
-        setState(156);
+        setState(153);
         lookupCommand();
         }
         break;
@@ -681,9 +669,9 @@ public final WhereCommandContext whereCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(159);
+      setState(156);
       match(WHERE);
-      setState(160);
+      setState(157);
       booleanExpression(0);
       }
     }
@@ -899,7 +887,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(192);
+      setState(189);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) {
       case 1:
@@ -908,9 +896,9 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
         _ctx = _localctx;
         _prevctx = _localctx;
 
-        setState(163);
+        setState(160);
         match(NOT);
-        setState(164);
+        setState(161);
         booleanExpression(8);
         }
         break;
@@ -919,7 +907,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
         _localctx = new BooleanDefaultContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(165);
+        setState(162);
         valueExpression();
         }
         break;
@@ -928,7 +916,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
         _localctx = new RegexExpressionContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(166);
+        setState(163);
         regexBooleanExpression();
         }
         break;
@@ -937,41 +925,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
         _localctx = new LogicalInContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(167);
+        setState(164);
         valueExpression();
-        setState(169);
+        setState(166);
         _errHandler.sync(this);
         _la = _input.LA(1);
         if (_la==NOT) {
           {
-          setState(168);
+          setState(165);
           match(NOT);
           }
         }
 
-        setState(171);
+        setState(168);
         match(IN);
-        setState(172);
+        setState(169);
         match(LP);
-        setState(173);
+        setState(170);
         valueExpression();
-        setState(178);
+        setState(175);
         _errHandler.sync(this);
         _la = _input.LA(1);
         while (_la==COMMA) {
           {
           {
-          setState(174);
+          setState(171);
           match(COMMA);
-          setState(175);
+          setState(172);
           valueExpression();
           }
           }
-          setState(180);
+          setState(177);
           _errHandler.sync(this);
           _la = _input.LA(1);
         }
-        setState(181);
+        setState(178);
         match(RP);
         }
         break;
@@ -980,21 +968,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
         _localctx = new IsNullContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(183);
+        setState(180);
         valueExpression();
-        setState(184);
+        setState(181);
         match(IS);
-        setState(186);
+        setState(183);
         _errHandler.sync(this);
         _la = _input.LA(1);
         if (_la==NOT) {
           {
-          setState(185);
+          setState(182);
           match(NOT);
           }
         }
 
-        setState(188);
+        setState(185);
         match(NULL);
         }
         break;
@@ -1003,15 +991,15 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
         _localctx = new MatchExpressionContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(190);
+        setState(187);
         if (!(this.isDevVersion())) throw new FailedPredicateException(this, "this.isDevVersion()");
-        setState(191);
+        setState(188);
         matchBooleanExpression();
         }
         break;
       }
       _ctx.stop = _input.LT(-1);
-      setState(202);
+      setState(199);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,8,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
@@ -1019,7 +1007,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
           if ( _parseListeners!=null ) triggerExitRuleEvent();
           _prevctx = _localctx;
           {
-          setState(200);
+          setState(197);
           _errHandler.sync(this);
           switch ( getInterpreter().adaptivePredict(_input,7,_ctx) ) {
           case 1:
@@ -1027,11 +1015,11 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
             _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState));
             ((LogicalBinaryContext)_localctx).left = _prevctx;
             pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression);
-            setState(194);
+            setState(191);
             if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)");
-            setState(195);
+            setState(192);
             ((LogicalBinaryContext)_localctx).operator = match(AND);
-            setState(196);
+            setState(193);
             ((LogicalBinaryContext)_localctx).right = booleanExpression(6);
             }
             break;
@@ -1040,18 +1028,18 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc
             _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState));
             ((LogicalBinaryContext)_localctx).left = _prevctx;
             pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression);
-            setState(197);
+            setState(194);
             if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)");
-            setState(198);
+            setState(195);
             ((LogicalBinaryContext)_localctx).operator = match(OR);
-            setState(199);
+            setState(196);
             ((LogicalBinaryContext)_localctx).right = booleanExpression(5);
             }
             break;
           }
           } 
         }
-        setState(204);
+        setState(201);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,8,_ctx);
       }
@@ -1106,48 +1094,48 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog
     enterRule(_localctx, 12, RULE_regexBooleanExpression);
     int _la;
     try {
-      setState(219);
+      setState(216);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,11,_ctx) ) {
       case 1:
         enterOuterAlt(_localctx, 1);
         {
-        setState(205);
+        setState(202);
         valueExpression();
-        setState(207);
+        setState(204);
         _errHandler.sync(this);
         _la = _input.LA(1);
         if (_la==NOT) {
           {
-          setState(206);
+          setState(203);
           match(NOT);
           }
         }
 
-        setState(209);
+        setState(206);
         ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE);
-        setState(210);
+        setState(207);
         ((RegexBooleanExpressionContext)_localctx).pattern = string();
         }
         break;
       case 2:
         enterOuterAlt(_localctx, 2);
         {
-        setState(212);
+        setState(209);
         valueExpression();
-        setState(214);
+        setState(211);
         _errHandler.sync(this);
         _la = _input.LA(1);
         if (_la==NOT) {
           {
-          setState(213);
+          setState(210);
           match(NOT);
           }
         }
 
-        setState(216);
+        setState(213);
         ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE);
-        setState(217);
+        setState(214);
         ((RegexBooleanExpressionContext)_localctx).pattern = string();
         }
         break;
@@ -1200,11 +1188,11 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(221);
+      setState(218);
       valueExpression();
-      setState(222);
+      setState(219);
       match(DEV_MATCH);
-      setState(223);
+      setState(220);
       ((MatchBooleanExpressionContext)_localctx).queryString = string();
       }
     }
@@ -1288,14 +1276,14 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio
     ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState());
     enterRule(_localctx, 16, RULE_valueExpression);
     try {
-      setState(230);
+      setState(227);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,12,_ctx) ) {
       case 1:
         _localctx = new ValueExpressionDefaultContext(_localctx);
         enterOuterAlt(_localctx, 1);
         {
-        setState(225);
+        setState(222);
         operatorExpression(0);
         }
         break;
@@ -1303,11 +1291,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio
         _localctx = new ComparisonContext(_localctx);
         enterOuterAlt(_localctx, 2);
         {
-        setState(226);
+        setState(223);
         ((ComparisonContext)_localctx).left = operatorExpression(0);
-        setState(227);
+        setState(224);
         comparisonOperator();
-        setState(228);
+        setState(225);
         ((ComparisonContext)_localctx).right = operatorExpression(0);
         }
         break;
@@ -1432,7 +1420,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(236);
+      setState(233);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,13,_ctx) ) {
       case 1:
@@ -1441,7 +1429,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
         _ctx = _localctx;
         _prevctx = _localctx;
 
-        setState(233);
+        setState(230);
         primaryExpression(0);
         }
         break;
@@ -1450,7 +1438,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
         _localctx = new ArithmeticUnaryContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(234);
+        setState(231);
         ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1);
         _la = _input.LA(1);
         if ( !(_la==PLUS || _la==MINUS) ) {
@@ -1461,13 +1449,13 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
           _errHandler.reportMatch(this);
           consume();
         }
-        setState(235);
+        setState(232);
         operatorExpression(3);
         }
         break;
       }
       _ctx.stop = _input.LT(-1);
-      setState(246);
+      setState(243);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,15,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
@@ -1475,7 +1463,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
           if ( _parseListeners!=null ) triggerExitRuleEvent();
           _prevctx = _localctx;
           {
-          setState(244);
+          setState(241);
           _errHandler.sync(this);
           switch ( getInterpreter().adaptivePredict(_input,14,_ctx) ) {
           case 1:
@@ -1483,12 +1471,12 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
             _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState));
             ((ArithmeticBinaryContext)_localctx).left = _prevctx;
             pushNewRecursionContext(_localctx, _startState, RULE_operatorExpression);
-            setState(238);
+            setState(235);
             if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)");
-            setState(239);
+            setState(236);
             ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1);
             _la = _input.LA(1);
-            if ( !(((((_la - 62)) & ~0x3f) == 0 && ((1L << (_la - 62)) & 7L) != 0)) ) {
+            if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & -2305843009213693952L) != 0)) ) {
               ((ArithmeticBinaryContext)_localctx).operator = (Token)_errHandler.recoverInline(this);
             }
             else {
@@ -1496,7 +1484,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
               _errHandler.reportMatch(this);
               consume();
             }
-            setState(240);
+            setState(237);
             ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3);
             }
             break;
@@ -1505,9 +1493,9 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
             _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState));
             ((ArithmeticBinaryContext)_localctx).left = _prevctx;
             pushNewRecursionContext(_localctx, _startState, RULE_operatorExpression);
-            setState(241);
+            setState(238);
             if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)");
-            setState(242);
+            setState(239);
             ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1);
             _la = _input.LA(1);
             if ( !(_la==PLUS || _la==MINUS) ) {
@@ -1518,14 +1506,14 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE
               _errHandler.reportMatch(this);
               consume();
             }
-            setState(243);
+            setState(240);
             ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2);
             }
             break;
           }
           } 
         }
-        setState(248);
+        setState(245);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,15,_ctx);
       }
@@ -1683,7 +1671,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(257);
+      setState(254);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,16,_ctx) ) {
       case 1:
@@ -1692,7 +1680,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc
         _ctx = _localctx;
         _prevctx = _localctx;
 
-        setState(250);
+        setState(247);
         constant();
         }
         break;
@@ -1701,7 +1689,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc
         _localctx = new DereferenceContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(251);
+        setState(248);
         qualifiedName();
         }
         break;
@@ -1710,7 +1698,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc
         _localctx = new FunctionContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(252);
+        setState(249);
         functionExpression();
         }
         break;
@@ -1719,17 +1707,17 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc
         _localctx = new ParenthesizedExpressionContext(_localctx);
         _ctx = _localctx;
         _prevctx = _localctx;
-        setState(253);
+        setState(250);
         match(LP);
-        setState(254);
+        setState(251);
         booleanExpression(0);
-        setState(255);
+        setState(252);
         match(RP);
         }
         break;
       }
       _ctx.stop = _input.LT(-1);
-      setState(264);
+      setState(261);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,17,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
@@ -1740,16 +1728,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc
           {
           _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState));
           pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression);
-          setState(259);
+          setState(256);
           if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)");
-          setState(260);
+          setState(257);
           match(CAST_OP);
-          setState(261);
+          setState(258);
           dataType();
           }
           } 
         }
-        setState(266);
+        setState(263);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,17,_ctx);
       }
@@ -1811,37 +1799,37 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(267);
+      setState(264);
       identifier();
-      setState(268);
+      setState(265);
       match(LP);
-      setState(278);
+      setState(275);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,19,_ctx) ) {
       case 1:
         {
-        setState(269);
+        setState(266);
         match(ASTERISK);
         }
         break;
       case 2:
         {
         {
-        setState(270);
+        setState(267);
         booleanExpression(0);
-        setState(275);
+        setState(272);
         _errHandler.sync(this);
         _la = _input.LA(1);
         while (_la==COMMA) {
           {
           {
-          setState(271);
+          setState(268);
           match(COMMA);
-          setState(272);
+          setState(269);
           booleanExpression(0);
           }
           }
-          setState(277);
+          setState(274);
           _errHandler.sync(this);
           _la = _input.LA(1);
         }
@@ -1849,7 +1837,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx
         }
         break;
       }
-      setState(280);
+      setState(277);
       match(RP);
       }
     }
@@ -1907,7 +1895,7 @@ public final DataTypeContext dataType() throws RecognitionException {
       _localctx = new ToDataTypeContext(_localctx);
       enterOuterAlt(_localctx, 1);
       {
-      setState(282);
+      setState(279);
       identifier();
       }
     }
@@ -1954,9 +1942,9 @@ public final RowCommandContext rowCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(284);
+      setState(281);
       match(ROW);
-      setState(285);
+      setState(282);
       fields();
       }
     }
@@ -2010,23 +1998,23 @@ public final FieldsContext fields() throws RecognitionException {
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(287);
+      setState(284);
       field();
-      setState(292);
+      setState(289);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,20,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(288);
+          setState(285);
           match(COMMA);
-          setState(289);
+          setState(286);
           field();
           }
           } 
         }
-        setState(294);
+        setState(291);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,20,_ctx);
       }
@@ -2076,24 +2064,24 @@ public final FieldContext field() throws RecognitionException {
     FieldContext _localctx = new FieldContext(_ctx, getState());
     enterRule(_localctx, 30, RULE_field);
     try {
-      setState(300);
+      setState(297);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,21,_ctx) ) {
       case 1:
         enterOuterAlt(_localctx, 1);
         {
-        setState(295);
+        setState(292);
         booleanExpression(0);
         }
         break;
       case 2:
         enterOuterAlt(_localctx, 2);
         {
-        setState(296);
+        setState(293);
         qualifiedName();
-        setState(297);
+        setState(294);
         match(ASSIGN);
-        setState(298);
+        setState(295);
         booleanExpression(0);
         }
         break;
@@ -2153,34 +2141,34 @@ public final FromCommandContext fromCommand() throws RecognitionException {
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(302);
+      setState(299);
       match(FROM);
-      setState(303);
+      setState(300);
       indexPattern();
-      setState(308);
+      setState(305);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,22,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(304);
+          setState(301);
           match(COMMA);
-          setState(305);
+          setState(302);
           indexPattern();
           }
           } 
         }
-        setState(310);
+        setState(307);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,22,_ctx);
       }
-      setState(312);
+      setState(309);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,23,_ctx) ) {
       case 1:
         {
-        setState(311);
+        setState(308);
         metadata();
         }
         break;
@@ -2231,24 +2219,24 @@ public final IndexPatternContext indexPattern() throws RecognitionException {
     IndexPatternContext _localctx = new IndexPatternContext(_ctx, getState());
     enterRule(_localctx, 34, RULE_indexPattern);
     try {
-      setState(319);
+      setState(316);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,24,_ctx) ) {
       case 1:
         enterOuterAlt(_localctx, 1);
         {
-        setState(314);
+        setState(311);
         clusterString();
-        setState(315);
+        setState(312);
         match(COLON);
-        setState(316);
+        setState(313);
         indexString();
         }
         break;
       case 2:
         enterOuterAlt(_localctx, 2);
         {
-        setState(318);
+        setState(315);
         indexString();
         }
         break;
@@ -2294,7 +2282,7 @@ public final ClusterStringContext clusterString() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(321);
+      setState(318);
       match(UNQUOTED_SOURCE);
       }
     }
@@ -2340,7 +2328,7 @@ public final IndexStringContext indexString() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(323);
+      setState(320);
       _la = _input.LA(1);
       if ( !(_la==QUOTED_STRING || _la==UNQUOTED_SOURCE) ) {
       _errHandler.recoverInline(this);
@@ -2395,20 +2383,20 @@ public final MetadataContext metadata() throws RecognitionException {
     MetadataContext _localctx = new MetadataContext(_ctx, getState());
     enterRule(_localctx, 40, RULE_metadata);
     try {
-      setState(327);
+      setState(324);
       _errHandler.sync(this);
       switch (_input.LA(1)) {
       case METADATA:
         enterOuterAlt(_localctx, 1);
         {
-        setState(325);
+        setState(322);
         metadataOption();
         }
         break;
       case OPENING_BRACKET:
         enterOuterAlt(_localctx, 2);
         {
-        setState(326);
+        setState(323);
         deprecated_metadata();
         }
         break;
@@ -2465,25 +2453,25 @@ public final MetadataOptionContext metadataOption() throws RecognitionException
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(329);
+      setState(326);
       match(METADATA);
-      setState(330);
+      setState(327);
       match(UNQUOTED_SOURCE);
-      setState(335);
+      setState(332);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,26,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(331);
+          setState(328);
           match(COMMA);
-          setState(332);
+          setState(329);
           match(UNQUOTED_SOURCE);
           }
           } 
         }
-        setState(337);
+        setState(334);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,26,_ctx);
       }
@@ -2532,11 +2520,11 @@ public final Deprecated_metadataContext deprecated_metadata() throws Recognition
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(338);
+      setState(335);
       match(OPENING_BRACKET);
-      setState(339);
+      setState(336);
       metadataOption();
-      setState(340);
+      setState(337);
       match(CLOSING_BRACKET);
       }
     }
@@ -2600,46 +2588,46 @@ public final MetricsCommandContext metricsCommand() throws RecognitionException
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(342);
+      setState(339);
       match(DEV_METRICS);
-      setState(343);
+      setState(340);
       indexPattern();
-      setState(348);
+      setState(345);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,27,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(344);
+          setState(341);
           match(COMMA);
-          setState(345);
+          setState(342);
           indexPattern();
           }
           } 
         }
-        setState(350);
+        setState(347);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,27,_ctx);
       }
-      setState(352);
+      setState(349);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,28,_ctx) ) {
       case 1:
         {
-        setState(351);
+        setState(348);
         ((MetricsCommandContext)_localctx).aggregates = fields();
         }
         break;
       }
-      setState(356);
+      setState(353);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,29,_ctx) ) {
       case 1:
         {
-        setState(354);
+        setState(351);
         match(BY);
-        setState(355);
+        setState(352);
         ((MetricsCommandContext)_localctx).grouping = fields();
         }
         break;
@@ -2689,9 +2677,9 @@ public final EvalCommandContext evalCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(358);
+      setState(355);
       match(EVAL);
-      setState(359);
+      setState(356);
       fields();
       }
     }
@@ -2744,26 +2732,26 @@ public final StatsCommandContext statsCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(361);
+      setState(358);
       match(STATS);
-      setState(363);
+      setState(360);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,30,_ctx) ) {
       case 1:
         {
-        setState(362);
+        setState(359);
         ((StatsCommandContext)_localctx).stats = fields();
         }
         break;
       }
-      setState(367);
+      setState(364);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,31,_ctx) ) {
       case 1:
         {
-        setState(365);
+        setState(362);
         match(BY);
-        setState(366);
+        setState(363);
         ((StatsCommandContext)_localctx).grouping = fields();
         }
         break;
@@ -2820,23 +2808,23 @@ public final QualifiedNameContext qualifiedName() throws RecognitionException {
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(369);
+      setState(366);
       identifier();
-      setState(374);
+      setState(371);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,32,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(370);
+          setState(367);
           match(DOT);
-          setState(371);
+          setState(368);
           identifier();
           }
           } 
         }
-        setState(376);
+        setState(373);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,32,_ctx);
       }
@@ -2892,23 +2880,23 @@ public final QualifiedNamePatternContext qualifiedNamePattern() throws Recogniti
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(377);
+      setState(374);
       identifierPattern();
-      setState(382);
+      setState(379);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,33,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(378);
+          setState(375);
           match(DOT);
-          setState(379);
+          setState(376);
           identifierPattern();
           }
           } 
         }
-        setState(384);
+        setState(381);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,33,_ctx);
       }
@@ -2964,23 +2952,23 @@ public final QualifiedNamePatternsContext qualifiedNamePatterns() throws Recogni
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(385);
+      setState(382);
       qualifiedNamePattern();
-      setState(390);
+      setState(387);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,34,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(386);
+          setState(383);
           match(COMMA);
-          setState(387);
+          setState(384);
           qualifiedNamePattern();
           }
           } 
         }
-        setState(392);
+        setState(389);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,34,_ctx);
       }
@@ -3028,7 +3016,7 @@ public final IdentifierContext identifier() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(393);
+      setState(390);
       _la = _input.LA(1);
       if ( !(_la==UNQUOTED_IDENTIFIER || _la==QUOTED_IDENTIFIER) ) {
       _errHandler.recoverInline(this);
@@ -3080,7 +3068,7 @@ public final IdentifierPatternContext identifierPattern() throws RecognitionExce
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(395);
+      setState(392);
       match(ID_PATTERN);
       }
     }
@@ -3351,14 +3339,14 @@ public final ConstantContext constant() throws RecognitionException {
     enterRule(_localctx, 62, RULE_constant);
     int _la;
     try {
-      setState(439);
+      setState(436);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,38,_ctx) ) {
       case 1:
         _localctx = new NullLiteralContext(_localctx);
         enterOuterAlt(_localctx, 1);
         {
-        setState(397);
+        setState(394);
         match(NULL);
         }
         break;
@@ -3366,9 +3354,9 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new QualifiedIntegerLiteralContext(_localctx);
         enterOuterAlt(_localctx, 2);
         {
-        setState(398);
+        setState(395);
         integerValue();
-        setState(399);
+        setState(396);
         match(UNQUOTED_IDENTIFIER);
         }
         break;
@@ -3376,7 +3364,7 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new DecimalLiteralContext(_localctx);
         enterOuterAlt(_localctx, 3);
         {
-        setState(401);
+        setState(398);
         decimalValue();
         }
         break;
@@ -3384,7 +3372,7 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new IntegerLiteralContext(_localctx);
         enterOuterAlt(_localctx, 4);
         {
-        setState(402);
+        setState(399);
         integerValue();
         }
         break;
@@ -3392,7 +3380,7 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new BooleanLiteralContext(_localctx);
         enterOuterAlt(_localctx, 5);
         {
-        setState(403);
+        setState(400);
         booleanValue();
         }
         break;
@@ -3400,7 +3388,7 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new InputParamsContext(_localctx);
         enterOuterAlt(_localctx, 6);
         {
-        setState(404);
+        setState(401);
         params();
         }
         break;
@@ -3408,7 +3396,7 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new StringLiteralContext(_localctx);
         enterOuterAlt(_localctx, 7);
         {
-        setState(405);
+        setState(402);
         string();
         }
         break;
@@ -3416,27 +3404,27 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new NumericArrayLiteralContext(_localctx);
         enterOuterAlt(_localctx, 8);
         {
-        setState(406);
+        setState(403);
         match(OPENING_BRACKET);
-        setState(407);
+        setState(404);
         numericValue();
-        setState(412);
+        setState(409);
         _errHandler.sync(this);
         _la = _input.LA(1);
         while (_la==COMMA) {
           {
           {
-          setState(408);
+          setState(405);
           match(COMMA);
-          setState(409);
+          setState(406);
           numericValue();
           }
           }
-          setState(414);
+          setState(411);
           _errHandler.sync(this);
           _la = _input.LA(1);
         }
-        setState(415);
+        setState(412);
         match(CLOSING_BRACKET);
         }
         break;
@@ -3444,27 +3432,27 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new BooleanArrayLiteralContext(_localctx);
         enterOuterAlt(_localctx, 9);
         {
-        setState(417);
+        setState(414);
         match(OPENING_BRACKET);
-        setState(418);
+        setState(415);
         booleanValue();
-        setState(423);
+        setState(420);
         _errHandler.sync(this);
         _la = _input.LA(1);
         while (_la==COMMA) {
           {
           {
-          setState(419);
+          setState(416);
           match(COMMA);
-          setState(420);
+          setState(417);
           booleanValue();
           }
           }
-          setState(425);
+          setState(422);
           _errHandler.sync(this);
           _la = _input.LA(1);
         }
-        setState(426);
+        setState(423);
         match(CLOSING_BRACKET);
         }
         break;
@@ -3472,27 +3460,27 @@ public final ConstantContext constant() throws RecognitionException {
         _localctx = new StringArrayLiteralContext(_localctx);
         enterOuterAlt(_localctx, 10);
         {
-        setState(428);
+        setState(425);
         match(OPENING_BRACKET);
-        setState(429);
+        setState(426);
         string();
-        setState(434);
+        setState(431);
         _errHandler.sync(this);
         _la = _input.LA(1);
         while (_la==COMMA) {
           {
           {
-          setState(430);
+          setState(427);
           match(COMMA);
-          setState(431);
+          setState(428);
           string();
           }
           }
-          setState(436);
+          setState(433);
           _errHandler.sync(this);
           _la = _input.LA(1);
         }
-        setState(437);
+        setState(434);
         match(CLOSING_BRACKET);
         }
         break;
@@ -3566,14 +3554,14 @@ public final ParamsContext params() throws RecognitionException {
     ParamsContext _localctx = new ParamsContext(_ctx, getState());
     enterRule(_localctx, 64, RULE_params);
     try {
-      setState(443);
+      setState(440);
       _errHandler.sync(this);
       switch (_input.LA(1)) {
       case PARAM:
         _localctx = new InputParamContext(_localctx);
         enterOuterAlt(_localctx, 1);
         {
-        setState(441);
+        setState(438);
         match(PARAM);
         }
         break;
@@ -3581,7 +3569,7 @@ public final ParamsContext params() throws RecognitionException {
         _localctx = new InputNamedOrPositionalParamContext(_localctx);
         enterOuterAlt(_localctx, 2);
         {
-        setState(442);
+        setState(439);
         match(NAMED_OR_POSITIONAL_PARAM);
         }
         break;
@@ -3630,9 +3618,9 @@ public final LimitCommandContext limitCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(445);
+      setState(442);
       match(LIMIT);
-      setState(446);
+      setState(443);
       match(INTEGER_LITERAL);
       }
     }
@@ -3687,25 +3675,25 @@ public final SortCommandContext sortCommand() throws RecognitionException {
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(448);
+      setState(445);
       match(SORT);
-      setState(449);
+      setState(446);
       orderExpression();
-      setState(454);
+      setState(451);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,40,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(450);
+          setState(447);
           match(COMMA);
-          setState(451);
+          setState(448);
           orderExpression();
           }
           } 
         }
-        setState(456);
+        setState(453);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,40,_ctx);
       }
@@ -3761,14 +3749,14 @@ public final OrderExpressionContext orderExpression() throws RecognitionExceptio
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(457);
+      setState(454);
       booleanExpression(0);
-      setState(459);
+      setState(456);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,41,_ctx) ) {
       case 1:
         {
-        setState(458);
+        setState(455);
         ((OrderExpressionContext)_localctx).ordering = _input.LT(1);
         _la = _input.LA(1);
         if ( !(_la==ASC || _la==DESC) ) {
@@ -3782,14 +3770,14 @@ public final OrderExpressionContext orderExpression() throws RecognitionExceptio
         }
         break;
       }
-      setState(463);
+      setState(460);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,42,_ctx) ) {
       case 1:
         {
-        setState(461);
+        setState(458);
         match(NULLS);
-        setState(462);
+        setState(459);
         ((OrderExpressionContext)_localctx).nullOrdering = _input.LT(1);
         _la = _input.LA(1);
         if ( !(_la==FIRST || _la==LAST) ) {
@@ -3848,9 +3836,9 @@ public final KeepCommandContext keepCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(465);
+      setState(462);
       match(KEEP);
-      setState(466);
+      setState(463);
       qualifiedNamePatterns();
       }
     }
@@ -3897,9 +3885,9 @@ public final DropCommandContext dropCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(468);
+      setState(465);
       match(DROP);
-      setState(469);
+      setState(466);
       qualifiedNamePatterns();
       }
     }
@@ -3954,25 +3942,25 @@ public final RenameCommandContext renameCommand() throws RecognitionException {
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(471);
+      setState(468);
       match(RENAME);
-      setState(472);
+      setState(469);
       renameClause();
-      setState(477);
+      setState(474);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,43,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(473);
+          setState(470);
           match(COMMA);
-          setState(474);
+          setState(471);
           renameClause();
           }
           } 
         }
-        setState(479);
+        setState(476);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,43,_ctx);
       }
@@ -4026,11 +4014,11 @@ public final RenameClauseContext renameClause() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(480);
+      setState(477);
       ((RenameClauseContext)_localctx).oldName = qualifiedNamePattern();
-      setState(481);
+      setState(478);
       match(AS);
-      setState(482);
+      setState(479);
       ((RenameClauseContext)_localctx).newName = qualifiedNamePattern();
       }
     }
@@ -4083,18 +4071,18 @@ public final DissectCommandContext dissectCommand() throws RecognitionException
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(484);
+      setState(481);
       match(DISSECT);
-      setState(485);
+      setState(482);
       primaryExpression(0);
-      setState(486);
+      setState(483);
       string();
-      setState(488);
+      setState(485);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,44,_ctx) ) {
       case 1:
         {
-        setState(487);
+        setState(484);
         commandOptions();
         }
         break;
@@ -4147,11 +4135,11 @@ public final GrokCommandContext grokCommand() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(490);
+      setState(487);
       match(GROK);
-      setState(491);
+      setState(488);
       primaryExpression(0);
-      setState(492);
+      setState(489);
       string();
       }
     }
@@ -4198,9 +4186,9 @@ public final MvExpandCommandContext mvExpandCommand() throws RecognitionExceptio
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(494);
+      setState(491);
       match(MV_EXPAND);
-      setState(495);
+      setState(492);
       qualifiedName();
       }
     }
@@ -4254,23 +4242,23 @@ public final CommandOptionsContext commandOptions() throws RecognitionException
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(497);
+      setState(494);
       commandOption();
-      setState(502);
+      setState(499);
       _errHandler.sync(this);
       _alt = getInterpreter().adaptivePredict(_input,45,_ctx);
       while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
         if ( _alt==1 ) {
           {
           {
-          setState(498);
+          setState(495);
           match(COMMA);
-          setState(499);
+          setState(496);
           commandOption();
           }
           } 
         }
-        setState(504);
+        setState(501);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,45,_ctx);
       }
@@ -4322,11 +4310,11 @@ public final CommandOptionContext commandOption() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(505);
+      setState(502);
       identifier();
-      setState(506);
+      setState(503);
       match(ASSIGN);
-      setState(507);
+      setState(504);
       constant();
       }
     }
@@ -4372,7 +4360,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(509);
+      setState(506);
       _la = _input.LA(1);
       if ( !(_la==FALSE || _la==TRUE) ) {
       _errHandler.recoverInline(this);
@@ -4427,20 +4415,20 @@ public final NumericValueContext numericValue() throws RecognitionException {
     NumericValueContext _localctx = new NumericValueContext(_ctx, getState());
     enterRule(_localctx, 92, RULE_numericValue);
     try {
-      setState(513);
+      setState(510);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) {
       case 1:
         enterOuterAlt(_localctx, 1);
         {
-        setState(511);
+        setState(508);
         decimalValue();
         }
         break;
       case 2:
         enterOuterAlt(_localctx, 2);
         {
-        setState(512);
+        setState(509);
         integerValue();
         }
         break;
@@ -4489,12 +4477,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(516);
+      setState(513);
       _errHandler.sync(this);
       _la = _input.LA(1);
       if (_la==PLUS || _la==MINUS) {
         {
-        setState(515);
+        setState(512);
         _la = _input.LA(1);
         if ( !(_la==PLUS || _la==MINUS) ) {
         _errHandler.recoverInline(this);
@@ -4507,7 +4495,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException {
         }
       }
 
-      setState(518);
+      setState(515);
       match(DECIMAL_LITERAL);
       }
     }
@@ -4554,12 +4542,12 @@ public final IntegerValueContext integerValue() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(521);
+      setState(518);
       _errHandler.sync(this);
       _la = _input.LA(1);
       if (_la==PLUS || _la==MINUS) {
         {
-        setState(520);
+        setState(517);
         _la = _input.LA(1);
         if ( !(_la==PLUS || _la==MINUS) ) {
         _errHandler.recoverInline(this);
@@ -4572,7 +4560,7 @@ public final IntegerValueContext integerValue() throws RecognitionException {
         }
       }
 
-      setState(523);
+      setState(520);
       match(INTEGER_LITERAL);
       }
     }
@@ -4616,7 +4604,7 @@ public final StringContext string() throws RecognitionException {
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(525);
+      setState(522);
       match(QUOTED_STRING);
       }
     }
@@ -4666,9 +4654,9 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(527);
+      setState(524);
       _la = _input.LA(1);
-      if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 1125899906842624000L) != 0)) ) {
+      if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 562949953421312000L) != 0)) ) {
       _errHandler.recoverInline(this);
       }
       else {
@@ -4721,9 +4709,9 @@ public final ExplainCommandContext explainCommand() throws RecognitionException
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(529);
+      setState(526);
       match(EXPLAIN);
-      setState(530);
+      setState(527);
       subqueryExpression();
       }
     }
@@ -4771,11 +4759,11 @@ public final SubqueryExpressionContext subqueryExpression() throws RecognitionEx
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(532);
+      setState(529);
       match(OPENING_BRACKET);
-      setState(533);
+      setState(530);
       query(0);
-      setState(534);
+      setState(531);
       match(CLOSING_BRACKET);
       }
     }
@@ -4832,9 +4820,9 @@ public final ShowCommandContext showCommand() throws RecognitionException {
       _localctx = new ShowInfoContext(_localctx);
       enterOuterAlt(_localctx, 1);
       {
-      setState(536);
+      setState(533);
       match(SHOW);
-      setState(537);
+      setState(534);
       match(INFO);
       }
     }
@@ -4849,65 +4837,6 @@ public final ShowCommandContext showCommand() throws RecognitionException {
     return _localctx;
   }
 
-  @SuppressWarnings("CheckReturnValue")
-  public static class MetaCommandContext extends ParserRuleContext {
-    @SuppressWarnings("this-escape")
-    public MetaCommandContext(ParserRuleContext parent, int invokingState) {
-      super(parent, invokingState);
-    }
-    @Override public int getRuleIndex() { return RULE_metaCommand; }
-   
-    @SuppressWarnings("this-escape")
-    public MetaCommandContext() { }
-    public void copyFrom(MetaCommandContext ctx) {
-      super.copyFrom(ctx);
-    }
-  }
-  @SuppressWarnings("CheckReturnValue")
-  public static class MetaFunctionsContext extends MetaCommandContext {
-    public TerminalNode META() { return getToken(EsqlBaseParser.META, 0); }
-    public TerminalNode FUNCTIONS() { return getToken(EsqlBaseParser.FUNCTIONS, 0); }
-    @SuppressWarnings("this-escape")
-    public MetaFunctionsContext(MetaCommandContext ctx) { copyFrom(ctx); }
-    @Override
-    public void enterRule(ParseTreeListener listener) {
-      if ( listener instanceof EsqlBaseParserListener ) ((EsqlBaseParserListener)listener).enterMetaFunctions(this);
-    }
-    @Override
-    public void exitRule(ParseTreeListener listener) {
-      if ( listener instanceof EsqlBaseParserListener ) ((EsqlBaseParserListener)listener).exitMetaFunctions(this);
-    }
-    @Override
-    public  T accept(ParseTreeVisitor visitor) {
-      if ( visitor instanceof EsqlBaseParserVisitor ) return ((EsqlBaseParserVisitor)visitor).visitMetaFunctions(this);
-      else return visitor.visitChildren(this);
-    }
-  }
-
-  public final MetaCommandContext metaCommand() throws RecognitionException {
-    MetaCommandContext _localctx = new MetaCommandContext(_ctx, getState());
-    enterRule(_localctx, 108, RULE_metaCommand);
-    try {
-      _localctx = new MetaFunctionsContext(_localctx);
-      enterOuterAlt(_localctx, 1);
-      {
-      setState(539);
-      match(META);
-      setState(540);
-      match(FUNCTIONS);
-      }
-    }
-    catch (RecognitionException re) {
-      _localctx.exception = re;
-      _errHandler.reportError(this, re);
-      _errHandler.recover(this, re);
-    }
-    finally {
-      exitRule();
-    }
-    return _localctx;
-  }
-
   @SuppressWarnings("CheckReturnValue")
   public static class EnrichCommandContext extends ParserRuleContext {
     public Token policyName;
@@ -4951,51 +4880,51 @@ public  T accept(ParseTreeVisitor visitor) {
 
   public final EnrichCommandContext enrichCommand() throws RecognitionException {
     EnrichCommandContext _localctx = new EnrichCommandContext(_ctx, getState());
-    enterRule(_localctx, 110, RULE_enrichCommand);
+    enterRule(_localctx, 108, RULE_enrichCommand);
     try {
       int _alt;
       enterOuterAlt(_localctx, 1);
       {
-      setState(542);
+      setState(536);
       match(ENRICH);
-      setState(543);
+      setState(537);
       ((EnrichCommandContext)_localctx).policyName = match(ENRICH_POLICY_NAME);
-      setState(546);
+      setState(540);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,49,_ctx) ) {
       case 1:
         {
-        setState(544);
+        setState(538);
         match(ON);
-        setState(545);
+        setState(539);
         ((EnrichCommandContext)_localctx).matchField = qualifiedNamePattern();
         }
         break;
       }
-      setState(557);
+      setState(551);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,51,_ctx) ) {
       case 1:
         {
-        setState(548);
+        setState(542);
         match(WITH);
-        setState(549);
+        setState(543);
         enrichWithClause();
-        setState(554);
+        setState(548);
         _errHandler.sync(this);
         _alt = getInterpreter().adaptivePredict(_input,50,_ctx);
         while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
           if ( _alt==1 ) {
             {
             {
-            setState(550);
+            setState(544);
             match(COMMA);
-            setState(551);
+            setState(545);
             enrichWithClause();
             }
             } 
           }
-          setState(556);
+          setState(550);
           _errHandler.sync(this);
           _alt = getInterpreter().adaptivePredict(_input,50,_ctx);
         }
@@ -5048,23 +4977,23 @@ public  T accept(ParseTreeVisitor visitor) {
 
   public final EnrichWithClauseContext enrichWithClause() throws RecognitionException {
     EnrichWithClauseContext _localctx = new EnrichWithClauseContext(_ctx, getState());
-    enterRule(_localctx, 112, RULE_enrichWithClause);
+    enterRule(_localctx, 110, RULE_enrichWithClause);
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(562);
+      setState(556);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) {
       case 1:
         {
-        setState(559);
+        setState(553);
         ((EnrichWithClauseContext)_localctx).newName = qualifiedNamePattern();
-        setState(560);
+        setState(554);
         match(ASSIGN);
         }
         break;
       }
-      setState(564);
+      setState(558);
       ((EnrichWithClauseContext)_localctx).enrichField = qualifiedNamePattern();
       }
     }
@@ -5113,17 +5042,17 @@ public  T accept(ParseTreeVisitor visitor) {
 
   public final LookupCommandContext lookupCommand() throws RecognitionException {
     LookupCommandContext _localctx = new LookupCommandContext(_ctx, getState());
-    enterRule(_localctx, 114, RULE_lookupCommand);
+    enterRule(_localctx, 112, RULE_lookupCommand);
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(566);
+      setState(560);
       match(DEV_LOOKUP);
-      setState(567);
+      setState(561);
       ((LookupCommandContext)_localctx).tableName = indexPattern();
-      setState(568);
+      setState(562);
       match(ON);
-      setState(569);
+      setState(563);
       ((LookupCommandContext)_localctx).matchFields = qualifiedNamePatterns();
       }
     }
@@ -5172,22 +5101,22 @@ public  T accept(ParseTreeVisitor visitor) {
 
   public final InlinestatsCommandContext inlinestatsCommand() throws RecognitionException {
     InlinestatsCommandContext _localctx = new InlinestatsCommandContext(_ctx, getState());
-    enterRule(_localctx, 116, RULE_inlinestatsCommand);
+    enterRule(_localctx, 114, RULE_inlinestatsCommand);
     try {
       enterOuterAlt(_localctx, 1);
       {
-      setState(571);
+      setState(565);
       match(DEV_INLINESTATS);
-      setState(572);
+      setState(566);
       ((InlinestatsCommandContext)_localctx).stats = fields();
-      setState(575);
+      setState(569);
       _errHandler.sync(this);
       switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) {
       case 1:
         {
-        setState(573);
+        setState(567);
         match(BY);
-        setState(574);
+        setState(568);
         ((InlinestatsCommandContext)_localctx).grouping = fields();
         }
         break;
@@ -5274,7 +5203,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in
   }
 
   public static final String _serializedATN =
-    "\u0004\u0001}\u0242\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+
+    "\u0004\u0001x\u023c\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\u0002"+
@@ -5289,359 +5218,355 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in
     "(\u0007(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007+\u0002,\u0007,\u0002"+
     "-\u0007-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u0002"+
     "2\u00072\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u0002"+
-    "7\u00077\u00028\u00078\u00029\u00079\u0002:\u0007:\u0001\u0000\u0001\u0000"+
-    "\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
-    "\u0001\u0001\u0005\u0001\u0080\b\u0001\n\u0001\f\u0001\u0083\t\u0001\u0001"+
-    "\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+
-    "\u0002\u0003\u0002\u008c\b\u0002\u0001\u0003\u0001\u0003\u0001\u0003\u0001"+
-    "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001"+
+    "7\u00077\u00028\u00078\u00029\u00079\u0001\u0000\u0001\u0000\u0001\u0000"+
+    "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
+    "\u0005\u0001~\b\u0001\n\u0001\f\u0001\u0081\t\u0001\u0001\u0002\u0001"+
+    "\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0003\u0002\u0089"+
+    "\b\u0002\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001"+
     "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001"+
-    "\u0003\u0003\u0003\u009e\b\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001"+
-    "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001"+
-    "\u0005\u0003\u0005\u00aa\b\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001"+
-    "\u0005\u0001\u0005\u0005\u0005\u00b1\b\u0005\n\u0005\f\u0005\u00b4\t\u0005"+
-    "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005"+
-    "\u00bb\b\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005"+
-    "\u00c1\b\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+
-    "\u0001\u0005\u0005\u0005\u00c9\b\u0005\n\u0005\f\u0005\u00cc\t\u0005\u0001"+
-    "\u0006\u0001\u0006\u0003\u0006\u00d0\b\u0006\u0001\u0006\u0001\u0006\u0001"+
-    "\u0006\u0001\u0006\u0001\u0006\u0003\u0006\u00d7\b\u0006\u0001\u0006\u0001"+
-    "\u0006\u0001\u0006\u0003\u0006\u00dc\b\u0006\u0001\u0007\u0001\u0007\u0001"+
-    "\u0007\u0001\u0007\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0003\b\u00e7"+
-    "\b\b\u0001\t\u0001\t\u0001\t\u0001\t\u0003\t\u00ed\b\t\u0001\t\u0001\t"+
-    "\u0001\t\u0001\t\u0001\t\u0001\t\u0005\t\u00f5\b\t\n\t\f\t\u00f8\t\t\u0001"+
-    "\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0003\n\u0102"+
-    "\b\n\u0001\n\u0001\n\u0001\n\u0005\n\u0107\b\n\n\n\f\n\u010a\t\n\u0001"+
-    "\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0005"+
-    "\u000b\u0112\b\u000b\n\u000b\f\u000b\u0115\t\u000b\u0003\u000b\u0117\b"+
-    "\u000b\u0001\u000b\u0001\u000b\u0001\f\u0001\f\u0001\r\u0001\r\u0001\r"+
-    "\u0001\u000e\u0001\u000e\u0001\u000e\u0005\u000e\u0123\b\u000e\n\u000e"+
-    "\f\u000e\u0126\t\u000e\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f"+
-    "\u0001\u000f\u0003\u000f\u012d\b\u000f\u0001\u0010\u0001\u0010\u0001\u0010"+
-    "\u0001\u0010\u0005\u0010\u0133\b\u0010\n\u0010\f\u0010\u0136\t\u0010\u0001"+
-    "\u0010\u0003\u0010\u0139\b\u0010\u0001\u0011\u0001\u0011\u0001\u0011\u0001"+
-    "\u0011\u0001\u0011\u0003\u0011\u0140\b\u0011\u0001\u0012\u0001\u0012\u0001"+
-    "\u0013\u0001\u0013\u0001\u0014\u0001\u0014\u0003\u0014\u0148\b\u0014\u0001"+
-    "\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0005\u0015\u014e\b\u0015\n"+
-    "\u0015\f\u0015\u0151\t\u0015\u0001\u0016\u0001\u0016\u0001\u0016\u0001"+
-    "\u0016\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0005\u0017\u015b"+
-    "\b\u0017\n\u0017\f\u0017\u015e\t\u0017\u0001\u0017\u0003\u0017\u0161\b"+
-    "\u0017\u0001\u0017\u0001\u0017\u0003\u0017\u0165\b\u0017\u0001\u0018\u0001"+
-    "\u0018\u0001\u0018\u0001\u0019\u0001\u0019\u0003\u0019\u016c\b\u0019\u0001"+
-    "\u0019\u0001\u0019\u0003\u0019\u0170\b\u0019\u0001\u001a\u0001\u001a\u0001"+
-    "\u001a\u0005\u001a\u0175\b\u001a\n\u001a\f\u001a\u0178\t\u001a\u0001\u001b"+
-    "\u0001\u001b\u0001\u001b\u0005\u001b\u017d\b\u001b\n\u001b\f\u001b\u0180"+
-    "\t\u001b\u0001\u001c\u0001\u001c\u0001\u001c\u0005\u001c\u0185\b\u001c"+
-    "\n\u001c\f\u001c\u0188\t\u001c\u0001\u001d\u0001\u001d\u0001\u001e\u0001"+
-    "\u001e\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001"+
-    "\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001"+
-    "\u001f\u0001\u001f\u0005\u001f\u019b\b\u001f\n\u001f\f\u001f\u019e\t\u001f"+
+    "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0003\u0003\u009b"+
+    "\b\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001"+
+    "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005\u00a7"+
+    "\b\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0005"+
+    "\u0005\u00ae\b\u0005\n\u0005\f\u0005\u00b1\t\u0005\u0001\u0005\u0001\u0005"+
+    "\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005\u00b8\b\u0005\u0001\u0005"+
+    "\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005\u00be\b\u0005\u0001\u0005"+
+    "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0005\u0005"+
+    "\u00c6\b\u0005\n\u0005\f\u0005\u00c9\t\u0005\u0001\u0006\u0001\u0006\u0003"+
+    "\u0006\u00cd\b\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001"+
+    "\u0006\u0003\u0006\u00d4\b\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0003"+
+    "\u0006\u00d9\b\u0006\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001"+
+    "\b\u0001\b\u0001\b\u0001\b\u0001\b\u0003\b\u00e4\b\b\u0001\t\u0001\t\u0001"+
+    "\t\u0001\t\u0003\t\u00ea\b\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001"+
+    "\t\u0005\t\u00f2\b\t\n\t\f\t\u00f5\t\t\u0001\n\u0001\n\u0001\n\u0001\n"+
+    "\u0001\n\u0001\n\u0001\n\u0001\n\u0003\n\u00ff\b\n\u0001\n\u0001\n\u0001"+
+    "\n\u0005\n\u0104\b\n\n\n\f\n\u0107\t\n\u0001\u000b\u0001\u000b\u0001\u000b"+
+    "\u0001\u000b\u0001\u000b\u0001\u000b\u0005\u000b\u010f\b\u000b\n\u000b"+
+    "\f\u000b\u0112\t\u000b\u0003\u000b\u0114\b\u000b\u0001\u000b\u0001\u000b"+
+    "\u0001\f\u0001\f\u0001\r\u0001\r\u0001\r\u0001\u000e\u0001\u000e\u0001"+
+    "\u000e\u0005\u000e\u0120\b\u000e\n\u000e\f\u000e\u0123\t\u000e\u0001\u000f"+
+    "\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0003\u000f\u012a\b\u000f"+
+    "\u0001\u0010\u0001\u0010\u0001\u0010\u0001\u0010\u0005\u0010\u0130\b\u0010"+
+    "\n\u0010\f\u0010\u0133\t\u0010\u0001\u0010\u0003\u0010\u0136\b\u0010\u0001"+
+    "\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0003\u0011\u013d"+
+    "\b\u0011\u0001\u0012\u0001\u0012\u0001\u0013\u0001\u0013\u0001\u0014\u0001"+
+    "\u0014\u0003\u0014\u0145\b\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0001"+
+    "\u0015\u0005\u0015\u014b\b\u0015\n\u0015\f\u0015\u014e\t\u0015\u0001\u0016"+
+    "\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017\u0001\u0017"+
+    "\u0001\u0017\u0005\u0017\u0158\b\u0017\n\u0017\f\u0017\u015b\t\u0017\u0001"+
+    "\u0017\u0003\u0017\u015e\b\u0017\u0001\u0017\u0001\u0017\u0003\u0017\u0162"+
+    "\b\u0017\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0019\u0001\u0019\u0003"+
+    "\u0019\u0169\b\u0019\u0001\u0019\u0001\u0019\u0003\u0019\u016d\b\u0019"+
+    "\u0001\u001a\u0001\u001a\u0001\u001a\u0005\u001a\u0172\b\u001a\n\u001a"+
+    "\f\u001a\u0175\t\u001a\u0001\u001b\u0001\u001b\u0001\u001b\u0005\u001b"+
+    "\u017a\b\u001b\n\u001b\f\u001b\u017d\t\u001b\u0001\u001c\u0001\u001c\u0001"+
+    "\u001c\u0005\u001c\u0182\b\u001c\n\u001c\f\u001c\u0185\t\u001c\u0001\u001d"+
+    "\u0001\u001d\u0001\u001e\u0001\u001e\u0001\u001f\u0001\u001f\u0001\u001f"+
     "\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f"+
-    "\u0005\u001f\u01a6\b\u001f\n\u001f\f\u001f\u01a9\t\u001f\u0001\u001f\u0001"+
-    "\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0005\u001f\u01b1"+
-    "\b\u001f\n\u001f\f\u001f\u01b4\t\u001f\u0001\u001f\u0001\u001f\u0003\u001f"+
-    "\u01b8\b\u001f\u0001 \u0001 \u0003 \u01bc\b \u0001!\u0001!\u0001!\u0001"+
-    "\"\u0001\"\u0001\"\u0001\"\u0005\"\u01c5\b\"\n\"\f\"\u01c8\t\"\u0001#"+
-    "\u0001#\u0003#\u01cc\b#\u0001#\u0001#\u0003#\u01d0\b#\u0001$\u0001$\u0001"+
-    "$\u0001%\u0001%\u0001%\u0001&\u0001&\u0001&\u0001&\u0005&\u01dc\b&\n&"+
-    "\f&\u01df\t&\u0001\'\u0001\'\u0001\'\u0001\'\u0001(\u0001(\u0001(\u0001"+
-    "(\u0003(\u01e9\b(\u0001)\u0001)\u0001)\u0001)\u0001*\u0001*\u0001*\u0001"+
-    "+\u0001+\u0001+\u0005+\u01f5\b+\n+\f+\u01f8\t+\u0001,\u0001,\u0001,\u0001"+
-    ",\u0001-\u0001-\u0001.\u0001.\u0003.\u0202\b.\u0001/\u0003/\u0205\b/\u0001"+
-    "/\u0001/\u00010\u00030\u020a\b0\u00010\u00010\u00011\u00011\u00012\u0001"+
-    "2\u00013\u00013\u00013\u00014\u00014\u00014\u00014\u00015\u00015\u0001"+
-    "5\u00016\u00016\u00016\u00017\u00017\u00017\u00017\u00037\u0223\b7\u0001"+
-    "7\u00017\u00017\u00017\u00057\u0229\b7\n7\f7\u022c\t7\u00037\u022e\b7"+
-    "\u00018\u00018\u00018\u00038\u0233\b8\u00018\u00018\u00019\u00019\u0001"+
-    "9\u00019\u00019\u0001:\u0001:\u0001:\u0001:\u0003:\u0240\b:\u0001:\u0000"+
-    "\u0004\u0002\n\u0012\u0014;\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010"+
-    "\u0012\u0014\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPR"+
-    "TVXZ\\^`bdfhjlnprt\u0000\b\u0001\u0000<=\u0001\u0000>@\u0002\u0000\u001b"+
-    "\u001bMM\u0001\u0000DE\u0002\u0000  $$\u0002\u0000\'\'**\u0002\u0000&"+
-    "&44\u0002\u0000557;\u025b\u0000v\u0001\u0000\u0000\u0000\u0002y\u0001"+
-    "\u0000\u0000\u0000\u0004\u008b\u0001\u0000\u0000\u0000\u0006\u009d\u0001"+
-    "\u0000\u0000\u0000\b\u009f\u0001\u0000\u0000\u0000\n\u00c0\u0001\u0000"+
-    "\u0000\u0000\f\u00db\u0001\u0000\u0000\u0000\u000e\u00dd\u0001\u0000\u0000"+
-    "\u0000\u0010\u00e6\u0001\u0000\u0000\u0000\u0012\u00ec\u0001\u0000\u0000"+
-    "\u0000\u0014\u0101\u0001\u0000\u0000\u0000\u0016\u010b\u0001\u0000\u0000"+
-    "\u0000\u0018\u011a\u0001\u0000\u0000\u0000\u001a\u011c\u0001\u0000\u0000"+
-    "\u0000\u001c\u011f\u0001\u0000\u0000\u0000\u001e\u012c\u0001\u0000\u0000"+
-    "\u0000 \u012e\u0001\u0000\u0000\u0000\"\u013f\u0001\u0000\u0000\u0000"+
-    "$\u0141\u0001\u0000\u0000\u0000&\u0143\u0001\u0000\u0000\u0000(\u0147"+
-    "\u0001\u0000\u0000\u0000*\u0149\u0001\u0000\u0000\u0000,\u0152\u0001\u0000"+
-    "\u0000\u0000.\u0156\u0001\u0000\u0000\u00000\u0166\u0001\u0000\u0000\u0000"+
-    "2\u0169\u0001\u0000\u0000\u00004\u0171\u0001\u0000\u0000\u00006\u0179"+
-    "\u0001\u0000\u0000\u00008\u0181\u0001\u0000\u0000\u0000:\u0189\u0001\u0000"+
-    "\u0000\u0000<\u018b\u0001\u0000\u0000\u0000>\u01b7\u0001\u0000\u0000\u0000"+
-    "@\u01bb\u0001\u0000\u0000\u0000B\u01bd\u0001\u0000\u0000\u0000D\u01c0"+
-    "\u0001\u0000\u0000\u0000F\u01c9\u0001\u0000\u0000\u0000H\u01d1\u0001\u0000"+
-    "\u0000\u0000J\u01d4\u0001\u0000\u0000\u0000L\u01d7\u0001\u0000\u0000\u0000"+
-    "N\u01e0\u0001\u0000\u0000\u0000P\u01e4\u0001\u0000\u0000\u0000R\u01ea"+
-    "\u0001\u0000\u0000\u0000T\u01ee\u0001\u0000\u0000\u0000V\u01f1\u0001\u0000"+
-    "\u0000\u0000X\u01f9\u0001\u0000\u0000\u0000Z\u01fd\u0001\u0000\u0000\u0000"+
-    "\\\u0201\u0001\u0000\u0000\u0000^\u0204\u0001\u0000\u0000\u0000`\u0209"+
-    "\u0001\u0000\u0000\u0000b\u020d\u0001\u0000\u0000\u0000d\u020f\u0001\u0000"+
-    "\u0000\u0000f\u0211\u0001\u0000\u0000\u0000h\u0214\u0001\u0000\u0000\u0000"+
-    "j\u0218\u0001\u0000\u0000\u0000l\u021b\u0001\u0000\u0000\u0000n\u021e"+
-    "\u0001\u0000\u0000\u0000p\u0232\u0001\u0000\u0000\u0000r\u0236\u0001\u0000"+
-    "\u0000\u0000t\u023b\u0001\u0000\u0000\u0000vw\u0003\u0002\u0001\u0000"+
-    "wx\u0005\u0000\u0000\u0001x\u0001\u0001\u0000\u0000\u0000yz\u0006\u0001"+
-    "\uffff\uffff\u0000z{\u0003\u0004\u0002\u0000{\u0081\u0001\u0000\u0000"+
-    "\u0000|}\n\u0001\u0000\u0000}~\u0005\u001a\u0000\u0000~\u0080\u0003\u0006"+
-    "\u0003\u0000\u007f|\u0001\u0000\u0000\u0000\u0080\u0083\u0001\u0000\u0000"+
-    "\u0000\u0081\u007f\u0001\u0000\u0000\u0000\u0081\u0082\u0001\u0000\u0000"+
-    "\u0000\u0082\u0003\u0001\u0000\u0000\u0000\u0083\u0081\u0001\u0000\u0000"+
-    "\u0000\u0084\u008c\u0003f3\u0000\u0085\u008c\u0003 \u0010\u0000\u0086"+
-    "\u008c\u0003l6\u0000\u0087\u008c\u0003\u001a\r\u0000\u0088\u008c\u0003"+
-    "j5\u0000\u0089\u008a\u0004\u0002\u0001\u0000\u008a\u008c\u0003.\u0017"+
-    "\u0000\u008b\u0084\u0001\u0000\u0000\u0000\u008b\u0085\u0001\u0000\u0000"+
-    "\u0000\u008b\u0086\u0001\u0000\u0000\u0000\u008b\u0087\u0001\u0000\u0000"+
-    "\u0000\u008b\u0088\u0001\u0000\u0000\u0000\u008b\u0089\u0001\u0000\u0000"+
-    "\u0000\u008c\u0005\u0001\u0000\u0000\u0000\u008d\u009e\u00030\u0018\u0000"+
-    "\u008e\u009e\u0003\b\u0004\u0000\u008f\u009e\u0003H$\u0000\u0090\u009e"+
-    "\u0003B!\u0000\u0091\u009e\u00032\u0019\u0000\u0092\u009e\u0003D\"\u0000"+
-    "\u0093\u009e\u0003J%\u0000\u0094\u009e\u0003L&\u0000\u0095\u009e\u0003"+
-    "P(\u0000\u0096\u009e\u0003R)\u0000\u0097\u009e\u0003n7\u0000\u0098\u009e"+
-    "\u0003T*\u0000\u0099\u009a\u0004\u0003\u0002\u0000\u009a\u009e\u0003t"+
-    ":\u0000\u009b\u009c\u0004\u0003\u0003\u0000\u009c\u009e\u0003r9\u0000"+
-    "\u009d\u008d\u0001\u0000\u0000\u0000\u009d\u008e\u0001\u0000\u0000\u0000"+
-    "\u009d\u008f\u0001\u0000\u0000\u0000\u009d\u0090\u0001\u0000\u0000\u0000"+
-    "\u009d\u0091\u0001\u0000\u0000\u0000\u009d\u0092\u0001\u0000\u0000\u0000"+
-    "\u009d\u0093\u0001\u0000\u0000\u0000\u009d\u0094\u0001\u0000\u0000\u0000"+
-    "\u009d\u0095\u0001\u0000\u0000\u0000\u009d\u0096\u0001\u0000\u0000\u0000"+
-    "\u009d\u0097\u0001\u0000\u0000\u0000\u009d\u0098\u0001\u0000\u0000\u0000"+
-    "\u009d\u0099\u0001\u0000\u0000\u0000\u009d\u009b\u0001\u0000\u0000\u0000"+
-    "\u009e\u0007\u0001\u0000\u0000\u0000\u009f\u00a0\u0005\u0011\u0000\u0000"+
-    "\u00a0\u00a1\u0003\n\u0005\u0000\u00a1\t\u0001\u0000\u0000\u0000\u00a2"+
-    "\u00a3\u0006\u0005\uffff\uffff\u0000\u00a3\u00a4\u0005-\u0000\u0000\u00a4"+
-    "\u00c1\u0003\n\u0005\b\u00a5\u00c1\u0003\u0010\b\u0000\u00a6\u00c1\u0003"+
-    "\f\u0006\u0000\u00a7\u00a9\u0003\u0010\b\u0000\u00a8\u00aa\u0005-\u0000"+
-    "\u0000\u00a9\u00a8\u0001\u0000\u0000\u0000\u00a9\u00aa\u0001\u0000\u0000"+
-    "\u0000\u00aa\u00ab\u0001\u0000\u0000\u0000\u00ab\u00ac\u0005(\u0000\u0000"+
-    "\u00ac\u00ad\u0005,\u0000\u0000\u00ad\u00b2\u0003\u0010\b\u0000\u00ae"+
-    "\u00af\u0005#\u0000\u0000\u00af\u00b1\u0003\u0010\b\u0000\u00b0\u00ae"+
-    "\u0001\u0000\u0000\u0000\u00b1\u00b4\u0001\u0000\u0000\u0000\u00b2\u00b0"+
-    "\u0001\u0000\u0000\u0000\u00b2\u00b3\u0001\u0000\u0000\u0000\u00b3\u00b5"+
-    "\u0001\u0000\u0000\u0000\u00b4\u00b2\u0001\u0000\u0000\u0000\u00b5\u00b6"+
-    "\u00053\u0000\u0000\u00b6\u00c1\u0001\u0000\u0000\u0000\u00b7\u00b8\u0003"+
-    "\u0010\b\u0000\u00b8\u00ba\u0005)\u0000\u0000\u00b9\u00bb\u0005-\u0000"+
-    "\u0000\u00ba\u00b9\u0001\u0000\u0000\u0000\u00ba\u00bb\u0001\u0000\u0000"+
-    "\u0000\u00bb\u00bc\u0001\u0000\u0000\u0000\u00bc\u00bd\u0005.\u0000\u0000"+
-    "\u00bd\u00c1\u0001\u0000\u0000\u0000\u00be\u00bf\u0004\u0005\u0004\u0000"+
-    "\u00bf\u00c1\u0003\u000e\u0007\u0000\u00c0\u00a2\u0001\u0000\u0000\u0000"+
-    "\u00c0\u00a5\u0001\u0000\u0000\u0000\u00c0\u00a6\u0001\u0000\u0000\u0000"+
-    "\u00c0\u00a7\u0001\u0000\u0000\u0000\u00c0\u00b7\u0001\u0000\u0000\u0000"+
-    "\u00c0\u00be\u0001\u0000\u0000\u0000\u00c1\u00ca\u0001\u0000\u0000\u0000"+
-    "\u00c2\u00c3\n\u0005\u0000\u0000\u00c3\u00c4\u0005\u001f\u0000\u0000\u00c4"+
-    "\u00c9\u0003\n\u0005\u0006\u00c5\u00c6\n\u0004\u0000\u0000\u00c6\u00c7"+
-    "\u00050\u0000\u0000\u00c7\u00c9\u0003\n\u0005\u0005\u00c8\u00c2\u0001"+
-    "\u0000\u0000\u0000\u00c8\u00c5\u0001\u0000\u0000\u0000\u00c9\u00cc\u0001"+
-    "\u0000\u0000\u0000\u00ca\u00c8\u0001\u0000\u0000\u0000\u00ca\u00cb\u0001"+
-    "\u0000\u0000\u0000\u00cb\u000b\u0001\u0000\u0000\u0000\u00cc\u00ca\u0001"+
-    "\u0000\u0000\u0000\u00cd\u00cf\u0003\u0010\b\u0000\u00ce\u00d0\u0005-"+
-    "\u0000\u0000\u00cf\u00ce\u0001\u0000\u0000\u0000\u00cf\u00d0\u0001\u0000"+
-    "\u0000\u0000\u00d0\u00d1\u0001\u0000\u0000\u0000\u00d1\u00d2\u0005+\u0000"+
-    "\u0000\u00d2\u00d3\u0003b1\u0000\u00d3\u00dc\u0001\u0000\u0000\u0000\u00d4"+
-    "\u00d6\u0003\u0010\b\u0000\u00d5\u00d7\u0005-\u0000\u0000\u00d6\u00d5"+
-    "\u0001\u0000\u0000\u0000\u00d6\u00d7\u0001\u0000\u0000\u0000\u00d7\u00d8"+
-    "\u0001\u0000\u0000\u0000\u00d8\u00d9\u00052\u0000\u0000\u00d9\u00da\u0003"+
-    "b1\u0000\u00da\u00dc\u0001\u0000\u0000\u0000\u00db\u00cd\u0001\u0000\u0000"+
-    "\u0000\u00db\u00d4\u0001\u0000\u0000\u0000\u00dc\r\u0001\u0000\u0000\u0000"+
-    "\u00dd\u00de\u0003\u0010\b\u0000\u00de\u00df\u0005\u0014\u0000\u0000\u00df"+
-    "\u00e0\u0003b1\u0000\u00e0\u000f\u0001\u0000\u0000\u0000\u00e1\u00e7\u0003"+
-    "\u0012\t\u0000\u00e2\u00e3\u0003\u0012\t\u0000\u00e3\u00e4\u0003d2\u0000"+
-    "\u00e4\u00e5\u0003\u0012\t\u0000\u00e5\u00e7\u0001\u0000\u0000\u0000\u00e6"+
-    "\u00e1\u0001\u0000\u0000\u0000\u00e6\u00e2\u0001\u0000\u0000\u0000\u00e7"+
-    "\u0011\u0001\u0000\u0000\u0000\u00e8\u00e9\u0006\t\uffff\uffff\u0000\u00e9"+
-    "\u00ed\u0003\u0014\n\u0000\u00ea\u00eb\u0007\u0000\u0000\u0000\u00eb\u00ed"+
-    "\u0003\u0012\t\u0003\u00ec\u00e8\u0001\u0000\u0000\u0000\u00ec\u00ea\u0001"+
-    "\u0000\u0000\u0000\u00ed\u00f6\u0001\u0000\u0000\u0000\u00ee\u00ef\n\u0002"+
-    "\u0000\u0000\u00ef\u00f0\u0007\u0001\u0000\u0000\u00f0\u00f5\u0003\u0012"+
-    "\t\u0003\u00f1\u00f2\n\u0001\u0000\u0000\u00f2\u00f3\u0007\u0000\u0000"+
-    "\u0000\u00f3\u00f5\u0003\u0012\t\u0002\u00f4\u00ee\u0001\u0000\u0000\u0000"+
-    "\u00f4\u00f1\u0001\u0000\u0000\u0000\u00f5\u00f8\u0001\u0000\u0000\u0000"+
-    "\u00f6\u00f4\u0001\u0000\u0000\u0000\u00f6\u00f7\u0001\u0000\u0000\u0000"+
-    "\u00f7\u0013\u0001\u0000\u0000\u0000\u00f8\u00f6\u0001\u0000\u0000\u0000"+
-    "\u00f9\u00fa\u0006\n\uffff\uffff\u0000\u00fa\u0102\u0003>\u001f\u0000"+
-    "\u00fb\u0102\u00034\u001a\u0000\u00fc\u0102\u0003\u0016\u000b\u0000\u00fd"+
-    "\u00fe\u0005,\u0000\u0000\u00fe\u00ff\u0003\n\u0005\u0000\u00ff\u0100"+
-    "\u00053\u0000\u0000\u0100\u0102\u0001\u0000\u0000\u0000\u0101\u00f9\u0001"+
-    "\u0000\u0000\u0000\u0101\u00fb\u0001\u0000\u0000\u0000\u0101\u00fc\u0001"+
-    "\u0000\u0000\u0000\u0101\u00fd\u0001\u0000\u0000\u0000\u0102\u0108\u0001"+
-    "\u0000\u0000\u0000\u0103\u0104\n\u0001\u0000\u0000\u0104\u0105\u0005\""+
-    "\u0000\u0000\u0105\u0107\u0003\u0018\f\u0000\u0106\u0103\u0001\u0000\u0000"+
-    "\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000"+
-    "\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u0015\u0001\u0000\u0000"+
-    "\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010c\u0003:\u001d\u0000"+
-    "\u010c\u0116\u0005,\u0000\u0000\u010d\u0117\u0005>\u0000\u0000\u010e\u0113"+
-    "\u0003\n\u0005\u0000\u010f\u0110\u0005#\u0000\u0000\u0110\u0112\u0003"+
-    "\n\u0005\u0000\u0111\u010f\u0001\u0000\u0000\u0000\u0112\u0115\u0001\u0000"+
-    "\u0000\u0000\u0113\u0111\u0001\u0000\u0000\u0000\u0113\u0114\u0001\u0000"+
-    "\u0000\u0000\u0114\u0117\u0001\u0000\u0000\u0000\u0115\u0113\u0001\u0000"+
-    "\u0000\u0000\u0116\u010d\u0001\u0000\u0000\u0000\u0116\u010e\u0001\u0000"+
-    "\u0000\u0000\u0116\u0117\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000"+
-    "\u0000\u0000\u0118\u0119\u00053\u0000\u0000\u0119\u0017\u0001\u0000\u0000"+
-    "\u0000\u011a\u011b\u0003:\u001d\u0000\u011b\u0019\u0001\u0000\u0000\u0000"+
-    "\u011c\u011d\u0005\r\u0000\u0000\u011d\u011e\u0003\u001c\u000e\u0000\u011e"+
-    "\u001b\u0001\u0000\u0000\u0000\u011f\u0124\u0003\u001e\u000f\u0000\u0120"+
-    "\u0121\u0005#\u0000\u0000\u0121\u0123\u0003\u001e\u000f\u0000\u0122\u0120"+
-    "\u0001\u0000\u0000\u0000\u0123\u0126\u0001\u0000\u0000\u0000\u0124\u0122"+
-    "\u0001\u0000\u0000\u0000\u0124\u0125\u0001\u0000\u0000\u0000\u0125\u001d"+
-    "\u0001\u0000\u0000\u0000\u0126\u0124\u0001\u0000\u0000\u0000\u0127\u012d"+
-    "\u0003\n\u0005\u0000\u0128\u0129\u00034\u001a\u0000\u0129\u012a\u0005"+
-    "!\u0000\u0000\u012a\u012b\u0003\n\u0005\u0000\u012b\u012d\u0001\u0000"+
-    "\u0000\u0000\u012c\u0127\u0001\u0000\u0000\u0000\u012c\u0128\u0001\u0000"+
-    "\u0000\u0000\u012d\u001f\u0001\u0000\u0000\u0000\u012e\u012f\u0005\u0006"+
-    "\u0000\u0000\u012f\u0134\u0003\"\u0011\u0000\u0130\u0131\u0005#\u0000"+
-    "\u0000\u0131\u0133\u0003\"\u0011\u0000\u0132\u0130\u0001\u0000\u0000\u0000"+
-    "\u0133\u0136\u0001\u0000\u0000\u0000\u0134\u0132\u0001\u0000\u0000\u0000"+
-    "\u0134\u0135\u0001\u0000\u0000\u0000\u0135\u0138\u0001\u0000\u0000\u0000"+
-    "\u0136\u0134\u0001\u0000\u0000\u0000\u0137\u0139\u0003(\u0014\u0000\u0138"+
-    "\u0137\u0001\u0000\u0000\u0000\u0138\u0139\u0001\u0000\u0000\u0000\u0139"+
-    "!\u0001\u0000\u0000\u0000\u013a\u013b\u0003$\u0012\u0000\u013b\u013c\u0005"+
-    "m\u0000\u0000\u013c\u013d\u0003&\u0013\u0000\u013d\u0140\u0001\u0000\u0000"+
-    "\u0000\u013e\u0140\u0003&\u0013\u0000\u013f\u013a\u0001\u0000\u0000\u0000"+
-    "\u013f\u013e\u0001\u0000\u0000\u0000\u0140#\u0001\u0000\u0000\u0000\u0141"+
-    "\u0142\u0005M\u0000\u0000\u0142%\u0001\u0000\u0000\u0000\u0143\u0144\u0007"+
-    "\u0002\u0000\u0000\u0144\'\u0001\u0000\u0000\u0000\u0145\u0148\u0003*"+
-    "\u0015\u0000\u0146\u0148\u0003,\u0016\u0000\u0147\u0145\u0001\u0000\u0000"+
-    "\u0000\u0147\u0146\u0001\u0000\u0000\u0000\u0148)\u0001\u0000\u0000\u0000"+
-    "\u0149\u014a\u0005L\u0000\u0000\u014a\u014f\u0005M\u0000\u0000\u014b\u014c"+
-    "\u0005#\u0000\u0000\u014c\u014e\u0005M\u0000\u0000\u014d\u014b\u0001\u0000"+
-    "\u0000\u0000\u014e\u0151\u0001\u0000\u0000\u0000\u014f\u014d\u0001\u0000"+
-    "\u0000\u0000\u014f\u0150\u0001\u0000\u0000\u0000\u0150+\u0001\u0000\u0000"+
-    "\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0153\u0005B\u0000\u0000"+
-    "\u0153\u0154\u0003*\u0015\u0000\u0154\u0155\u0005C\u0000\u0000\u0155-"+
-    "\u0001\u0000\u0000\u0000\u0156\u0157\u0005\u0015\u0000\u0000\u0157\u015c"+
-    "\u0003\"\u0011\u0000\u0158\u0159\u0005#\u0000\u0000\u0159\u015b\u0003"+
-    "\"\u0011\u0000\u015a\u0158\u0001\u0000\u0000\u0000\u015b\u015e\u0001\u0000"+
-    "\u0000\u0000\u015c\u015a\u0001\u0000\u0000\u0000\u015c\u015d\u0001\u0000"+
-    "\u0000\u0000\u015d\u0160\u0001\u0000\u0000\u0000\u015e\u015c\u0001\u0000"+
-    "\u0000\u0000\u015f\u0161\u0003\u001c\u000e\u0000\u0160\u015f\u0001\u0000"+
-    "\u0000\u0000\u0160\u0161\u0001\u0000\u0000\u0000\u0161\u0164\u0001\u0000"+
-    "\u0000\u0000\u0162\u0163\u0005\u001e\u0000\u0000\u0163\u0165\u0003\u001c"+
-    "\u000e\u0000\u0164\u0162\u0001\u0000\u0000\u0000\u0164\u0165\u0001\u0000"+
-    "\u0000\u0000\u0165/\u0001\u0000\u0000\u0000\u0166\u0167\u0005\u0004\u0000"+
-    "\u0000\u0167\u0168\u0003\u001c\u000e\u0000\u01681\u0001\u0000\u0000\u0000"+
-    "\u0169\u016b\u0005\u0010\u0000\u0000\u016a\u016c\u0003\u001c\u000e\u0000"+
-    "\u016b\u016a\u0001\u0000\u0000\u0000\u016b\u016c\u0001\u0000\u0000\u0000"+
-    "\u016c\u016f\u0001\u0000\u0000\u0000\u016d\u016e\u0005\u001e\u0000\u0000"+
-    "\u016e\u0170\u0003\u001c\u000e\u0000\u016f\u016d\u0001\u0000\u0000\u0000"+
-    "\u016f\u0170\u0001\u0000\u0000\u0000\u01703\u0001\u0000\u0000\u0000\u0171"+
-    "\u0176\u0003:\u001d\u0000\u0172\u0173\u0005%\u0000\u0000\u0173\u0175\u0003"+
-    ":\u001d\u0000\u0174\u0172\u0001\u0000\u0000\u0000\u0175\u0178\u0001\u0000"+
-    "\u0000\u0000\u0176\u0174\u0001\u0000\u0000\u0000\u0176\u0177\u0001\u0000"+
-    "\u0000\u0000\u01775\u0001\u0000\u0000\u0000\u0178\u0176\u0001\u0000\u0000"+
-    "\u0000\u0179\u017e\u0003<\u001e\u0000\u017a\u017b\u0005%\u0000\u0000\u017b"+
-    "\u017d\u0003<\u001e\u0000\u017c\u017a\u0001\u0000\u0000\u0000\u017d\u0180"+
-    "\u0001\u0000\u0000\u0000\u017e\u017c\u0001\u0000\u0000\u0000\u017e\u017f"+
-    "\u0001\u0000\u0000\u0000\u017f7\u0001\u0000\u0000\u0000\u0180\u017e\u0001"+
-    "\u0000\u0000\u0000\u0181\u0186\u00036\u001b\u0000\u0182\u0183\u0005#\u0000"+
-    "\u0000\u0183\u0185\u00036\u001b\u0000\u0184\u0182\u0001\u0000\u0000\u0000"+
-    "\u0185\u0188\u0001\u0000\u0000\u0000\u0186\u0184\u0001\u0000\u0000\u0000"+
-    "\u0186\u0187\u0001\u0000\u0000\u0000\u01879\u0001\u0000\u0000\u0000\u0188"+
-    "\u0186\u0001\u0000\u0000\u0000\u0189\u018a\u0007\u0003\u0000\u0000\u018a"+
-    ";\u0001\u0000\u0000\u0000\u018b\u018c\u0005Q\u0000\u0000\u018c=\u0001"+
-    "\u0000\u0000\u0000\u018d\u01b8\u0005.\u0000\u0000\u018e\u018f\u0003`0"+
-    "\u0000\u018f\u0190\u0005D\u0000\u0000\u0190\u01b8\u0001\u0000\u0000\u0000"+
-    "\u0191\u01b8\u0003^/\u0000\u0192\u01b8\u0003`0\u0000\u0193\u01b8\u0003"+
-    "Z-\u0000\u0194\u01b8\u0003@ \u0000\u0195\u01b8\u0003b1\u0000\u0196\u0197"+
-    "\u0005B\u0000\u0000\u0197\u019c\u0003\\.\u0000\u0198\u0199\u0005#\u0000"+
-    "\u0000\u0199\u019b\u0003\\.\u0000\u019a\u0198\u0001\u0000\u0000\u0000"+
-    "\u019b\u019e\u0001\u0000\u0000\u0000\u019c\u019a\u0001\u0000\u0000\u0000"+
-    "\u019c\u019d\u0001\u0000\u0000\u0000\u019d\u019f\u0001\u0000\u0000\u0000"+
-    "\u019e\u019c\u0001\u0000\u0000\u0000\u019f\u01a0\u0005C\u0000\u0000\u01a0"+
-    "\u01b8\u0001\u0000\u0000\u0000\u01a1\u01a2\u0005B\u0000\u0000\u01a2\u01a7"+
-    "\u0003Z-\u0000\u01a3\u01a4\u0005#\u0000\u0000\u01a4\u01a6\u0003Z-\u0000"+
-    "\u01a5\u01a3\u0001\u0000\u0000\u0000\u01a6\u01a9\u0001\u0000\u0000\u0000"+
-    "\u01a7\u01a5\u0001\u0000\u0000\u0000\u01a7\u01a8\u0001\u0000\u0000\u0000"+
-    "\u01a8\u01aa\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000"+
-    "\u01aa\u01ab\u0005C\u0000\u0000\u01ab\u01b8\u0001\u0000\u0000\u0000\u01ac"+
-    "\u01ad\u0005B\u0000\u0000\u01ad\u01b2\u0003b1\u0000\u01ae\u01af\u0005"+
-    "#\u0000\u0000\u01af\u01b1\u0003b1\u0000\u01b0\u01ae\u0001\u0000\u0000"+
-    "\u0000\u01b1\u01b4\u0001\u0000\u0000\u0000\u01b2\u01b0\u0001\u0000\u0000"+
-    "\u0000\u01b2\u01b3\u0001\u0000\u0000\u0000\u01b3\u01b5\u0001\u0000\u0000"+
-    "\u0000\u01b4\u01b2\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005C\u0000\u0000"+
-    "\u01b6\u01b8\u0001\u0000\u0000\u0000\u01b7\u018d\u0001\u0000\u0000\u0000"+
-    "\u01b7\u018e\u0001\u0000\u0000\u0000\u01b7\u0191\u0001\u0000\u0000\u0000"+
-    "\u01b7\u0192\u0001\u0000\u0000\u0000\u01b7\u0193\u0001\u0000\u0000\u0000"+
-    "\u01b7\u0194\u0001\u0000\u0000\u0000\u01b7\u0195\u0001\u0000\u0000\u0000"+
-    "\u01b7\u0196\u0001\u0000\u0000\u0000\u01b7\u01a1\u0001\u0000\u0000\u0000"+
-    "\u01b7\u01ac\u0001\u0000\u0000\u0000\u01b8?\u0001\u0000\u0000\u0000\u01b9"+
-    "\u01bc\u00051\u0000\u0000\u01ba\u01bc\u0005A\u0000\u0000\u01bb\u01b9\u0001"+
-    "\u0000\u0000\u0000\u01bb\u01ba\u0001\u0000\u0000\u0000\u01bcA\u0001\u0000"+
-    "\u0000\u0000\u01bd\u01be\u0005\t\u0000\u0000\u01be\u01bf\u0005\u001c\u0000"+
-    "\u0000\u01bfC\u0001\u0000\u0000\u0000\u01c0\u01c1\u0005\u000f\u0000\u0000"+
-    "\u01c1\u01c6\u0003F#\u0000\u01c2\u01c3\u0005#\u0000\u0000\u01c3\u01c5"+
-    "\u0003F#\u0000\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c5\u01c8\u0001\u0000"+
-    "\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c6\u01c7\u0001\u0000"+
-    "\u0000\u0000\u01c7E\u0001\u0000\u0000\u0000\u01c8\u01c6\u0001\u0000\u0000"+
-    "\u0000\u01c9\u01cb\u0003\n\u0005\u0000\u01ca\u01cc\u0007\u0004\u0000\u0000"+
-    "\u01cb\u01ca\u0001\u0000\u0000\u0000\u01cb\u01cc\u0001\u0000\u0000\u0000"+
-    "\u01cc\u01cf\u0001\u0000\u0000\u0000\u01cd\u01ce\u0005/\u0000\u0000\u01ce"+
-    "\u01d0\u0007\u0005\u0000\u0000\u01cf\u01cd\u0001\u0000\u0000\u0000\u01cf"+
-    "\u01d0\u0001\u0000\u0000\u0000\u01d0G\u0001\u0000\u0000\u0000\u01d1\u01d2"+
-    "\u0005\b\u0000\u0000\u01d2\u01d3\u00038\u001c\u0000\u01d3I\u0001\u0000"+
-    "\u0000\u0000\u01d4\u01d5\u0005\u0002\u0000\u0000\u01d5\u01d6\u00038\u001c"+
-    "\u0000\u01d6K\u0001\u0000\u0000\u0000\u01d7\u01d8\u0005\f\u0000\u0000"+
-    "\u01d8\u01dd\u0003N\'\u0000\u01d9\u01da\u0005#\u0000\u0000\u01da\u01dc"+
-    "\u0003N\'\u0000\u01db\u01d9\u0001\u0000\u0000\u0000\u01dc\u01df\u0001"+
-    "\u0000\u0000\u0000\u01dd\u01db\u0001\u0000\u0000\u0000\u01dd\u01de\u0001"+
-    "\u0000\u0000\u0000\u01deM\u0001\u0000\u0000\u0000\u01df\u01dd\u0001\u0000"+
-    "\u0000\u0000\u01e0\u01e1\u00036\u001b\u0000\u01e1\u01e2\u0005U\u0000\u0000"+
-    "\u01e2\u01e3\u00036\u001b\u0000\u01e3O\u0001\u0000\u0000\u0000\u01e4\u01e5"+
-    "\u0005\u0001\u0000\u0000\u01e5\u01e6\u0003\u0014\n\u0000\u01e6\u01e8\u0003"+
-    "b1\u0000\u01e7\u01e9\u0003V+\u0000\u01e8\u01e7\u0001\u0000\u0000\u0000"+
-    "\u01e8\u01e9\u0001\u0000\u0000\u0000\u01e9Q\u0001\u0000\u0000\u0000\u01ea"+
-    "\u01eb\u0005\u0007\u0000\u0000\u01eb\u01ec\u0003\u0014\n\u0000\u01ec\u01ed"+
-    "\u0003b1\u0000\u01edS\u0001\u0000\u0000\u0000\u01ee\u01ef\u0005\u000b"+
-    "\u0000\u0000\u01ef\u01f0\u00034\u001a\u0000\u01f0U\u0001\u0000\u0000\u0000"+
-    "\u01f1\u01f6\u0003X,\u0000\u01f2\u01f3\u0005#\u0000\u0000\u01f3\u01f5"+
-    "\u0003X,\u0000\u01f4\u01f2\u0001\u0000\u0000\u0000\u01f5\u01f8\u0001\u0000"+
-    "\u0000\u0000\u01f6\u01f4\u0001\u0000\u0000\u0000\u01f6\u01f7\u0001\u0000"+
-    "\u0000\u0000\u01f7W\u0001\u0000\u0000\u0000\u01f8\u01f6\u0001\u0000\u0000"+
-    "\u0000\u01f9\u01fa\u0003:\u001d\u0000\u01fa\u01fb\u0005!\u0000\u0000\u01fb"+
-    "\u01fc\u0003>\u001f\u0000\u01fcY\u0001\u0000\u0000\u0000\u01fd\u01fe\u0007"+
-    "\u0006\u0000\u0000\u01fe[\u0001\u0000\u0000\u0000\u01ff\u0202\u0003^/"+
-    "\u0000\u0200\u0202\u0003`0\u0000\u0201\u01ff\u0001\u0000\u0000\u0000\u0201"+
-    "\u0200\u0001\u0000\u0000\u0000\u0202]\u0001\u0000\u0000\u0000\u0203\u0205"+
-    "\u0007\u0000\u0000\u0000\u0204\u0203\u0001\u0000\u0000\u0000\u0204\u0205"+
-    "\u0001\u0000\u0000\u0000\u0205\u0206\u0001\u0000\u0000\u0000\u0206\u0207"+
-    "\u0005\u001d\u0000\u0000\u0207_\u0001\u0000\u0000\u0000\u0208\u020a\u0007"+
-    "\u0000\u0000\u0000\u0209\u0208\u0001\u0000\u0000\u0000\u0209\u020a\u0001"+
-    "\u0000\u0000\u0000\u020a\u020b\u0001\u0000\u0000\u0000\u020b\u020c\u0005"+
-    "\u001c\u0000\u0000\u020ca\u0001\u0000\u0000\u0000\u020d\u020e\u0005\u001b"+
-    "\u0000\u0000\u020ec\u0001\u0000\u0000\u0000\u020f\u0210\u0007\u0007\u0000"+
-    "\u0000\u0210e\u0001\u0000\u0000\u0000\u0211\u0212\u0005\u0005\u0000\u0000"+
-    "\u0212\u0213\u0003h4\u0000\u0213g\u0001\u0000\u0000\u0000\u0214\u0215"+
-    "\u0005B\u0000\u0000\u0215\u0216\u0003\u0002\u0001\u0000\u0216\u0217\u0005"+
-    "C\u0000\u0000\u0217i\u0001\u0000\u0000\u0000\u0218\u0219\u0005\u000e\u0000"+
-    "\u0000\u0219\u021a\u0005e\u0000\u0000\u021ak\u0001\u0000\u0000\u0000\u021b"+
-    "\u021c\u0005\n\u0000\u0000\u021c\u021d\u0005i\u0000\u0000\u021dm\u0001"+
-    "\u0000\u0000\u0000\u021e\u021f\u0005\u0003\u0000\u0000\u021f\u0222\u0005"+
-    "[\u0000\u0000\u0220\u0221\u0005Y\u0000\u0000\u0221\u0223\u00036\u001b"+
-    "\u0000\u0222\u0220\u0001\u0000\u0000\u0000\u0222\u0223\u0001\u0000\u0000"+
-    "\u0000\u0223\u022d\u0001\u0000\u0000\u0000\u0224\u0225\u0005Z\u0000\u0000"+
-    "\u0225\u022a\u0003p8\u0000\u0226\u0227\u0005#\u0000\u0000\u0227\u0229"+
-    "\u0003p8\u0000\u0228\u0226\u0001\u0000\u0000\u0000\u0229\u022c\u0001\u0000"+
-    "\u0000\u0000\u022a\u0228\u0001\u0000\u0000\u0000\u022a\u022b\u0001\u0000"+
-    "\u0000\u0000\u022b\u022e\u0001\u0000\u0000\u0000\u022c\u022a\u0001\u0000"+
-    "\u0000\u0000\u022d\u0224\u0001\u0000\u0000\u0000\u022d\u022e\u0001\u0000"+
-    "\u0000\u0000\u022eo\u0001\u0000\u0000\u0000\u022f\u0230\u00036\u001b\u0000"+
-    "\u0230\u0231\u0005!\u0000\u0000\u0231\u0233\u0001\u0000\u0000\u0000\u0232"+
-    "\u022f\u0001\u0000\u0000\u0000\u0232\u0233\u0001\u0000\u0000\u0000\u0233"+
-    "\u0234\u0001\u0000\u0000\u0000\u0234\u0235\u00036\u001b\u0000\u0235q\u0001"+
-    "\u0000\u0000\u0000\u0236\u0237\u0005\u0013\u0000\u0000\u0237\u0238\u0003"+
-    "\"\u0011\u0000\u0238\u0239\u0005Y\u0000\u0000\u0239\u023a\u00038\u001c"+
-    "\u0000\u023as\u0001\u0000\u0000\u0000\u023b\u023c\u0005\u0012\u0000\u0000"+
-    "\u023c\u023f\u0003\u001c\u000e\u0000\u023d\u023e\u0005\u001e\u0000\u0000"+
-    "\u023e\u0240\u0003\u001c\u000e\u0000\u023f\u023d\u0001\u0000\u0000\u0000"+
-    "\u023f\u0240\u0001\u0000\u0000\u0000\u0240u\u0001\u0000\u0000\u00006\u0081"+
-    "\u008b\u009d\u00a9\u00b2\u00ba\u00c0\u00c8\u00ca\u00cf\u00d6\u00db\u00e6"+
-    "\u00ec\u00f4\u00f6\u0101\u0108\u0113\u0116\u0124\u012c\u0134\u0138\u013f"+
-    "\u0147\u014f\u015c\u0160\u0164\u016b\u016f\u0176\u017e\u0186\u019c\u01a7"+
-    "\u01b2\u01b7\u01bb\u01c6\u01cb\u01cf\u01dd\u01e8\u01f6\u0201\u0204\u0209"+
-    "\u0222\u022a\u022d\u0232\u023f";
+    "\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0005\u001f\u0198\b\u001f"+
+    "\n\u001f\f\u001f\u019b\t\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001"+
+    "\u001f\u0001\u001f\u0001\u001f\u0005\u001f\u01a3\b\u001f\n\u001f\f\u001f"+
+    "\u01a6\t\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f"+
+    "\u0001\u001f\u0005\u001f\u01ae\b\u001f\n\u001f\f\u001f\u01b1\t\u001f\u0001"+
+    "\u001f\u0001\u001f\u0003\u001f\u01b5\b\u001f\u0001 \u0001 \u0003 \u01b9"+
+    "\b \u0001!\u0001!\u0001!\u0001\"\u0001\"\u0001\"\u0001\"\u0005\"\u01c2"+
+    "\b\"\n\"\f\"\u01c5\t\"\u0001#\u0001#\u0003#\u01c9\b#\u0001#\u0001#\u0003"+
+    "#\u01cd\b#\u0001$\u0001$\u0001$\u0001%\u0001%\u0001%\u0001&\u0001&\u0001"+
+    "&\u0001&\u0005&\u01d9\b&\n&\f&\u01dc\t&\u0001\'\u0001\'\u0001\'\u0001"+
+    "\'\u0001(\u0001(\u0001(\u0001(\u0003(\u01e6\b(\u0001)\u0001)\u0001)\u0001"+
+    ")\u0001*\u0001*\u0001*\u0001+\u0001+\u0001+\u0005+\u01f2\b+\n+\f+\u01f5"+
+    "\t+\u0001,\u0001,\u0001,\u0001,\u0001-\u0001-\u0001.\u0001.\u0003.\u01ff"+
+    "\b.\u0001/\u0003/\u0202\b/\u0001/\u0001/\u00010\u00030\u0207\b0\u0001"+
+    "0\u00010\u00011\u00011\u00012\u00012\u00013\u00013\u00013\u00014\u0001"+
+    "4\u00014\u00014\u00015\u00015\u00015\u00016\u00016\u00016\u00016\u0003"+
+    "6\u021d\b6\u00016\u00016\u00016\u00016\u00056\u0223\b6\n6\f6\u0226\t6"+
+    "\u00036\u0228\b6\u00017\u00017\u00017\u00037\u022d\b7\u00017\u00017\u0001"+
+    "8\u00018\u00018\u00018\u00018\u00019\u00019\u00019\u00019\u00039\u023a"+
+    "\b9\u00019\u0000\u0004\u0002\n\u0012\u0014:\u0000\u0002\u0004\u0006\b"+
+    "\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a\u001c\u001e \"$&(*,.02"+
+    "468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnpr\u0000\b\u0001\u0000;<\u0001\u0000=?"+
+    "\u0002\u0000\u001a\u001aLL\u0001\u0000CD\u0002\u0000\u001f\u001f##\u0002"+
+    "\u0000&&))\u0002\u0000%%33\u0002\u0000446:\u0255\u0000t\u0001\u0000\u0000"+
+    "\u0000\u0002w\u0001\u0000\u0000\u0000\u0004\u0088\u0001\u0000\u0000\u0000"+
+    "\u0006\u009a\u0001\u0000\u0000\u0000\b\u009c\u0001\u0000\u0000\u0000\n"+
+    "\u00bd\u0001\u0000\u0000\u0000\f\u00d8\u0001\u0000\u0000\u0000\u000e\u00da"+
+    "\u0001\u0000\u0000\u0000\u0010\u00e3\u0001\u0000\u0000\u0000\u0012\u00e9"+
+    "\u0001\u0000\u0000\u0000\u0014\u00fe\u0001\u0000\u0000\u0000\u0016\u0108"+
+    "\u0001\u0000\u0000\u0000\u0018\u0117\u0001\u0000\u0000\u0000\u001a\u0119"+
+    "\u0001\u0000\u0000\u0000\u001c\u011c\u0001\u0000\u0000\u0000\u001e\u0129"+
+    "\u0001\u0000\u0000\u0000 \u012b\u0001\u0000\u0000\u0000\"\u013c\u0001"+
+    "\u0000\u0000\u0000$\u013e\u0001\u0000\u0000\u0000&\u0140\u0001\u0000\u0000"+
+    "\u0000(\u0144\u0001\u0000\u0000\u0000*\u0146\u0001\u0000\u0000\u0000,"+
+    "\u014f\u0001\u0000\u0000\u0000.\u0153\u0001\u0000\u0000\u00000\u0163\u0001"+
+    "\u0000\u0000\u00002\u0166\u0001\u0000\u0000\u00004\u016e\u0001\u0000\u0000"+
+    "\u00006\u0176\u0001\u0000\u0000\u00008\u017e\u0001\u0000\u0000\u0000:"+
+    "\u0186\u0001\u0000\u0000\u0000<\u0188\u0001\u0000\u0000\u0000>\u01b4\u0001"+
+    "\u0000\u0000\u0000@\u01b8\u0001\u0000\u0000\u0000B\u01ba\u0001\u0000\u0000"+
+    "\u0000D\u01bd\u0001\u0000\u0000\u0000F\u01c6\u0001\u0000\u0000\u0000H"+
+    "\u01ce\u0001\u0000\u0000\u0000J\u01d1\u0001\u0000\u0000\u0000L\u01d4\u0001"+
+    "\u0000\u0000\u0000N\u01dd\u0001\u0000\u0000\u0000P\u01e1\u0001\u0000\u0000"+
+    "\u0000R\u01e7\u0001\u0000\u0000\u0000T\u01eb\u0001\u0000\u0000\u0000V"+
+    "\u01ee\u0001\u0000\u0000\u0000X\u01f6\u0001\u0000\u0000\u0000Z\u01fa\u0001"+
+    "\u0000\u0000\u0000\\\u01fe\u0001\u0000\u0000\u0000^\u0201\u0001\u0000"+
+    "\u0000\u0000`\u0206\u0001\u0000\u0000\u0000b\u020a\u0001\u0000\u0000\u0000"+
+    "d\u020c\u0001\u0000\u0000\u0000f\u020e\u0001\u0000\u0000\u0000h\u0211"+
+    "\u0001\u0000\u0000\u0000j\u0215\u0001\u0000\u0000\u0000l\u0218\u0001\u0000"+
+    "\u0000\u0000n\u022c\u0001\u0000\u0000\u0000p\u0230\u0001\u0000\u0000\u0000"+
+    "r\u0235\u0001\u0000\u0000\u0000tu\u0003\u0002\u0001\u0000uv\u0005\u0000"+
+    "\u0000\u0001v\u0001\u0001\u0000\u0000\u0000wx\u0006\u0001\uffff\uffff"+
+    "\u0000xy\u0003\u0004\u0002\u0000y\u007f\u0001\u0000\u0000\u0000z{\n\u0001"+
+    "\u0000\u0000{|\u0005\u0019\u0000\u0000|~\u0003\u0006\u0003\u0000}z\u0001"+
+    "\u0000\u0000\u0000~\u0081\u0001\u0000\u0000\u0000\u007f}\u0001\u0000\u0000"+
+    "\u0000\u007f\u0080\u0001\u0000\u0000\u0000\u0080\u0003\u0001\u0000\u0000"+
+    "\u0000\u0081\u007f\u0001\u0000\u0000\u0000\u0082\u0089\u0003f3\u0000\u0083"+
+    "\u0089\u0003 \u0010\u0000\u0084\u0089\u0003\u001a\r\u0000\u0085\u0089"+
+    "\u0003j5\u0000\u0086\u0087\u0004\u0002\u0001\u0000\u0087\u0089\u0003."+
+    "\u0017\u0000\u0088\u0082\u0001\u0000\u0000\u0000\u0088\u0083\u0001\u0000"+
+    "\u0000\u0000\u0088\u0084\u0001\u0000\u0000\u0000\u0088\u0085\u0001\u0000"+
+    "\u0000\u0000\u0088\u0086\u0001\u0000\u0000\u0000\u0089\u0005\u0001\u0000"+
+    "\u0000\u0000\u008a\u009b\u00030\u0018\u0000\u008b\u009b\u0003\b\u0004"+
+    "\u0000\u008c\u009b\u0003H$\u0000\u008d\u009b\u0003B!\u0000\u008e\u009b"+
+    "\u00032\u0019\u0000\u008f\u009b\u0003D\"\u0000\u0090\u009b\u0003J%\u0000"+
+    "\u0091\u009b\u0003L&\u0000\u0092\u009b\u0003P(\u0000\u0093\u009b\u0003"+
+    "R)\u0000\u0094\u009b\u0003l6\u0000\u0095\u009b\u0003T*\u0000\u0096\u0097"+
+    "\u0004\u0003\u0002\u0000\u0097\u009b\u0003r9\u0000\u0098\u0099\u0004\u0003"+
+    "\u0003\u0000\u0099\u009b\u0003p8\u0000\u009a\u008a\u0001\u0000\u0000\u0000"+
+    "\u009a\u008b\u0001\u0000\u0000\u0000\u009a\u008c\u0001\u0000\u0000\u0000"+
+    "\u009a\u008d\u0001\u0000\u0000\u0000\u009a\u008e\u0001\u0000\u0000\u0000"+
+    "\u009a\u008f\u0001\u0000\u0000\u0000\u009a\u0090\u0001\u0000\u0000\u0000"+
+    "\u009a\u0091\u0001\u0000\u0000\u0000\u009a\u0092\u0001\u0000\u0000\u0000"+
+    "\u009a\u0093\u0001\u0000\u0000\u0000\u009a\u0094\u0001\u0000\u0000\u0000"+
+    "\u009a\u0095\u0001\u0000\u0000\u0000\u009a\u0096\u0001\u0000\u0000\u0000"+
+    "\u009a\u0098\u0001\u0000\u0000\u0000\u009b\u0007\u0001\u0000\u0000\u0000"+
+    "\u009c\u009d\u0005\u0010\u0000\u0000\u009d\u009e\u0003\n\u0005\u0000\u009e"+
+    "\t\u0001\u0000\u0000\u0000\u009f\u00a0\u0006\u0005\uffff\uffff\u0000\u00a0"+
+    "\u00a1\u0005,\u0000\u0000\u00a1\u00be\u0003\n\u0005\b\u00a2\u00be\u0003"+
+    "\u0010\b\u0000\u00a3\u00be\u0003\f\u0006\u0000\u00a4\u00a6\u0003\u0010"+
+    "\b\u0000\u00a5\u00a7\u0005,\u0000\u0000\u00a6\u00a5\u0001\u0000\u0000"+
+    "\u0000\u00a6\u00a7\u0001\u0000\u0000\u0000\u00a7\u00a8\u0001\u0000\u0000"+
+    "\u0000\u00a8\u00a9\u0005\'\u0000\u0000\u00a9\u00aa\u0005+\u0000\u0000"+
+    "\u00aa\u00af\u0003\u0010\b\u0000\u00ab\u00ac\u0005\"\u0000\u0000\u00ac"+
+    "\u00ae\u0003\u0010\b\u0000\u00ad\u00ab\u0001\u0000\u0000\u0000\u00ae\u00b1"+
+    "\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0"+
+    "\u0001\u0000\u0000\u0000\u00b0\u00b2\u0001\u0000\u0000\u0000\u00b1\u00af"+
+    "\u0001\u0000\u0000\u0000\u00b2\u00b3\u00052\u0000\u0000\u00b3\u00be\u0001"+
+    "\u0000\u0000\u0000\u00b4\u00b5\u0003\u0010\b\u0000\u00b5\u00b7\u0005("+
+    "\u0000\u0000\u00b6\u00b8\u0005,\u0000\u0000\u00b7\u00b6\u0001\u0000\u0000"+
+    "\u0000\u00b7\u00b8\u0001\u0000\u0000\u0000\u00b8\u00b9\u0001\u0000\u0000"+
+    "\u0000\u00b9\u00ba\u0005-\u0000\u0000\u00ba\u00be\u0001\u0000\u0000\u0000"+
+    "\u00bb\u00bc\u0004\u0005\u0004\u0000\u00bc\u00be\u0003\u000e\u0007\u0000"+
+    "\u00bd\u009f\u0001\u0000\u0000\u0000\u00bd\u00a2\u0001\u0000\u0000\u0000"+
+    "\u00bd\u00a3\u0001\u0000\u0000\u0000\u00bd\u00a4\u0001\u0000\u0000\u0000"+
+    "\u00bd\u00b4\u0001\u0000\u0000\u0000\u00bd\u00bb\u0001\u0000\u0000\u0000"+
+    "\u00be\u00c7\u0001\u0000\u0000\u0000\u00bf\u00c0\n\u0005\u0000\u0000\u00c0"+
+    "\u00c1\u0005\u001e\u0000\u0000\u00c1\u00c6\u0003\n\u0005\u0006\u00c2\u00c3"+
+    "\n\u0004\u0000\u0000\u00c3\u00c4\u0005/\u0000\u0000\u00c4\u00c6\u0003"+
+    "\n\u0005\u0005\u00c5\u00bf\u0001\u0000\u0000\u0000\u00c5\u00c2\u0001\u0000"+
+    "\u0000\u0000\u00c6\u00c9\u0001\u0000\u0000\u0000\u00c7\u00c5\u0001\u0000"+
+    "\u0000\u0000\u00c7\u00c8\u0001\u0000\u0000\u0000\u00c8\u000b\u0001\u0000"+
+    "\u0000\u0000\u00c9\u00c7\u0001\u0000\u0000\u0000\u00ca\u00cc\u0003\u0010"+
+    "\b\u0000\u00cb\u00cd\u0005,\u0000\u0000\u00cc\u00cb\u0001\u0000\u0000"+
+    "\u0000\u00cc\u00cd\u0001\u0000\u0000\u0000\u00cd\u00ce\u0001\u0000\u0000"+
+    "\u0000\u00ce\u00cf\u0005*\u0000\u0000\u00cf\u00d0\u0003b1\u0000\u00d0"+
+    "\u00d9\u0001\u0000\u0000\u0000\u00d1\u00d3\u0003\u0010\b\u0000\u00d2\u00d4"+
+    "\u0005,\u0000\u0000\u00d3\u00d2\u0001\u0000\u0000\u0000\u00d3\u00d4\u0001"+
+    "\u0000\u0000\u0000\u00d4\u00d5\u0001\u0000\u0000\u0000\u00d5\u00d6\u0005"+
+    "1\u0000\u0000\u00d6\u00d7\u0003b1\u0000\u00d7\u00d9\u0001\u0000\u0000"+
+    "\u0000\u00d8\u00ca\u0001\u0000\u0000\u0000\u00d8\u00d1\u0001\u0000\u0000"+
+    "\u0000\u00d9\r\u0001\u0000\u0000\u0000\u00da\u00db\u0003\u0010\b\u0000"+
+    "\u00db\u00dc\u0005\u0013\u0000\u0000\u00dc\u00dd\u0003b1\u0000\u00dd\u000f"+
+    "\u0001\u0000\u0000\u0000\u00de\u00e4\u0003\u0012\t\u0000\u00df\u00e0\u0003"+
+    "\u0012\t\u0000\u00e0\u00e1\u0003d2\u0000\u00e1\u00e2\u0003\u0012\t\u0000"+
+    "\u00e2\u00e4\u0001\u0000\u0000\u0000\u00e3\u00de\u0001\u0000\u0000\u0000"+
+    "\u00e3\u00df\u0001\u0000\u0000\u0000\u00e4\u0011\u0001\u0000\u0000\u0000"+
+    "\u00e5\u00e6\u0006\t\uffff\uffff\u0000\u00e6\u00ea\u0003\u0014\n\u0000"+
+    "\u00e7\u00e8\u0007\u0000\u0000\u0000\u00e8\u00ea\u0003\u0012\t\u0003\u00e9"+
+    "\u00e5\u0001\u0000\u0000\u0000\u00e9\u00e7\u0001\u0000\u0000\u0000\u00ea"+
+    "\u00f3\u0001\u0000\u0000\u0000\u00eb\u00ec\n\u0002\u0000\u0000\u00ec\u00ed"+
+    "\u0007\u0001\u0000\u0000\u00ed\u00f2\u0003\u0012\t\u0003\u00ee\u00ef\n"+
+    "\u0001\u0000\u0000\u00ef\u00f0\u0007\u0000\u0000\u0000\u00f0\u00f2\u0003"+
+    "\u0012\t\u0002\u00f1\u00eb\u0001\u0000\u0000\u0000\u00f1\u00ee\u0001\u0000"+
+    "\u0000\u0000\u00f2\u00f5\u0001\u0000\u0000\u0000\u00f3\u00f1\u0001\u0000"+
+    "\u0000\u0000\u00f3\u00f4\u0001\u0000\u0000\u0000\u00f4\u0013\u0001\u0000"+
+    "\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f6\u00f7\u0006\n\uffff"+
+    "\uffff\u0000\u00f7\u00ff\u0003>\u001f\u0000\u00f8\u00ff\u00034\u001a\u0000"+
+    "\u00f9\u00ff\u0003\u0016\u000b\u0000\u00fa\u00fb\u0005+\u0000\u0000\u00fb"+
+    "\u00fc\u0003\n\u0005\u0000\u00fc\u00fd\u00052\u0000\u0000\u00fd\u00ff"+
+    "\u0001\u0000\u0000\u0000\u00fe\u00f6\u0001\u0000\u0000\u0000\u00fe\u00f8"+
+    "\u0001\u0000\u0000\u0000\u00fe\u00f9\u0001\u0000\u0000\u0000\u00fe\u00fa"+
+    "\u0001\u0000\u0000\u0000\u00ff\u0105\u0001\u0000\u0000\u0000\u0100\u0101"+
+    "\n\u0001\u0000\u0000\u0101\u0102\u0005!\u0000\u0000\u0102\u0104\u0003"+
+    "\u0018\f\u0000\u0103\u0100\u0001\u0000\u0000\u0000\u0104\u0107\u0001\u0000"+
+    "\u0000\u0000\u0105\u0103\u0001\u0000\u0000\u0000\u0105\u0106\u0001\u0000"+
+    "\u0000\u0000\u0106\u0015\u0001\u0000\u0000\u0000\u0107\u0105\u0001\u0000"+
+    "\u0000\u0000\u0108\u0109\u0003:\u001d\u0000\u0109\u0113\u0005+\u0000\u0000"+
+    "\u010a\u0114\u0005=\u0000\u0000\u010b\u0110\u0003\n\u0005\u0000\u010c"+
+    "\u010d\u0005\"\u0000\u0000\u010d\u010f\u0003\n\u0005\u0000\u010e\u010c"+
+    "\u0001\u0000\u0000\u0000\u010f\u0112\u0001\u0000\u0000\u0000\u0110\u010e"+
+    "\u0001\u0000\u0000\u0000\u0110\u0111\u0001\u0000\u0000\u0000\u0111\u0114"+
+    "\u0001\u0000\u0000\u0000\u0112\u0110\u0001\u0000\u0000\u0000\u0113\u010a"+
+    "\u0001\u0000\u0000\u0000\u0113\u010b\u0001\u0000\u0000\u0000\u0113\u0114"+
+    "\u0001\u0000\u0000\u0000\u0114\u0115\u0001\u0000\u0000\u0000\u0115\u0116"+
+    "\u00052\u0000\u0000\u0116\u0017\u0001\u0000\u0000\u0000\u0117\u0118\u0003"+
+    ":\u001d\u0000\u0118\u0019\u0001\u0000\u0000\u0000\u0119\u011a\u0005\f"+
+    "\u0000\u0000\u011a\u011b\u0003\u001c\u000e\u0000\u011b\u001b\u0001\u0000"+
+    "\u0000\u0000\u011c\u0121\u0003\u001e\u000f\u0000\u011d\u011e\u0005\"\u0000"+
+    "\u0000\u011e\u0120\u0003\u001e\u000f\u0000\u011f\u011d\u0001\u0000\u0000"+
+    "\u0000\u0120\u0123\u0001\u0000\u0000\u0000\u0121\u011f\u0001\u0000\u0000"+
+    "\u0000\u0121\u0122\u0001\u0000\u0000\u0000\u0122\u001d\u0001\u0000\u0000"+
+    "\u0000\u0123\u0121\u0001\u0000\u0000\u0000\u0124\u012a\u0003\n\u0005\u0000"+
+    "\u0125\u0126\u00034\u001a\u0000\u0126\u0127\u0005 \u0000\u0000\u0127\u0128"+
+    "\u0003\n\u0005\u0000\u0128\u012a\u0001\u0000\u0000\u0000\u0129\u0124\u0001"+
+    "\u0000\u0000\u0000\u0129\u0125\u0001\u0000\u0000\u0000\u012a\u001f\u0001"+
+    "\u0000\u0000\u0000\u012b\u012c\u0005\u0006\u0000\u0000\u012c\u0131\u0003"+
+    "\"\u0011\u0000\u012d\u012e\u0005\"\u0000\u0000\u012e\u0130\u0003\"\u0011"+
+    "\u0000\u012f\u012d\u0001\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000"+
+    "\u0000\u0131\u012f\u0001\u0000\u0000\u0000\u0131\u0132\u0001\u0000\u0000"+
+    "\u0000\u0132\u0135\u0001\u0000\u0000\u0000\u0133\u0131\u0001\u0000\u0000"+
+    "\u0000\u0134\u0136\u0003(\u0014\u0000\u0135\u0134\u0001\u0000\u0000\u0000"+
+    "\u0135\u0136\u0001\u0000\u0000\u0000\u0136!\u0001\u0000\u0000\u0000\u0137"+
+    "\u0138\u0003$\u0012\u0000\u0138\u0139\u0005h\u0000\u0000\u0139\u013a\u0003"+
+    "&\u0013\u0000\u013a\u013d\u0001\u0000\u0000\u0000\u013b\u013d\u0003&\u0013"+
+    "\u0000\u013c\u0137\u0001\u0000\u0000\u0000\u013c\u013b\u0001\u0000\u0000"+
+    "\u0000\u013d#\u0001\u0000\u0000\u0000\u013e\u013f\u0005L\u0000\u0000\u013f"+
+    "%\u0001\u0000\u0000\u0000\u0140\u0141\u0007\u0002\u0000\u0000\u0141\'"+
+    "\u0001\u0000\u0000\u0000\u0142\u0145\u0003*\u0015\u0000\u0143\u0145\u0003"+
+    ",\u0016\u0000\u0144\u0142\u0001\u0000\u0000\u0000\u0144\u0143\u0001\u0000"+
+    "\u0000\u0000\u0145)\u0001\u0000\u0000\u0000\u0146\u0147\u0005K\u0000\u0000"+
+    "\u0147\u014c\u0005L\u0000\u0000\u0148\u0149\u0005\"\u0000\u0000\u0149"+
+    "\u014b\u0005L\u0000\u0000\u014a\u0148\u0001\u0000\u0000\u0000\u014b\u014e"+
+    "\u0001\u0000\u0000\u0000\u014c\u014a\u0001\u0000\u0000\u0000\u014c\u014d"+
+    "\u0001\u0000\u0000\u0000\u014d+\u0001\u0000\u0000\u0000\u014e\u014c\u0001"+
+    "\u0000\u0000\u0000\u014f\u0150\u0005A\u0000\u0000\u0150\u0151\u0003*\u0015"+
+    "\u0000\u0151\u0152\u0005B\u0000\u0000\u0152-\u0001\u0000\u0000\u0000\u0153"+
+    "\u0154\u0005\u0014\u0000\u0000\u0154\u0159\u0003\"\u0011\u0000\u0155\u0156"+
+    "\u0005\"\u0000\u0000\u0156\u0158\u0003\"\u0011\u0000\u0157\u0155\u0001"+
+    "\u0000\u0000\u0000\u0158\u015b\u0001\u0000\u0000\u0000\u0159\u0157\u0001"+
+    "\u0000\u0000\u0000\u0159\u015a\u0001\u0000\u0000\u0000\u015a\u015d\u0001"+
+    "\u0000\u0000\u0000\u015b\u0159\u0001\u0000\u0000\u0000\u015c\u015e\u0003"+
+    "\u001c\u000e\u0000\u015d\u015c\u0001\u0000\u0000\u0000\u015d\u015e\u0001"+
+    "\u0000\u0000\u0000\u015e\u0161\u0001\u0000\u0000\u0000\u015f\u0160\u0005"+
+    "\u001d\u0000\u0000\u0160\u0162\u0003\u001c\u000e\u0000\u0161\u015f\u0001"+
+    "\u0000\u0000\u0000\u0161\u0162\u0001\u0000\u0000\u0000\u0162/\u0001\u0000"+
+    "\u0000\u0000\u0163\u0164\u0005\u0004\u0000\u0000\u0164\u0165\u0003\u001c"+
+    "\u000e\u0000\u01651\u0001\u0000\u0000\u0000\u0166\u0168\u0005\u000f\u0000"+
+    "\u0000\u0167\u0169\u0003\u001c\u000e\u0000\u0168\u0167\u0001\u0000\u0000"+
+    "\u0000\u0168\u0169\u0001\u0000\u0000\u0000\u0169\u016c\u0001\u0000\u0000"+
+    "\u0000\u016a\u016b\u0005\u001d\u0000\u0000\u016b\u016d\u0003\u001c\u000e"+
+    "\u0000\u016c\u016a\u0001\u0000\u0000\u0000\u016c\u016d\u0001\u0000\u0000"+
+    "\u0000\u016d3\u0001\u0000\u0000\u0000\u016e\u0173\u0003:\u001d\u0000\u016f"+
+    "\u0170\u0005$\u0000\u0000\u0170\u0172\u0003:\u001d\u0000\u0171\u016f\u0001"+
+    "\u0000\u0000\u0000\u0172\u0175\u0001\u0000\u0000\u0000\u0173\u0171\u0001"+
+    "\u0000\u0000\u0000\u0173\u0174\u0001\u0000\u0000\u0000\u01745\u0001\u0000"+
+    "\u0000\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u017b\u0003<\u001e"+
+    "\u0000\u0177\u0178\u0005$\u0000\u0000\u0178\u017a\u0003<\u001e\u0000\u0179"+
+    "\u0177\u0001\u0000\u0000\u0000\u017a\u017d\u0001\u0000\u0000\u0000\u017b"+
+    "\u0179\u0001\u0000\u0000\u0000\u017b\u017c\u0001\u0000\u0000\u0000\u017c"+
+    "7\u0001\u0000\u0000\u0000\u017d\u017b\u0001\u0000\u0000\u0000\u017e\u0183"+
+    "\u00036\u001b\u0000\u017f\u0180\u0005\"\u0000\u0000\u0180\u0182\u0003"+
+    "6\u001b\u0000\u0181\u017f\u0001\u0000\u0000\u0000\u0182\u0185\u0001\u0000"+
+    "\u0000\u0000\u0183\u0181\u0001\u0000\u0000\u0000\u0183\u0184\u0001\u0000"+
+    "\u0000\u0000\u01849\u0001\u0000\u0000\u0000\u0185\u0183\u0001\u0000\u0000"+
+    "\u0000\u0186\u0187\u0007\u0003\u0000\u0000\u0187;\u0001\u0000\u0000\u0000"+
+    "\u0188\u0189\u0005P\u0000\u0000\u0189=\u0001\u0000\u0000\u0000\u018a\u01b5"+
+    "\u0005-\u0000\u0000\u018b\u018c\u0003`0\u0000\u018c\u018d\u0005C\u0000"+
+    "\u0000\u018d\u01b5\u0001\u0000\u0000\u0000\u018e\u01b5\u0003^/\u0000\u018f"+
+    "\u01b5\u0003`0\u0000\u0190\u01b5\u0003Z-\u0000\u0191\u01b5\u0003@ \u0000"+
+    "\u0192\u01b5\u0003b1\u0000\u0193\u0194\u0005A\u0000\u0000\u0194\u0199"+
+    "\u0003\\.\u0000\u0195\u0196\u0005\"\u0000\u0000\u0196\u0198\u0003\\.\u0000"+
+    "\u0197\u0195\u0001\u0000\u0000\u0000\u0198\u019b\u0001\u0000\u0000\u0000"+
+    "\u0199\u0197\u0001\u0000\u0000\u0000\u0199\u019a\u0001\u0000\u0000\u0000"+
+    "\u019a\u019c\u0001\u0000\u0000\u0000\u019b\u0199\u0001\u0000\u0000\u0000"+
+    "\u019c\u019d\u0005B\u0000\u0000\u019d\u01b5\u0001\u0000\u0000\u0000\u019e"+
+    "\u019f\u0005A\u0000\u0000\u019f\u01a4\u0003Z-\u0000\u01a0\u01a1\u0005"+
+    "\"\u0000\u0000\u01a1\u01a3\u0003Z-\u0000\u01a2\u01a0\u0001\u0000\u0000"+
+    "\u0000\u01a3\u01a6\u0001\u0000\u0000\u0000\u01a4\u01a2\u0001\u0000\u0000"+
+    "\u0000\u01a4\u01a5\u0001\u0000\u0000\u0000\u01a5\u01a7\u0001\u0000\u0000"+
+    "\u0000\u01a6\u01a4\u0001\u0000\u0000\u0000\u01a7\u01a8\u0005B\u0000\u0000"+
+    "\u01a8\u01b5\u0001\u0000\u0000\u0000\u01a9\u01aa\u0005A\u0000\u0000\u01aa"+
+    "\u01af\u0003b1\u0000\u01ab\u01ac\u0005\"\u0000\u0000\u01ac\u01ae\u0003"+
+    "b1\u0000\u01ad\u01ab\u0001\u0000\u0000\u0000\u01ae\u01b1\u0001\u0000\u0000"+
+    "\u0000\u01af\u01ad\u0001\u0000\u0000\u0000\u01af\u01b0\u0001\u0000\u0000"+
+    "\u0000\u01b0\u01b2\u0001\u0000\u0000\u0000\u01b1\u01af\u0001\u0000\u0000"+
+    "\u0000\u01b2\u01b3\u0005B\u0000\u0000\u01b3\u01b5\u0001\u0000\u0000\u0000"+
+    "\u01b4\u018a\u0001\u0000\u0000\u0000\u01b4\u018b\u0001\u0000\u0000\u0000"+
+    "\u01b4\u018e\u0001\u0000\u0000\u0000\u01b4\u018f\u0001\u0000\u0000\u0000"+
+    "\u01b4\u0190\u0001\u0000\u0000\u0000\u01b4\u0191\u0001\u0000\u0000\u0000"+
+    "\u01b4\u0192\u0001\u0000\u0000\u0000\u01b4\u0193\u0001\u0000\u0000\u0000"+
+    "\u01b4\u019e\u0001\u0000\u0000\u0000\u01b4\u01a9\u0001\u0000\u0000\u0000"+
+    "\u01b5?\u0001\u0000\u0000\u0000\u01b6\u01b9\u00050\u0000\u0000\u01b7\u01b9"+
+    "\u0005@\u0000\u0000\u01b8\u01b6\u0001\u0000\u0000\u0000\u01b8\u01b7\u0001"+
+    "\u0000\u0000\u0000\u01b9A\u0001\u0000\u0000\u0000\u01ba\u01bb\u0005\t"+
+    "\u0000\u0000\u01bb\u01bc\u0005\u001b\u0000\u0000\u01bcC\u0001\u0000\u0000"+
+    "\u0000\u01bd\u01be\u0005\u000e\u0000\u0000\u01be\u01c3\u0003F#\u0000\u01bf"+
+    "\u01c0\u0005\"\u0000\u0000\u01c0\u01c2\u0003F#\u0000\u01c1\u01bf\u0001"+
+    "\u0000\u0000\u0000\u01c2\u01c5\u0001\u0000\u0000\u0000\u01c3\u01c1\u0001"+
+    "\u0000\u0000\u0000\u01c3\u01c4\u0001\u0000\u0000\u0000\u01c4E\u0001\u0000"+
+    "\u0000\u0000\u01c5\u01c3\u0001\u0000\u0000\u0000\u01c6\u01c8\u0003\n\u0005"+
+    "\u0000\u01c7\u01c9\u0007\u0004\u0000\u0000\u01c8\u01c7\u0001\u0000\u0000"+
+    "\u0000\u01c8\u01c9\u0001\u0000\u0000\u0000\u01c9\u01cc\u0001\u0000\u0000"+
+    "\u0000\u01ca\u01cb\u0005.\u0000\u0000\u01cb\u01cd\u0007\u0005\u0000\u0000"+
+    "\u01cc\u01ca\u0001\u0000\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000"+
+    "\u01cdG\u0001\u0000\u0000\u0000\u01ce\u01cf\u0005\b\u0000\u0000\u01cf"+
+    "\u01d0\u00038\u001c\u0000\u01d0I\u0001\u0000\u0000\u0000\u01d1\u01d2\u0005"+
+    "\u0002\u0000\u0000\u01d2\u01d3\u00038\u001c\u0000\u01d3K\u0001\u0000\u0000"+
+    "\u0000\u01d4\u01d5\u0005\u000b\u0000\u0000\u01d5\u01da\u0003N\'\u0000"+
+    "\u01d6\u01d7\u0005\"\u0000\u0000\u01d7\u01d9\u0003N\'\u0000\u01d8\u01d6"+
+    "\u0001\u0000\u0000\u0000\u01d9\u01dc\u0001\u0000\u0000\u0000\u01da\u01d8"+
+    "\u0001\u0000\u0000\u0000\u01da\u01db\u0001\u0000\u0000\u0000\u01dbM\u0001"+
+    "\u0000\u0000\u0000\u01dc\u01da\u0001\u0000\u0000\u0000\u01dd\u01de\u0003"+
+    "6\u001b\u0000\u01de\u01df\u0005T\u0000\u0000\u01df\u01e0\u00036\u001b"+
+    "\u0000\u01e0O\u0001\u0000\u0000\u0000\u01e1\u01e2\u0005\u0001\u0000\u0000"+
+    "\u01e2\u01e3\u0003\u0014\n\u0000\u01e3\u01e5\u0003b1\u0000\u01e4\u01e6"+
+    "\u0003V+\u0000\u01e5\u01e4\u0001\u0000\u0000\u0000\u01e5\u01e6\u0001\u0000"+
+    "\u0000\u0000\u01e6Q\u0001\u0000\u0000\u0000\u01e7\u01e8\u0005\u0007\u0000"+
+    "\u0000\u01e8\u01e9\u0003\u0014\n\u0000\u01e9\u01ea\u0003b1\u0000\u01ea"+
+    "S\u0001\u0000\u0000\u0000\u01eb\u01ec\u0005\n\u0000\u0000\u01ec\u01ed"+
+    "\u00034\u001a\u0000\u01edU\u0001\u0000\u0000\u0000\u01ee\u01f3\u0003X"+
+    ",\u0000\u01ef\u01f0\u0005\"\u0000\u0000\u01f0\u01f2\u0003X,\u0000\u01f1"+
+    "\u01ef\u0001\u0000\u0000\u0000\u01f2\u01f5\u0001\u0000\u0000\u0000\u01f3"+
+    "\u01f1\u0001\u0000\u0000\u0000\u01f3\u01f4\u0001\u0000\u0000\u0000\u01f4"+
+    "W\u0001\u0000\u0000\u0000\u01f5\u01f3\u0001\u0000\u0000\u0000\u01f6\u01f7"+
+    "\u0003:\u001d\u0000\u01f7\u01f8\u0005 \u0000\u0000\u01f8\u01f9\u0003>"+
+    "\u001f\u0000\u01f9Y\u0001\u0000\u0000\u0000\u01fa\u01fb\u0007\u0006\u0000"+
+    "\u0000\u01fb[\u0001\u0000\u0000\u0000\u01fc\u01ff\u0003^/\u0000\u01fd"+
+    "\u01ff\u0003`0\u0000\u01fe\u01fc\u0001\u0000\u0000\u0000\u01fe\u01fd\u0001"+
+    "\u0000\u0000\u0000\u01ff]\u0001\u0000\u0000\u0000\u0200\u0202\u0007\u0000"+
+    "\u0000\u0000\u0201\u0200\u0001\u0000\u0000\u0000\u0201\u0202\u0001\u0000"+
+    "\u0000\u0000\u0202\u0203\u0001\u0000\u0000\u0000\u0203\u0204\u0005\u001c"+
+    "\u0000\u0000\u0204_\u0001\u0000\u0000\u0000\u0205\u0207\u0007\u0000\u0000"+
+    "\u0000\u0206\u0205\u0001\u0000\u0000\u0000\u0206\u0207\u0001\u0000\u0000"+
+    "\u0000\u0207\u0208\u0001\u0000\u0000\u0000\u0208\u0209\u0005\u001b\u0000"+
+    "\u0000\u0209a\u0001\u0000\u0000\u0000\u020a\u020b\u0005\u001a\u0000\u0000"+
+    "\u020bc\u0001\u0000\u0000\u0000\u020c\u020d\u0007\u0007\u0000\u0000\u020d"+
+    "e\u0001\u0000\u0000\u0000\u020e\u020f\u0005\u0005\u0000\u0000\u020f\u0210"+
+    "\u0003h4\u0000\u0210g\u0001\u0000\u0000\u0000\u0211\u0212\u0005A\u0000"+
+    "\u0000\u0212\u0213\u0003\u0002\u0001\u0000\u0213\u0214\u0005B\u0000\u0000"+
+    "\u0214i\u0001\u0000\u0000\u0000\u0215\u0216\u0005\r\u0000\u0000\u0216"+
+    "\u0217\u0005d\u0000\u0000\u0217k\u0001\u0000\u0000\u0000\u0218\u0219\u0005"+
+    "\u0003\u0000\u0000\u0219\u021c\u0005Z\u0000\u0000\u021a\u021b\u0005X\u0000"+
+    "\u0000\u021b\u021d\u00036\u001b\u0000\u021c\u021a\u0001\u0000\u0000\u0000"+
+    "\u021c\u021d\u0001\u0000\u0000\u0000\u021d\u0227\u0001\u0000\u0000\u0000"+
+    "\u021e\u021f\u0005Y\u0000\u0000\u021f\u0224\u0003n7\u0000\u0220\u0221"+
+    "\u0005\"\u0000\u0000\u0221\u0223\u0003n7\u0000\u0222\u0220\u0001\u0000"+
+    "\u0000\u0000\u0223\u0226\u0001\u0000\u0000\u0000\u0224\u0222\u0001\u0000"+
+    "\u0000\u0000\u0224\u0225\u0001\u0000\u0000\u0000\u0225\u0228\u0001\u0000"+
+    "\u0000\u0000\u0226\u0224\u0001\u0000\u0000\u0000\u0227\u021e\u0001\u0000"+
+    "\u0000\u0000\u0227\u0228\u0001\u0000\u0000\u0000\u0228m\u0001\u0000\u0000"+
+    "\u0000\u0229\u022a\u00036\u001b\u0000\u022a\u022b\u0005 \u0000\u0000\u022b"+
+    "\u022d\u0001\u0000\u0000\u0000\u022c\u0229\u0001\u0000\u0000\u0000\u022c"+
+    "\u022d\u0001\u0000\u0000\u0000\u022d\u022e\u0001\u0000\u0000\u0000\u022e"+
+    "\u022f\u00036\u001b\u0000\u022fo\u0001\u0000\u0000\u0000\u0230\u0231\u0005"+
+    "\u0012\u0000\u0000\u0231\u0232\u0003\"\u0011\u0000\u0232\u0233\u0005X"+
+    "\u0000\u0000\u0233\u0234\u00038\u001c\u0000\u0234q\u0001\u0000\u0000\u0000"+
+    "\u0235\u0236\u0005\u0011\u0000\u0000\u0236\u0239\u0003\u001c\u000e\u0000"+
+    "\u0237\u0238\u0005\u001d\u0000\u0000\u0238\u023a\u0003\u001c\u000e\u0000"+
+    "\u0239\u0237\u0001\u0000\u0000\u0000\u0239\u023a\u0001\u0000\u0000\u0000"+
+    "\u023as\u0001\u0000\u0000\u00006\u007f\u0088\u009a\u00a6\u00af\u00b7\u00bd"+
+    "\u00c5\u00c7\u00cc\u00d3\u00d8\u00e3\u00e9\u00f1\u00f3\u00fe\u0105\u0110"+
+    "\u0113\u0121\u0129\u0131\u0135\u013c\u0144\u014c\u0159\u015d\u0161\u0168"+
+    "\u016c\u0173\u017b\u0183\u0199\u01a4\u01af\u01b4\u01b8\u01c3\u01c8\u01cc"+
+    "\u01da\u01e5\u01f3\u01fe\u0201\u0206\u021c\u0224\u0227\u022c\u0239";
   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/EsqlBaseParserBaseListener.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseListener.java
index 192b169cc9587..1442aaa99a929 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseListener.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseListener.java
@@ -956,18 +956,6 @@ public class EsqlBaseParserBaseListener implements EsqlBaseParserListener {
    * 

The default implementation does nothing.

*/ @Override public void exitShowInfo(EsqlBaseParser.ShowInfoContext ctx) { } - /** - * {@inheritDoc} - * - *

The default implementation does nothing.

- */ - @Override public void enterMetaFunctions(EsqlBaseParser.MetaFunctionsContext ctx) { } - /** - * {@inheritDoc} - * - *

The default implementation does nothing.

- */ - @Override public void exitMetaFunctions(EsqlBaseParser.MetaFunctionsContext ctx) { } /** * {@inheritDoc} * diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java index de98d4333c1d4..3a3ef05c7a465 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java @@ -566,13 +566,6 @@ public class EsqlBaseParserBaseVisitor extends AbstractParseTreeVisitor im * {@link #visitChildren} on {@code ctx}.

*/ @Override public T visitShowInfo(EsqlBaseParser.ShowInfoContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitMetaFunctions(EsqlBaseParser.MetaFunctionsContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java index 4348c641d9f69..5d2d417f30c50 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java @@ -861,18 +861,6 @@ public interface EsqlBaseParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitShowInfo(EsqlBaseParser.ShowInfoContext ctx); - /** - * Enter a parse tree produced by the {@code metaFunctions} - * labeled alternative in {@link EsqlBaseParser#metaCommand}. - * @param ctx the parse tree - */ - void enterMetaFunctions(EsqlBaseParser.MetaFunctionsContext ctx); - /** - * Exit a parse tree produced by the {@code metaFunctions} - * labeled alternative in {@link EsqlBaseParser#metaCommand}. - * @param ctx the parse tree - */ - void exitMetaFunctions(EsqlBaseParser.MetaFunctionsContext ctx); /** * Enter a parse tree produced by {@link EsqlBaseParser#enrichCommand}. * @param ctx the parse tree diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java index c334526abfe39..51f2e845bcc55 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java @@ -519,13 +519,6 @@ public interface EsqlBaseParserVisitor extends ParseTreeVisitor { * @return the visitor result */ T visitShowInfo(EsqlBaseParser.ShowInfoContext ctx); - /** - * Visit a parse tree produced by the {@code metaFunctions} - * labeled alternative in {@link EsqlBaseParser#metaCommand}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitMetaFunctions(EsqlBaseParser.MetaFunctionsContext ctx); /** * Visit a parse tree produced by {@link EsqlBaseParser#enrichCommand}. * @param ctx the parse tree 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 8dc07e2e1017f..d97c1aefd5487 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 @@ -53,7 +53,6 @@ import org.elasticsearch.xpack.esql.plan.logical.Rename; import org.elasticsearch.xpack.esql.plan.logical.Row; import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation; -import org.elasticsearch.xpack.esql.plan.logical.meta.MetaFunctions; import org.elasticsearch.xpack.esql.plan.logical.show.ShowInfo; import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.joni.exception.SyntaxException; @@ -412,11 +411,6 @@ public LogicalPlan visitShowInfo(EsqlBaseParser.ShowInfoContext ctx) { return new ShowInfo(source(ctx)); } - @Override - public LogicalPlan visitMetaFunctions(EsqlBaseParser.MetaFunctionsContext ctx) { - return new MetaFunctions(source(ctx)); - } - @Override public PlanFactory visitEnrichCommand(EsqlBaseParser.EnrichCommandContext ctx) { return p -> { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/meta/MetaFunctions.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/meta/MetaFunctions.java deleted file mode 100644 index 029cb6164167c..0000000000000 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/meta/MetaFunctions.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.esql.plan.logical.meta; - -import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.xpack.esql.core.expression.Attribute; -import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute; -import org.elasticsearch.xpack.esql.core.tree.NodeInfo; -import org.elasticsearch.xpack.esql.core.tree.Source; -import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; -import org.elasticsearch.xpack.esql.plan.logical.LeafPlan; -import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN; -import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; - -public class MetaFunctions extends LeafPlan { - - private final List attributes; - - public MetaFunctions(Source source) { - super(source); - - attributes = new ArrayList<>(); - for (var name : List.of("name", "synopsis", "argNames", "argTypes", "argDescriptions", "returnType", "description")) { - attributes.add(new ReferenceAttribute(Source.EMPTY, name, KEYWORD)); - } - for (var name : List.of("optionalArgs", "variadic", "isAggregation")) { - attributes.add(new ReferenceAttribute(Source.EMPTY, name, BOOLEAN)); - } - } - - @Override - public void writeTo(StreamOutput out) { - throw new UnsupportedOperationException("not serialized"); - } - - @Override - public String getWriteableName() { - throw new UnsupportedOperationException("not serialized"); - } - - @Override - public List output() { - return attributes; - } - - public List> values(EsqlFunctionRegistry functionRegistry) { - List> rows = new ArrayList<>(); - for (var def : functionRegistry.listFunctions(null)) { - EsqlFunctionRegistry.FunctionDescription signature = EsqlFunctionRegistry.description(def); - List row = new ArrayList<>(); - row.add(asBytesRefOrNull(signature.name())); - row.add(new BytesRef(signature.fullSignature())); - row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::name)); - row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::type)); - row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::description)); - row.add(withPipes(signature.returnType())); - row.add(signature.description()); - row.add(collect(signature, EsqlFunctionRegistry.ArgSignature::optional)); - row.add(signature.variadic()); - row.add(signature.isAggregation()); - rows.add(row); - } - rows.sort(Comparator.comparing(x -> ((BytesRef) x.get(0)))); - return rows; - } - - private Object collect(EsqlFunctionRegistry.FunctionDescription signature, Function x) { - if (signature.args().size() == 0) { - return null; - } - if (signature.args().size() == 1) { - Object result = x.apply(signature.args().get(0)); - if (result instanceof String[] r) { - return withPipes(r); - } - return result; - } - - List args = signature.args(); - List result = signature.args().stream().map(x).collect(Collectors.toList()); - boolean withPipes = result.get(0) instanceof String[]; - if (result.isEmpty() == false) { - List newResult = new ArrayList<>(); - for (int i = 0; i < result.size(); i++) { - if (signature.variadic() && args.get(i).optional()) { - continue; - } - newResult.add(withPipes ? withPipes((String[]) result.get(i)) : result.get(i)); - } - return newResult; - } - return result; - } - - public static String withPipes(String[] items) { - return Arrays.stream(items).collect(Collectors.joining("|")); - } - - private static BytesRef asBytesRefOrNull(String string) { - return Strings.hasText(string) ? new BytesRef(string) : null; - } - - @Override - public String commandName() { - return "META FUNCTIONS"; - } - - @Override - public boolean expressionsResolved() { - return true; - } - - @Override - protected NodeInfo info() { - return NodeInfo.create(this); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object obj) { - return this == obj || obj != null && getClass() == obj.getClass(); - } -} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/Mapper.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/Mapper.java index 9613fa1f3fcde..e571be54692c4 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/Mapper.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/Mapper.java @@ -32,7 +32,6 @@ import org.elasticsearch.xpack.esql.plan.logical.join.JoinConfig; import org.elasticsearch.xpack.esql.plan.logical.join.JoinType; import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation; -import org.elasticsearch.xpack.esql.plan.logical.meta.MetaFunctions; import org.elasticsearch.xpack.esql.plan.logical.show.ShowInfo; import org.elasticsearch.xpack.esql.plan.physical.AggregateExec; import org.elasticsearch.xpack.esql.plan.physical.DissectExec; @@ -98,9 +97,6 @@ public PhysicalPlan map(LogicalPlan p) { } // Commands - if (p instanceof MetaFunctions metaFunctions) { - return new ShowExec(metaFunctions.source(), metaFunctions.output(), metaFunctions.values(functionRegistry)); - } if (p instanceof ShowInfo showInfo) { return new ShowExec(showInfo.source(), showInfo.output(), showInfo.values()); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/FeatureMetric.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/FeatureMetric.java index c4d890a818ec7..4cae2a9c247f3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/FeatureMetric.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/FeatureMetric.java @@ -21,7 +21,6 @@ import org.elasticsearch.xpack.esql.plan.logical.OrderBy; import org.elasticsearch.xpack.esql.plan.logical.Rename; import org.elasticsearch.xpack.esql.plan.logical.Row; -import org.elasticsearch.xpack.esql.plan.logical.meta.MetaFunctions; import org.elasticsearch.xpack.esql.plan.logical.show.ShowInfo; import java.util.BitSet; @@ -29,11 +28,6 @@ import java.util.function.Predicate; public enum FeatureMetric { - /** - * The order of these enum values is important, do not change it. - * For any new values added to it, they should go at the end of the list. - * see {@link org.elasticsearch.xpack.esql.analysis.Verifier#gatherMetrics} - */ DISSECT(Dissect.class::isInstance), EVAL(Eval.class::isInstance), GROK(Grok.class::isInstance), @@ -48,8 +42,7 @@ public enum FeatureMetric { FROM(EsRelation.class::isInstance), DROP(Drop.class::isInstance), KEEP(Keep.class::isInstance), - RENAME(Rename.class::isInstance), - META(MetaFunctions.class::isInstance); + RENAME(Rename.class::isInstance); private Predicate planCheck; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java index adf31ca983067..27656c8122e30 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java @@ -921,7 +921,6 @@ public void testDeprecatedIsNullFunction() { public void testMetadataFieldOnOtherSources() { expectError("row a = 1 metadata _index", "line 1:20: extraneous input '_index' expecting "); - expectError("meta functions metadata _index", "line 1:16: token recognition error at: 'm'"); expectError("show info metadata _index", "line 1:11: token recognition error at: 'm'"); expectError( "explain [from foo] metadata _index", diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/VerifierMetricsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/VerifierMetricsTests.java index ab004a3a055ce..203e5c3bd37ee 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/VerifierMetricsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/VerifierMetricsTests.java @@ -24,7 +24,6 @@ import static org.elasticsearch.xpack.esql.stats.FeatureMetric.GROK; import static org.elasticsearch.xpack.esql.stats.FeatureMetric.KEEP; import static org.elasticsearch.xpack.esql.stats.FeatureMetric.LIMIT; -import static org.elasticsearch.xpack.esql.stats.FeatureMetric.META; import static org.elasticsearch.xpack.esql.stats.FeatureMetric.MV_EXPAND; import static org.elasticsearch.xpack.esql.stats.FeatureMetric.RENAME; import static org.elasticsearch.xpack.esql.stats.FeatureMetric.ROW; @@ -55,7 +54,6 @@ public void testDissectQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testEvalQuery() { @@ -75,7 +73,6 @@ public void testEvalQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testGrokQuery() { @@ -95,7 +92,6 @@ public void testGrokQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testLimitQuery() { @@ -115,7 +111,6 @@ public void testLimitQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testSortQuery() { @@ -135,7 +130,6 @@ public void testSortQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testStatsQuery() { @@ -155,7 +149,6 @@ public void testStatsQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testWhereQuery() { @@ -175,7 +168,6 @@ public void testWhereQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testTwoWhereQuery() { @@ -195,7 +187,6 @@ public void testTwoWhereQuery() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testTwoQueriesExecuted() { @@ -235,7 +226,6 @@ public void testTwoQueriesExecuted() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testEnrich() { @@ -261,7 +251,6 @@ public void testEnrich() { assertEquals(0, drop(c)); assertEquals(1L, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testMvExpand() { @@ -290,27 +279,6 @@ public void testMvExpand() { assertEquals(0, drop(c)); assertEquals(1L, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); - } - - public void testMetaFunctions() { - Counters c = esql("meta functions | stats a = count(*) | mv_expand a"); - assertEquals(0, dissect(c)); - assertEquals(0, eval(c)); - assertEquals(0, grok(c)); - assertEquals(0, limit(c)); - assertEquals(0, sort(c)); - assertEquals(1L, stats(c)); - assertEquals(0, where(c)); - assertEquals(0, enrich(c)); - assertEquals(1L, mvExpand(c)); - assertEquals(0, show(c)); - assertEquals(0, row(c)); - assertEquals(0, from(c)); - assertEquals(0, drop(c)); - assertEquals(0, keep(c)); - assertEquals(0, rename(c)); - assertEquals(1L, meta(c)); } public void testShowInfo() { @@ -330,7 +298,6 @@ public void testShowInfo() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testRow() { @@ -350,7 +317,6 @@ public void testRow() { assertEquals(0, drop(c)); assertEquals(0, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } public void testDropAndRename() { @@ -370,7 +336,6 @@ public void testDropAndRename() { assertEquals(1L, drop(c)); assertEquals(0, keep(c)); assertEquals(1L, rename(c)); - assertEquals(0, meta(c)); } public void testKeep() { @@ -395,7 +360,6 @@ public void testKeep() { assertEquals(0, drop(c)); assertEquals(1L, keep(c)); assertEquals(0, rename(c)); - assertEquals(0, meta(c)); } private long dissect(Counters c) { @@ -458,10 +422,6 @@ private long rename(Counters c) { return c.get(FPREFIX + RENAME); } - private long meta(Counters c) { - return c.get(FPREFIX + META); - } - private Counters esql(String esql) { return esql(esql, null); } diff --git a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/CohereServiceMixedIT.java b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/CohereServiceMixedIT.java index 69274b46d75c1..8cb37ad645358 100644 --- a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/CohereServiceMixedIT.java +++ b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/CohereServiceMixedIT.java @@ -22,6 +22,7 @@ import java.util.Map; import static org.elasticsearch.xpack.inference.qa.mixed.MixedClusterSpecTestCase.bwcVersion; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasSize; @@ -32,6 +33,7 @@ public class CohereServiceMixedIT extends BaseMixedTestCase { private static final String COHERE_EMBEDDINGS_ADDED = "8.13.0"; private static final String COHERE_RERANK_ADDED = "8.14.0"; + private static final String COHERE_EMBEDDINGS_CHUNKING_SETTINGS_ADDED = "8.16.0"; private static final String BYTE_ALIAS_FOR_INT8_ADDED = "8.14.0"; private static final String MINIMUM_SUPPORTED_VERSION = "8.15.0"; @@ -65,13 +67,28 @@ public void testCohereEmbeddings() throws IOException { final String inferenceIdInt8 = "mixed-cluster-cohere-embeddings-int8"; final String inferenceIdFloat = "mixed-cluster-cohere-embeddings-float"; - // queue a response as PUT will call the service - cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseByte())); - put(inferenceIdInt8, embeddingConfigInt8(getUrl(cohereEmbeddingsServer)), TaskType.TEXT_EMBEDDING); - - // float model - cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseFloat())); - put(inferenceIdFloat, embeddingConfigFloat(getUrl(cohereEmbeddingsServer)), TaskType.TEXT_EMBEDDING); + try { + // queue a response as PUT will call the service + cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseByte())); + put(inferenceIdInt8, embeddingConfigInt8(getUrl(cohereEmbeddingsServer)), TaskType.TEXT_EMBEDDING); + + // float model + cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseFloat())); + put(inferenceIdFloat, embeddingConfigFloat(getUrl(cohereEmbeddingsServer)), TaskType.TEXT_EMBEDDING); + } catch (Exception e) { + if (bwcVersion.before(Version.fromString(COHERE_EMBEDDINGS_CHUNKING_SETTINGS_ADDED))) { + // Chunking settings were added in 8.16.0. if the version is before that, an exception will be thrown if the index mapping + // was created based on a mapping from an old node + assertThat( + e.getMessage(), + containsString( + "One or more nodes in your cluster does not support chunking_settings. " + + "Please update all nodes in your cluster to the latest version to use chunking_settings." + ) + ); + return; + } + } var configs = (List>) get(TaskType.TEXT_EMBEDDING, inferenceIdInt8).get("endpoints"); assertEquals("cohere", configs.get(0).get("service")); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java index a3f2105054639..87b7be717d31b 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceFeatures.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.inference.rank.random.RandomRankRetrieverBuilder; import org.elasticsearch.xpack.inference.rank.textsimilarity.TextSimilarityRankRetrieverBuilder; +import java.util.HashSet; import java.util.Set; /** @@ -23,13 +24,16 @@ public class InferenceFeatures implements FeatureSpecification { @Override public Set getFeatures() { - return Set.of( - TextSimilarityRankRetrieverBuilder.TEXT_SIMILARITY_RERANKER_RETRIEVER_SUPPORTED, - RandomRankRetrieverBuilder.RANDOM_RERANKER_RETRIEVER_SUPPORTED, - SemanticTextFieldMapper.SEMANTIC_TEXT_SEARCH_INFERENCE_ID, - SemanticQueryBuilder.SEMANTIC_TEXT_INNER_HITS, - TextSimilarityRankRetrieverBuilder.TEXT_SIMILARITY_RERANKER_COMPOSITION_SUPPORTED - ); + var features = new HashSet(); + features.add(TextSimilarityRankRetrieverBuilder.TEXT_SIMILARITY_RERANKER_RETRIEVER_SUPPORTED); + features.add(RandomRankRetrieverBuilder.RANDOM_RERANKER_RETRIEVER_SUPPORTED); + features.add(SemanticTextFieldMapper.SEMANTIC_TEXT_SEARCH_INFERENCE_ID); + features.add(SemanticQueryBuilder.SEMANTIC_TEXT_INNER_HITS); + features.add(TextSimilarityRankRetrieverBuilder.TEXT_SIMILARITY_RERANKER_COMPOSITION_SUPPORTED); + if (DefaultElserFeatureFlag.isEnabled()) { + features.add(SemanticTextFieldMapper.SEMANTIC_TEXT_DEFAULT_ELSER_2); + } + return Set.copyOf(features); } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/alibabacloudsearch/AlibabaCloudSearchResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/alibabacloudsearch/AlibabaCloudSearchResponseHandler.java index 05d51372d9cdc..ecfa988b5035e 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/alibabacloudsearch/AlibabaCloudSearchResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/alibabacloudsearch/AlibabaCloudSearchResponseHandler.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.inference.external.alibabacloudsearch; import org.apache.logging.log4j.Logger; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xpack.inference.external.http.HttpResult; import org.elasticsearch.xpack.inference.external.http.retry.BaseResponseHandler; import org.elasticsearch.xpack.inference.external.http.retry.ResponseParser; @@ -43,7 +44,7 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R */ void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (RestStatus.isSuccessful(statusCode)) { return; } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/anthropic/AnthropicResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/anthropic/AnthropicResponseHandler.java index cab2c655b9ffb..aec47f19b2642 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/anthropic/AnthropicResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/anthropic/AnthropicResponseHandler.java @@ -61,12 +61,12 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R * @throws RetryException Throws if status code is {@code >= 300 or < 200 } */ void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } // handle error codes + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw new RetryException(true, buildError(SERVER_ERROR, request, result)); } else if (statusCode == 529) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/cohere/CohereResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/cohere/CohereResponseHandler.java index 3579cd4100bbb..ac2e1747f8057 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/cohere/CohereResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/cohere/CohereResponseHandler.java @@ -74,12 +74,12 @@ public InferenceServiceResults parseResult(Request request, Flow.Publisher= 300 or < 200 } */ void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } // handle error codes + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw new RetryException(true, buildError(SERVER_ERROR, request, result)); } else if (statusCode > 500) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/elastic/ElasticInferenceServiceResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/elastic/ElasticInferenceServiceResponseHandler.java index 15e543fadad71..2b79afb3b56fd 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/elastic/ElasticInferenceServiceResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/elastic/ElasticInferenceServiceResponseHandler.java @@ -33,11 +33,11 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R } void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw new RetryException(true, buildError(SERVER_ERROR, request, result)); } else if (statusCode == 400) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googleaistudio/GoogleAiStudioResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googleaistudio/GoogleAiStudioResponseHandler.java index 1138cfcb7cdc6..4ba5b552f802a 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googleaistudio/GoogleAiStudioResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googleaistudio/GoogleAiStudioResponseHandler.java @@ -43,12 +43,12 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R * @throws RetryException Throws if status code is {@code >= 300 or < 200 } */ void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } // handle error codes + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw new RetryException(true, buildError(SERVER_ERROR, request, result)); } else if (statusCode == 503) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googlevertexai/GoogleVertexAiResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googlevertexai/GoogleVertexAiResponseHandler.java index 872bf51f3662a..6b1aef9856d33 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googlevertexai/GoogleVertexAiResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/googlevertexai/GoogleVertexAiResponseHandler.java @@ -35,12 +35,12 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R } void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } // handle error codes + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw new RetryException(true, buildError(SERVER_ERROR, request, result)); } else if (statusCode == 503) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/HttpResult.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/HttpResult.java index 6c79daa2dedc0..68a94ac0b0c0e 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/HttpResult.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/HttpResult.java @@ -10,6 +10,7 @@ import org.apache.http.HttpResponse; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.core.Streams; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xpack.inference.common.SizeLimitInputStream; import java.io.ByteArrayOutputStream; @@ -47,4 +48,8 @@ private static byte[] limitBody(ByteSizeValue maxResponseSize, HttpResponse resp public boolean isBodyEmpty() { return body().length == 0; } + + public boolean isSuccessfulResponse() { + return RestStatus.isSuccessful(response.getStatusLine().getStatusCode()); + } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/huggingface/HuggingFaceResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/huggingface/HuggingFaceResponseHandler.java index 59804b37e465b..f6fd9afabe28d 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/huggingface/HuggingFaceResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/huggingface/HuggingFaceResponseHandler.java @@ -41,11 +41,11 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R * @throws RetryException thrown if status code is {@code >= 300 or < 200} */ void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 503 || statusCode == 502 || statusCode == 429) { throw new RetryException(true, buildError(RATE_LIMIT, request, result)); } else if (statusCode >= 500) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/ibmwatsonx/IbmWatsonxResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/ibmwatsonx/IbmWatsonxResponseHandler.java index 161ca6966cece..cb686ddb654db 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/ibmwatsonx/IbmWatsonxResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/ibmwatsonx/IbmWatsonxResponseHandler.java @@ -42,11 +42,11 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R * @throws RetryException thrown if status code is {@code >= 300 or < 200} */ void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw new RetryException(true, buildError(SERVER_ERROR, request, result)); } else if (statusCode == 404) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/openai/OpenAiResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/openai/OpenAiResponseHandler.java index c193280e1978b..6404236d51184 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/openai/OpenAiResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/openai/OpenAiResponseHandler.java @@ -67,12 +67,12 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R * @throws RetryException Throws if status code is {@code >= 300 or < 200 } */ void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } // handle error codes + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw new RetryException(true, buildError(SERVER_ERROR, request, result)); } else if (statusCode == 503) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/ibmwatsonx/IbmWatsonxRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/ibmwatsonx/IbmWatsonxRequest.java index e5ac64624a697..d9dd6c4c8b44d 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/ibmwatsonx/IbmWatsonxRequest.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/ibmwatsonx/IbmWatsonxRequest.java @@ -78,7 +78,7 @@ static void decorateWithBearerToken(HttpPost httpPost, DefaultSecretSettings sec static void validateResponse(String bearerTokenGenUrl, String inferenceId, HttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (RestStatus.isSuccessful(statusCode)) { return; } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureMistralOpenAiExternalResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureMistralOpenAiExternalResponseHandler.java index e4e96ca644c7f..3116bf0f6cd2d 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureMistralOpenAiExternalResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureMistralOpenAiExternalResponseHandler.java @@ -59,12 +59,12 @@ public void validateResponse(ThrottlerManager throttlerManager, Logger logger, R } public void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { - int statusCode = result.response().getStatusLine().getStatusCode(); - if (statusCode >= 200 && statusCode < 300) { + if (result.isSuccessfulResponse()) { return; } // handle error codes + int statusCode = result.response().getStatusLine().getStatusCode(); if (statusCode == 500) { throw handle500Error(request, result); } else if (statusCode == 503) { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextField.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextField.java index 0c807c1166608..e60e95b58770f 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextField.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextField.java @@ -58,6 +58,7 @@ public record SemanticTextField(String fieldName, List originalValues, I static final String TEXT_FIELD = "text"; static final String INFERENCE_FIELD = "inference"; static final String INFERENCE_ID_FIELD = "inference_id"; + static final String SEARCH_INFERENCE_ID_FIELD = "search_inference_id"; static final String CHUNKS_FIELD = "chunks"; static final String CHUNKED_EMBEDDINGS_FIELD = "embeddings"; static final String CHUNKED_TEXT_FIELD = "text"; diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java index 0483296cd2c6a..a5702b38ea3f2 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java @@ -54,6 +54,7 @@ import org.elasticsearch.xcontent.XContentParserConfiguration; import org.elasticsearch.xpack.core.ml.inference.results.MlTextEmbeddingResults; import org.elasticsearch.xpack.core.ml.inference.results.TextExpansionResults; +import org.elasticsearch.xpack.inference.DefaultElserFeatureFlag; import java.io.IOException; import java.util.ArrayList; @@ -71,18 +72,23 @@ import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.CHUNKS_FIELD; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.INFERENCE_FIELD; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.INFERENCE_ID_FIELD; +import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.MODEL_SETTINGS_FIELD; +import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.SEARCH_INFERENCE_ID_FIELD; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.TEXT_FIELD; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.getChunksFieldName; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.getEmbeddingsFieldName; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.getOriginalTextFieldName; +import static org.elasticsearch.xpack.inference.services.elasticsearch.ElasticsearchInternalService.DEFAULT_ELSER_ID; /** * A {@link FieldMapper} for semantic text fields. */ public class SemanticTextFieldMapper extends FieldMapper implements InferenceFieldMapper { public static final NodeFeature SEMANTIC_TEXT_SEARCH_INFERENCE_ID = new NodeFeature("semantic_text.search_inference_id"); + public static final NodeFeature SEMANTIC_TEXT_DEFAULT_ELSER_2 = new NodeFeature("semantic_text.default_elser_2"); public static final String CONTENT_TYPE = "semantic_text"; + public static final String DEFAULT_ELSER_2_INFERENCE_ID = DEFAULT_ELSER_ID; private final IndexSettings indexSettings; @@ -96,25 +102,37 @@ public static class Builder extends FieldMapper.Builder { private final IndexSettings indexSettings; private final Parameter inferenceId = Parameter.stringParam( - "inference_id", + INFERENCE_ID_FIELD, false, mapper -> ((SemanticTextFieldType) mapper.fieldType()).inferenceId, - null + DefaultElserFeatureFlag.isEnabled() ? DEFAULT_ELSER_2_INFERENCE_ID : null ).addValidator(v -> { if (Strings.isEmpty(v)) { - throw new IllegalArgumentException("field [inference_id] must be specified"); + // If the default ELSER feature flag is enabled, the only way we get here is if the user explicitly sets the param to an + // empty value. However, if the feature flag is disabled, we can get here if the user didn't set the param. + // Adjust the error message appropriately. + String message = DefaultElserFeatureFlag.isEnabled() + ? "[" + INFERENCE_ID_FIELD + "] on mapper [" + leafName() + "] of type [" + CONTENT_TYPE + "] must not be empty" + : "[" + INFERENCE_ID_FIELD + "] on mapper [" + leafName() + "] of type [" + CONTENT_TYPE + "] must be specified"; + throw new IllegalArgumentException(message); } }); private final Parameter searchInferenceId = Parameter.stringParam( - "search_inference_id", + SEARCH_INFERENCE_ID_FIELD, true, mapper -> ((SemanticTextFieldType) mapper.fieldType()).searchInferenceId, null - ).acceptsNull(); + ).acceptsNull().addValidator(v -> { + if (v != null && Strings.isEmpty(v)) { + throw new IllegalArgumentException( + "[" + SEARCH_INFERENCE_ID_FIELD + "] on mapper [" + leafName() + "] of type [" + CONTENT_TYPE + "] must not be empty" + ); + } + }); private final Parameter modelSettings = new Parameter<>( - "model_settings", + MODEL_SETTINGS_FIELD, true, () -> null, (n, c, o) -> SemanticTextField.parseModelSettingsFromMap(o), @@ -204,6 +222,7 @@ public SemanticTextFieldMapper build(MapperBuilderContext context) { } var childContext = context.createChildContext(leafName(), ObjectMapper.Dynamic.FALSE); final ObjectMapper inferenceField = inferenceFieldBuilder.apply(childContext); + return new SemanticTextFieldMapper( leafName(), new SemanticTextFieldType( diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockService.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockService.java index bc0d10279ae44..c7c073660624d 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockService.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockService.java @@ -16,6 +16,7 @@ import org.elasticsearch.core.TimeValue; import org.elasticsearch.inference.ChunkedInferenceServiceResults; import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InferenceServiceResults; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.Model; @@ -23,6 +24,8 @@ import org.elasticsearch.inference.ModelSecrets; import org.elasticsearch.inference.TaskType; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xpack.core.inference.ChunkingSettingsFeatureFlag; +import org.elasticsearch.xpack.inference.chunking.ChunkingSettingsBuilder; import org.elasticsearch.xpack.inference.chunking.EmbeddingRequestChunker; import org.elasticsearch.xpack.inference.external.action.amazonbedrock.AmazonBedrockActionCreator; import org.elasticsearch.xpack.inference.external.amazonbedrock.AmazonBedrockRequestSender; @@ -99,8 +102,20 @@ protected void doChunkedInfer( var actionCreator = new AmazonBedrockActionCreator(amazonBedrockSender, this.getServiceComponents(), timeout); if (model instanceof AmazonBedrockModel baseAmazonBedrockModel) { var maxBatchSize = getEmbeddingsMaxBatchSize(baseAmazonBedrockModel.provider()); - var batchedRequests = new EmbeddingRequestChunker(inputs.getInputs(), maxBatchSize, EmbeddingRequestChunker.EmbeddingType.FLOAT) - .batchRequestsWithListeners(listener); + + List batchedRequests; + if (ChunkingSettingsFeatureFlag.isEnabled()) { + batchedRequests = new EmbeddingRequestChunker( + inputs.getInputs(), + maxBatchSize, + EmbeddingRequestChunker.EmbeddingType.FLOAT, + baseAmazonBedrockModel.getConfigurations().getChunkingSettings() + ).batchRequestsWithListeners(listener); + } else { + batchedRequests = new EmbeddingRequestChunker(inputs.getInputs(), maxBatchSize, EmbeddingRequestChunker.EmbeddingType.FLOAT) + .batchRequestsWithListeners(listener); + } + for (var request : batchedRequests) { var action = baseAmazonBedrockModel.accept(actionCreator, taskSettings); action.execute(new DocumentsOnlyInput(request.batch().inputs()), timeout, request.listener()); @@ -126,11 +141,19 @@ public void parseRequestConfig( Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap( + removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS) + ); + } + AmazonBedrockModel model = createModel( modelId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, serviceSettingsMap, TaskType.unsupportedTaskTypeErrorMsg(taskType, NAME), ConfigurationParseContext.REQUEST @@ -157,11 +180,17 @@ public Model parsePersistedConfigWithSecrets( Map taskSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.TASK_SETTINGS); Map secretSettingsMap = removeFromMapOrDefaultEmpty(secrets, ModelSecrets.SECRET_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap(removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS)); + } + return createModel( modelId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, secretSettingsMap, parsePersistedConfigErrorMsg(modelId, NAME), ConfigurationParseContext.PERSISTENT @@ -173,11 +202,17 @@ public Model parsePersistedConfig(String modelId, TaskType taskType, Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap(removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS)); + } + return createModel( modelId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, null, parsePersistedConfigErrorMsg(modelId, NAME), ConfigurationParseContext.PERSISTENT @@ -189,6 +224,7 @@ private static AmazonBedrockModel createModel( TaskType taskType, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, @Nullable Map secretSettings, String failureMessage, ConfigurationParseContext context @@ -201,6 +237,7 @@ private static AmazonBedrockModel createModel( NAME, serviceSettings, taskSettings, + chunkingSettings, secretSettings, context ); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModel.java index 0e3a954a03279..186d977d20672 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModel.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.inference.services.amazonbedrock.embeddings; import org.elasticsearch.common.ValidationException; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.EmptyTaskSettings; import org.elasticsearch.inference.Model; import org.elasticsearch.inference.ModelConfigurations; @@ -42,6 +43,7 @@ public AmazonBedrockEmbeddingsModel( String service, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, Map secretSettings, ConfigurationParseContext context ) { @@ -51,6 +53,7 @@ public AmazonBedrockEmbeddingsModel( service, AmazonBedrockEmbeddingsServiceSettings.fromMap(serviceSettings, context), new EmptyTaskSettings(), + chunkingSettings, AmazonBedrockSecretSettings.fromMap(secretSettings) ); } @@ -61,10 +64,11 @@ public AmazonBedrockEmbeddingsModel( String service, AmazonBedrockEmbeddingsServiceSettings serviceSettings, TaskSettings taskSettings, + ChunkingSettings chunkingSettings, AmazonBedrockSecretSettings secrets ) { super( - new ModelConfigurations(inferenceEntityId, taskType, service, serviceSettings, new EmptyTaskSettings()), + new ModelConfigurations(inferenceEntityId, taskType, service, serviceSettings, new EmptyTaskSettings(), chunkingSettings), new ModelSecrets(secrets) ); } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiService.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiService.java index 96399bb954cd2..07708ee072099 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiService.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiService.java @@ -16,6 +16,7 @@ import org.elasticsearch.core.TimeValue; import org.elasticsearch.inference.ChunkedInferenceServiceResults; import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InferenceServiceResults; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.Model; @@ -24,6 +25,8 @@ import org.elasticsearch.inference.SimilarityMeasure; import org.elasticsearch.inference.TaskType; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xpack.core.inference.ChunkingSettingsFeatureFlag; +import org.elasticsearch.xpack.inference.chunking.ChunkingSettingsBuilder; import org.elasticsearch.xpack.inference.chunking.EmbeddingRequestChunker; import org.elasticsearch.xpack.inference.external.action.azureopenai.AzureOpenAiActionCreator; import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput; @@ -70,11 +73,19 @@ public void parseRequestConfig( Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap( + removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS) + ); + } + AzureOpenAiModel model = createModel( inferenceEntityId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, serviceSettingsMap, TaskType.unsupportedTaskTypeErrorMsg(taskType, NAME), ConfigurationParseContext.REQUEST @@ -95,6 +106,7 @@ private static AzureOpenAiModel createModelFromPersistent( TaskType taskType, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, @Nullable Map secretSettings, String failureMessage ) { @@ -103,6 +115,7 @@ private static AzureOpenAiModel createModelFromPersistent( taskType, serviceSettings, taskSettings, + chunkingSettings, secretSettings, failureMessage, ConfigurationParseContext.PERSISTENT @@ -114,6 +127,7 @@ private static AzureOpenAiModel createModel( TaskType taskType, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, @Nullable Map secretSettings, String failureMessage, ConfigurationParseContext context @@ -126,6 +140,7 @@ private static AzureOpenAiModel createModel( NAME, serviceSettings, taskSettings, + chunkingSettings, secretSettings, context ); @@ -156,11 +171,17 @@ public AzureOpenAiModel parsePersistedConfigWithSecrets( Map taskSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.TASK_SETTINGS); Map secretSettingsMap = removeFromMapOrDefaultEmpty(secrets, ModelSecrets.SECRET_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap(removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS)); + } + return createModelFromPersistent( inferenceEntityId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, secretSettingsMap, parsePersistedConfigErrorMsg(inferenceEntityId, NAME) ); @@ -171,11 +192,17 @@ public AzureOpenAiModel parsePersistedConfig(String inferenceEntityId, TaskType Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap(removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS)); + } + return createModelFromPersistent( inferenceEntityId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, null, parsePersistedConfigErrorMsg(inferenceEntityId, NAME) ); @@ -218,11 +245,23 @@ protected void doChunkedInfer( } AzureOpenAiModel azureOpenAiModel = (AzureOpenAiModel) model; var actionCreator = new AzureOpenAiActionCreator(getSender(), getServiceComponents()); - var batchedRequests = new EmbeddingRequestChunker( - inputs.getInputs(), - EMBEDDING_MAX_BATCH_SIZE, - EmbeddingRequestChunker.EmbeddingType.FLOAT - ).batchRequestsWithListeners(listener); + + List batchedRequests; + if (ChunkingSettingsFeatureFlag.isEnabled()) { + batchedRequests = new EmbeddingRequestChunker( + inputs.getInputs(), + EMBEDDING_MAX_BATCH_SIZE, + EmbeddingRequestChunker.EmbeddingType.FLOAT, + azureOpenAiModel.getConfigurations().getChunkingSettings() + ).batchRequestsWithListeners(listener); + } else { + batchedRequests = new EmbeddingRequestChunker( + inputs.getInputs(), + EMBEDDING_MAX_BATCH_SIZE, + EmbeddingRequestChunker.EmbeddingType.FLOAT + ).batchRequestsWithListeners(listener); + } + for (var request : batchedRequests) { var action = azureOpenAiModel.accept(actionCreator, taskSettings); action.execute(new DocumentsOnlyInput(request.batch().inputs()), timeout, request.listener()); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModel.java index 377bb33f58619..7b83d5322a696 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModel.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.inference.services.azureopenai.embeddings; import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.ModelConfigurations; import org.elasticsearch.inference.ModelSecrets; import org.elasticsearch.inference.TaskType; @@ -38,6 +39,7 @@ public AzureOpenAiEmbeddingsModel( String service, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, @Nullable Map secrets, ConfigurationParseContext context ) { @@ -47,6 +49,7 @@ public AzureOpenAiEmbeddingsModel( service, AzureOpenAiEmbeddingsServiceSettings.fromMap(serviceSettings, context), AzureOpenAiEmbeddingsTaskSettings.fromMap(taskSettings), + chunkingSettings, AzureOpenAiSecretSettings.fromMap(secrets) ); } @@ -58,10 +61,11 @@ public AzureOpenAiEmbeddingsModel( String service, AzureOpenAiEmbeddingsServiceSettings serviceSettings, AzureOpenAiEmbeddingsTaskSettings taskSettings, + ChunkingSettings chunkingSettings, @Nullable AzureOpenAiSecretSettings secrets ) { super( - new ModelConfigurations(inferenceEntityId, taskType, service, serviceSettings, taskSettings), + new ModelConfigurations(inferenceEntityId, taskType, service, serviceSettings, taskSettings, chunkingSettings), new ModelSecrets(secrets), serviceSettings ); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java index 3ba93dd8d1b66..1804d3bdf5936 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java @@ -15,6 +15,7 @@ import org.elasticsearch.core.TimeValue; import org.elasticsearch.inference.ChunkedInferenceServiceResults; import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InferenceServiceResults; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.Model; @@ -23,6 +24,8 @@ import org.elasticsearch.inference.SimilarityMeasure; import org.elasticsearch.inference.TaskType; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xpack.core.inference.ChunkingSettingsFeatureFlag; +import org.elasticsearch.xpack.inference.chunking.ChunkingSettingsBuilder; import org.elasticsearch.xpack.inference.chunking.EmbeddingRequestChunker; import org.elasticsearch.xpack.inference.external.action.cohere.CohereActionCreator; import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput; @@ -76,11 +79,19 @@ public void parseRequestConfig( Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap( + removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS) + ); + } + CohereModel model = createModel( inferenceEntityId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, serviceSettingsMap, TaskType.unsupportedTaskTypeErrorMsg(taskType, NAME), ConfigurationParseContext.REQUEST @@ -101,6 +112,7 @@ private static CohereModel createModelWithoutLoggingDeprecations( TaskType taskType, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, @Nullable Map secretSettings, String failureMessage ) { @@ -109,6 +121,7 @@ private static CohereModel createModelWithoutLoggingDeprecations( taskType, serviceSettings, taskSettings, + chunkingSettings, secretSettings, failureMessage, ConfigurationParseContext.PERSISTENT @@ -120,6 +133,7 @@ private static CohereModel createModel( TaskType taskType, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, @Nullable Map secretSettings, String failureMessage, ConfigurationParseContext context @@ -131,6 +145,7 @@ private static CohereModel createModel( NAME, serviceSettings, taskSettings, + chunkingSettings, secretSettings, context ); @@ -159,11 +174,17 @@ public CohereModel parsePersistedConfigWithSecrets( Map taskSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.TASK_SETTINGS); Map secretSettingsMap = removeFromMapOrThrowIfNull(secrets, ModelSecrets.SECRET_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap(removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS)); + } + return createModelWithoutLoggingDeprecations( inferenceEntityId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, secretSettingsMap, parsePersistedConfigErrorMsg(inferenceEntityId, NAME) ); @@ -174,11 +195,17 @@ public CohereModel parsePersistedConfig(String inferenceEntityId, TaskType taskT Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); Map taskSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.TASK_SETTINGS); + ChunkingSettings chunkingSettings = null; + if (ChunkingSettingsFeatureFlag.isEnabled() && TaskType.TEXT_EMBEDDING.equals(taskType)) { + chunkingSettings = ChunkingSettingsBuilder.fromMap(removeFromMapOrDefaultEmpty(config, ModelConfigurations.CHUNKING_SETTINGS)); + } + return createModelWithoutLoggingDeprecations( inferenceEntityId, taskType, serviceSettingsMap, taskSettingsMap, + chunkingSettings, null, parsePersistedConfigErrorMsg(inferenceEntityId, NAME) ); @@ -223,11 +250,22 @@ protected void doChunkedInfer( CohereModel cohereModel = (CohereModel) model; var actionCreator = new CohereActionCreator(getSender(), getServiceComponents()); - var batchedRequests = new EmbeddingRequestChunker( - inputs.getInputs(), - EMBEDDING_MAX_BATCH_SIZE, - EmbeddingRequestChunker.EmbeddingType.fromDenseVectorElementType(model.getServiceSettings().elementType()) - ).batchRequestsWithListeners(listener); + List batchedRequests; + if (ChunkingSettingsFeatureFlag.isEnabled()) { + batchedRequests = new EmbeddingRequestChunker( + inputs.getInputs(), + EMBEDDING_MAX_BATCH_SIZE, + EmbeddingRequestChunker.EmbeddingType.fromDenseVectorElementType(model.getServiceSettings().elementType()), + cohereModel.getConfigurations().getChunkingSettings() + ).batchRequestsWithListeners(listener); + } else { + batchedRequests = new EmbeddingRequestChunker( + inputs.getInputs(), + EMBEDDING_MAX_BATCH_SIZE, + EmbeddingRequestChunker.EmbeddingType.fromDenseVectorElementType(model.getServiceSettings().elementType()) + ).batchRequestsWithListeners(listener); + } + for (var request : batchedRequests) { var action = cohereModel.accept(actionCreator, taskSettings, inputType); action.execute(new DocumentsOnlyInput(request.batch().inputs()), timeout, request.listener()); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModel.java index fea5226bf9c6f..0f62ab51145f4 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModel.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.inference.services.cohere.embeddings; import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.ModelConfigurations; import org.elasticsearch.inference.ModelSecrets; @@ -33,6 +34,7 @@ public CohereEmbeddingsModel( String service, Map serviceSettings, Map taskSettings, + ChunkingSettings chunkingSettings, @Nullable Map secrets, ConfigurationParseContext context ) { @@ -42,6 +44,7 @@ public CohereEmbeddingsModel( service, CohereEmbeddingsServiceSettings.fromMap(serviceSettings, context), CohereEmbeddingsTaskSettings.fromMap(taskSettings), + chunkingSettings, DefaultSecretSettings.fromMap(secrets) ); } @@ -53,10 +56,11 @@ public CohereEmbeddingsModel( String service, CohereEmbeddingsServiceSettings serviceSettings, CohereEmbeddingsTaskSettings taskSettings, + ChunkingSettings chunkingSettings, @Nullable DefaultSecretSettings secretSettings ) { super( - new ModelConfigurations(modelId, taskType, service, serviceSettings, taskSettings), + new ModelConfigurations(modelId, taskType, service, serviceSettings, taskSettings, chunkingSettings), new ModelSecrets(secretSettings), secretSettings, serviceSettings.getCommonSettings() diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java index 1697b33fedd92..7c8d1bbf9fb4d 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java @@ -23,6 +23,7 @@ import org.apache.lucene.search.join.QueryBitSetProducer; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; +import org.elasticsearch.common.CheckedBiConsumer; import org.elasticsearch.common.CheckedBiFunction; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; @@ -58,6 +59,7 @@ import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.json.JsonXContent; +import org.elasticsearch.xpack.inference.DefaultElserFeatureFlag; import org.elasticsearch.xpack.inference.InferencePlugin; import org.elasticsearch.xpack.inference.model.TestModel; import org.junit.AssumptionViolatedException; @@ -77,8 +79,10 @@ import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.INFERENCE_FIELD; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.INFERENCE_ID_FIELD; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.MODEL_SETTINGS_FIELD; +import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.SEARCH_INFERENCE_ID_FIELD; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.getChunksFieldName; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.getEmbeddingsFieldName; +import static org.elasticsearch.xpack.inference.mapper.SemanticTextFieldMapper.DEFAULT_ELSER_2_INFERENCE_ID; import static org.elasticsearch.xpack.inference.mapper.SemanticTextFieldTests.randomSemanticText; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -92,7 +96,10 @@ protected Collection getPlugins() { @Override protected void minimalMapping(XContentBuilder b) throws IOException { - b.field("type", "semantic_text").field("inference_id", "test_model"); + b.field("type", "semantic_text"); + if (DefaultElserFeatureFlag.isEnabled() == false) { + b.field("inference_id", "test_model"); + } } @Override @@ -155,8 +162,16 @@ protected void assertSearchable(MappedFieldType fieldType) { } public void testDefaults() throws Exception { - DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); - assertEquals(Strings.toString(fieldMapping(this::minimalMapping)), mapper.mappingSource().toString()); + final String fieldName = "field"; + final XContentBuilder fieldMapping = fieldMapping(this::minimalMapping); + + MapperService mapperService = createMapperService(fieldMapping); + DocumentMapper mapper = mapperService.documentMapper(); + assertEquals(Strings.toString(fieldMapping), mapper.mappingSource().toString()); + assertSemanticTextField(mapperService, fieldName, false); + if (DefaultElserFeatureFlag.isEnabled()) { + assertInferenceEndpoints(mapperService, fieldName, DEFAULT_ELSER_2_INFERENCE_ID, DEFAULT_ELSER_2_INFERENCE_ID); + } ParsedDocument doc1 = mapper.parse(source(this::writeField)); List fields = doc1.rootDoc().getFields("field"); @@ -172,12 +187,80 @@ public void testFieldHasValue() { assertTrue(fieldType.fieldHasValue(fieldInfos)); } - public void testInferenceIdNotPresent() { - Exception e = expectThrows( - MapperParsingException.class, - () -> createMapperService(fieldMapping(b -> b.field("type", "semantic_text"))) - ); - assertThat(e.getMessage(), containsString("field [inference_id] must be specified")); + public void testSetInferenceEndpoints() throws IOException { + final String fieldName = "field"; + final String inferenceId = "foo"; + final String searchInferenceId = "bar"; + + CheckedBiConsumer assertSerialization = (expectedMapping, mapperService) -> { + DocumentMapper mapper = mapperService.documentMapper(); + assertEquals(Strings.toString(expectedMapping), mapper.mappingSource().toString()); + }; + + { + final XContentBuilder fieldMapping = fieldMapping(b -> b.field("type", "semantic_text").field(INFERENCE_ID_FIELD, inferenceId)); + final MapperService mapperService = createMapperService(fieldMapping); + assertSemanticTextField(mapperService, fieldName, false); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, inferenceId); + assertSerialization.accept(fieldMapping, mapperService); + } + { + if (DefaultElserFeatureFlag.isEnabled()) { + final XContentBuilder fieldMapping = fieldMapping( + b -> b.field("type", "semantic_text").field(SEARCH_INFERENCE_ID_FIELD, searchInferenceId) + ); + final MapperService mapperService = createMapperService(fieldMapping); + assertSemanticTextField(mapperService, fieldName, false); + assertInferenceEndpoints(mapperService, fieldName, DEFAULT_ELSER_2_INFERENCE_ID, searchInferenceId); + assertSerialization.accept(fieldMapping, mapperService); + } + } + { + final XContentBuilder fieldMapping = fieldMapping( + b -> b.field("type", "semantic_text") + .field(INFERENCE_ID_FIELD, inferenceId) + .field(SEARCH_INFERENCE_ID_FIELD, searchInferenceId) + ); + MapperService mapperService = createMapperService(fieldMapping); + assertSemanticTextField(mapperService, fieldName, false); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, searchInferenceId); + assertSerialization.accept(fieldMapping, mapperService); + } + } + + public void testInvalidInferenceEndpoints() { + { + Exception e = expectThrows( + MapperParsingException.class, + () -> createMapperService(fieldMapping(b -> b.field("type", "semantic_text").field(INFERENCE_ID_FIELD, (String) null))) + ); + assertThat( + e.getMessage(), + containsString("[inference_id] on mapper [field] of type [semantic_text] must not have a [null] value") + ); + } + { + final String expectedMessage = DefaultElserFeatureFlag.isEnabled() + ? "[inference_id] on mapper [field] of type [semantic_text] must not be empty" + : "[inference_id] on mapper [field] of type [semantic_text] must be specified"; + Exception e = expectThrows( + MapperParsingException.class, + () -> createMapperService(fieldMapping(b -> b.field("type", "semantic_text").field(INFERENCE_ID_FIELD, ""))) + ); + assertThat(e.getMessage(), containsString(expectedMessage)); + } + { + if (DefaultElserFeatureFlag.isEnabled()) { + Exception e = expectThrows( + MapperParsingException.class, + () -> createMapperService(fieldMapping(b -> b.field("type", "semantic_text").field(SEARCH_INFERENCE_ID_FIELD, ""))) + ); + assertThat( + e.getMessage(), + containsString("[search_inference_id] on mapper [field] of type [semantic_text] must not be empty") + ); + } + } } public void testCannotBeUsedInMultiFields() { @@ -221,7 +304,7 @@ public void testDynamicUpdate() throws IOException { new SemanticTextField.ModelSettings(TaskType.SPARSE_EMBEDDING, null, null, null) ); assertSemanticTextField(mapperService, fieldName, true); - assertSearchInferenceId(mapperService, fieldName, inferenceId); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, inferenceId); } { @@ -232,7 +315,7 @@ public void testDynamicUpdate() throws IOException { new SemanticTextField.ModelSettings(TaskType.SPARSE_EMBEDDING, null, null, null) ); assertSemanticTextField(mapperService, fieldName, true); - assertSearchInferenceId(mapperService, fieldName, searchInferenceId); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, searchInferenceId); } } @@ -331,19 +414,19 @@ public void testUpdateSearchInferenceId() throws IOException { String fieldName = randomFieldName(depth); MapperService mapperService = createMapperService(buildMapping.apply(fieldName, null)); assertSemanticTextField(mapperService, fieldName, false); - assertSearchInferenceId(mapperService, fieldName, inferenceId); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, inferenceId); merge(mapperService, buildMapping.apply(fieldName, searchInferenceId1)); assertSemanticTextField(mapperService, fieldName, false); - assertSearchInferenceId(mapperService, fieldName, searchInferenceId1); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, searchInferenceId1); merge(mapperService, buildMapping.apply(fieldName, searchInferenceId2)); assertSemanticTextField(mapperService, fieldName, false); - assertSearchInferenceId(mapperService, fieldName, searchInferenceId2); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, searchInferenceId2); merge(mapperService, buildMapping.apply(fieldName, null)); assertSemanticTextField(mapperService, fieldName, false); - assertSearchInferenceId(mapperService, fieldName, inferenceId); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, inferenceId); mapperService = mapperServiceForFieldWithModelSettings( fieldName, @@ -351,19 +434,19 @@ public void testUpdateSearchInferenceId() throws IOException { new SemanticTextField.ModelSettings(TaskType.SPARSE_EMBEDDING, null, null, null) ); assertSemanticTextField(mapperService, fieldName, true); - assertSearchInferenceId(mapperService, fieldName, inferenceId); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, inferenceId); merge(mapperService, buildMapping.apply(fieldName, searchInferenceId1)); assertSemanticTextField(mapperService, fieldName, true); - assertSearchInferenceId(mapperService, fieldName, searchInferenceId1); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, searchInferenceId1); merge(mapperService, buildMapping.apply(fieldName, searchInferenceId2)); assertSemanticTextField(mapperService, fieldName, true); - assertSearchInferenceId(mapperService, fieldName, searchInferenceId2); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, searchInferenceId2); merge(mapperService, buildMapping.apply(fieldName, null)); assertSemanticTextField(mapperService, fieldName, true); - assertSearchInferenceId(mapperService, fieldName, inferenceId); + assertInferenceEndpoints(mapperService, fieldName, inferenceId, inferenceId); } } @@ -409,11 +492,17 @@ private static void assertSemanticTextField(MapperService mapperService, String } } - private static void assertSearchInferenceId(MapperService mapperService, String fieldName, String expectedSearchInferenceId) { + private static void assertInferenceEndpoints( + MapperService mapperService, + String fieldName, + String expectedInferenceId, + String expectedSearchInferenceId + ) { var fieldType = mapperService.fieldType(fieldName); assertNotNull(fieldType); assertThat(fieldType, instanceOf(SemanticTextFieldMapper.SemanticTextFieldType.class)); SemanticTextFieldMapper.SemanticTextFieldType semanticTextFieldType = (SemanticTextFieldMapper.SemanticTextFieldType) fieldType; + assertEquals(expectedInferenceId, semanticTextFieldType.getInferenceId()); assertEquals(expectedSearchInferenceId, semanticTextFieldType.getSearchInferenceId()); } @@ -433,9 +522,19 @@ public void testSuccessfulParse() throws IOException { MapperService mapperService = createMapperService(mapping); assertSemanticTextField(mapperService, fieldName1, false); - assertSearchInferenceId(mapperService, fieldName1, setSearchInferenceId ? searchInferenceId : model1.getInferenceEntityId()); + assertInferenceEndpoints( + mapperService, + fieldName1, + model1.getInferenceEntityId(), + setSearchInferenceId ? searchInferenceId : model1.getInferenceEntityId() + ); assertSemanticTextField(mapperService, fieldName2, false); - assertSearchInferenceId(mapperService, fieldName2, setSearchInferenceId ? searchInferenceId : model2.getInferenceEntityId()); + assertInferenceEndpoints( + mapperService, + fieldName2, + model2.getInferenceEntityId(), + setSearchInferenceId ? searchInferenceId : model2.getInferenceEntityId() + ); DocumentMapper documentMapper = mapperService.documentMapper(); ParsedDocument doc = documentMapper.parse( diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockServiceTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockServiceTests.java index 0b3cf533d818f..9c746e7c2aed9 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockServiceTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/AmazonBedrockServiceTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.core.TimeValue; import org.elasticsearch.inference.ChunkedInferenceServiceResults; import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InferenceServiceResults; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.Model; @@ -25,6 +26,7 @@ import org.elasticsearch.inference.TaskType; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.core.inference.ChunkingSettingsFeatureFlag; import org.elasticsearch.xpack.core.inference.action.InferenceAction; import org.elasticsearch.xpack.core.inference.results.ChatCompletionResults; import org.elasticsearch.xpack.core.inference.results.InferenceChunkedTextEmbeddingFloatResults; @@ -57,6 +59,8 @@ import static org.elasticsearch.xpack.inference.Utils.getInvalidModel; import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool; import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty; +import static org.elasticsearch.xpack.inference.chunking.ChunkingSettingsTests.createRandomChunkingSettings; +import static org.elasticsearch.xpack.inference.chunking.ChunkingSettingsTests.createRandomChunkingSettingsMap; import static org.elasticsearch.xpack.inference.results.ChatCompletionResultsTests.buildExpectationCompletion; import static org.elasticsearch.xpack.inference.results.TextEmbeddingResultsTests.buildExpectationFloat; import static org.elasticsearch.xpack.inference.services.ServiceComponentsTests.createWithEmptySettings; @@ -305,6 +309,93 @@ public void testParseRequestConfig_MovesModel() throws IOException { } } + public void testParseRequestConfig_ThrowsElasticsearchStatusExceptionWhenChunkingSettingsProvidedAndFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createAmazonBedrockService()) { + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat(exception.getMessage(), containsString("Model configuration contains settings")); + } + ); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, null, null, null), + Map.of(), + createRandomChunkingSettingsMap(), + getAmazonBedrockSecretSettingsMap("access", "secret") + ), + modelVerificationListener + ); + } + } + + public void testParseRequestConfig_CreatesAnAmazonBedrockEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAmazonBedrockService()) { + ActionListener modelVerificationListener = ActionListener.wrap(model -> { + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + var secretSettings = (AmazonBedrockSecretSettings) model.getSecretSettings(); + assertThat(secretSettings.accessKey.toString(), is("access")); + assertThat(secretSettings.secretKey.toString(), is("secret")); + assertThat(model.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + }, exception -> fail("Unexpected exception: " + exception)); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, null, null, null), + Map.of(), + createRandomChunkingSettingsMap(), + getAmazonBedrockSecretSettingsMap("access", "secret") + ), + modelVerificationListener + ); + } + } + + public void testParseRequestConfig_CreatesAnAmazonBedrockEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAmazonBedrockService()) { + ActionListener modelVerificationListener = ActionListener.wrap(model -> { + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + var secretSettings = (AmazonBedrockSecretSettings) model.getSecretSettings(); + assertThat(secretSettings.accessKey.toString(), is("access")); + assertThat(secretSettings.secretKey.toString(), is("secret")); + assertThat(model.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + }, exception -> fail("Unexpected exception: " + exception)); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, null, null, null), + Map.of(), + getAmazonBedrockSecretSettingsMap("access", "secret") + ), + modelVerificationListener + ); + } + } + public void testCreateModel_ForEmbeddingsTask_DimensionsIsNotAllowed() throws IOException { try (var service = createAmazonBedrockService()) { ActionListener modelVerificationListener = ActionListener.wrap( @@ -354,6 +445,100 @@ public void testParsePersistedConfigWithSecrets_CreatesAnAmazonBedrockEmbeddings } } + public void testParsePersistedConfigWithSecrets_CreatesAnAmazonBedrockEmbeddingsModelWithoutChunkingSettingsWhenFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createAmazonBedrockService()) { + var settingsMap = createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, false, null, null); + var secretSettingsMap = getAmazonBedrockSecretSettingsMap("access", "secret"); + + var persistedConfig = getPersistedConfigMap(settingsMap, new HashMap(Map.of()), secretSettingsMap); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + assertNull(model.getConfigurations().getChunkingSettings()); + var secretSettings = (AmazonBedrockSecretSettings) model.getSecretSettings(); + assertThat(secretSettings.accessKey.toString(), is("access")); + assertThat(secretSettings.secretKey.toString(), is("secret")); + } + } + + public void testParsePersistedConfigWithSecrets_CreatesAnAmazonBedrockEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAmazonBedrockService()) { + var settingsMap = createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, false, null, null); + var secretSettingsMap = getAmazonBedrockSecretSettingsMap("access", "secret"); + + var persistedConfig = getPersistedConfigMap( + settingsMap, + new HashMap(Map.of()), + createRandomChunkingSettingsMap(), + secretSettingsMap + ); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + assertThat(model.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + var secretSettings = (AmazonBedrockSecretSettings) model.getSecretSettings(); + assertThat(secretSettings.accessKey.toString(), is("access")); + assertThat(secretSettings.secretKey.toString(), is("secret")); + } + } + + public + void + testParsePersistedConfigWithSecrets_CreatesAnAmazonBedrockEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAmazonBedrockService()) { + var settingsMap = createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, false, null, null); + var secretSettingsMap = getAmazonBedrockSecretSettingsMap("access", "secret"); + + var persistedConfig = getPersistedConfigMap(settingsMap, new HashMap(Map.of()), secretSettingsMap); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + assertThat(model.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + var secretSettings = (AmazonBedrockSecretSettings) model.getSecretSettings(); + assertThat(secretSettings.accessKey.toString(), is("access")); + assertThat(secretSettings.secretKey.toString(), is("secret")); + } + } + public void testParsePersistedConfigWithSecrets_ThrowsErrorTryingToParseInvalidModel() throws IOException { try (var service = createAmazonBedrockService()) { var settingsMap = createChatCompletionRequestSettingsMap("region", "model", "amazontitan"); @@ -538,6 +723,84 @@ public void testParsePersistedConfig_CreatesAnAmazonBedrockEmbeddingsModel() thr } } + public + void + testParsePersistedConfig_CreatesAnAmazonBedrockEmbeddingsModelWithoutChunkingSettingsWhenChunkingSettingsFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createAmazonBedrockService()) { + var settingsMap = createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, false, null, null); + var secretSettingsMap = getAmazonBedrockSecretSettingsMap("access", "secret"); + + var persistedConfig = getPersistedConfigMap( + settingsMap, + new HashMap(Map.of()), + createRandomChunkingSettingsMap(), + secretSettingsMap + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + assertNull(model.getConfigurations().getChunkingSettings()); + assertNull(model.getSecretSettings()); + } + } + + public void testParsePersistedConfig_CreatesAnAmazonBedrockEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAmazonBedrockService()) { + var settingsMap = createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, false, null, null); + var secretSettingsMap = getAmazonBedrockSecretSettingsMap("access", "secret"); + + var persistedConfig = getPersistedConfigMap( + settingsMap, + new HashMap(Map.of()), + createRandomChunkingSettingsMap(), + secretSettingsMap + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + assertThat(model.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertNull(model.getSecretSettings()); + } + } + + public void testParsePersistedConfig_CreatesAnAmazonBedrockEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAmazonBedrockService()) { + var settingsMap = createEmbeddingsRequestSettingsMap("region", "model", "amazontitan", null, false, null, null); + var secretSettingsMap = getAmazonBedrockSecretSettingsMap("access", "secret"); + + var persistedConfig = getPersistedConfigMap(settingsMap, new HashMap(Map.of()), secretSettingsMap); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + assertThat(model, instanceOf(AmazonBedrockEmbeddingsModel.class)); + + var settings = (AmazonBedrockEmbeddingsServiceSettings) model.getServiceSettings(); + assertThat(settings.region(), is("region")); + assertThat(settings.modelId(), is("model")); + assertThat(settings.provider(), is(AmazonBedrockProvider.AMAZONTITAN)); + assertThat(model.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertNull(model.getSecretSettings()); + } + } + public void testParsePersistedConfig_CreatesAnAmazonBedrockChatCompletionModel() throws IOException { try (var service = createAmazonBedrockService()) { var settingsMap = createChatCompletionRequestSettingsMap("region", "model", "anthropic"); @@ -1034,6 +1297,49 @@ public void testInfer_UnauthorizedResponse() throws IOException { } public void testChunkedInfer_CallsInfer_ConvertsFloatResponse_ForEmbeddings() throws IOException { + var model = AmazonBedrockEmbeddingsModelTests.createModel( + "id", + "region", + "model", + AmazonBedrockProvider.AMAZONTITAN, + "access", + "secret" + ); + + testChunkedInfer(model); + } + + public void testChunkedInfer_ChunkingSettingsSetAndFeatureFlagEnabled() throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + var model = AmazonBedrockEmbeddingsModelTests.createModel( + "id", + "region", + "model", + AmazonBedrockProvider.AMAZONTITAN, + createRandomChunkingSettings(), + "access", + "secret" + ); + + testChunkedInfer(model); + } + + public void testChunkedInfer_ChunkingSettingsNotSetAndFeatureFlagEnabled() throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + var model = AmazonBedrockEmbeddingsModelTests.createModel( + "id", + "region", + "model", + AmazonBedrockProvider.AMAZONTITAN, + null, + "access", + "secret" + ); + + testChunkedInfer(model); + } + + private void testChunkedInfer(AmazonBedrockEmbeddingsModel model) throws IOException { var sender = mock(Sender.class); var factory = mock(HttpRequestSender.Factory.class); when(factory.createSender()).thenReturn(sender); @@ -1058,14 +1364,6 @@ public void testChunkedInfer_CallsInfer_ConvertsFloatResponse_ForEmbeddings() th requestSender.enqueue(mockResults2); } - var model = AmazonBedrockEmbeddingsModelTests.createModel( - "id", - "region", - "model", - AmazonBedrockProvider.AMAZONTITAN, - "access", - "secret" - ); PlainActionFuture> listener = new PlainActionFuture<>(); service.chunkedInfer( model, @@ -1106,6 +1404,18 @@ private AmazonBedrockService createAmazonBedrockService() { return new AmazonBedrockService(mock(HttpRequestSender.Factory.class), amazonBedrockFactory, createWithEmptySettings(threadPool)); } + private Map getRequestConfigMap( + Map serviceSettings, + Map taskSettings, + Map chunkingSettings, + Map secretSettings + ) { + var requestConfigMap = getRequestConfigMap(serviceSettings, taskSettings, secretSettings); + requestConfigMap.put(ModelConfigurations.CHUNKING_SETTINGS, chunkingSettings); + + return requestConfigMap; + } + private Map getRequestConfigMap( Map serviceSettings, Map taskSettings, @@ -1120,6 +1430,18 @@ private Map getRequestConfigMap( ); } + private Utils.PersistedConfig getPersistedConfigMap( + Map serviceSettings, + Map taskSettings, + Map chunkingSettings, + Map secretSettings + ) { + var persistedConfigMap = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + persistedConfigMap.config().put(ModelConfigurations.CHUNKING_SETTINGS, chunkingSettings); + + return persistedConfigMap; + } + private Utils.PersistedConfig getPersistedConfigMap( Map serviceSettings, Map taskSettings, diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModelTests.java index 711e3cbb5a511..72dc696ddd81b 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModelTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/amazonbedrock/embeddings/AmazonBedrockEmbeddingsModelTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.ValidationException; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.EmptyTaskSettings; import org.elasticsearch.inference.SimilarityMeasure; import org.elasticsearch.inference.TaskType; @@ -47,6 +48,65 @@ public static AmazonBedrockEmbeddingsModel createModel( return createModel(inferenceId, region, model, provider, null, false, null, null, new RateLimitSettings(240), accessKey, secretKey); } + public static AmazonBedrockEmbeddingsModel createModel( + String inferenceId, + String region, + String model, + AmazonBedrockProvider provider, + ChunkingSettings chunkingSettings, + String accessKey, + String secretKey + ) { + return createModel( + inferenceId, + region, + model, + provider, + null, + false, + null, + null, + new RateLimitSettings(240), + chunkingSettings, + accessKey, + secretKey + ); + } + + public static AmazonBedrockEmbeddingsModel createModel( + String inferenceId, + String region, + String model, + AmazonBedrockProvider provider, + @Nullable Integer dimensions, + boolean dimensionsSetByUser, + @Nullable Integer maxTokens, + @Nullable SimilarityMeasure similarity, + RateLimitSettings rateLimitSettings, + ChunkingSettings chunkingSettings, + String accessKey, + String secretKey + ) { + return new AmazonBedrockEmbeddingsModel( + inferenceId, + TaskType.TEXT_EMBEDDING, + "amazonbedrock", + new AmazonBedrockEmbeddingsServiceSettings( + region, + model, + provider, + dimensions, + dimensionsSetByUser, + maxTokens, + similarity, + rateLimitSettings + ), + new EmptyTaskSettings(), + chunkingSettings, + new AmazonBedrockSecretSettings(new SecureString(accessKey), new SecureString(secretKey)) + ); + } + public static AmazonBedrockEmbeddingsModel createModel( String inferenceId, String region, @@ -75,6 +135,7 @@ public static AmazonBedrockEmbeddingsModel createModel( rateLimitSettings ), new EmptyTaskSettings(), + null, new AmazonBedrockSecretSettings(new SecureString(accessKey), new SecureString(secretKey)) ); } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiServiceTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiServiceTests.java index 098e41b72ea8f..0fc8f3f2b0eb3 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiServiceTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiServiceTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.core.TimeValue; import org.elasticsearch.inference.ChunkedInferenceServiceResults; import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InferenceServiceResults; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.Model; @@ -29,6 +30,7 @@ import org.elasticsearch.test.http.MockWebServer; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.inference.ChunkingSettingsFeatureFlag; import org.elasticsearch.xpack.core.inference.action.InferenceAction; import org.elasticsearch.xpack.core.inference.results.InferenceChunkedTextEmbeddingFloatResults; import org.elasticsearch.xpack.inference.external.http.HttpClientManager; @@ -56,6 +58,8 @@ import static org.elasticsearch.xpack.inference.Utils.getPersistedConfigMap; import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool; import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty; +import static org.elasticsearch.xpack.inference.chunking.ChunkingSettingsTests.createRandomChunkingSettings; +import static org.elasticsearch.xpack.inference.chunking.ChunkingSettingsTests.createRandomChunkingSettingsMap; import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; import static org.elasticsearch.xpack.inference.external.http.Utils.getUrl; import static org.elasticsearch.xpack.inference.external.request.azureopenai.AzureOpenAiUtils.API_KEY_HEADER; @@ -122,6 +126,88 @@ public void testParseRequestConfig_CreatesAnOpenAiEmbeddingsModel() throws IOExc } } + public void testParseRequestConfig_ThrowsElasticsearchStatusExceptionWhenChunkingSettingsProvidedAndFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createAzureOpenAiService()) { + var config = getRequestConfigMap( + getRequestAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", null, null), + getAzureOpenAiRequestTaskSettingsMap("user"), + createRandomChunkingSettingsMap(), + getAzureOpenAiSecretSettingsMap("secret", null) + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat(exception.getMessage(), containsString("Model configuration contains settings")); + } + ); + + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, modelVerificationListener); + } + } + + public void testParseRequestConfig_CreatesAnOpenAiEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAzureOpenAiService()) { + ActionListener modelVerificationListener = ActionListener.wrap(model -> { + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + }, exception -> fail("Unexpected exception: " + exception)); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + getRequestAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", null, null), + getAzureOpenAiRequestTaskSettingsMap("user"), + createRandomChunkingSettingsMap(), + getAzureOpenAiSecretSettingsMap("secret", null) + ), + modelVerificationListener + ); + } + } + + public void testParseRequestConfig_CreatesAnOpenAiEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAzureOpenAiService()) { + ActionListener modelVerificationListener = ActionListener.wrap(model -> { + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + }, exception -> fail("Unexpected exception: " + exception)); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + getRequestAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", null, null), + getAzureOpenAiRequestTaskSettingsMap("user"), + getAzureOpenAiSecretSettingsMap("secret", null) + ), + modelVerificationListener + ); + } + } + public void testParseRequestConfig_ThrowsUnsupportedModelType() throws IOException { try (var service = createAzureOpenAiService()) { ActionListener modelVerificationListener = ActionListener.wrap( @@ -298,6 +384,103 @@ public void testParsePersistedConfigWithSecrets_CreatesAnAzureOpenAiEmbeddingsMo } } + public + void + testParsePersistedConfigWithSecrets_CreatesAnOpenAiEmbeddingsModelWithoutChunkingSettingsWhenChunkingSettingsFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createAzureOpenAiService()) { + var persistedConfig = getPersistedConfigMap( + getPersistentAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", 100, 512), + getAzureOpenAiRequestTaskSettingsMap("user"), + createRandomChunkingSettingsMap(), + getAzureOpenAiSecretSettingsMap("secret", null) + ); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getServiceSettings().dimensions(), is(100)); + assertThat(embeddingsModel.getServiceSettings().maxInputTokens(), is(512)); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertNull(embeddingsModel.getConfigurations().getChunkingSettings()); + assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + } + } + + public void testParsePersistedConfigWithSecrets_CreatesAnOpenAiEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAzureOpenAiService()) { + var persistedConfig = getPersistedConfigMap( + getPersistentAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", 100, 512), + getAzureOpenAiRequestTaskSettingsMap("user"), + createRandomChunkingSettingsMap(), + getAzureOpenAiSecretSettingsMap("secret", null) + ); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getServiceSettings().dimensions(), is(100)); + assertThat(embeddingsModel.getServiceSettings().maxInputTokens(), is(512)); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + } + } + + public void testParsePersistedConfigWithSecrets_CreatesAnOpenAiEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAzureOpenAiService()) { + var persistedConfig = getPersistedConfigMap( + getPersistentAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", 100, 512), + getAzureOpenAiRequestTaskSettingsMap("user"), + getAzureOpenAiSecretSettingsMap("secret", null) + ); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getServiceSettings().dimensions(), is(100)); + assertThat(embeddingsModel.getServiceSettings().maxInputTokens(), is(512)); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + } + } + public void testParsePersistedConfigWithSecrets_ThrowsErrorTryingToParseInvalidModel() throws IOException { try (var service = createAzureOpenAiService()) { var persistedConfig = getPersistedConfigMap( @@ -494,6 +677,77 @@ public void testParsePersistedConfig_CreatesAnAzureOpenAiEmbeddingsModel() throw } } + public void testParsePersistedConfig_CreatesAnAzureOpenAiEmbeddingsModelWithoutChunkingSettingsWhenChunkingSettingsFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createAzureOpenAiService()) { + var persistedConfig = getPersistedConfigMap( + getPersistentAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", null, null), + getAzureOpenAiRequestTaskSettingsMap("user"), + createRandomChunkingSettingsMap() + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertNull(embeddingsModel.getConfigurations().getChunkingSettings()); + assertNull(embeddingsModel.getSecretSettings()); + } + } + + public void testParsePersistedConfig_CreatesAnAzureOpenAiEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAzureOpenAiService()) { + var persistedConfig = getPersistedConfigMap( + getPersistentAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", null, null), + getAzureOpenAiRequestTaskSettingsMap("user"), + createRandomChunkingSettingsMap() + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertNull(embeddingsModel.getSecretSettings()); + } + } + + public void testParsePersistedConfig_CreatesAnOpenAiEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createAzureOpenAiService()) { + var persistedConfig = getPersistedConfigMap( + getPersistentAzureOpenAiServiceSettingsMap("resource_name", "deployment_id", "api_version", null, null), + getAzureOpenAiRequestTaskSettingsMap("user") + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + assertThat(model, instanceOf(AzureOpenAiEmbeddingsModel.class)); + + var embeddingsModel = (AzureOpenAiEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().resourceName(), is("resource_name")); + assertThat(embeddingsModel.getServiceSettings().deploymentId(), is("deployment_id")); + assertThat(embeddingsModel.getServiceSettings().apiVersion(), is("api_version")); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertNull(embeddingsModel.getSecretSettings()); + } + } + public void testParsePersistedConfig_ThrowsErrorTryingToParseInvalidModel() throws IOException { try (var service = createAzureOpenAiService()) { var persistedConfig = getPersistedConfigMap( @@ -1064,6 +1318,35 @@ public void testInfer_UnauthorisedResponse() throws IOException, URISyntaxExcept } public void testChunkedInfer_CallsInfer_ConvertsFloatResponse() throws IOException, URISyntaxException { + var model = AzureOpenAiEmbeddingsModelTests.createModel("resource", "deployment", "apiversion", "user", "apikey", null, "id"); + + testChunkedInfer(model); + } + + public void testChunkedInfer_ChunkingSettingsSetAndFeatureFlagEnabled() throws IOException, URISyntaxException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + var model = AzureOpenAiEmbeddingsModelTests.createModel( + "resource", + "deployment", + "apiversion", + "user", + createRandomChunkingSettings(), + "apikey", + null, + "id" + ); + + testChunkedInfer(model); + } + + public void testChunkedInfer_ChunkingSettingsNotSetAndFeatureFlagEnabled() throws IOException, URISyntaxException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + var model = AzureOpenAiEmbeddingsModelTests.createModel("resource", "deployment", "apiversion", "user", null, "apikey", null, "id"); + + testChunkedInfer(model); + } + + private void testChunkedInfer(AzureOpenAiEmbeddingsModel model) throws IOException, URISyntaxException { var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); try (var service = new AzureOpenAiService(senderFactory, createWithEmptySettings(threadPool))) { @@ -1098,7 +1381,6 @@ public void testChunkedInfer_CallsInfer_ConvertsFloatResponse() throws IOExcepti """; webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); - var model = AzureOpenAiEmbeddingsModelTests.createModel("resource", "deployment", "apiversion", "user", "apikey", null, "id"); model.setUri(new URI(getUrl(webServer))); PlainActionFuture> listener = new PlainActionFuture<>(); service.chunkedInfer( @@ -1145,6 +1427,18 @@ private AzureOpenAiService createAzureOpenAiService() { return new AzureOpenAiService(mock(HttpRequestSender.Factory.class), createWithEmptySettings(threadPool)); } + private Map getRequestConfigMap( + Map serviceSettings, + Map taskSettings, + Map chunkingSettings, + Map secretSettings + ) { + var requestConfigMap = getRequestConfigMap(serviceSettings, taskSettings, secretSettings); + requestConfigMap.put(ModelConfigurations.CHUNKING_SETTINGS, chunkingSettings); + + return requestConfigMap; + } + private Map getRequestConfigMap( Map serviceSettings, Map taskSettings, diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModelTests.java index 1747155623a98..2f6760cb36e9f 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModelTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsModelTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.SimilarityMeasure; import org.elasticsearch.inference.TaskType; import org.elasticsearch.test.ESTestCase; @@ -100,6 +101,7 @@ public static AzureOpenAiEmbeddingsModel createModel( String deploymentId, String apiVersion, String user, + ChunkingSettings chunkingSettings, @Nullable String apiKey, @Nullable String entraId, String inferenceEntityId @@ -112,6 +114,29 @@ public static AzureOpenAiEmbeddingsModel createModel( "service", new AzureOpenAiEmbeddingsServiceSettings(resourceName, deploymentId, apiVersion, null, false, null, null, null), new AzureOpenAiEmbeddingsTaskSettings(user), + chunkingSettings, + new AzureOpenAiSecretSettings(secureApiKey, secureEntraId) + ); + } + + public static AzureOpenAiEmbeddingsModel createModel( + String resourceName, + String deploymentId, + String apiVersion, + String user, + @Nullable String apiKey, + @Nullable String entraId, + String inferenceEntityId + ) { + var secureApiKey = apiKey != null ? new SecureString(apiKey.toCharArray()) : null; + var secureEntraId = entraId != null ? new SecureString(entraId.toCharArray()) : null; + return new AzureOpenAiEmbeddingsModel( + inferenceEntityId, + TaskType.TEXT_EMBEDDING, + "service", + new AzureOpenAiEmbeddingsServiceSettings(resourceName, deploymentId, apiVersion, null, false, null, null, null), + new AzureOpenAiEmbeddingsTaskSettings(user), + null, new AzureOpenAiSecretSettings(secureApiKey, secureEntraId) ); } @@ -147,6 +172,7 @@ public static AzureOpenAiEmbeddingsModel createModel( null ), new AzureOpenAiEmbeddingsTaskSettings(user), + null, new AzureOpenAiSecretSettings(secureApiKey, secureEntraId) ); } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceTests.java index 420a635963a29..758c38166778b 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceTests.java @@ -19,6 +19,7 @@ import org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper; import org.elasticsearch.inference.ChunkedInferenceServiceResults; import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InferenceServiceResults; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.Model; @@ -30,6 +31,7 @@ import org.elasticsearch.test.http.MockWebServer; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.inference.ChunkingSettingsFeatureFlag; import org.elasticsearch.xpack.core.inference.action.InferenceAction; import org.elasticsearch.xpack.core.inference.results.InferenceChunkedTextEmbeddingByteResults; import org.elasticsearch.xpack.core.inference.results.InferenceChunkedTextEmbeddingFloatResults; @@ -62,6 +64,8 @@ import static org.elasticsearch.xpack.inference.Utils.getPersistedConfigMap; import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool; import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty; +import static org.elasticsearch.xpack.inference.chunking.ChunkingSettingsTests.createRandomChunkingSettings; +import static org.elasticsearch.xpack.inference.chunking.ChunkingSettingsTests.createRandomChunkingSettingsMap; import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; import static org.elasticsearch.xpack.inference.external.http.Utils.getUrl; import static org.elasticsearch.xpack.inference.results.TextEmbeddingResultsTests.buildExpectationFloat; @@ -130,6 +134,95 @@ public void testParseRequestConfig_CreatesACohereEmbeddingsModel() throws IOExce } } + public void testParseRequestConfig_ThrowsElasticsearchStatusExceptionWhenChunkingSettingsProvidedAndFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createCohereService()) { + var serviceSettings = CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", null); + + var config = getRequestConfigMap( + serviceSettings, + getTaskSettingsMap(null, null), + createRandomChunkingSettingsMap(), + getSecretSettingsMap("secret") + ); + + var failureListener = ActionListener.wrap((model) -> fail("Model parsing should have failed"), e -> { + MatcherAssert.assertThat(e, instanceOf(ElasticsearchStatusException.class)); + MatcherAssert.assertThat(e.getMessage(), containsString("Model configuration contains settings")); + }); + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, failureListener); + } + } + + public void testParseRequestConfig_CreatesACohereEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createCohereService()) { + ActionListener modelListener = ActionListener.wrap(model -> { + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getEmbeddingType(), is(CohereEmbeddingType.FLOAT)); + MatcherAssert.assertThat( + embeddingsModel.getTaskSettings(), + is(new CohereEmbeddingsTaskSettings(InputType.INGEST, CohereTruncation.START)) + ); + MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + }, e -> fail("Model parsing should have succeeded " + e.getMessage())); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", CohereEmbeddingType.FLOAT), + getTaskSettingsMap(InputType.INGEST, CohereTruncation.START), + createRandomChunkingSettingsMap(), + getSecretSettingsMap("secret") + ), + modelListener + ); + + } + } + + public void testParseRequestConfig_CreatesACohereEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createCohereService()) { + ActionListener modelListener = ActionListener.wrap(model -> { + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getEmbeddingType(), is(CohereEmbeddingType.FLOAT)); + MatcherAssert.assertThat( + embeddingsModel.getTaskSettings(), + is(new CohereEmbeddingsTaskSettings(InputType.INGEST, CohereTruncation.START)) + ); + MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + }, e -> fail("Model parsing should have succeeded " + e.getMessage())); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", CohereEmbeddingType.FLOAT), + getTaskSettingsMap(InputType.INGEST, CohereTruncation.START), + getSecretSettingsMap("secret") + ), + modelListener + ); + + } + } + public void testParseRequestConfig_OptionalTaskSettings() throws IOException { try (var service = createCohereService()) { @@ -305,6 +398,92 @@ public void testParsePersistedConfigWithSecrets_CreatesACohereEmbeddingsModel() } } + public void testParsePersistedConfigWithSecrets_CreatesACohereEmbeddingsModelWithoutChunkingSettingsWhenFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createCohereService()) { + var persistedConfig = getPersistedConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", null), + getTaskSettingsMap(null, null), + createRandomChunkingSettingsMap(), + getSecretSettingsMap("secret") + ); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new CohereEmbeddingsTaskSettings(null, null))); + assertNull(embeddingsModel.getConfigurations().getChunkingSettings()); + MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + } + } + + public void testParsePersistedConfigWithSecrets_CreatesACohereEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createCohereService()) { + var persistedConfig = getPersistedConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", null), + getTaskSettingsMap(null, null), + createRandomChunkingSettingsMap(), + getSecretSettingsMap("secret") + ); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new CohereEmbeddingsTaskSettings(null, null))); + MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + } + } + + public void testParsePersistedConfigWithSecrets_CreatesACohereEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createCohereService()) { + var persistedConfig = getPersistedConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", null), + getTaskSettingsMap(null, null), + getSecretSettingsMap("secret") + ); + + var model = service.parsePersistedConfigWithSecrets( + "id", + TaskType.TEXT_EMBEDDING, + persistedConfig.config(), + persistedConfig.secrets() + ); + + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new CohereEmbeddingsTaskSettings(null, null))); + MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + MatcherAssert.assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + } + } + public void testParsePersistedConfigWithSecrets_ThrowsErrorTryingToParseInvalidModel() throws IOException { try (var service = createCohereService()) { var persistedConfig = getPersistedConfigMap( @@ -507,6 +686,74 @@ public void testParsePersistedConfig_CreatesACohereEmbeddingsModel() throws IOEx } } + public void testParsePersistedConfig_CreatesACohereEmbeddingsModelWithoutChunkingSettingsWhenChunkingSettingsFeatureFlagDisabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is disabled", ChunkingSettingsFeatureFlag.isEnabled() == false); + try (var service = createCohereService()) { + var persistedConfig = getPersistedConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", null), + getTaskSettingsMap(null, CohereTruncation.NONE), + createRandomChunkingSettingsMap() + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new CohereEmbeddingsTaskSettings(null, CohereTruncation.NONE))); + assertNull(embeddingsModel.getConfigurations().getChunkingSettings()); + assertNull(embeddingsModel.getSecretSettings()); + } + } + + public void testParsePersistedConfig_CreatesACohereEmbeddingsModelWhenChunkingSettingsProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createCohereService()) { + var persistedConfig = getPersistedConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", null), + getTaskSettingsMap(null, CohereTruncation.NONE), + createRandomChunkingSettingsMap() + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new CohereEmbeddingsTaskSettings(null, CohereTruncation.NONE))); + MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertNull(embeddingsModel.getSecretSettings()); + } + } + + public void testParsePersistedConfig_CreatesACohereEmbeddingsModelWhenChunkingSettingsNotProvidedAndFeatureFlagEnabled() + throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + try (var service = createCohereService()) { + var persistedConfig = getPersistedConfigMap( + CohereEmbeddingsServiceSettingsTests.getServiceSettingsMap("url", "model", null), + getTaskSettingsMap(null, CohereTruncation.NONE) + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, persistedConfig.config()); + + MatcherAssert.assertThat(model, instanceOf(CohereEmbeddingsModel.class)); + + var embeddingsModel = (CohereEmbeddingsModel) model; + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().uri().toString(), is("url")); + MatcherAssert.assertThat(embeddingsModel.getServiceSettings().getCommonSettings().modelId(), is("model")); + MatcherAssert.assertThat(embeddingsModel.getTaskSettings(), is(new CohereEmbeddingsTaskSettings(null, CohereTruncation.NONE))); + MatcherAssert.assertThat(embeddingsModel.getConfigurations().getChunkingSettings(), instanceOf(ChunkingSettings.class)); + assertNull(embeddingsModel.getSecretSettings()); + } + } + public void testParsePersistedConfig_ThrowsErrorTryingToParseInvalidModel() throws IOException { try (var service = createCohereService()) { var persistedConfig = getPersistedConfigMap( @@ -1164,6 +1411,52 @@ public void testInfer_DoesNotSetInputType_WhenNotPresentInTaskSettings_AndUnspec } public void testChunkedInfer_BatchesCalls() throws IOException { + var model = CohereEmbeddingsModelTests.createModel( + getUrl(webServer), + "secret", + new CohereEmbeddingsTaskSettings(null, null), + 1024, + 1024, + "model", + null + ); + + testChunkedInfer(model); + } + + public void testChunkedInfer_BatchesCallsChunkingSettingsSetAndFeatureFlagEnabled() throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + var model = CohereEmbeddingsModelTests.createModel( + getUrl(webServer), + "secret", + new CohereEmbeddingsTaskSettings(null, null), + createRandomChunkingSettings(), + 1024, + 1024, + "model", + null + ); + + testChunkedInfer(model); + } + + public void testChunkedInfer_ChunkingSettingsNotSetAndFeatureFlagEnabled() throws IOException { + assumeTrue("Only if 'inference_chunking_settings' feature flag is enabled", ChunkingSettingsFeatureFlag.isEnabled()); + var model = CohereEmbeddingsModelTests.createModel( + getUrl(webServer), + "secret", + new CohereEmbeddingsTaskSettings(null, null), + null, + 1024, + 1024, + "model", + null + ); + + testChunkedInfer(model); + } + + private void testChunkedInfer(CohereEmbeddingsModel model) throws IOException { var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); try (var service = new CohereService(senderFactory, createWithEmptySettings(threadPool))) { @@ -1200,15 +1493,6 @@ public void testChunkedInfer_BatchesCalls() throws IOException { """; webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); - var model = CohereEmbeddingsModelTests.createModel( - getUrl(webServer), - "secret", - new CohereEmbeddingsTaskSettings(null, null), - 1024, - 1024, - "model", - null - ); PlainActionFuture> listener = new PlainActionFuture<>(); // 2 inputs service.chunkedInfer( @@ -1399,6 +1683,18 @@ public void testInfer_StreamRequest_ErrorResponse() throws Exception { .hasErrorContaining("how dare you"); } + private Map getRequestConfigMap( + Map serviceSettings, + Map taskSettings, + Map chunkingSettings, + Map secretSettings + ) { + var requestConfigMap = getRequestConfigMap(serviceSettings, taskSettings, secretSettings); + requestConfigMap.put(ModelConfigurations.CHUNKING_SETTINGS, chunkingSettings); + + return requestConfigMap; + } + private Map getRequestConfigMap( Map serviceSettings, Map taskSettings, diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModelTests.java index 093283c0b37d6..670e63a85cf9d 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModelTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsModelTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ChunkingSettings; import org.elasticsearch.inference.InputType; import org.elasticsearch.inference.SimilarityMeasure; import org.elasticsearch.inference.TaskType; @@ -208,6 +209,7 @@ public static CohereEmbeddingsModel createModel( String url, String apiKey, CohereEmbeddingsTaskSettings taskSettings, + ChunkingSettings chunkingSettings, @Nullable Integer tokenLimit, @Nullable Integer dimensions, @Nullable String model, @@ -222,6 +224,30 @@ public static CohereEmbeddingsModel createModel( Objects.requireNonNullElse(embeddingType, CohereEmbeddingType.FLOAT) ), taskSettings, + chunkingSettings, + new DefaultSecretSettings(new SecureString(apiKey.toCharArray())) + ); + } + + public static CohereEmbeddingsModel createModel( + String url, + String apiKey, + CohereEmbeddingsTaskSettings taskSettings, + @Nullable Integer tokenLimit, + @Nullable Integer dimensions, + @Nullable String model, + @Nullable CohereEmbeddingType embeddingType + ) { + return new CohereEmbeddingsModel( + "id", + TaskType.TEXT_EMBEDDING, + "service", + new CohereEmbeddingsServiceSettings( + new CohereServiceSettings(url, SimilarityMeasure.DOT_PRODUCT, dimensions, tokenLimit, model, null), + Objects.requireNonNullElse(embeddingType, CohereEmbeddingType.FLOAT) + ), + taskSettings, + null, new DefaultSecretSettings(new SecureString(apiKey.toCharArray())) ); } @@ -245,6 +271,7 @@ public static CohereEmbeddingsModel createModel( Objects.requireNonNullElse(embeddingType, CohereEmbeddingType.FLOAT) ), taskSettings, + null, new DefaultSecretSettings(new SecureString(apiKey.toCharArray())) ); } diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/30_semantic_text_inference.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/30_semantic_text_inference.yml index f58a5c33fd85d..1795d754d2a9c 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/30_semantic_text_inference.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/30_semantic_text_inference.yml @@ -547,3 +547,34 @@ setup: - match: { _source.dense_field.text: "another updated inference test" } - match: { _source.dense_field.inference.chunks.0.text: "another updated inference test" } - exists: _source.dense_field.inference.chunks.0.embeddings + +--- +"Calculates embeddings using the default ELSER 2 endpoint": + - requires: + cluster_features: "semantic_text.default_elser_2" + reason: semantic_text default ELSER 2 inference ID introduced in 8.16.0 + + - do: + indices.create: + index: test-elser-2-default-index + body: + mappings: + properties: + sparse_field: + type: semantic_text + + - do: + index: + index: test-elser-2-default-index + id: doc_1 + body: + sparse_field: "inference test" + + - do: + get: + index: test-elser-2-default-index + id: doc_1 + + - match: { _source.sparse_field.text: "inference test" } + - exists: _source.sparse_field.inference.chunks.0.embeddings + - match: { _source.sparse_field.inference.chunks.0.text: "inference test" } diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/40_semantic_text_query.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/40_semantic_text_query.yml index 2070b3752791a..10858acc0aff8 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/40_semantic_text_query.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/40_semantic_text_query.yml @@ -839,3 +839,38 @@ setup: - match: { error.type: "resource_not_found_exception" } - match: { error.reason: "Inference endpoint not found [invalid-inference-id]" } + +--- +"Query a field that uses the default ELSER 2 endpoint": + - requires: + cluster_features: "semantic_text.default_elser_2" + reason: semantic_text default ELSER 2 inference ID introduced in 8.16.0 + + - do: + indices.create: + index: test-elser-2-default-index + body: + mappings: + properties: + sparse_field: + type: semantic_text + + - do: + index: + index: test-elser-2-default-index + id: doc_1 + body: + sparse_field: "inference test" + refresh: true + + - do: + search: + index: test-elser-2-default-index + body: + query: + semantic: + field: "sparse_field" + query: "inference test" + + - match: { hits.total.value: 1 } + - match: { hits.hits.0._id: "doc_1" } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScaler.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScaler.java index 05e7202b8efe9..58259b87c6b00 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScaler.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScaler.java @@ -170,8 +170,7 @@ Integer scale() { if (maxNumberOfAllocations != null) { numberOfAllocations = Math.min(numberOfAllocations, maxNumberOfAllocations); } - if (ScaleToZeroFeatureFlag.isEnabled() - && (minNumberOfAllocations == null || minNumberOfAllocations == 0) + if ((minNumberOfAllocations == null || minNumberOfAllocations == 0) && timeWithoutRequestsSeconds > SCALE_TO_ZERO_AFTER_NO_REQUESTS_TIME_SECONDS) { logger.debug("[{}] adaptive allocations scaler: scaling down to zero, because of no requests.", deploymentId); numberOfAllocations = 0; diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerService.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerService.java index 775279a6b2553..193fa9e7e07f9 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerService.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerService.java @@ -421,8 +421,7 @@ private void processDeploymentStats(GetDeploymentStatsAction.Response statsRespo } public boolean maybeStartAllocation(TrainedModelAssignment assignment) { - if (ScaleToZeroFeatureFlag.isEnabled() - && assignment.getAdaptiveAllocationsSettings() != null + if (assignment.getAdaptiveAllocationsSettings() != null && assignment.getAdaptiveAllocationsSettings().getEnabled() == Boolean.TRUE) { lastScaleUpTimesMillis.put(assignment.getDeploymentId(), System.currentTimeMillis()); updateNumberOfAllocations(assignment.getDeploymentId(), 1); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/ScaleToZeroFeatureFlag.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/ScaleToZeroFeatureFlag.java deleted file mode 100644 index 072b8c5593c93..0000000000000 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/ScaleToZeroFeatureFlag.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.ml.inference.adaptiveallocations; - -import org.elasticsearch.common.util.FeatureFlag; - -public class ScaleToZeroFeatureFlag { - private ScaleToZeroFeatureFlag() {} - - private static final FeatureFlag FEATURE_FLAG = new FeatureFlag("inference_scale_to_zero"); - - public static boolean isEnabled() { - return FEATURE_FLAG.isEnabled(); - } -} diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerTests.java index 7d98aaf67a7f3..1887ebe8050e0 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/adaptiveallocations/AdaptiveAllocationsScalerTests.java @@ -148,8 +148,6 @@ public void testAutoscaling_maxAllocationsSafeguard() { } public void testAutoscaling_scaleDownToZeroAllocations() { - assumeTrue("Should only run if adaptive allocations feature flag is enabled", ScaleToZeroFeatureFlag.isEnabled()); - AdaptiveAllocationsScaler adaptiveAllocationsScaler = new AdaptiveAllocationsScaler("test-deployment", 1); // 1 hour with 1 request per 1 seconds, so don't scale. for (int i = 0; i < 3600; i++) { @@ -180,8 +178,6 @@ public void testAutoscaling_scaleDownToZeroAllocations() { } public void testAutoscaling_dontScaleDownToZeroAllocationsWhenMinAllocationsIsSet() { - assumeTrue("Should only run if adaptive allocations feature flag is enabled", ScaleToZeroFeatureFlag.isEnabled()); - AdaptiveAllocationsScaler adaptiveAllocationsScaler = new AdaptiveAllocationsScaler("test-deployment", 1); adaptiveAllocationsScaler.setMinMaxNumberOfAllocations(1, null); diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRankPlugin.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRankPlugin.java index ece08d1a3d558..9404d863f1d28 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRankPlugin.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRankPlugin.java @@ -25,7 +25,7 @@ public class RRFRankPlugin extends Plugin implements SearchPlugin { public static final LicensedFeature.Momentary RANK_RRF_FEATURE = LicensedFeature.momentary( null, "rank-rrf", - License.OperationMode.PLATINUM + License.OperationMode.ENTERPRISE ); public static final String NAME = "rrf"; diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml index 74c0e9ef1bb31..8bbdb27a87d1a 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/60_usage.yml @@ -1,8 +1,13 @@ --- setup: - requires: - cluster_features: ["gte_v8.14.0"] - reason: "Introduction of META tracking in 8.14+" + capabilities: + - method: POST + path: /_query + parameters: [ method, path, parameters, capabilities ] + capabilities: [ no_meta ] + reason: "META command removed which changes the count of the data returned" + test_runner_features: [capabilities] - do: indices.create: @@ -23,7 +28,7 @@ setup: - do: {xpack.usage: {}} - match: { esql.available: true } - match: { esql.enabled: true } - - length: { esql.features: 16 } + - length: { esql.features: 15 } - set: {esql.features.dissect: dissect_counter} - set: {esql.features.drop: drop_counter} - set: {esql.features.eval: eval_counter} @@ -32,7 +37,6 @@ setup: - set: {esql.features.grok: grok_counter} - set: {esql.features.keep: keep_counter} - set: {esql.features.limit: limit_counter} - - set: {esql.features.meta: meta_counter} - set: {esql.features.mv_expand: mv_expand_counter} - set: {esql.features.rename: rename_counter} - set: {esql.features.row: row_counter} diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/email/attachment/HttpEmailAttachementParser.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/email/attachment/HttpEmailAttachementParser.java index e4d7fcc3a2935..19b9f68ae9619 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/email/attachment/HttpEmailAttachementParser.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/email/attachment/HttpEmailAttachementParser.java @@ -10,6 +10,7 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xpack.core.watcher.execution.WatchExecutionContext; @@ -84,7 +85,7 @@ public Attachment toAttachment(WatchExecutionContext context, Payload payload, H HttpResponse response = webhookService.modifyAndExecuteHttpRequest(httpRequest).v2(); // check for status 200, only then append attachment - if (response.status() >= 200 && response.status() < 300) { + if (RestStatus.isSuccessful(response.status())) { if (response.hasContent()) { String contentType = attachment.getContentType(); String attachmentContentType = Strings.hasLength(contentType) ? contentType : response.contentType(); diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/slack/SentMessages.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/slack/SentMessages.java index 94d17844f06da..f98bb99525864 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/slack/SentMessages.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/slack/SentMessages.java @@ -8,6 +8,7 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.core.Nullable; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.ToXContentObject; import org.elasticsearch.xcontent.XContentBuilder; @@ -110,7 +111,7 @@ public Exception getException() { } public boolean isSuccess() { - return response != null && response.status() >= 200 && response.status() < 300; + return response != null && RestStatus.isSuccessful(response.status()); } @Override